pythonwrench 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonwrench/__init__.py +490 -0
- pythonwrench/__main__.py +7 -0
- pythonwrench/_core.py +192 -0
- pythonwrench/abc.py +30 -0
- pythonwrench/argparse/__init__.py +81 -0
- pythonwrench/argparse/dataclass_.py +284 -0
- pythonwrench/argparse/parsers.py +619 -0
- pythonwrench/cast.py +247 -0
- pythonwrench/checksum.py +427 -0
- pythonwrench/collections/__init__.py +104 -0
- pythonwrench/collections/collections.py +900 -0
- pythonwrench/collections/prop.py +104 -0
- pythonwrench/collections/reducers.py +330 -0
- pythonwrench/concurrent.py +73 -0
- pythonwrench/csv.py +12 -0
- pythonwrench/dataclasses.py +117 -0
- pythonwrench/datetime.py +17 -0
- pythonwrench/difflib.py +39 -0
- pythonwrench/disk_cache.py +615 -0
- pythonwrench/entrypoints/info.py +44 -0
- pythonwrench/entrypoints/safe_rmdir.py +98 -0
- pythonwrench/entrypoints/tree.py +113 -0
- pythonwrench/enum.py +55 -0
- pythonwrench/functools.py +234 -0
- pythonwrench/hashlib.py +95 -0
- pythonwrench/importlib.py +243 -0
- pythonwrench/inspect.py +69 -0
- pythonwrench/json.py +12 -0
- pythonwrench/jsonl.py +12 -0
- pythonwrench/logging.py +252 -0
- pythonwrench/math.py +107 -0
- pythonwrench/os.py +226 -0
- pythonwrench/pickle.py +12 -0
- pythonwrench/random.py +60 -0
- pythonwrench/re.py +139 -0
- pythonwrench/semver.py +406 -0
- pythonwrench/serialization/__init__.py +70 -0
- pythonwrench/serialization/_core.py +70 -0
- pythonwrench/serialization/csv.py +493 -0
- pythonwrench/serialization/json.py +178 -0
- pythonwrench/serialization/jsonl.py +215 -0
- pythonwrench/serialization/pickle.py +186 -0
- pythonwrench/time.py +34 -0
- pythonwrench/typing/__init__.py +125 -0
- pythonwrench/typing/checks.py +551 -0
- pythonwrench/typing/classes.py +251 -0
- pythonwrench/warnings.py +118 -0
- pythonwrench-0.6.4.dist-info/METADATA +242 -0
- pythonwrench-0.6.4.dist-info/RECORD +52 -0
- pythonwrench-0.6.4.dist-info/WHEEL +4 -0
- pythonwrench-0.6.4.dist-info/entry_points.txt +10 -0
- pythonwrench-0.6.4.dist-info/licenses/LICENSE +21 -0
pythonwrench/abc.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from typing import Any, ClassVar, Dict, Type
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Singleton(type):
|
|
8
|
+
"""Singleton metaclass.
|
|
9
|
+
|
|
10
|
+
To use it, just inherit from metaclass.
|
|
11
|
+
|
|
12
|
+
Example
|
|
13
|
+
-------
|
|
14
|
+
>>> class MyClass(metaclass=Singleton):
|
|
15
|
+
>>> pass
|
|
16
|
+
>>> a1 = MyClass()
|
|
17
|
+
>>> a2 = MyClass()
|
|
18
|
+
>>> # a1 and a2 are exactly the same instance, i.e. id(a1) == id(a2)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
_instances: ClassVar[Dict[Type, Any]] = {}
|
|
22
|
+
|
|
23
|
+
def __call__(cls, *args, **kwargs) -> Any:
|
|
24
|
+
"""Call the instance."""
|
|
25
|
+
if cls not in cls._instances:
|
|
26
|
+
instance = super().__call__(*args, **kwargs)
|
|
27
|
+
cls._instances[cls] = instance
|
|
28
|
+
else:
|
|
29
|
+
instance = cls._instances[cls]
|
|
30
|
+
return instance
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import lazy_loader as lazy # type: ignore
|
|
8
|
+
except ImportError:
|
|
9
|
+
lazy = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING or lazy is None:
|
|
13
|
+
from . import dataclass_ as dataclass_
|
|
14
|
+
from . import parsers as parsers
|
|
15
|
+
from .dataclass_ import (
|
|
16
|
+
add_dataclass_fields_to_parser,
|
|
17
|
+
new_parser_from_dataclass,
|
|
18
|
+
parse_args_using_dataclass,
|
|
19
|
+
)
|
|
20
|
+
from .parsers import (
|
|
21
|
+
get_parse_fn,
|
|
22
|
+
parse_to,
|
|
23
|
+
parse_to_bool,
|
|
24
|
+
parse_to_float,
|
|
25
|
+
parse_to_int,
|
|
26
|
+
parse_to_none,
|
|
27
|
+
parse_to_optional_bool,
|
|
28
|
+
parse_to_optional_float,
|
|
29
|
+
parse_to_optional_int,
|
|
30
|
+
parse_to_optional_str,
|
|
31
|
+
parse_to_type,
|
|
32
|
+
register_parser_fn,
|
|
33
|
+
str_to_bool,
|
|
34
|
+
str_to_float,
|
|
35
|
+
str_to_int,
|
|
36
|
+
str_to_none,
|
|
37
|
+
str_to_optional_bool,
|
|
38
|
+
str_to_optional_float,
|
|
39
|
+
str_to_optional_int,
|
|
40
|
+
str_to_optional_str,
|
|
41
|
+
str_to_type,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
else:
|
|
45
|
+
__getattr__, __dir__, __all__ = lazy.attach(
|
|
46
|
+
__name__,
|
|
47
|
+
submodules=["dataclass_", "parsers"],
|
|
48
|
+
submod_attrs={
|
|
49
|
+
"dataclass_": [
|
|
50
|
+
"add_dataclass_fields_to_parser",
|
|
51
|
+
"new_parser_from_dataclass",
|
|
52
|
+
"parse_args_using_dataclass",
|
|
53
|
+
],
|
|
54
|
+
"parsers": [
|
|
55
|
+
"get_parse_fn",
|
|
56
|
+
"parse_to",
|
|
57
|
+
"parse_to_bool",
|
|
58
|
+
"parse_to_float",
|
|
59
|
+
"parse_to_int",
|
|
60
|
+
"parse_to_none",
|
|
61
|
+
"parse_to_optional_bool",
|
|
62
|
+
"parse_to_optional_float",
|
|
63
|
+
"parse_to_optional_int",
|
|
64
|
+
"parse_to_optional_str",
|
|
65
|
+
"parse_to_type",
|
|
66
|
+
"register_parser_fn",
|
|
67
|
+
"str_to_bool",
|
|
68
|
+
"str_to_float",
|
|
69
|
+
"str_to_int",
|
|
70
|
+
"str_to_none",
|
|
71
|
+
"str_to_optional_bool",
|
|
72
|
+
"str_to_optional_float",
|
|
73
|
+
"str_to_optional_int",
|
|
74
|
+
"str_to_optional_str",
|
|
75
|
+
"str_to_type",
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
del TYPE_CHECKING, lazy
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from argparse import ArgumentParser
|
|
5
|
+
from dataclasses import MISSING, fields
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Dict,
|
|
9
|
+
Iterable,
|
|
10
|
+
Literal,
|
|
11
|
+
Optional,
|
|
12
|
+
Tuple,
|
|
13
|
+
Type,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
16
|
+
get_args,
|
|
17
|
+
overload,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from pythonwrench.argparse.parsers import (
|
|
21
|
+
ListParsing,
|
|
22
|
+
_is_iterable_type_like,
|
|
23
|
+
_is_literal_type,
|
|
24
|
+
_search_parse_fn,
|
|
25
|
+
)
|
|
26
|
+
from pythonwrench.functools import filter_and_call
|
|
27
|
+
from pythonwrench.typing.classes import (
|
|
28
|
+
Dataclass,
|
|
29
|
+
DataclassInstance,
|
|
30
|
+
)
|
|
31
|
+
from pythonwrench.warnings import deprecated_alias
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
from argparse import BooleanOptionalAction # type: ignore
|
|
35
|
+
except ImportError:
|
|
36
|
+
|
|
37
|
+
class BooleanOptionalAction: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
T_Dataclass = TypeVar("T_Dataclass", bound=Dataclass)
|
|
41
|
+
T_DataclassInstance = TypeVar("T_DataclassInstance", bound=DataclassInstance)
|
|
42
|
+
T_DataclassInstance_2 = TypeVar("T_DataclassInstance_2", bound=DataclassInstance)
|
|
43
|
+
T_DataclassInstance_3 = TypeVar("T_DataclassInstance_3", bound=DataclassInstance)
|
|
44
|
+
T_DataclassInstance_4 = TypeVar("T_DataclassInstance_4", bound=DataclassInstance)
|
|
45
|
+
T_DataclassInstance_5 = TypeVar("T_DataclassInstance_5", bound=DataclassInstance)
|
|
46
|
+
|
|
47
|
+
_BoolActionName = Literal["store", "store_true", "store_false", "bool_optional"]
|
|
48
|
+
BoolAction = Union[_BoolActionName, Type[BooleanOptionalAction]]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def parse_args_using_dataclass(
|
|
53
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
54
|
+
*,
|
|
55
|
+
args: Optional[Iterable[str]] = None,
|
|
56
|
+
parser: Optional[ArgumentParser] = None,
|
|
57
|
+
list_parsing: ListParsing = "argparse",
|
|
58
|
+
bool_action: BoolAction = "store",
|
|
59
|
+
add_dashed_arg: bool = True,
|
|
60
|
+
) -> T_DataclassInstance: ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@overload
|
|
64
|
+
def parse_args_using_dataclass(
|
|
65
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
66
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
67
|
+
/,
|
|
68
|
+
*,
|
|
69
|
+
args: Optional[Iterable[str]] = None,
|
|
70
|
+
parser: Optional[ArgumentParser] = None,
|
|
71
|
+
list_parsing: ListParsing = "argparse",
|
|
72
|
+
bool_action: BoolAction = "store",
|
|
73
|
+
add_dashed_arg: bool = True,
|
|
74
|
+
) -> Tuple[
|
|
75
|
+
T_DataclassInstance,
|
|
76
|
+
T_DataclassInstance_2,
|
|
77
|
+
]: ...
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@overload
|
|
81
|
+
def parse_args_using_dataclass(
|
|
82
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
83
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
84
|
+
dataclass_type_3: Type[T_DataclassInstance_3],
|
|
85
|
+
/,
|
|
86
|
+
*,
|
|
87
|
+
args: Optional[Iterable[str]] = None,
|
|
88
|
+
parser: Optional[ArgumentParser] = None,
|
|
89
|
+
list_parsing: ListParsing = "argparse",
|
|
90
|
+
bool_action: BoolAction = "store",
|
|
91
|
+
add_dashed_arg: bool = True,
|
|
92
|
+
) -> Tuple[
|
|
93
|
+
T_DataclassInstance,
|
|
94
|
+
T_DataclassInstance_2,
|
|
95
|
+
T_DataclassInstance_3,
|
|
96
|
+
]: ...
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@overload
|
|
100
|
+
def parse_args_using_dataclass(
|
|
101
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
102
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
103
|
+
dataclass_type_3: Type[T_DataclassInstance_3],
|
|
104
|
+
dataclass_type_4: Type[T_DataclassInstance_4],
|
|
105
|
+
/,
|
|
106
|
+
*,
|
|
107
|
+
args: Optional[Iterable[str]] = None,
|
|
108
|
+
parser: Optional[ArgumentParser] = None,
|
|
109
|
+
list_parsing: ListParsing = "argparse",
|
|
110
|
+
bool_action: BoolAction = "store",
|
|
111
|
+
add_dashed_arg: bool = True,
|
|
112
|
+
) -> Tuple[
|
|
113
|
+
T_DataclassInstance,
|
|
114
|
+
T_DataclassInstance_2,
|
|
115
|
+
T_DataclassInstance_3,
|
|
116
|
+
T_DataclassInstance_4,
|
|
117
|
+
]: ...
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@overload
|
|
121
|
+
def parse_args_using_dataclass(
|
|
122
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
123
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
124
|
+
dataclass_type_3: Type[T_DataclassInstance_3],
|
|
125
|
+
dataclass_type_4: Type[T_DataclassInstance_4],
|
|
126
|
+
dataclass_type_5: Type[T_DataclassInstance_5],
|
|
127
|
+
/,
|
|
128
|
+
*,
|
|
129
|
+
args: Optional[Iterable[str]] = None,
|
|
130
|
+
parser: Optional[ArgumentParser] = None,
|
|
131
|
+
list_parsing: ListParsing = "argparse",
|
|
132
|
+
bool_action: BoolAction = "store",
|
|
133
|
+
add_dashed_arg: bool = True,
|
|
134
|
+
) -> Tuple[
|
|
135
|
+
T_DataclassInstance,
|
|
136
|
+
T_DataclassInstance_2,
|
|
137
|
+
T_DataclassInstance_3,
|
|
138
|
+
T_DataclassInstance_4,
|
|
139
|
+
T_DataclassInstance_5,
|
|
140
|
+
]: ...
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def parse_args_using_dataclass(
|
|
144
|
+
dataclass_type: Type[DataclassInstance],
|
|
145
|
+
*dataclass_types: Type[DataclassInstance],
|
|
146
|
+
args: Optional[Iterable[str]] = None,
|
|
147
|
+
parser: Optional[ArgumentParser] = None,
|
|
148
|
+
list_parsing: ListParsing = "argparse",
|
|
149
|
+
bool_action: BoolAction = "store",
|
|
150
|
+
add_dashed_arg: bool = True,
|
|
151
|
+
) -> Union[
|
|
152
|
+
DataclassInstance,
|
|
153
|
+
Tuple[DataclassInstance, ...],
|
|
154
|
+
]:
|
|
155
|
+
"""Converts prog args to a typed dataclass using argparse.
|
|
156
|
+
|
|
157
|
+
Currently only supports dataclasses that contains only builtin scalars: str, int, float, None, bool OR list of builtin scalars.
|
|
158
|
+
"""
|
|
159
|
+
init_parser = parser
|
|
160
|
+
dataclass_types = (dataclass_type,) + dataclass_types
|
|
161
|
+
del dataclass_type
|
|
162
|
+
|
|
163
|
+
for dataclass_type_i in dataclass_types:
|
|
164
|
+
parser = add_dataclass_fields_to_parser(
|
|
165
|
+
dataclass_type_i,
|
|
166
|
+
parser=parser,
|
|
167
|
+
list_parsing=list_parsing,
|
|
168
|
+
bool_action=bool_action,
|
|
169
|
+
add_dashed_arg=add_dashed_arg,
|
|
170
|
+
)
|
|
171
|
+
assert parser is not None
|
|
172
|
+
|
|
173
|
+
parsed, argv = parser.parse_known_args(args)
|
|
174
|
+
if len(argv) > 0:
|
|
175
|
+
msg = f"Found {len(argv)} unknown arguments: {argv}."
|
|
176
|
+
raise ValueError(msg)
|
|
177
|
+
|
|
178
|
+
dataclass_insts = []
|
|
179
|
+
for dataclass_type_i in dataclass_types:
|
|
180
|
+
if init_parser is None and len(dataclass_types) == 1:
|
|
181
|
+
instance = dataclass_type_i(**parsed.__dict__)
|
|
182
|
+
else:
|
|
183
|
+
instance = filter_and_call(
|
|
184
|
+
dataclass_type_i,
|
|
185
|
+
_fill_all_arguments=True,
|
|
186
|
+
**parsed.__dict__,
|
|
187
|
+
)
|
|
188
|
+
dataclass_insts.append(instance)
|
|
189
|
+
|
|
190
|
+
if len(dataclass_insts) == 1:
|
|
191
|
+
return dataclass_insts[0]
|
|
192
|
+
else:
|
|
193
|
+
return tuple(dataclass_insts)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def add_dataclass_fields_to_parser(
|
|
197
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
198
|
+
*,
|
|
199
|
+
parser: Optional[ArgumentParser],
|
|
200
|
+
list_parsing: ListParsing = "argparse",
|
|
201
|
+
bool_action: BoolAction = "store",
|
|
202
|
+
add_dashed_arg: bool = True,
|
|
203
|
+
) -> ArgumentParser:
|
|
204
|
+
"""Perform the add dataclass fields to parser operation."""
|
|
205
|
+
if parser is None:
|
|
206
|
+
parser = ArgumentParser()
|
|
207
|
+
|
|
208
|
+
for field in fields(dataclass_type):
|
|
209
|
+
kwds = {}
|
|
210
|
+
posargs = [f"--{field.name}"]
|
|
211
|
+
if add_dashed_arg and "_" in field.name:
|
|
212
|
+
dashed_arg_name = field.name.replace("_", "-")
|
|
213
|
+
posargs.append(f"--{dashed_arg_name}")
|
|
214
|
+
|
|
215
|
+
if field.default is MISSING and field.default_factory is MISSING:
|
|
216
|
+
if bool_action != "store" and field.type is bool:
|
|
217
|
+
msg = f"Invalid arguments: boolean '{field.name}' without default value is incompatible with {bool_action=}."
|
|
218
|
+
raise RuntimeError(msg)
|
|
219
|
+
kwds["required"] = True
|
|
220
|
+
|
|
221
|
+
elif field.default is not MISSING:
|
|
222
|
+
kwds["default"] = field.default
|
|
223
|
+
elif field.default_factory is not MISSING:
|
|
224
|
+
kwds["default"] = field.default_factory() # type: ignore
|
|
225
|
+
else:
|
|
226
|
+
msg = f"Invalid field {field.name}: found values for default and default_factory."
|
|
227
|
+
raise ValueError(msg)
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
inner_kwds = _get_kwds_for_type(field.type, list_parsing, bool_action)
|
|
231
|
+
except (ValueError, TypeError, RuntimeError) as err:
|
|
232
|
+
msg = f"Invalid field {field.name}: field type '{field.type}' is not supported."
|
|
233
|
+
raise type(err)(msg) from err
|
|
234
|
+
|
|
235
|
+
kwds.update(inner_kwds)
|
|
236
|
+
parser.add_argument(*posargs, **kwds)
|
|
237
|
+
|
|
238
|
+
return parser
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _get_kwds_for_type(
|
|
242
|
+
field_type: Any,
|
|
243
|
+
list_parsing: Optional[ListParsing],
|
|
244
|
+
bool_action: BoolAction,
|
|
245
|
+
) -> Dict[str, Any]:
|
|
246
|
+
"""Perform the get kwds for type operation."""
|
|
247
|
+
if bool_action == "bool_optional":
|
|
248
|
+
bool_action = BooleanOptionalAction
|
|
249
|
+
kwds = {}
|
|
250
|
+
|
|
251
|
+
if bool_action != "store" and field_type is bool:
|
|
252
|
+
kwds["action"] = bool_action
|
|
253
|
+
return kwds
|
|
254
|
+
|
|
255
|
+
elif list_parsing == "argparse" and _is_iterable_type_like(field_type):
|
|
256
|
+
type_args = get_args(field_type)
|
|
257
|
+
if isinstance(type_args, tuple) and len(type_args) == 1:
|
|
258
|
+
item_type = type_args[0]
|
|
259
|
+
kwds = _get_kwds_for_type(
|
|
260
|
+
item_type, list_parsing=None, bool_action=bool_action
|
|
261
|
+
)
|
|
262
|
+
kwds["nargs"] = "*"
|
|
263
|
+
return kwds
|
|
264
|
+
|
|
265
|
+
parse_fn = _search_parse_fn(field_type, list_parsing=list_parsing)
|
|
266
|
+
|
|
267
|
+
if parse_fn is not None:
|
|
268
|
+
kwds["type"] = parse_fn
|
|
269
|
+
if _is_literal_type(field_type):
|
|
270
|
+
kwds["choices"] = get_args(field_type)
|
|
271
|
+
return kwds
|
|
272
|
+
|
|
273
|
+
else:
|
|
274
|
+
msg = (
|
|
275
|
+
f"Unsupported type {field_type}. (with {list_parsing=} and {bool_action=})"
|
|
276
|
+
)
|
|
277
|
+
raise TypeError(msg)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# ALIASES
|
|
281
|
+
@deprecated_alias(add_dataclass_fields_to_parser)
|
|
282
|
+
def new_parser_from_dataclass(*args, **kwargs):
|
|
283
|
+
"""Perform the new parser from dataclass operation."""
|
|
284
|
+
...
|