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.
Files changed (52) hide show
  1. pythonwrench/__init__.py +490 -0
  2. pythonwrench/__main__.py +7 -0
  3. pythonwrench/_core.py +192 -0
  4. pythonwrench/abc.py +30 -0
  5. pythonwrench/argparse/__init__.py +81 -0
  6. pythonwrench/argparse/dataclass_.py +284 -0
  7. pythonwrench/argparse/parsers.py +619 -0
  8. pythonwrench/cast.py +247 -0
  9. pythonwrench/checksum.py +427 -0
  10. pythonwrench/collections/__init__.py +104 -0
  11. pythonwrench/collections/collections.py +900 -0
  12. pythonwrench/collections/prop.py +104 -0
  13. pythonwrench/collections/reducers.py +330 -0
  14. pythonwrench/concurrent.py +73 -0
  15. pythonwrench/csv.py +12 -0
  16. pythonwrench/dataclasses.py +117 -0
  17. pythonwrench/datetime.py +17 -0
  18. pythonwrench/difflib.py +39 -0
  19. pythonwrench/disk_cache.py +615 -0
  20. pythonwrench/entrypoints/info.py +44 -0
  21. pythonwrench/entrypoints/safe_rmdir.py +98 -0
  22. pythonwrench/entrypoints/tree.py +113 -0
  23. pythonwrench/enum.py +55 -0
  24. pythonwrench/functools.py +234 -0
  25. pythonwrench/hashlib.py +95 -0
  26. pythonwrench/importlib.py +243 -0
  27. pythonwrench/inspect.py +69 -0
  28. pythonwrench/json.py +12 -0
  29. pythonwrench/jsonl.py +12 -0
  30. pythonwrench/logging.py +252 -0
  31. pythonwrench/math.py +107 -0
  32. pythonwrench/os.py +226 -0
  33. pythonwrench/pickle.py +12 -0
  34. pythonwrench/random.py +60 -0
  35. pythonwrench/re.py +139 -0
  36. pythonwrench/semver.py +406 -0
  37. pythonwrench/serialization/__init__.py +70 -0
  38. pythonwrench/serialization/_core.py +70 -0
  39. pythonwrench/serialization/csv.py +493 -0
  40. pythonwrench/serialization/json.py +178 -0
  41. pythonwrench/serialization/jsonl.py +215 -0
  42. pythonwrench/serialization/pickle.py +186 -0
  43. pythonwrench/time.py +34 -0
  44. pythonwrench/typing/__init__.py +125 -0
  45. pythonwrench/typing/checks.py +551 -0
  46. pythonwrench/typing/classes.py +251 -0
  47. pythonwrench/warnings.py +118 -0
  48. pythonwrench-0.6.4.dist-info/METADATA +242 -0
  49. pythonwrench-0.6.4.dist-info/RECORD +52 -0
  50. pythonwrench-0.6.4.dist-info/WHEEL +4 -0
  51. pythonwrench-0.6.4.dist-info/entry_points.txt +10 -0
  52. 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
+ ...