Passed
Push — develop ( b7e2c3...d0345b )
by Plexxi
08:11 queued 04:18
created

ResourceManager.query()   D

Complexity

Conditions 10

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 10
dl 0
loc 22
rs 4.5957

How to fix   Complexity   

Complexity

Complex classes like ResourceManager.query() 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
import os
17
import json
18
import logging
19
from functools import wraps
20
21
import six
22
23
from six.moves import urllib
24
from st2client.utils import httpclient
25
26
27
LOG = logging.getLogger(__name__)
28
29
30
def add_auth_token_to_kwargs_from_env(func):
31
    @wraps(func)
32
    def decorate(*args, **kwargs):
33
        if not kwargs.get('token') and os.environ.get('ST2_AUTH_TOKEN', None):
34
            kwargs['token'] = os.environ.get('ST2_AUTH_TOKEN')
35
        return func(*args, **kwargs)
36
    return decorate
37
38
39
class Resource(object):
40
41
    # An alias to use for the resource if different than the class name.
42
    _alias = None
43
44
    # Display name of the resource. This may be different than its resource
45
    # name specifically when the resource name is composed of multiple words.
46
    _display_name = None
47
48
    # URL path for the resource.
49
    _url_path = None
50
51
    # Plural form of the resource name. This will be used to build the
52
    # latter part of the REST URL.
53
    _plural = None
54
55
    # Plural form of the resource display name.
56
    _plural_display_name = None
57
58
    # A list of class attributes which will be included in __repr__ return value
59
    _repr_attributes = []
60
61
    def __init__(self, *args, **kwargs):
62
        for k, v in six.iteritems(kwargs):
63
            setattr(self, k, v)
64
65
    def to_dict(self, exclude_attributes=None):
66
        """
67
        Return a dictionary representation of this object.
68
69
        :param exclude_attributes: Optional list of attributes to exclude.
70
        :type exclude_attributes: ``list``
71
72
        :rtype: ``dict``
73
        """
74
        exclude_attributes = exclude_attributes or []
75
76
        attributes = self.__dict__.keys()
77
        attributes = [attr for attr in attributes if not attr.startswith('__') and
78
                      attr not in exclude_attributes]
79
80
        result = {}
81
        for attribute in attributes:
82
            value = getattr(self, attribute, None)
83
            result[attribute] = value
84
85
        return result
86
87
    @classmethod
88
    def get_alias(cls):
89
        return cls._alias if cls._alias else cls.__name__
90
91
    @classmethod
92
    def get_display_name(cls):
93
        return cls._display_name if cls._display_name else cls.__name__
94
95
    @classmethod
96
    def get_plural_name(cls):
97
        if not cls._plural:
98
            raise Exception('The %s class is missing class attributes '
99
                            'in its definition.' % cls.__name__)
100
        return cls._plural
101
102
    @classmethod
103
    def get_plural_display_name(cls):
104
        return (cls._plural_display_name
105
                if cls._plural_display_name
106
                else cls._plural)
107
108
    @classmethod
109
    def get_url_path_name(cls):
110
        if cls._url_path:
111
            return cls._url_path
112
113
        return cls.get_plural_name().lower()
114
115
    def serialize(self):
116
        return dict((k, v)
117
                    for k, v in six.iteritems(self.__dict__)
118
                    if not k.startswith('_'))
119
120
    @classmethod
121
    def deserialize(cls, doc):
122
        if type(doc) is not dict:
123
            doc = json.loads(doc)
124
        return cls(**doc)
125
126
    def __str__(self):
127
        return str(self.__repr__())
128
129
    def __repr__(self):
130
        if not self._repr_attributes:
131
            return super(Resource, self).__repr__()
132
133
        attributes = []
134
        for attribute in self._repr_attributes:
135
            value = getattr(self, attribute, None)
136
            attributes.append('%s=%s' % (attribute, value))
137
138
        attributes = ','.join(attributes)
139
        class_name = self.__class__.__name__
140
        result = '<%s %s>' % (class_name, attributes)
141
        return result
