Passed
Push — master ( 3bd527...df3bcb )
by Fabio
01:18
created

benedict.dicts.parse.parse_util   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 55
eloc 148
dl 0
loc 204
rs 6
c 0
b 0
f 0

24 Functions

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