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

MongoDBAccess   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 147
rs 8.2769
c 0
b 0
f 0
wmc 41

20 Methods

Rating   Name   Duplication   Size   Complexity  
A get_by_id() 0 2 1
F _process_datetime_range_filters() 0 29 9
A get_by_pack() 0 2 1
A add_or_update() 0 3 1
A insert() 0 3 1
A get_by_uid() 0 2 1
A get_by_ref() 0 2 1
A aggregate() 0 2 1
A delete() 0 2 1
B get() 0 16 5
A _undo_dict_field_escape() 0 6 3
B _process_null_filters() 0 11 6
A get_by_name() 0 2 1
A __init__() 0 2 1
A query() 0 22 3
A update() 0 2 1
A distinct() 0 5 1
A get_all() 0 2 1
A delete_by_query() 0 6 1
A count() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like MongoDBAccess 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 copy
17
import importlib
18
import ssl as ssl_lib
19
20
import six
21
import mongoengine
22
from pymongo.errors import OperationFailure
23
24
from st2common import log as logging
25
from st2common.util import isotime
26
from st2common.models.db import stormbase
27
from st2common.models.utils.profiling import log_query_and_profile_data_for_queryset
28
from st2common.exceptions.db import StackStormDBObjectNotFoundError
29
30
31
LOG = logging.getLogger(__name__)
32
33
MODEL_MODULE_NAMES = [
34
    'st2common.models.db.auth',
35
    'st2common.models.db.action',
36
    'st2common.models.db.actionalias',
37
    'st2common.models.db.keyvalue',
38
    'st2common.models.db.execution',
39
    'st2common.models.db.executionstate',
40
    'st2common.models.db.liveaction',
41
    'st2common.models.db.pack',
42
    'st2common.models.db.policy',
43
    'st2common.models.db.rule',
44
    'st2common.models.db.runner',
45
    'st2common.models.db.sensor',
46
    'st2common.models.db.trigger',
47
]
48
49
50
def get_model_classes():
51
    """
52
    Retrieve a list of all the defined model classes.
53
54
    :rtype: ``list``
55
    """
56
    result = []
57
    for module_name in MODEL_MODULE_NAMES:
58
        module = importlib.import_module(module_name)
59
        model_classes = getattr(module, 'MODELS', [])
60
        result.extend(model_classes)
61
62
    return result
63
64
65
def db_setup(db_name, db_host, db_port, username=None, password=None, ensure_indexes=True,
66
             ssl=False, ssl_keyfile=None, ssl_certfile=None,
67
             ssl_cert_reqs=None, ssl_ca_certs=None, ssl_match_hostname=True):
68
    LOG.info('Connecting to database "%s" @ "%s:%s" as user "%s".',
69
             db_name, db_host, db_port, str(username))
70
71
    ssl_kwargs = _get_ssl_kwargs(ssl=ssl, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile,
72
                                 ssl_cert_reqs=ssl_cert_reqs, ssl_ca_certs=ssl_ca_certs,
73
                                 ssl_match_hostname=ssl_match_hostname)
74
75
    connection = mongoengine.connection.connect(db_name, host=db_host,
76
                                                port=db_port, tz_aware=True,
77
                                                username=username, password=password,
78
                                                **ssl_kwargs)
79
80
    # Create all the indexes upfront to prevent race-conditions caused by
81
    # lazy index creation
82
    if ensure_indexes:
83
        db_ensure_indexes()
84
85
    return connection
86
87
88
def db_ensure_indexes():
89
    """
90
    This function ensures that indexes for all the models have been created and the
91
    extra indexes cleaned up.
92
93
    Note #1: When calling this method database connection already needs to be
94
    established.
95
96
    Note #2: This method blocks until all the index have been created (indexes
97
    are created in real-time and not in background).
98
    """
99
    LOG.debug('Ensuring database indexes...')
100
    model_classes = get_model_classes()
101
102
    for model_class in model_classes:
103
        # Note: We need to ensure / create new indexes before removing extra ones
104
        LOG.debug('Ensuring indexes for model "%s"...' % (model_class.__name__))
105
        model_class.ensure_indexes()
