Passed
Push — main ( ddff4b...7b3fbc )
by Douglas
04:33
created

Mapx.int_date()   A

Complexity

Conditions 3

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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