Passed
Push — main ( fe2164...a4a582 )
by Douglas
01:37
created

Mapx.get_str()   A

Complexity

Conditions 4

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nop 2
dl 0
loc 10
rs 9.95
c 0
b 0
f 0
1
"""
2
Support classes to help with querying and processing web data.
3
"""
4
from __future__ import annotations
5
6
import re
7
from datetime import date, datetime
8
from typing import (
9
    Any,
10
    Callable,
11
    Set,
12
    Union,
13
    Optional,
14
    Type,
15
    TypeVar,
16
    Iterable,
17
    FrozenSet,
18
)
19
20
from pocketutils.tools.base_tools import BaseTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.base_tools'
Loading history...
21
from pocketutils.tools.string_tools import StringTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.string_tools'
Loading history...
22
23
from mandos import logger
0 ignored issues
show
Unused Code introduced by
Unused logger imported from mandos
Loading history...
24
from mandos.model.pubchem_support._nav_model import FilterFn
25
26
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...
27
28
empty_frozenset = frozenset([])
29
30
31
class Filter:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
32
    @classmethod
33
    def has_key(cls, key: str) -> FilterFn:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
34
        return FilterFn(lambda content: key in content)
35
36
    @classmethod
37
    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...
38
        return FilterFn(lambda content: content.get(key) != disallowed_value)
39
40
    @classmethod
41
    def key_equals(cls, key: str, allowed_value: Any) -> FilterFn:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
42
        return FilterFn(lambda content: content.get(key) == allowed_value)
43
44
    @classmethod
45
    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...
46
        return FilterFn(lambda content: content.get(key) not in disallowed_values)
47
48
    @classmethod
49
    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...
50
        return FilterFn(lambda content: content.get(key) in allowed_values)
51
52
53
class Flatmap:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
54
    @classmethod
55
    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...
56
        """
57
        Function that constructs an instance whose attributes are from the passed list.
58
        """
59
60
        def construct(things: Iterable[str]) -> Optional[str]:
61
            return tp(*things)
62
63
        return construct
64
65
    @classmethod
