Passed
Pull Request — 2.x (#1961)
by Ramon
05:40
created

senaite.core.api.geo   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 202
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
eloc 101
dl 0
loc 202
rs 8.96
c 0
b 0
f 0

8 Functions

Rating   Name   Duplication   Size   Complexity  
B get_country() 0 24 7
D get_subdivision() 0 45 12
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_countries() 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
from six import string_types
22
23
import pycountry
24
from bika.lims.api import to_utf8
25
26
_marker = object()
27
28
29
def get_countries():
30
    """Return the list of countries sorted by name ascending
31
    :return: list of countries sorted by name ascending
32
    :rtype: list of Country objects
33
    """
34
    countries = pycountry.countries
35
    return sorted(list(countries), key=lambda s: s.name)
36
37
38
def get_country(thing, default=_marker):
39
    """Returns the country object
40
    :param thing: country, subdivision object or search term
41
    :type thing: Country/Subdivision/string
42
    :returns: the country that matches with the parameter passed in
43
    :rtype: pycountry.db.Country
44
    """
45
    if is_country(thing):
46
        return thing
47
48
    if is_subdivision(thing):
49
        return get_country(thing.country_code)
50
51
    if not isinstance(thing, string_types):
52
        if default is _marker:
53
            raise TypeError("{} is not supported".format(repr(thing)))
54
        return default
55
56
    try:
57
        return pycountry.countries.lookup(thing)
58
    except LookupError as e:
59
        if default is _marker:
60
            raise ValueError(str(e))
61
        return default
62
63
64
def get_country_code(thing, default=_marker):
65
    """Returns the 2-character code (alpha2) of the country
66
    :param thing: country, subdivision object or search term
67
    :return: the 2-character (alpha2) code of the country
68
    :rtype: string
69
    """
70
    thing = get_country_or_subdivision(thing, default=default)
71
    if is_country(thing):
72
        return thing.alpha_2
73
    if is_subdivision(thing):
74
        return thing.country_code
75
    return default
76
77
78
def get_subdivision(subdivision_or_term, parent=None, default=_marker):
79
    """Returns the Subdivision object
80
    :param subdivision_or_term: subdivision or search term
81
    :param subdivision_or_term: Subdivision/string
82
    :param parent: filter by parent subdivision or country
83
    :returns: the subdivision that matches with the parameter passed in
84
    :rtype: pycountry.db.Subdivision
85
    """
86
    if is_subdivision(subdivision_or_term):
87
        return subdivision_or_term
88
89
    if not isinstance(subdivision_or_term, string_types):
90
        if default is _marker:
91
            raise TypeError("{} is not supported".format(
92
                repr(subdivision_or_term)))
93
        return default
94
95
    # Search by parent
96
    if parent:
97
98
        def is_match(subdivision):
99
            terms = [subdivision.name, subdivision.code]
100
            needle = to_utf8(subdivision_or_term)
101
            return needle in map(to_utf8, terms)
102
103
        subdivisions = get_subdivisions(parent, default=[])
104
        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...
105
        if len(subdivisions) == 1:
106
            return subdivisions[0]
107
        elif len(subdivisions) > 1:
108
            if default is _marker:
109
                raise ValueError("More than one subdivision found")
110
            return default
111
        else:
112
            if default is _marker:
113
                raise ValueError("No subdivisions found")
114
            return None
115
116
    # Search directly by term
117
    try:
118
        return pycountry.subdivisions.lookup(subdivision_or_term)
119
    except LookupError as e:
120
        if default is _marker:
121
            raise ValueError(str(e))
122
        return default
123
124
125
def is_country(thing):
126
    """Returns whether the value passed in is a country object
127
    """
128
    if not thing:
129
        return False
130
    # pycountry generates the classes dynamically, we cannot use isinstance
131
    return "Country" in repr(type(thing))
132
133
134
def is_subdivision(thing):
135
    """Returns whether the value passed in is a subdivision object
136
    """
137
    if not thing:
138
        return False
139
    # pycountry generates the classes dynamically, we cannot use isinstance
140
    return "Subdivision" in repr(type(thing))
141
142
143
def get_subdivisions(thing, default=_marker):
144
    """Returns the first-level subdivisions of the country or subdivision,
145
    sorted by code ascending
146
    :param thing: country, subdivision object or search term
147
    :return: the list of first-level subdivisions of the subdivision/country
148
    :rtype: list of pycountry.db.Subdivision
149
    """
150
    try:
151
        country_or_subdivision = get_country_or_subdivision(thing)
152
        country_code = get_country_code(country_or_subdivision)
153
    except (ValueError, TypeError) as e:
154
        if default is _marker:
155
            raise e
156
        return default
157
158
    # Extract the subdivisions
159
    subdivisions = pycountry.subdivisions.get(country_code=country_code)
160
161
    # Bail out those that are not first-level
162
    if is_subdivision(country_or_subdivision):
163
        code = country_or_subdivision.code
164
        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...
165
    else:
166
        subdivisions = filter(lambda sub: sub.parent_code is None, subdivisions)
167
168
    # Sort by code
169
    return sorted(subdivisions, key=lambda s: s.code)
170
171
172
def get_country_or_subdivision(thing, default=_marker):
173
    """Returns the country or subdivision for the thing passed-in
174
    :param thing: the thing or search term to look for a country or subdivision
175
    :type thing: Country/Subdivision/string
176
    :return: the country or subdivision for the given thing
177
    """
178
    if is_country(thing):
179
        return thing
180
    if is_subdivision(thing):
181
        return thing
182
183
    if not isinstance(thing, string_types):
184
        if default is _marker:
185
            raise TypeError("{} is not supported".format(repr(thing)))
186
        return default
187
188
    # Maybe a country
189
    country = get_country(thing, default=None)
190
    if country:
191
        return country
192
193
    # Maybe a subdivision
194
    subdivision = get_subdivision(thing, default=None)
195
    if subdivision:
196
        return subdivision
197
198
    if default is _marker:
199
        raise ValueError("Could not find a record for '{}'".format(
200
            thing.lower()))
201
    return default
202