Completed
Pull Request — master (#2842)
by Edward
04:45
created

search_pack_index()   F

Complexity

Conditions 11

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 11
dl 0
loc 30
rs 3.1764
c 7
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like search_pack_index() 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 itertools
17
import json
18
19
import requests
20
import six
21
from oslo_config import cfg
22
23
from st2common import log as logging
24
from st2common.persistence.pack import Pack
25
26
__all__ = [
27
    'get_pack_by_ref',
28
    'fetch_pack_index',
29
    'get_pack_from_index',
30
    'search_pack_index'
31
]
32
33
EXCLUDE_FIELDS = [
34
    "repo_url",
35
    "email"
36
]
37
38
SEARCH_PRIORITY = [
39
    "name",
40
    "keywords"
41
]
42
43
LOG = logging.getLogger(__name__)
44
45
46
def _build_index_list(index_url):
47
    if not index_url:
48
        # Reversing the indexes list from config so that the indexes have
49
        # descending (left-to-right) priority.
50
        # When multiple indexes have a pack with a given name, the index
51
        # that comes first in the list will be used.
52
        index_urls = cfg.CONF.content.index_url[::-1]
53
    elif isinstance(index_url, str):
54
        index_urls = [index_url]
55
    elif hasattr(index_url, '__iter__'):
56
        index_urls = index_url
57
    else:
58
        raise TypeError('"index_url" should either be a string or an iterable object.')
59
    return index_urls
60
61
62
def _fetch_and_compile_index(index_urls, logger=None):
63
    """
64
    Go through the index list and compile results into a single object.
65
    """
66
    status = []
67
    index = {}
68
69
    for index_url in index_urls:
70
71
        index_status = {
72
            'url': index_url,
73
            'packs': 0,
74
            'message': None,
75
            'error': None,
76
        }
77
        index_json = None
78
79
        try:
80
            request = requests.get(index_url)
81
            request.raise_for_status()
82
            index_json = request.json()
83
        except ValueError as e:
84
            index_status['error'] = 'malformed'
85
            index_status['message'] = repr(e)
86
        except requests.exceptions.RequestException as e:
87
            index_status['error'] = 'unresponsive'
88
            index_status['message'] = repr(e)
89
90
        if index_json == {}:
91
            index_status['error'] = 'empty'
92
            index_status['message'] = 'The index URL returned an empty object.'
93
        elif type(index_json) is list:
94
            index_status['error'] = 'malformed'
95
            index_status['message'] = 'Expected an index object, got a list instead.'
96
97
        if index_status['error']:
98
            logger.error("Index parsing error: %s" % json.dumps(index_status, indent=4))
99
        else:
100
            index_status['message'] = 'Success.'
101
            index_status['packs'] = len(index_json)
102
            index.update(index_json)
103
104
        status.append(index_status)
105
106
    return index, status
107
108
109
def get_pack_by_ref(pack_ref):
110
    """
111
    Retrieve PackDB by the provided reference.
112
    """
113
    pack_db = Pack.get_by_ref(pack_ref)
114
    return pack_db
115
116
117
def fetch_pack_index(index_url=None, logger=None, allow_empty=False):
118
    """
119
    Fetch the pack indexes (either from the config or provided as an argument)
120
    and return the object.
121
    """
122
    logger = logger or LOG
123
124
    index_urls = _build_index_list(index_url)
125
    index, status = _fetch_and_compile_index(index_urls, logger)
126
127
    # If one of the indexes on the list is unresponsive, we do not throw
128
    # immediately. The only case where an exception is raised is when no
129
    # results could be obtained from all listed indexes.
130
    # This behavior allows for mirrors / backups and handling connection
131
    # or network issues in one of the indexes.
132
    if not index and not allow_empty:
133
        raise ValueError("No results from the %s: tried %s.\nStatus: %s" % (
134
            ("index" if len(index_urls) == 1 else "indexes"),
135
            ", ".join(index_urls),
136
            json.dumps(status, indent=4)
137
        ))
138
    return (index, status)
139
140
141
def get_pack_from_index(pack):
142
    """
143
    Search index by pack name.
144
    Returns a pack.
145
    """
146
    if not pack:
147
        raise ValueError("Pack name must be specified.")
148
149
    index, _ = fetch_pack_index()
150
151
    return index.get(pack)
152
153
154
def search_pack_index(query, exclude=None, priority=None):
155
    """
156
    Search the pack index by query.
157
    Returns a list of matches for a query.
158
    """
159
    if not query:
160
        raise ValueError("Query must be specified.")
161
162
    if not exclude:
163
        exclude = EXCLUDE_FIELDS
164
    if not priority:
165
        priority = SEARCH_PRIORITY
166
167
    index, _ = fetch_pack_index()
168
169
    matches = [[] for i in range(len(priority) + 1)]
170
    for pack in six.itervalues(index):
171
172
        for key, value in six.iteritems(pack):
173
            if not hasattr(value, '__contains__'):
174
                value = str(value)
175
176
            if key not in exclude and query in value:
177
                if key in priority:
178
                    matches[priority.index(key)].append(pack)
179
                else:
180
                    matches[-1].append(pack)
181
                break
182
183
    return list(itertools.chain.from_iterable(matches))
184