Passed
Pull Request — 2.x (#1961)
by Jordi
05:13
created

senaite.core.api.geo   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 44
eloc 104
dl 0
loc 204
rs 8.8798
c 0
b 0
f 0

8 Functions

Rating   Name   Duplication   Size   Complexity  
A get_countries() 0 7 2
B get_country() 0 24 7
D get_subdivision() 0 49 13
A is_subdivision() 0 7 2
B get_subdivisions() 0 27 7
B get_country_or_subdivision() 0 30 8
A is_country() 0 7 2
A get_country_code() 0 12 3

How to fix   Complexity   

Complexity

Complex classes like senaite.core.api.geo 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
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2022 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import pycountry
22
from six import string_types
23
24
_marker = object()
25
26
27
def get_countries():
28
    """Return the list of countries sorted by name ascending
29
    :return: list of countries sorted by name ascending
30
    :rtype: list of Country objects
31
    """
32
    countries = pycountry.countries
33
    return sorted(list(countries), key=lambda s: s.name)
34
35
36
def get_country(thing, default=_marker):
37
    """Returns the country object
38
    :param thing: country, subdivision object or search term
39
    :type thing: Country/Subdivision/string
40
    :returns: the country that matches with the parameter passed in
41
    :rtype: pycountry.db.Country
42
    """
43
    if is_country(thing):
44
        return thing
45
46
    if is_subdivision(thing):
47
        return get_country(thing.country_code)
48
49
    if not isinstance(thing, string_types):
50
        if default is _marker:
51
            raise TypeError("{} is not supported".format(repr(thing)))
52
        return default
53
54
    try:
55
        return pycountry.countries.lookup(thing)
56
    except LookupError as e:
57
        if default is _marker:
58
            raise ValueError(str(e))
59
        return default
60
61
62
def get_country_code(thing, default=_marker):
63
    """Returns the 2-character code (alpha2) of the country
64
    :param thing: country, subdivision object or search term
65
    :return: the 2-character (alpha2) code of the country
66
    :rtype: string
67
    """
68
    thing = get_country_or_subdivision(thing, default=default)
69
    if is_country(thing):
70
        return thing.alpha_2
71
    if is_subdivision(thing):
72
        return thing.country_code
73
    return default
74
75
76
def get_subdivision(subdivision_or_term, parent=None, default=_marker):
77
    """Returns the Subdivision object
78
    :param subdivision_or_term: subdivision or search term
79
    :param subdivision_or_term: Subdivision/string
80
    :param parent: filter by parent subdivision or country
81
    :returns: the subdivision that matches with the parameter passed in
82
    :rtype: pycountry.db.Subdivision
83
    """
84
    if is_subdivision(subdivision_or_term):
85
        return subdivision_or_term
86
87
    if not isinstance(subdivision_or_term, string_types):
88
        if default is _marker:
89
            raise TypeError("{} is not supported".format(
90
                repr(subdivision_or_term)))
91
        return default
92
93
    if parent:
94
        # Search by parent
95
        def to_utf8(value):
96
            if isinstance(value, unicode):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable unicode does not seem to be defined.
Loading history...
97
                return value.encode("utf-8")
98
            return value
99
100
        def is_match(subdivision):
101
            terms = [subdivision.name, subdivision.code]
102
            needle = to_utf8(subdivision_or_term)
103
            return needle in map(to_utf8, terms)
104
105
        subdivisions = get_subdivisions(parent, default=[])
106
        subdivisions = filter(lambda subdiv: is_match(subdiv), subdivisions)
0 ignored issues
show
introduced by
The variable is_match does not seem to be defined for all execution paths.
Loading history...
107
        if len(subdivisions) == 1:
108
            return subdivisions[0]
109
        elif len(subdivisions) > 1:
110
            if default is _marker:
111
                raise ValueError("More than one subdivision found")
112
            return default
113
        else:
114
            if default is _marker:
115
                raise ValueError("No subdivisions found")
116
            return None
117
118
    # Search directly by term
119
    try:
120
        return pycountry.subdivisions.lookup(subdivision_or_term)
121
    except LookupError as e:
122
        if default is _marker:
123
            raise ValueError(str(e))
124
        return default
125
126
127
def is_country(thing):
128
    """Returns whether the value passed in is a country object
129
    """
130
    if not thing:
131
        return False
132
    # pycountry generates the classes dynamically, we cannot use isinstance
133
    return "Country" in repr(type(thing))
134
135
136
def is_subdivision(thing):
137
    """Returns whether the value passed in is a subdivision object
138
    """
139
    if not thing:
140
        return False
141
    # pycountry generates the classes dynamically, we cannot use isinstance
142
    return "Subdivision" in repr(type(thing))
143
144
145
def get_subdivisions(thing, default=_marker):
146
    """Returns the first-level subdivisions of the country or subdivision,
147
    sorted by code ascending
148
    :param thing: country, subdivision object or search term
149
    :return: the list of first-level subdivisions of the subdivision/country
150
    :rtype: list of pycountry.db.Subdivision
151
    """
152
    try:
153
        country_or_subdivision = get_country_or_subdivision(thing)
154
        country_code = get_country_code(country_or_subdivision)
155
    except (ValueError, TypeError) as e:
156
        if default is _marker:
157
            raise e
158
        return default
159
160
    # Extract the subdivisions
161
    subdivisions = pycountry.subdivisions.get(country_code=country_code)
162
163
    # Bail out those that are not first-level
164
    if is_subdivision(country_or_subdivision):
165
        code = country_or_subdivision.code
166
        subdivisions = filter(lambda sub: sub.parent_code == code, subdivisions)
0 ignored issues
show
introduced by
The variable code does not seem to be defined for all execution paths.
Loading history...
167
    else:
168
        subdivisions = filter(lambda sub: sub.parent_code is None, subdivisions)
169
170
    # Sort by code
171
    return sorted(subdivisions, key=lambda s: s.code)
172
173
174
def get_country_or_subdivision(thing, default=_marker):
175
    """Returns the country or subdivision for the thing passed-in
176
    :param thing: the thing or search term to look for a country or subdivision
177
    :type thing: Country/Subdivision/string
178
    :return: the country or subdivision for the given thing
179
    """
180
    if is_country(thing):
181
        return thing
182
    if is_subdivision(thing):
183
        return thing
184
185
    if not isinstance(thing, string_types):
186
        if default is _marker:
187
            raise TypeError("{} is not supported".format(repr(thing)))
188
        return default
189
190
    # Maybe a country
191
    country = get_country(thing, default=None)
192
    if country:
193
        return country
194
195
    # Maybe a subdivision
196
    subdivision = get_subdivision(thing, default=None)
197
    if subdivision:
198
        return subdivision
199
200
    if default is _marker:
201
        raise ValueError("Could not find a record for '{}'".format(
202
            thing.lower()))
203
    return default
204