142
143
144
class ResourceManager(object):
145
146
    def __init__(self, resource, endpoint, cacert=None, debug=False):
147
        self.resource = resource
148
        self.debug = debug
149
        self.client = httpclient.HTTPClient(endpoint, cacert=cacert, debug=debug)
150
151
    @staticmethod
152
    def handle_error(response):
153
        try:
154
            content = response.json()
155
            fault = content.get('faultstring', '') if content else ''
156
            if fault:
157
                response.reason += '\nMESSAGE: %s' % fault
158
        except Exception as e:
159
            response.reason += ('\nUnable to retrieve detailed message '
160
                                'from the HTTP response. %s\n' % str(e))
161
        response.raise_for_status()
162
163
    @add_auth_token_to_kwargs_from_env
164
    def get_all(self, **kwargs):
165
        # TODO: This is ugly, stop abusing kwargs
166
        url = '/%s' % self.resource.get_url_path_name()
167
        limit = kwargs.pop('limit', None)
168
        pack = kwargs.pop('pack', None)
169
        prefix = kwargs.pop('prefix', None)
170
        user = kwargs.pop('user', None)
171
172
        params = kwargs.pop('params', {})
173
        if limit and limit <= 0:
174
            limit = None
175
        if limit:
176
            params['limit'] = limit
177
178
        if pack:
179
            params['pack'] = pack
180
181
        if prefix:
182
            params['prefix'] = prefix
183
184
        if user:
185
            params['user'] = user
186
187
        response = self.client.get(url=url, params=params, **kwargs)
188
        if response.status_code != 200:
189
            self.handle_error(response)
190
        return [self.resource.deserialize(item)
191
                for item in response.json()]
192
193
    @add_auth_token_to_kwargs_from_env
194
    def get_by_id(self, id, **kwargs):
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...
195
        url = '/%s/%s' % (self.resource.get_url_path_name(), id)
196
        response = self.client.get(url, **kwargs)
197
        if response.status_code == 404:
198
            return None
199
        if response.status_code != 200:
200
            self.handle_error(response)
201
        return self.resource.deserialize(response.json())
202
203
    @add_auth_token_to_kwargs_from_env
204
    def get_property(self, id_, property_name, self_deserialize=True, **kwargs):
205
        """
206
        Gets a property of a Resource.
207
        id_ : Id of the resource
208
        property_name: Name of the property
209
        self_deserialize: #Implies use the deserialize method implemented by this resource.
210
        """
211
        token = None
212
        if kwargs:
213
            token = kwargs.pop('token', None)
214
215
            url = '/%s/%s/%s/?%s' % (self.resource.get_url_path_name(), id_, property_name,
216
                                     urllib.parse.urlencode(kwargs))
217
        else:
218
            url = '/%s/%s/%s/' % (self.resource.get_url_path_name(), id_, property_name)
219
220
        response = self.client.get(url, token=token) if token else self.client.get(url)
221
222
        if response.status_code == 404:
223
            return None
224
        if response.status_code != 200:
225
            self.handle_error(response)
226
227
        if self_deserialize:
228
            return [self.resource.deserialize(item) for item in response.json()]
229
        else:
230
            return response.json()
231
232
    @add_auth_token_to_kwargs_from_env
233
    def get_by_ref_or_id(self, ref_or_id, **kwargs):
234
        return self.get_by_id(id=ref_or_id, **kwargs)
235
236
    @add_auth_token_to_kwargs_from_env
237
    def query(self, **kwargs):
238
        if not kwargs:
239
            raise Exception('Query parameter is not provided.')
240
        if 'limit' in kwargs and kwargs.get('limit') <= 0:
241
            kwargs.pop('limit')
242
        token = kwargs.get('token', None)
243
        params = kwargs.get('params', {})
244
        print(params)
245
        for k, v in six.iteritems(kwargs):
246
            if k != 'token':
247
                params[k] = v
248
        url = '/%s/?%s' % (self.resource.get_url_path_name(),
249
                           urllib.parse.urlencode(params))
250
        response = self.client.get(url, token=token) if token else self.client.get(url)
251
        if response.status_code == 404:
252
            return []
