GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Failed
Push — develop-v1.6.0 ( 9d5181...7efb31 )
by
unknown
04:49
created

ResourceController._get_all()   F

Complexity

Conditions 17

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 17
dl 0
loc 85
rs 2
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like ResourceController._get_all() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
# pylint: disable=no-member
17
18
import abc
19
import copy
20
21
from oslo_config import cfg
22
from mongoengine import ValidationError
23
import pecan
24
from pecan import rest
25
import six
26
from six.moves import http_client
27
28
from st2common.models.api.base import jsexpose
29
from st2common import log as logging
30
from st2common.models.system.common import InvalidResourceReferenceError
31
from st2common.models.system.common import ResourceReference
32
from st2common.exceptions.db import StackStormDBObjectNotFoundError
33
34
LOG = logging.getLogger(__name__)
35
36
RESERVED_QUERY_PARAMS = {
37
    'id': 'id',
38
    'name': 'name',
39
    'sort': 'order_by'
40
}
41
42
43
@six.add_metaclass(abc.ABCMeta)
44
class ResourceController(rest.RestController):
45
    model = abc.abstractproperty
46
    access = abc.abstractproperty
47
    supported_filters = abc.abstractproperty
48
49
    # Default kwargs passed to "APIClass.from_model" method
50
    from_model_kwargs = {}
51
52
    # Maximum value of limit which can be specified by user
53
    max_limit = cfg.CONF.api.max_page_size
54
55
    # Default number of items returned per page if no limit is explicitly provided
56
    default_limit = 100
57
58
    query_options = {
59
        'sort': []
60
    }
61
62
    # A list of optional transformation functions for user provided filter values
63
    filter_transform_functions = {}
64
65
    # A list of attributes which can be specified using ?exclude_attributes filter
66
    valid_exclude_attributes = []
67
68
    # Method responsible for retrieving an instance of the corresponding model DB object
69
    # Note: This method should throw StackStormDBObjectNotFoundError if the corresponding DB
70
    # object doesn't exist
71
    get_one_db_method = None
72
73
    def __init__(self):
74
        self.supported_filters = copy.deepcopy(self.__class__.supported_filters)
75
        self.supported_filters.update(RESERVED_QUERY_PARAMS)
76
        self.get_one_db_method = self._get_by_name_or_id
77
78
    @jsexpose()
79
    def get_all(self, **kwargs):
80
        return self._get_all(**kwargs)
81
82
    @jsexpose(arg_types=[str])
83
    def get_one(self, id):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
84
        return self._get_one_by_id(id=id)
85
86
    def _get_all(self, exclude_fields=None, sort=None, offset=0, limit=None, query_options=None,
87
                 from_model_kwargs=None, **kwargs):
88
        """
89
        :param exclude_fields: A list of object fields to exclude.
90
        :type exclude_fields: ``list``
91
        """
92
        kwargs = copy.deepcopy(kwargs)
93
94
        exclude_fields = exclude_fields or []
95
        query_options = query_options if query_options else self.query_options
96
97
        # TODO: Why do we use comma delimited string, user can just specify
98
        # multiple values using ?sort=foo&sort=bar and we get a list back
99
        sort = sort.split(',') if sort else []
100
101
        db_sort_values = []
102
        for sort_key in sort:
103
            if sort_key.startswith('-'):
104
                direction = '-'
105
                sort_key = sort_key[1:]
106
            elif sort_key.startswith('+'):
107
                direction = '+'
108
                sort_key = sort_key[1:]
109
            else:
110
                direction = ''
111
112
            if sort_key not in self.supported_filters:
113
                # Skip unsupported sort key
114
                continue
115
116
            sort_value = direction + self.supported_filters[sort_key]
117
            db_sort_values.append(sort_value)
118
119
        default_sort_values = copy.copy(query_options.get('sort'))
120
        kwargs['sort'] = db_sort_values if db_sort_values else default_sort_values
121
122
        # TODO: To protect us from DoS, we need to make max_limit mandatory
123
        offset = int(offset)
124
125
        if limit and int(limit) > self.max_limit:
126
            # TODO: We should throw here, I don't like this.
127
            msg = 'Limit "%s" specified, maximum value is "%s"' % (limit, self.max_limit)
128
            raise ValueError(msg)
129
130
        eop = offset + int(limit) if limit else None
131
132
        filters = {}
133
        for k, v in six.iteritems(self.supported_filters):
134
            filter_value = kwargs.get(k, None)
135
136
            if not filter_value:
137
                continue
138
139
            value_transform_function = self.filter_transform_functions.get(k, None)
140
            value_transform_function = value_transform_function or (lambda value: value)
141
            filter_value = value_transform_function(value=filter_value)
142
143
            filters['__'.join(v.split('.'))] = filter_value
144
145
        extra = {
146
            'filters': filters,
147
            'sort': sort,
148
            'offset': offset,
149
            'limit': limit
150
        }
151
        LOG.info('GET all %s with filters=%s' % (pecan.request.path, filters), extra=extra)
152
153
        instances = self.access.query(exclude_fields=exclude_fields, **filters)
154
        if limit == 1:
155
            # Perform the filtering on the DB side