106
107
        LOG.debug('Removing extra indexes for model "%s"...' % (model_class.__name__))
108
        removed_count = cleanup_extra_indexes(model_class=model_class)
109
        LOG.debug('Removed "%s" extra indexes for model "%s"' %
110
                  (removed_count, model_class.__name__))
111
112
113
def cleanup_extra_indexes(model_class):
114
    """
115
    Finds any extra indexes and removes those from mongodb.
116
    """
117
    extra_indexes = model_class.compare_indexes().get('extra', None)
118
    if not extra_indexes:
119
        return 0
120
121
    # mongoengine does not have the necessary method so we need to drop to
122
    # pymongo interfaces via some private methods.
123
    removed_count = 0
124
    c = model_class._get_collection()
125
    for extra_index in extra_indexes:
126
        try:
127
            c.drop_index(extra_index)
128
            LOG.debug('Dropped index %s for model %s.', extra_index, model_class.__name__)
129
            removed_count += 1
130
        except OperationFailure:
131
            LOG.warning('Attempt to cleanup index %s failed.', extra_index, exc_info=True)
132
133
    return removed_count
134
135
136
def db_teardown():
137
    mongoengine.connection.disconnect()
138
139
140
def _get_ssl_kwargs(ssl=False, ssl_keyfile=None, ssl_certfile=None, ssl_cert_reqs=None,
141
                    ssl_ca_certs=None, ssl_match_hostname=True):
142
    ssl_kwargs = {
143
        'ssl': ssl,
144
    }
145
    if ssl_keyfile:
146
        ssl_kwargs['ssl'] = True
147
        ssl_kwargs['ssl_keyfile'] = ssl_keyfile
148
    if ssl_certfile:
149
        ssl_kwargs['ssl'] = True
150
        ssl_kwargs['ssl_certfile'] = ssl_certfile
151
    if ssl_cert_reqs:
152
        if ssl_cert_reqs is 'none':
153
            ssl_cert_reqs = ssl_lib.CERT_NONE
154
        elif ssl_cert_reqs is 'optional':
155
            ssl_cert_reqs = ssl_lib.CERT_OPTIONAL
156
        elif ssl_cert_reqs is 'required':
157
            ssl_cert_reqs = ssl_lib.CERT_REQUIRED
158
        ssl_kwargs['ssl_cert_reqs'] = ssl_cert_reqs
159
    if ssl_ca_certs:
160
        ssl_kwargs['ssl'] = True
161
        ssl_kwargs['ssl_ca_certs'] = ssl_ca_certs
162
    if ssl_kwargs.get('ssl', False):
163
        # pass in ssl_match_hostname only if ssl is True. The right default value
164
        # for ssl_match_hostname in almost all cases is True.
165
        ssl_kwargs['ssl_match_hostname'] = ssl_match_hostname
166
    return ssl_kwargs
167
168
169
class MongoDBAccess(object):
170
    """Database object access class that provides general functions for a model type."""
171
172
    def __init__(self, model):
173
        self.model = model
174
175
    def get_by_name(self, value):
176
        return self.get(name=value, raise_exception=True)
177
178
    def get_by_id(self, value):
179
        return self.get(id=value, raise_exception=True)
180
181
    def get_by_uid(self, value):
182
        return self.get(uid=value, raise_exception=True)
183
184
    def get_by_ref(self, value):
185
        return self.get(ref=value, raise_exception=True)
186
187
    def get_by_pack(self, value):
188
        return self.get(pack=value, raise_exception=True)
189
190
    def get(self, exclude_fields=None, *args, **kwargs):
191
        raise_exception = kwargs.pop('raise_exception', False)
192
193
        instances = self.model.objects(**kwargs)
194
195
        if exclude_fields:
196
            instances = instances.exclude(*exclude_fields)
197
198
        instance = instances[0] if instances else None
199
        log_query_and_profile_data_for_queryset(queryset=instances)
200
201
        if not instance and raise_exception:
202
            msg = 'Unable to find the %s instance. %s' % (self.model.__name__, kwargs)
203
            raise StackStormDBObjectNotFoundError(msg)
204
205
        return instance
