Passed
Push — dependabot/pip/sphinx-autoapi-... ( 48fb62 )
by
unknown
04:13 queued 01:53
created

mandos.model.pubchem_support._nav_fns   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 155
dl 0
loc 217
rs 5.04
c 0
b 0
f 0
wmc 57

21 Methods

Rating   Name   Duplication   Size   Complexity  
A Flatmap.construct() 0 10 1
A Filter.key_is_in() 0 3 2
A Filter.has_key() 0 3 2
A Mapx.extract_group_1() 0 15 4
A Flatmap.require_only() 0 3 1
A Mapx.split_to() 0 8 1
A Mapx.int_date() 0 8 3
A Filter.key_does_not_equal() 0 3 2
A Mapx.n_bar_items() 0 6 1
B Mapx.req_is() 0 11 6
A Mapx.lowercase_unless_acronym() 0 7 2
B Mapx.str_to() 0 15 8
A Mapx.split() 0 10 4
A Mapx.not_null() 0 3 2
A Flatmap.join_nonnulls() 0 7 2
A Flatmap.request_only() 0 13 3
A Mapx.roman_to_arabic() 0 8 1
A Filter.key_is_not_in() 0 3 2
A Filter.key_equals() 0 3 2
B Mapx.split_and_flatten_nonnulls() 0 14 6
A Mapx.identity() 0 3 2

How to fix   Complexity   

Complexity

Complex classes like mandos.model.pubchem_support._nav_fns 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
"""
2
Support classes to help with querying and processing web data.
3
"""
4
from __future__ import annotations
5
6
import logging
7
import re
8
from datetime import date, datetime
9
from typing import (
10
    Any,
11
    Callable,
12
    Set,
13
    Union,
14
    Optional,
15
    Type,
16
    TypeVar,
17
    Iterable,
18
    FrozenSet,
19
)
20
21
from pocketutils.tools.base_tools import BaseTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.base_tools'
Loading history...
22
from pocketutils.tools.string_tools import StringTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.string_tools'
Loading history...
23
24
from mandos.model.pubchem_support._nav_model import FilterFn
25
26
logger = logging.getLogger("mandos")
27
28
T = TypeVar("T")
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
29
30
empty_frozenset = frozenset([])
31
32
33
class Filter:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
34
    @classmethod
35
    def has_key(cls, key: str) -> FilterFn:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
36
        return FilterFn(lambda content: key in content)
37
38
    @classmethod
39
    def key_does_not_equal(cls, key: str, disallowed_value: Any) -> FilterFn:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
40
        return FilterFn(lambda content: content.get(key) != disallowed_value)
41
42
    @classmethod
43
    def key_equals(cls, key: str, allowed_value: Any) -> FilterFn:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
44
        return FilterFn(lambda content: content.get(key) == allowed_value)
45
46
    @classmethod
47
    def key_is_not_in(cls, key: str, disallowed_values: Set[Any]) -> FilterFn:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
48
        return FilterFn(lambda content: content.get(key) not in disallowed_values)
49
50
    @classmethod
51
    def key_is_in(cls, key: str, allowed_values: Set[Any]) -> FilterFn:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
52
        return FilterFn(lambda content: content.get(key) in allowed_values)
53
54
55
class Flatmap:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
56
    @classmethod
57
    def construct(cls, tp: Type[T]) -> Callable[[Iterable[Any]], T]:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "tp" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
58
        """
59
        Function that constructs an instance whose attributes are from the passed list.
60
        """
61
62
        def construct(things: Iterable[str]) -> Optional[str]:
63
            return tp(*things)
64
65
        return construct
66
67
    @classmethod