156
            instances = instances.limit(limit)
157
158
        if limit:
159
            pecan.response.headers['X-Limit'] = str(limit)
160
        pecan.response.headers['X-Total-Count'] = str(instances.count())
161
162
        from_model_kwargs = from_model_kwargs or {}
163
        from_model_kwargs.update(self._get_from_model_kwargs_for_request(request=pecan.request))
164
165
        result = []
166
        for instance in instances[offset:eop]:
167
            item = self.model.from_model(instance, **from_model_kwargs)
168
            result.append(item)
169
170
        return result
171
172
    def _get_one(self, id, exclude_fields=None):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
173
        # Note: This is here for backward compatibility reasons
174
        return self._get_one_by_id(id=id, exclude_fields=exclude_fields)
175
176
    def _get_one_by_id(self, id, exclude_fields=None, from_model_kwargs=None):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
177
        """
178
        :param exclude_fields: A list of object fields to exclude.
179
        :type exclude_fields: ``list``
180
        """
181
182
        LOG.info('GET %s with id=%s', pecan.request.path, id)
183
184
        instance = self._get_by_id(resource_id=id, exclude_fields=exclude_fields)
185
186
        if not instance:
187
            msg = 'Unable to identify resource with id "%s".' % id
188
            pecan.abort(http_client.NOT_FOUND, msg)
189
190
        from_model_kwargs = from_model_kwargs or {}
191
        from_model_kwargs.update(self._get_from_model_kwargs_for_request(request=pecan.request))
192
        result = self.model.from_model(instance, **from_model_kwargs)
193
        LOG.debug('GET %s with id=%s, client_result=%s', pecan.request.path, id, result)
194
195
        return result
196
197
    def _get_one_by_name_or_id(self, name_or_id, exclude_fields=None, from_model_kwargs=None):
198
        """
199
        :param exclude_fields: A list of object fields to exclude.
200
        :type exclude_fields: ``list``
201
        """
202
203
        LOG.info('GET %s with name_or_id=%s', pecan.request.path, name_or_id)
204
205
        instance = self._get_by_name_or_id(name_or_id=name_or_id, exclude_fields=exclude_fields)
206
207
        if not instance:
208
            msg = 'Unable to identify resource with name_or_id "%s".' % (name_or_id)
209
            pecan.abort(http_client.NOT_FOUND, msg)
210
211
        from_model_kwargs = from_model_kwargs or {}
212
        from_model_kwargs.update(self._get_from_model_kwargs_for_request(request=pecan.request))
213
        result = self.model.from_model(instance, **from_model_kwargs)
214
        LOG.debug('GET %s with name_or_id=%s, client_result=%s', pecan.request.path, id, result)
215
216
        return result
217
218
    def _get_one_by_pack_ref(self, pack_ref, exclude_fields=None, from_model_kwargs=None):
219
        LOG.info('GET %s with pack_ref=%s', pecan.request.path, pack_ref)
220
221
        instance = self._get_by_pack_ref(pack_ref=pack_ref, exclude_fields=exclude_fields)
222
223
        if not instance:
224
            msg = 'Unable to identify resource with pack_ref "%s".' % (pack_ref)
225
            pecan.abort(http_client.NOT_FOUND, msg)
226
227
        from_model_kwargs = from_model_kwargs or {}
228
        from_model_kwargs.update(self._get_from_model_kwargs_for_request(request=pecan.request))
229
        result = self.model.from_model(instance, **from_model_kwargs)
230
        LOG.debug('GET %s with pack_ref=%s, client_result=%s', pecan.request.path, id, result)
231
232
        return result
233
234
    def _get_by_id(self, resource_id, exclude_fields=None):
235
        try:
236
            resource_db = self.access.get(id=resource_id, exclude_fields=exclude_fields)
237
        except ValidationError:
238
            resource_db = None
239
240
        return resource_db
241
242
    def _get_by_name(self, resource_name, exclude_fields=None):
243
        try:
244
            resource_db = self.access.get(name=resource_name, exclude_fields=exclude_fields)
245
        except Exception:
246
            resource_db = None
247
248
        return resource_db
249
250
    def _get_by_pack_ref(self, pack_ref, exclude_fields=None):
251
        try:
252
            resource_db = self.access.get(pack=pack_ref, exclude_fields=exclude_fields)
253
        except Exception:
254
            resource_db = None
255
256
        return resource_db
257
258
    def _get_by_name_or_id(self, name_or_id, exclude_fields=None):
259
        """
260
        Retrieve resource object by an id of a name.
261
        """
262
        resource_db = self._get_by_id(resource_id=name_or_id, exclude_fields=exclude_fields)
263
264
        if not resource_db:
265
            # Try name
266
            resource_db = self._get_by_name(resource_name=name_or_id, exclude_fields=exclude_fields)
267
268
        if not resource_db:
269
            msg = 'Resource with a name or id "%s" not found' % (name_or_id)
270
            raise StackStormDBObjectNotFoundError(msg)
271
272
        return resource_db
273
274
    def _get_from_model_kwargs_for_request(self, request):