206
207
    def get_all(self, *args, **kwargs):
208
        return self.query(*args, **kwargs)
209
210
    def count(self, *args, **kwargs):
211
        result = self.model.objects(**kwargs).count()
212
        log_query_and_profile_data_for_queryset(queryset=result)
213
        return result
214
215
    def query(self, offset=0, limit=None, order_by=None, exclude_fields=None,
216
              **filters):
217
        order_by = order_by or []
218
        exclude_fields = exclude_fields or []
219
        eop = offset + int(limit) if limit else None
220
221
        # Process the filters
222
        # Note: Both of those functions manipulate "filters" variable so the order in which they
223
        # are called matters
224
        filters, order_by = self._process_datetime_range_filters(filters=filters, order_by=order_by)
225
        filters = self._process_null_filters(filters=filters)
226
227
        result = self.model.objects(**filters)
228
229
        if exclude_fields:
230
            result = result.exclude(*exclude_fields)
231
232
        result = result.order_by(*order_by)
233
        result = result[offset:eop]
234
        log_query_and_profile_data_for_queryset(queryset=result)
235
236
        return result
237
238
    def distinct(self, *args, **kwargs):
239
        field = kwargs.pop('field')
240
        result = self.model.objects(**kwargs).distinct(field)
241
        log_query_and_profile_data_for_queryset(queryset=result)
242
        return result
243
244
    def aggregate(self, *args, **kwargs):
245
        return self.model.objects(**kwargs)._collection.aggregate(*args, **kwargs)
246
247
    def insert(self, instance):
248
        instance = self.model.objects.insert(instance)
249
        return self._undo_dict_field_escape(instance)
250
251
    def add_or_update(self, instance):
252
        instance.save()
253
        return self._undo_dict_field_escape(instance)
254
255
    def update(self, instance, **kwargs):
256
        return instance.update(**kwargs)
257
258
    def delete(self, instance):
259
        return instance.delete()
260
261
    def delete_by_query(self, **query):
262
        qs = self.model.objects.filter(**query)
263
        qs.delete()
264
        log_query_and_profile_data_for_queryset(queryset=qs)
265
        # mongoengine does not return anything useful so cannot return anything meaningful.
266
        return None
267
268
    def _undo_dict_field_escape(self, instance):
269
        for attr, field in instance._fields.iteritems():
270
            if isinstance(field, stormbase.EscapedDictField):
271
                value = getattr(instance, attr)
272
                setattr(instance, attr, field.to_python(value))
273
        return instance
274
275
    def _process_null_filters(self, filters):
276
        result = copy.deepcopy(filters)
277
278
        null_filters = {k: v for k, v in six.iteritems(filters)
279
                        if v is None or (type(v) in [str, unicode] and str(v.lower()) == 'null')}
280
281
        for key in null_filters.keys():
282
            result['%s__exists' % (key)] = False
283
            del result[key]
284
285
        return result
286
287
    def _process_datetime_range_filters(self, filters, order_by=None):
288
        ranges = {k: v for k, v in filters.iteritems()
289
                  if type(v) in [str, unicode] and '..' in v}
290
291
        order_by_list = copy.deepcopy(order_by) if order_by else []
292
        for k, v in ranges.iteritems():
293
            values = v.split('..')
294
            dt1 = isotime.parse(values[0])
295
            dt2 = isotime.parse(values[1])
296
297
            k__gte = '%s__gte' % k
298
            k__lte = '%s__lte' % k
299
            if dt1 < dt2:
300
                query = {k__gte: dt1, k__lte: dt2}
301
                sort_key, reverse_sort_key = k, '-' + k
302
            else:
303
                query = {k__gte: dt2, k__lte: dt1}
304
                sort_key, reverse_sort_key = '-' + k, k
305
            del filters[k]
306
            filters.update(query)
307
308
            if reverse_sort_key in order_by_list:
309
                idx = order_by_list.index(reverse_sort_key)
310
                order_by_list.pop(idx)
311
                order_by_list.insert(idx, sort_key)
312
            elif sort_key not in order_by_list:
313
                order_by_list = [sort_key] + order_by_list
314
315
        return filters, order_by_list
316