253
        if response.status_code != 200:
254
            self.handle_error(response)
255
        items = response.json()
256
        instances = [self.resource.deserialize(item) for item in items]
257
        return instances
258
259
    @add_auth_token_to_kwargs_from_env
260
    def get_by_name(self, name_or_id, **kwargs):
261
        instances = self.query(name=name_or_id, **kwargs)
262
        if not instances:
263
            return None
264
        else:
265
            if len(instances) > 1:
266
                raise Exception('More than one %s named "%s" are found.' %
267
                                (self.resource.__name__.lower(), name_or_id))
268
            return instances[0]
269
270
    @add_auth_token_to_kwargs_from_env
271
    def create(self, instance, **kwargs):
272
        url = '/%s' % self.resource.get_url_path_name()
273
        response = self.client.post(url, instance.serialize(), **kwargs)
274
        if response.status_code != 200:
275
            self.handle_error(response)
276
        instance = self.resource.deserialize(response.json())
277
        return instance
278
279
    @add_auth_token_to_kwargs_from_env
280
    def update(self, instance, **kwargs):
281
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance.id)
282
        response = self.client.put(url, instance.serialize(), **kwargs)
283
        if response.status_code != 200:
284
            self.handle_error(response)
285
        instance = self.resource.deserialize(response.json())
286
        return instance
287
288
    @add_auth_token_to_kwargs_from_env
289
    def delete(self, instance, **kwargs):
290
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance.id)
291
        response = self.client.delete(url, **kwargs)
292
293
        if response.status_code not in [200, 204, 404]:
294
            self.handle_error(response)
295
            return False
296
297
        return True
298
299
    @add_auth_token_to_kwargs_from_env
300
    def delete_by_id(self, instance_id, **kwargs):
301
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance_id)
302
        response = self.client.delete(url, **kwargs)
303
        if response.status_code not in [200, 204, 404]:
304
            self.handle_error(response)
305
            return False
306
        try:
307
            resp_json = response.json()
308
            if resp_json:
309
                return resp_json
310
        except:
311
            pass
312
        return True
313
314
315
class ActionAliasResourceManager(ResourceManager):
316
    def __init__(self, resource, endpoint, cacert=None, debug=False):
0 ignored issues
show
Bug introduced by
The __init__ method of the super-class ResourceManager is not called.

It is generally advisable to initialize the super-class by calling its __init__ method:

class SomeParent:
    def __init__(self):
        self.x = 1

class SomeChild(SomeParent):
    def __init__(self):
        # Initialize the super class
        SomeParent.__init__(self)
Loading history...
317
        self.resource = resource
318
        self.debug = debug
319
        self.client = httpclient.HTTPClient(root=endpoint, cacert=cacert, debug=debug)
320
321
322
class LiveActionResourceManager(ResourceManager):
323
    @add_auth_token_to_kwargs_from_env
324
    def re_run(self, execution_id, parameters=None, tasks=None, no_reset=None, **kwargs):
325
        url = '/%s/%s/re_run' % (self.resource.get_url_path_name(), execution_id)
326
327
        tasks = tasks or []
328
        no_reset = no_reset or []
329
330
        if list(set(no_reset) - set(tasks)):
331
            raise ValueError('List of tasks to reset does not match the tasks to rerun.')
332
333
        data = {
334
            'parameters': parameters,
335
            'tasks': tasks,
336
            'reset': list(set(tasks) - set(no_reset))
337
        }
338
339
        response = self.client.post(url, data, **kwargs)
340
        if response.status_code != 200:
341
            self.handle_error(response)
342
343
        instance = self.resource.deserialize(response.json())
344
        return instance
345
346
347
class TriggerInstanceResourceManager(ResourceManager):
348
    @add_auth_token_to_kwargs_from_env
349
    def re_emit(self, trigger_instance_id, **kwargs):
350
        url = '/%s/%s/re_emit' % (self.resource.get_url_path_name(), trigger_instance_id)
351
        response = self.client.post(url, None)
352
        if response.status_code != 200:
353
            self.handle_error(response)
354
        return response.json()
355