68
    def join_nonnulls(cls, sep: str = "; ") -> Callable[[Iterable[str]], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
69
        def opt_join(things: Iterable[str]) -> Optional[str]:
70
            x = [s.strip() for s in things]
0 ignored issues
show
Coding Style Naming introduced by
Variable name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
71
            return None if len(x) == 0 else sep.join(x)
72
73
        return opt_join
74
75
    @classmethod
76
    def request_only(cls) -> Callable[[Iterable[str]], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
77
        def only_nonreq(things: Iterable[str]) -> Optional[str]:
78
            # TODO: Did I mean to excludeNone here?
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
79
            things = [s.strip() for s in things if s is not None]
80
            if len(things) > 1:
0 ignored issues
show
Unused Code introduced by
Unnecessary "elif" after "raise"
Loading history...
81
                raise ValueError(f"{len(things)} items in {things}")
82
            elif len(things) == 0:
83
                return None
84
            else:
85
                return things[0]
86
87
        return only_nonreq
88
89
    @classmethod
90
    def require_only(cls) -> Callable[[Iterable[str]], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
91
        return BaseTools.only
92
93
94
class Mapx:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
95
    @classmethod
96
    def roman_to_arabic(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
97
        cls, min_val: Optional[int] = None, max_val: Optional[int] = None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
98
    ) -> Callable[[str], int]:
99
        def roman_to_arabic(s: str) -> int:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
100
            return StringTools.roman_to_arabic(s.strip(), min_val=min_val, max_val=max_val)
101
102
        return roman_to_arabic
103
104
    @classmethod
105
    def split_and_flatten_nonnulls(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
106
        cls, sep: str, skip_nulls: bool = False
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
107
    ) -> Callable[[Iterable[Union[str, int, float, None]]], Set[str]]:
108
        def split_flat(things: Iterable[str]) -> Set[str]:
109
            results = set()
110
            for thing in things:
111
                if thing is not None and thing != float("NaN") or not skip_nulls:
112
                    # let it fail if skip_nulls is False
113
                    for bit in str(thing).split(sep):
114
                        results.add(bit.strip())
115
            return results
116
117
        return split_flat
118
119
    @classmethod
120
    def extract_group_1(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
121
        cls, pattern: Union[str, re.Pattern]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
122
    ) -> Callable[[Optional[Any]], Optional[str]]:
123
        pattern = pattern if isinstance(pattern, re.Pattern) else re.compile(pattern)
124
125
        def _match(thing: Optional[str]) -> Optional[str]:
126
            if thing is None:
127
                return None
128
            match = pattern.fullmatch(str(thing))
129
            if match is None:
130
                return None
131
            return match.group(1)
132
133
        return _match
134
135
    @classmethod
136
    def lowercase_unless_acronym(cls) -> Callable[[str], str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
137
        def lowercase_unless_acronym(s: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
138
            s = s.strip()
139
            return s if s.isupper() else s.lower()
140
141
        return lowercase_unless_acronym
142
143
    @classmethod
144
    def n_bar_items(cls, sep: str = "||") -> Callable[[Optional[str]], int]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
145
        def n_bar_items(value: str) -> int:
146
            return len(value.split(sep))
147
148
        return n_bar_items
149
150
    @classmethod
151
    def not_null(cls) -> Callable[[Any], bool]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
152
        return lambda value: value is not None
153
154
    @classmethod
155
    def identity(cls) -> Callable[[T], T]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
156
        return lambda value: value
157
158
    @classmethod
159
    def int_date(cls, nullable: bool = False) -> Callable[[Optional[str]], Optional[date]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
160
        def int_date(s: Optional[str]) -> Optional[date]:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
161
            if s is None and nullable:
162
                return None
163
            return datetime.strptime(str(s).strip(), "%Y%m%d").date()
164
165
        return int_date
166
167
    @classmethod
168
    def req_is(cls, type_, nullable: bool = False, then_convert=None) -> Callable[[str], str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
169
        def req_is(value):
170
            if nullable and value is None:
171
                pass
172
            elif not isinstance(value, type_):
173
                raise TypeError(f"{value} is a {type(value)}, not {type_}")
174
            return value if then_convert is None else then_convert(value)
175
176
        req_is.__name__ = f"req_is_{type_}" + ("_or_null" if nullable else "")
177
        return req_is
178
179
    @classmethod
180
    def str_to(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
181
        cls, type_: Callable[[str], T], nullable: bool = False, flex_type: bool = False
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
182
    ) -> Callable[[str], str]:
183
        def str_to(value: Optional[str]) -> Optional[T]:
184
            if type_ is not None and not flex_type and not isinstance(value, str):
185
                raise TypeError(f"{value} is a {type(value)}, not str")
186
            if not nullable and value is None:
0 ignored issues
show
Unused Code introduced by
Unnecessary "elif" after "raise"
Loading history...
187
                raise ValueError(f"Value for type {type_} is None")
188
            elif value is None:
189
                return None
190
            return type_(str(value).strip())
191
192
        str_to.__name__ = f"req_is_{type_}" + ("_or_null" if nullable else "")
193
        return str_to
194
195
    @classmethod
196
    def split_to(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
197
        cls, type_, sep: str = "||", nullable: bool = False
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
198
    ) -> Callable[[Optional[str]], FrozenSet[int]]:
199
        def split_to(value: str) -> FrozenSet[int]:
200
            return frozenset([type_(x) for x in cls.split(sep, nullable=nullable)(value)])
201
202
        return split_to
203
204
    @classmethod
205
    def split(cls, sep: str, nullable: bool = False) -> Callable[[Optional[str]], FrozenSet[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
206
        def split(value: str) -> FrozenSet[str]:
207
            if value is None and not nullable:
208
                raise ValueError(f"Value is None")
0 ignored issues
show
introduced by
Using an f-string that does not have any interpolated variables
Loading history...
209
            if value is None:
210
                return empty_frozenset
211
            return frozenset([s.strip() for s in value.split(sep)])
212
213
        return split
214
215
216
__all__ = ["Filter", "Mapx", "Flatmap"]
217