Completed
Pull Request — master (#2842)
by Edward
05:07
created

search_pack_index()   F

Complexity

Conditions 11

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 11
c 5
b 0
f 0
dl 0
loc 31
rs 3.1764

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
18
import requests
19
import six
20
from oslo_config import cfg
21
22
from st2common import log as logging
23
from st2common.models.api.pack import PackAPI
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 get_pack_by_ref(pack_ref):
47
    """
48
    Retrieve PackDB by the provided reference.
49
    """
50
    pack_db = Pack.get_by_ref(pack_ref)
51
    return pack_db
52
53
54
def fetch_pack_index(index_url=None, logger=None):
55
    """
56
    Fetch the pack indexes (either from the config or provided as an argument)
57
    and return the object.
58
    """
59
    logger = logger or LOG
60
61
    if not index_url:
62
        # Reversing the indexes list from config so that the indexes have
63
        # descending (left-to-right) priority.
64
        # When multiple indexes have a pack with a given name, the index
65
        # that comes first in the list will be used.
66
        index_urls = cfg.CONF.content.index_url[::-1]
67
    elif isinstance(index_url, str):
68
        index_urls = [index_url]
69
    elif hasattr(index_url, '__iter__'):
70
        index_urls = index_url
71
    else:
72
        raise TypeError('"index_url" should either be a string or an iterable object.')
73
74
    errors = []
75
    result = {}
76
    for index_url in index_urls:
77
        try:
78
            result.update(requests.get(index_url).json())
79
        except ValueError:
80
            errors.append(index_url)
81
            logger.debug('Malformed index: %s' % index_url)
82
        except requests.exceptions.RequestException:
83
            errors.append(index_url)
84
            logger.debug('Could not fetch index: %s' % index_url)
85
    # If one of the indexes on the list is unresponsive, we do not throw
86
    # immediately. The only case where an exception is raised is when no
87
    # results could be obtained from all listed indexes.
88
    # This behavior allows for mirrors / backups and handling connection
89
    # or network issues in one of the indexes.
90
    if not result:
91
        raise ValueError("Malformed or empty %s: could not get results from %s." % (
92
            ("index" if len(errors) == 1 else "indexes"), ", ".join(errors)
93
        ))
94
    return result
95
96
97
def get_pack_from_index(pack):
98
    """
99
    Search index by pack name.
100
    Returns a pack.
101
    """
102
    if not pack:
103
        raise ValueError("Pack name must be specified.")
104
105
    index = fetch_pack_index()
106
107
    return PackAPI(**index.get(pack))
108
109
110
def search_pack_index(query, exclude=None, priority=None):
111
    """
112
    Search the pack index by query.
113
    Returns a list of matches for a query.
114
    """
115
    if not query:
116
        raise ValueError("Query must be specified.")
117
118
    if not exclude:
119
        exclude = EXCLUDE_FIELDS
120
    if not priority:
121
        priority = SEARCH_PRIORITY
122
123
    index = fetch_pack_index()
124
125
    matches = [[] for _ in range(len(priority) + 1)]
126
    for pack_dict in six.itervalues(index):
127
        pack = PackAPI(**pack_dict)
128
129
        for key, value in six.iteritems(vars(pack)):
130
            if not hasattr(value, '__contains__'):
131
                value = str(value)
132
133
            if key not in exclude and query in value:
134
                if key in priority:
135
                    matches[priority.index(key)].append(pack)
136
                else:
137
                    matches[-1].append(pack)
138
                break
139
140
    return list(itertools.chain.from_iterable(matches))
141