benedict.dicts.parse.parse_util   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 62
eloc 159
dl 0
loc 220
rs 3.44
c 0
b 0
f 0

26 Functions

Rating   Name   Duplication   Size   Complexity  
A parse_uuid() 0 3 2
A parse_slug() 0 3 1
A _parse_slug() 0 2 1
A _parse_email() 0 9 4
A _parse_int() 0 5 2
A _parse_decimal() 0 5 2
A parse_int() 0 2 1
A parse_str() 0 8 2
A parse_date() 0 5 2
A _parse_datetime_with_format() 0 5 2
A parse_dict() 0 2 1
A parse_email() 0 2 1
A _parse_datetime_from_timestamp() 0 5 2
A parse_datetime() 0 8 3
A _parse_phonenumber() 0 16 3
A _parse_bool() 0 7 3
A parse_phonenumber() 0 11 5
B _parse_list() 0 19 8
A _parse_float() 0 5 2
A parse_float() 0 2 1
A parse_bool() 0 2 1
A _parse_datetime_without_format() 0 5 2
A _parse_dict() 0 9 3
A parse_decimal() 0 2 1
A _parse_with() 0 9 5
A parse_list() 0 3 2

How to fix   Complexity   

Complexity

Complex classes like benedict.dicts.parse.parse_util 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
import re
2
from datetime import datetime
3
from decimal import Decimal, DecimalException
4
5
import ftfy
6
import phonenumbers
7
from dateutil import parser as date_parser
8
from MailChecker import MailChecker
9
from phonenumbers import PhoneNumberFormat, phonenumberutil
10
from slugify import slugify
11
12
from benedict.serializers import JSONSerializer
13
from benedict.utils import type_util
14
15
16
def _parse_with(val, type_checker, parser, **kwargs):
17
    if val is None:
18
        return None
19
    if callable(type_checker) and type_checker(val):
20
        return val
21
    s = str(val)
22
    if not len(s):
23
        return None
24
    return parser(s, **kwargs)
25
26
27
def _parse_bool(val):
28
    val = val.lower()
29
    if val in ["1", "true", "yes", "ok", "on"]:
30
        return True
31
    elif val in ["0", "false", "no", "ko", "off"]:
32
        return False
33
    return None
34
35
36
def parse_bool(val):
37
    return _parse_with(val, type_util.is_bool, _parse_bool)
38
39
40
def parse_date(val, format=None):
41
    val = parse_datetime(val, format)
42
    if val:
43
        return val.date()
44
    return None
45
46
47
def _parse_datetime_with_format(val, format):
48
    try:
49
        return datetime.strptime(val, format)
50
    except Exception:
51
        return None
52
53
54
def _parse_datetime_without_format(val):
55
    try:
56
        return date_parser.parse(val)
57
    except Exception:
58
        return _parse_datetime_from_timestamp(val)
59
60
61
def _parse_datetime_from_timestamp(val):
62
    try:
63
        return datetime.fromtimestamp(float(val))
64
    except Exception:
65
        return None
66
67
68
def parse_datetime(val, format=None):
69
    if type_util.is_datetime(val):
70
        return val
71
    s = str(val)
72
    if format:
73
        return _parse_datetime_with_format(s, format)
74
    else:
75
        return _parse_datetime_without_format(s)
76
77
78
def _parse_decimal(val):
79
    try:
80
        return Decimal(val)
81
    except (ValueError, DecimalException):
82
        return None
83
84
85
def parse_decimal(val):
86
    return _parse_with(val, type_util.is_decimal, _parse_decimal)
87
88
89
def _parse_dict(val):
90
    serializer = JSONSerializer()
91
    try:
92
        d = serializer.decode(val)
93
        if type_util.is_dict(d):
94
            return d
95
        return None
96
    except Exception:
97
        return None
98
99
100
def parse_dict(val):
101
    return _parse_with(val, type_util.is_dict, _parse_dict)
102
103
104
def _parse_float(val):
105
    try:
106
        return float(val)
107
    except ValueError:
108
        return None
109
110
111
def parse_float(val):
112
    return _parse_with(val, type_util.is_float, _parse_float)
113
114
115
def _parse_email(val, check_blacklist=True):
116
    val = val.lower()
117
    if check_blacklist:
118
        if not MailChecker.is_valid(val):
119
            return None
120
    else:
121
        if not MailChecker.is_valid_email_format(val):
122
            return None
123
    return val
124
125
126
def parse_email(val, check_blacklist=True):
127
    return _parse_with(val, None, _parse_email, check_blacklist=check_blacklist)
128
129
130
def _parse_int(val):
131
    try:
132
        return int(val)
133
    except ValueError:
134
        return None
135
136
137
def parse_int(val):
138
    return _parse_with(val, type_util.is_integer, _parse_int)
139
140
141
def _parse_list(val, separator=None):
142
    if (
143
        val.startswith("{")
144
        and val.endswith("}")
145
        or val.startswith("[")
146
        and val.endswith("]")
147
    ):
148
        try:
149
            serializer = JSONSerializer()
150
            ls = serializer.decode(val)
151
            if type_util.is_list(ls):
152
                return ls
153
            return None
154
        except Exception:
155
            pass
156
    if separator:
157
        ls = list(val.split(separator))
158
        return ls
159
    return None
160
161
162
def parse_list(val, separator=None):
163
    val = _parse_with(val, type_util.is_list_or_tuple, _parse_list, separator=separator)
164
    return list(val) if type_util.is_list_or_tuple(val) else val
165
166
167
def _parse_phonenumber(val, country_code=None):
168
    try:
169
        phone_obj = phonenumbers.parse(val, country_code)
170
        if phonenumbers.is_valid_number(phone_obj):
171
            return {
172
                "e164": phonenumbers.format_number(phone_obj, PhoneNumberFormat.E164),
173
                "international": phonenumbers.format_number(
174
                    phone_obj, PhoneNumberFormat.INTERNATIONAL
175
                ),
176
                "national": phonenumbers.format_number(
177
                    phone_obj, PhoneNumberFormat.NATIONAL
178
                ),
179
            }
180
        return None
181
    except phonenumberutil.NumberParseException:
182
        return None
183
184
185
def parse_phonenumber(val, country_code=None):
186
    s = parse_str(val)
187
    if not s:
188
        return None
189
    phone_raw = re.sub(r"[^0-9\+]", " ", s)
190
    phone_raw = phone_raw.strip()
191
    if phone_raw.startswith("00"):
192
        phone_raw = "+" + phone_raw[2:]
193
    if country_code and len(country_code) >= 2:
194
        country_code = country_code[0:2].upper()
195
    return _parse_with(phone_raw, None, _parse_phonenumber, country_code=country_code)
196
197
198
def _parse_slug(val):
199
    return slugify(val)
200
201
202
def parse_slug(val):
203
    s = parse_str(val)
204
    return _parse_slug(s)
205
206
207
def parse_str(val):
208
    if type_util.is_string(val):
209
        val = ftfy.fix_text(val)
210
    else:
211
        val = str(val)
212
    val = val.strip()
213
    val = " ".join(val.split())
214
    return val
215
216
217
def parse_uuid(val):
218
    s = parse_str(val)
219
    return s if type_util.is_uuid(s) else None
220