66
    def join_nonnulls(cls, sep: str = "; ") -> Callable[[Iterable[str]], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
67
        def opt_join(things: Iterable[str]) -> Optional[str]:
68
            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...
69
            return None if len(x) == 0 else sep.join(x)
70
71
        return opt_join
72
73
    @classmethod
74
    def request_only(cls) -> Callable[[Iterable[str]], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
75
        def only_nonreq(things: Iterable[str]) -> Optional[str]:
76
            # 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...
77
            things = [s.strip() for s in things if s is not None]
78
            if len(things) > 1:
0 ignored issues
show
Unused Code introduced by
Unnecessary "elif" after "raise"
Loading history...
79
                raise ValueError(f"{len(things)} items in {things}")
80
            elif len(things) == 0:
81
                return None
82
            else:
83
                return things[0]
84
85
        return only_nonreq
86
87
    @classmethod
88
    def require_only(cls) -> Callable[[Iterable[str]], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
89
        return BaseTools.only
90
91
92
class Mapx:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
93
    @classmethod
94
    def roman_to_arabic(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
95
        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...
96
    ) -> Callable[[str], int]:
97
        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...
98
            return StringTools.roman_to_arabic(s.strip(), min_val=min_val, max_val=max_val)
99
100
        return roman_to_arabic
101
102
    @classmethod
103
    def split_and_flatten_nonnulls(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
104
        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...
105
    ) -> Callable[[Iterable[Union[str, int, float, None]]], Set[str]]:
106
        def split_flat(things: Iterable[str]) -> Set[str]:
107
            results = set()
108
            for thing in things:
109
                if thing is not None and thing != float("NaN") or not skip_nulls:
110
                    # let it fail if skip_nulls is False
111
                    for bit in str(thing).split(sep):
112
                        results.add(bit.strip())
113
            return results
114
115
        return split_flat
116
117
    @classmethod
118
    def extract_group_1(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
119
        cls, pattern: Union[str, re.Pattern]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
120
    ) -> Callable[[Optional[Any]], Optional[str]]:
121
        pattern = pattern if isinstance(pattern, re.Pattern) else re.compile(pattern)
122
123
        def _match(thing: Optional[str]) -> Optional[str]:
124
            if thing is None:
125
                return None
126
            match = pattern.fullmatch(str(thing))
127
            if match is None:
128
                return None
129
            return match.group(1)
130
131
        return _match
132
133
    @classmethod
134
    def lowercase_unless_acronym(cls) -> Callable[[str], str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
135
        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...
136
            s = s.strip()
137
            return s if s.isupper() else s.lower()
138
139
        return lowercase_unless_acronym
140
141
    @classmethod
142
    def n_bar_items(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
143
        cls, sep: str = "||", null_is_zero: bool = False
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
144
    ) -> Callable[[Optional[str]], int]:
145
        def n_bar_items(value: str) -> int:
146
            if null_is_zero and value is None:
147
                return 0
148
            return len(value.split(sep))
149
150
        return n_bar_items
151
152
    @classmethod
153
    def not_null(cls) -> Callable[[Any], bool]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
154
        return lambda value: value is not None
155
156
    @classmethod
157
    def identity(cls) -> Callable[[T], T]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
158
        return lambda value: value
159
160
    @classmethod
161
    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...
162
        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...
163
            if s is None and nullable:
164
                return None
165
            return datetime.strptime(str(s).strip(), "%Y%m%d").date()
166
167
        return int_date
168
169
    @classmethod
170
    def get_str(cls, nullable: bool = False) -> Callable[[str], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
171
        def get_str(value):
172
            if nullable and value is None:
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
173
                return None
174
            elif not isinstance(value, (int, float, str)):
175
                raise TypeError(f"{value} is a {type(value)}, not int-like")
176
            return str(value)
177
178
        return get_str
179
180
    @classmethod
181
    def get_float(cls, nullable: bool = False) -> Callable[[str], Optional[str]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
182
        def get_float(value):
183
            if nullable and value is None:
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
184
                return None
185
            elif not isinstance(value, (int, float, str)):
186
                raise TypeError(f"{value} is a {type(value)}, not int-like")
187
            return float(value)
188
189
        return get_float
190
191
    @classmethod
192
    def get_int(cls, nullable: bool = False) -> Callable[[str], Optional[int]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
193
        def get_int(value):
194
            if nullable and value is None:
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
195
                return None
196
            elif not isinstance(value, (int, float, str)):
197
                raise TypeError(f"{value} is a {type(value)}, not int-like")
198
            return int(value)
199
200
        return get_int
201
202
    @classmethod
203
    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...
204
        def req_is(value):
205
            if nullable and value is None:
206
                pass
207
            elif not isinstance(value, type_):
208
                raise TypeError(f"{value} is a {type(value)}, not {type_}")
209
            return value if then_convert is None else then_convert(value)
210
211
        req_is.__name__ = f"req_is_{type_}" + ("_or_null" if nullable else "")
212
        return req_is
213
214
    @classmethod
215
    def str_to(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
216
        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...
217
    ) -> Callable[[str], str]:
218
        def str_to(value: Optional[str]) -> Optional[T]:
219
            if value is None and nullable:
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
220
                return None
221
            elif value is None:
222
                raise ValueError(f"Value for type {type_} is None")
223
            if type_ is not None and not flex_type and not isinstance(value, str):
224
                raise TypeError(f"{value} is a {type(value)}, not str")
225
            return type_(str(value).strip())
226
227
        str_to.__name__ = f"req_is_{type_}" + ("_or_null" if nullable else "")
228
        return str_to
229
230
    @classmethod
231
    def split_to(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
232
        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...
233
    ) -> Callable[[Optional[str]], FrozenSet[int]]:
234
        def split_to(value: str) -> FrozenSet[int]:
235
            return frozenset([type_(x) for x in cls.split(sep, nullable=nullable)(value)])
236
237
        return split_to
238
239
    @classmethod
240
    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...
241
        def split(value: str) -> FrozenSet[str]:
242
            if value is None and nullable:
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
243
                return empty_frozenset
244
            elif value is None:
245
                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...
246
            return frozenset([s.strip() for s in str(value).split(sep)])
247
248
        return split
249
250
251
__all__ = ["Filter", "Mapx", "Flatmap"]
252