275
        """
276
        Retrieve kwargs which are passed to "LiveActionAPI.model" method.
277
278
        :param request: Pecan request object.
279
280
        :rtype: ``dict``
281
        """
282
        return self.from_model_kwargs
283
284
    def _get_one_by_scope_and_name(self, scope, name, from_model_kwargs=None):
285
        """
286
        Retrieve an item given scope and name. Only KeyValuePair now has concept of 'scope'.
287
288
        :param scope: Scope the key belongs to.
289
        :type scope: ``str``
290
291
        :param name: Name of the key.
292
        :type name: ``str``
293
        """
294
        instance = self.access.get_by_scope_and_name(scope=scope, name=name)
295
        if not instance:
296
            msg = 'KeyValuePair with name: %s and scope: %s not found in db.' % (name, scope)
297
            raise StackStormDBObjectNotFoundError(msg)
298
        from_model_kwargs = from_model_kwargs or {}
299
        result = self.model.from_model(instance, **from_model_kwargs)
300
        LOG.debug('GET with scope=%s and name=%s, client_result=%s', scope, name, result)
301
302
        return result
303
304
    def _validate_exclude_fields(self, exclude_fields):
305
        """
306
        Validate that provided exclude fields are valid.
307
        """
308
        if not exclude_fields:
309
            return exclude_fields
310
311
        for field in exclude_fields:
312
            if field not in self.valid_exclude_attributes:
313
                msg = 'Invalid or unsupported attribute specified: %s' % (field)
314
                raise ValueError(msg)
315
316
        return exclude_fields
317
318
319
class ContentPackResourceController(ResourceController):
320
    include_reference = False
321
322
    def __init__(self):
323
        super(ContentPackResourceController, self).__init__()
324
        self.get_one_db_method = self._get_by_ref_or_id
325
326
    @jsexpose(arg_types=[str])
327
    def get_one(self, ref_or_id):
328
        return self._get_one(ref_or_id)
329
330
    @jsexpose()
331
    def get_all(self, **kwargs):
332
        return self._get_all(**kwargs)
333
334
    def _get_one(self, ref_or_id, exclude_fields=None):
335
        LOG.info('GET %s with ref_or_id=%s', pecan.request.path, ref_or_id)
336
337
        try:
338
            instance = self._get_by_ref_or_id(ref_or_id=ref_or_id, exclude_fields=exclude_fields)
339
        except Exception as e:
340
            LOG.exception(e.message)
341
            pecan.abort(http_client.NOT_FOUND, e.message)
342
            return
343
344
        from_model_kwargs = self._get_from_model_kwargs_for_request(request=pecan.request)
345
        result = self.model.from_model(instance, **from_model_kwargs)
346
        if result and self.include_reference:
347
            pack = getattr(result, 'pack', None)
348
            name = getattr(result, 'name', None)
349
            result.ref = ResourceReference(pack=pack, name=name).ref
350
351
        LOG.debug('GET %s with ref_or_id=%s, client_result=%s',
352
                  pecan.request.path, ref_or_id, result)
353
354
        return result
355
356
    def _get_all(self, **kwargs):
357
        result = super(ContentPackResourceController, self)._get_all(**kwargs)
358
359
        if self.include_reference:
360
            for item in result:
361
                pack = getattr(item, 'pack', None)
362
                name = getattr(item, 'name', None)
363
                item.ref = ResourceReference(pack=pack, name=name).ref
364
365
        return result
366
367
    def _get_by_ref_or_id(self, ref_or_id, exclude_fields=None):
368
        """
369
        Retrieve resource object by an id of a reference.
370
371
        Note: This method throws StackStormDBObjectNotFoundError exception if the object is not
372
        found in the database.
373
        """
374
375
        if ResourceReference.is_resource_reference(ref_or_id):
376
            # references always contain a dot and id's can't contain it
377
            is_reference = True
378
        else:
379
            is_reference = False
380
381
        if is_reference:
382
            resource_db = self._get_by_ref(resource_ref=ref_or_id, exclude_fields=exclude_fields)
383
        else:
384
            resource_db = self._get_by_id(resource_id=ref_or_id, exclude_fields=exclude_fields)
385
386
        if not resource_db:
387
            msg = 'Resource with a reference or id "%s" not found' % (ref_or_id)
388
            raise StackStormDBObjectNotFoundError(msg)
389
390
        return resource_db
391
392
    def _get_by_ref(self, resource_ref, exclude_fields=None):
393
        try:
394
            ref = ResourceReference.from_string_reference(ref=resource_ref)
395
        except Exception:
396
            return None
397
398
        resource_db = self.access.query(name=ref.name, pack=ref.pack,
399
                                        exclude_fields=exclude_fields).first()
400
        return resource_db
401
402
    def _get_filters(self, **kwargs):
403
        filters = copy.deepcopy(kwargs)
404
        ref = filters.get('ref', None)
405
406
        if ref:
407
            try:
408
                ref_obj = ResourceReference.from_string_reference(ref=ref)
409
            except InvalidResourceReferenceError:
410
                raise
411
412
            filters['name'] = ref_obj.name
413
            filters['pack'] = ref_obj.pack
414
            del filters['ref']
415
416
        return filters
417