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
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from typing import (
5
+ Any,
6
+ ClassVar,
7
+ Dict,
8
+ Iterable,
9
+ Iterator,
10
+ List,
11
+ Protocol,
12
+ Tuple,
13
+ Type,
14
+ Union,
15
+ runtime_checkable,
16
+ )
17
+
18
+ try:
19
+ from types import UnionType # type: ignore
20
+ except ImportError:
21
+ # support older python versions
22
+ UnionType = type(Union)
23
+
24
+ from typing_extensions import TypeAlias, TypeVar
25
+
26
+ NoneType: TypeAlias = type(None) # type: ignore
27
+ EllipsisType: TypeAlias = type(...) # type: ignore
28
+
29
+ BuiltinCollection: TypeAlias = Union[list, tuple, dict, set, frozenset]
30
+ BuiltinNumber: TypeAlias = Union[bool, int, float, complex]
31
+ BuiltinScalar: TypeAlias = Union[bool, int, float, complex, NoneType, str, bytes]
32
+
33
+ _T_Item = TypeVar("_T_Item", covariant=True)
34
+ _T_Index = TypeVar("_T_Index", contravariant=True, default=Any)
35
+ _T_Other = TypeVar("_T_Other", contravariant=True, default=Any)
36
+ _T_Index2 = TypeVar("_T_Index2", contravariant=True)
37
+
38
+ T_BuiltinNumber = TypeVar(
39
+ "T_BuiltinNumber",
40
+ bound=BuiltinNumber,
41
+ default=BuiltinNumber,
42
+ covariant=True,
43
+ )
44
+ T_BuiltinScalar = TypeVar(
45
+ "T_BuiltinScalar",
46
+ bound=BuiltinScalar,
47
+ default=BuiltinScalar,
48
+ covariant=True,
49
+ )
50
+
51
+ ListOrTuple = Union[List[_T_Item], Tuple[_T_Item, ...]]
52
+ SupportsIter = Iterable
53
+
54
+
55
+ @runtime_checkable
56
+ class DataclassInstance(Protocol):
57
+ # Class meant for typing purpose only
58
+ __dataclass_fields__: ClassVar[Dict[str, Any]]
59
+
60
+
61
+ Dataclass = Type[DataclassInstance]
62
+
63
+
64
+ @runtime_checkable
65
+ class NamedTupleInstance(Protocol):
66
+ # Class meant for typing purpose only
67
+ _fields: Tuple[str, ...]
68
+ _field_defaults: Dict[str, Any]
69
+
70
+ def _asdict(self) -> Dict[str, Any]:
71
+ """Perform the asdict operation."""
72
+ raise NotImplementedError
73
+
74
+ def __getitem__(self, idx, /):
75
+ """Return the item at the requested index or key."""
76
+ raise NotImplementedError
77
+
78
+ def __len__(self) -> int:
79
+ """Return the number of items in the instance."""
80
+ raise NotImplementedError
81
+
82
+
83
+ @runtime_checkable
84
+ class SupportsAdd(Protocol[_T_Other]):
85
+ """Protocol that support `__add__` (+) method."""
86
+
87
+ def __add__(self, other: _T_Other, /):
88
+ """Return the result of adding another object."""
89
+ raise NotImplementedError
90
+
91
+
92
+ @runtime_checkable
93
+ class SupportsAnd(Protocol[_T_Other]):
94
+ """Protocol that support `__and__` (&) method."""
95
+
96
+ def __and__(self, other: _T_Other, /):
97
+ """Return the result of applying the and operator."""
98
+ raise NotImplementedError
99
+
100
+
101
+ @runtime_checkable
102
+ class SupportsBool(Protocol):
103
+ """Protocol that support `__bool__` method."""
104
+
105
+ def __bool__(self) -> bool:
106
+ """Return the truth value of the instance."""
107
+ raise NotImplementedError
108
+
109
+
110
+ @runtime_checkable
111
+ class SupportsDiv(Protocol[_T_Other]):
112
+ """Protocol that support `__div__` (/) method."""
113
+
114
+ def __div__(self, other: _T_Other, /):
115
+ """Return the result of dividing by another object."""
116
+ raise NotImplementedError
117
+
118
+
119
+ @runtime_checkable
120
+ class SupportsGetitem(Protocol[_T_Item, _T_Index]):
121
+ """Protocol that support `__getitem__` method."""
122
+
123
+ def __getitem__(self, idx: _T_Index, /) -> _T_Item:
124
+ """Return the item at the requested index or key."""
125
+ raise NotImplementedError
126
+
127
+
128
+ @runtime_checkable
129
+ class SupportsGetitem2(Protocol[_T_Index2, _T_Item]):
130
+ """Protocol that support `__getitem__` method.
131
+
132
+ Same than `SupportsGetitem` except that generic parameters are in reversed order: [T_Index, T_Item].
133
+ """
134
+
135
+ def __getitem__(self, idx: _T_Index2, /) -> _T_Item:
136
+ """Return the item at the requested index or key."""
137
+ raise NotImplementedError
138
+
139
+
140
+ @runtime_checkable
141
+ class SupportsGetitemLen(Protocol[_T_Item, _T_Index]):
142
+ """Protocol that support `__getitem__` and `__len__` methods."""
143
+
144
+ def __getitem__(self, idx: _T_Index, /) -> _T_Item:
145
+ """Return the item at the requested index or key."""
146
+ raise NotImplementedError
147
+
148
+ def __len__(self) -> int:
149
+ """Return the number of items in the instance."""
150
+ raise NotImplementedError
151
+
152
+
153
+ @runtime_checkable
154
+ class SupportsGetitemLen2(Protocol[_T_Index2, _T_Item]):
155
+ """Protocol that support `__getitem__` and `__len__` methods.
156
+
157
+ Same than `SupportsGetitemLen` except that generic parameters are in reversed order: [T_Index, T_Item]."""
158
+
159
+ def __getitem__(self, idx: _T_Index2, /) -> _T_Item:
160
+ """Return the item at the requested index or key."""
161
+ raise NotImplementedError
162
+
163
+ def __len__(self) -> int:
164
+ """Return the number of items in the instance."""
165
+ raise NotImplementedError
166
+
167
+
168
+ @runtime_checkable
169
+ class SupportsGetitemIterLen(Protocol[_T_Item, _T_Index]):
170
+ """Protocol that support `__getitem__`, `__iter__` and `__len__` methods."""
171
+
172
+ def __getitem__(self, idx: _T_Index, /) -> _T_Item:
173
+ """Return the item at the requested index or key."""
174
+ raise NotImplementedError
175
+
176
+ def __iter__(self) -> Iterator[_T_Item]:
177
+ """Return an iterator over the instance."""
178
+ raise NotImplementedError
179
+
180
+ def __len__(self) -> int:
181
+ """Return the number of items in the instance."""
182
+ raise NotImplementedError
183
+
184
+
185
+ @runtime_checkable
186
+ class SupportsGetitemIterLen2(Protocol[_T_Index2, _T_Item]):
187
+ """Protocol that support `__getitem__`, `__iter__` and `__len__` methods.
188
+
189
+ Same than `SupportsGetitemIterLen` except that generic parameters are in reversed order: [T_Index, T_Item].
190
+ """
191
+
192
+ def __getitem__(self, idx: _T_Index2, /) -> _T_Item:
193
+ """Return the item at the requested index or key."""
194
+ raise NotImplementedError
195
+
196
+ def __iter__(self) -> Iterator[_T_Item]:
197
+ """Return an iterator over the instance."""
198
+ raise NotImplementedError
199
+
200
+ def __len__(self) -> int:
201
+ """Return the number of items in the instance."""
202
+ raise NotImplementedError
203
+
204
+
205
+ @runtime_checkable
206
+ class SupportsIterLen(Protocol[_T_Item]):
207
+ """Protocol that support `__iter__` and `__len__` methods."""
208
+
209
+ def __iter__(self) -> Iterator[_T_Item]:
210
+ """Return an iterator over the instance."""
211
+ raise NotImplementedError
212
+
213
+ def __len__(self) -> int:
214
+ """Return the number of items in the instance."""
215
+ raise NotImplementedError
216
+
217
+
218
+ @runtime_checkable
219
+ class SupportsLen(Protocol):
220
+ """Protocol that support `__len__` method."""
221
+
222
+ def __len__(self) -> int:
223
+ """Return the number of items in the instance."""
224
+ raise NotImplementedError
225
+
226
+
227
+ @runtime_checkable
228
+ class SupportsMul(Protocol[_T_Other]):
229
+ """Protocol that support `__mul__` (*) method."""
230
+
231
+ def __mul__(self, other: _T_Other, /):
232
+ """Return the result of multiplying by another object."""
233
+ raise NotImplementedError
234
+
235
+
236
+ @runtime_checkable
237
+ class SupportsOr(Protocol[_T_Other]):
238
+ """Protocol that support `__or__` (|) method."""
239
+
240
+ def __or__(self, other: _T_Other, /):
241
+ """Return the result of applying the or operator."""
242
+ raise NotImplementedError
243
+
244
+
245
+ @runtime_checkable
246
+ class SupportsMatmul(Protocol[_T_Other]):
247
+ """Protocol that support `__matmul__` (@) method."""
248
+
249
+ def __matmul__(self, other: _T_Other, /):
250
+ """Return the result of matrix multiplication."""
251
+ raise NotImplementedError
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import warnings
5
+ from functools import lru_cache, partial
6
+ from typing import Any, Callable, Optional, Type, TypeVar, Union, overload
7
+
8
+ from typing_extensions import ParamSpec
9
+
10
+ from pythonwrench._core import T_Function, _decorator_factory, return_none
11
+
12
+ P = ParamSpec("P")
13
+ U = TypeVar("U")
14
+
15
+
16
+ @overload
17
+ def warn_once(
18
+ message: str,
19
+ category: Optional[Type[Warning]] = None,
20
+ stacklevel: int = 1,
21
+ source: Any = None,
22
+ ) -> None:
23
+ """Perform the warn once operation."""
24
+ ...
25
+
26
+
27
+ @overload
28
+ def warn_once(
29
+ message: Warning,
30
+ category: Any = None,
31
+ stacklevel: int = 1,
32
+ source: Any = None,
33
+ ) -> None:
34
+ """Perform the warn once operation."""
35
+ ...
36
+
37
+
38
+ @lru_cache(maxsize=None)
39
+ def warn_once(
40
+ message: Union[str, Warning],
41
+ category: Optional[Type[Warning]] = None,
42
+ stacklevel: int = 1,
43
+ source: Any = None,
44
+ ) -> None:
45
+ """Warn message once using warnings module."""
46
+ warnings.warn(message, category, stacklevel, source)
47
+
48
+
49
+ def deprecated_alias(
50
+ alternative: T_Function,
51
+ msg_fmt: str = "Deprecated call to '{fn_name}', use '{alternative_name}' instead.",
52
+ warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
53
+ *,
54
+ pre_fn: Optional[Callable[..., Any]] = None,
55
+ post_fn: Optional[Callable[..., Any]] = None,
56
+ ) -> Callable[..., T_Function]:
57
+ """Decorator to wrap deprecated function aliases."""
58
+ alternative_name = alternative.__name__ if alternative is not None else "None"
59
+ if pre_fn is None:
60
+ pre_fn = return_none
61
+
62
+ def inner_pre_fn(fn, *args, **kwargs) -> None:
63
+ """Perform the inner pre fn operation."""
64
+ msg = msg_fmt.format(fn_name=fn.__name__, alternative_name=alternative_name)
65
+ warn_fn(msg)
66
+ pre_fn(fn, *args, **kwargs)
67
+
68
+ return _decorator_factory(alternative, pre_fn=inner_pre_fn, post_fn=post_fn)
69
+
70
+
71
+ @overload
72
+ def deprecated_function(
73
+ fn: None = None,
74
+ /,
75
+ *,
76
+ msg_fmt: str = "Deprecated call to '{fn_name}'.",
77
+ warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
78
+ ) -> Callable[[T_Function], T_Function]:
79
+ """Perform the deprecated function operation."""
80
+ ...
81
+
82
+
83
+ @overload
84
+ def deprecated_function(
85
+ fn: T_Function,
86
+ /,
87
+ *,
88
+ msg_fmt: str = "Deprecated call to '{fn_name}'.",
89
+ warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
90
+ ) -> T_Function:
91
+ """Perform the deprecated function operation."""
92
+ ...
93
+
94
+
95
+ def deprecated_function(
96
+ fn: Optional[T_Function] = None,
97
+ /,
98
+ *,
99
+ msg_fmt: str = "Deprecated call to '{fn_name}'.",
100
+ warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
101
+ pre_fn: Optional[Callable[..., Any]] = None,
102
+ post_fn: Optional[Callable[..., Any]] = None,
103
+ ) -> Union[Callable[[T_Function], T_Function], T_Function]:
104
+ """Decorator to wrap deprecated functions."""
105
+ if pre_fn is None:
106
+ pre_fn = return_none
107
+
108
+ def inner_pre_fn(fn, *args, **kwargs):
109
+ """Perform the inner pre fn operation."""
110
+ msg = msg_fmt.format(fn_name=fn.__qualname__)
111
+ warn_fn(msg)
112
+ pre_fn(fn, *args, **kwargs)
113
+
114
+ decorator = _decorator_factory(None, pre_fn=inner_pre_fn, post_fn=post_fn)
115
+ if fn is None:
116
+ return decorator
117
+ else:
118
+ return decorator(fn)
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.5
2
+ Name: pythonwrench
3
+ Version: 0.6.4
4
+ Summary: Python library with tools for typing, manipulating collections, and more!
5
+ Project-URL: Homepage, https://pypi.org/project/pythonwrench/
6
+ Project-URL: Documentation, https://pythonwrench.readthedocs.io/
7
+ Project-URL: Repository, https://github.com/Labbeti/pythonwrench.git
8
+ Project-URL: Changelog, https://github.com/Labbeti/pythonwrench/blob/main/CHANGELOG.md
9
+ Project-URL: Tracker, https://github.com/Labbeti/pythonwrench/issues
10
+ Author-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
11
+ Maintainer-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
12
+ License: MIT License
13
+
14
+ Copyright (c) 2026 Labbeti
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the "Software"), to deal
18
+ in the Software without restriction, including without limitation the rights
19
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ copies of the Software, and to permit persons to whom the Software is
21
+ furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
33
+ License-File: LICENSE
34
+ Keywords: python,tools,utilities
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: Intended Audience :: Science/Research
37
+ Classifier: Operating System :: MacOS :: MacOS X
38
+ Classifier: Operating System :: Microsoft :: Windows
39
+ Classifier: Operating System :: OS Independent
40
+ Classifier: Operating System :: POSIX
41
+ Classifier: Programming Language :: Python :: 3
42
+ Classifier: Programming Language :: Python :: 3.8
43
+ Classifier: Programming Language :: Python :: 3.9
44
+ Classifier: Programming Language :: Python :: 3.10
45
+ Classifier: Programming Language :: Python :: 3.11
46
+ Classifier: Programming Language :: Python :: 3.12
47
+ Classifier: Programming Language :: Python :: 3.13
48
+ Classifier: Programming Language :: Python :: 3.14
49
+ Classifier: Topic :: Scientific/Engineering
50
+ Requires-Python: <3.15,>=3.8
51
+ Requires-Dist: typing-extensions>=4.10.0
52
+ Provides-Extra: dev
53
+ Requires-Dist: coverage[toml]>=7.6.1; extra == 'dev'
54
+ Requires-Dist: ipykernel>=6.29.5; extra == 'dev'
55
+ Requires-Dist: ipython>=8.12.3; extra == 'dev'
56
+ Requires-Dist: pandas>=2.0.3; extra == 'dev'
57
+ Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
58
+ Requires-Dist: pytest>=8.3.5; extra == 'dev'
59
+ Requires-Dist: ruff~=0.14.9; extra == 'dev'
60
+ Requires-Dist: sphinx-immaterial>=0.11.14; extra == 'dev'
61
+ Requires-Dist: sphinx<9.0.0,>=7.1.2; extra == 'dev'
62
+ Requires-Dist: twine>=6.1.0; extra == 'dev'
63
+ Provides-Extra: docs
64
+ Requires-Dist: sphinx-immaterial>=0.11.14; extra == 'docs'
65
+ Requires-Dist: sphinx<9.0.0,>=7.1.2; extra == 'docs'
66
+ Provides-Extra: lazy
67
+ Requires-Dist: lazy-loader<1,>=0.4; extra == 'lazy'
68
+ Provides-Extra: tests
69
+ Requires-Dist: coverage[toml]>=7.6.1; extra == 'tests'
70
+ Requires-Dist: pytest>=8.3.5; extra == 'tests'
71
+ Requires-Dist: ruff~=0.14.9; extra == 'tests'
72
+ Description-Content-Type: text/markdown
73
+
74
+ # pythonwrench
75
+
76
+ <center>
77
+
78
+ <a href="https://www.python.org/">
79
+ <img alt="Python" src="https://img.shields.io/badge/-Python 3.8+-blue?style=for-the-badge&logo=python&logoColor=white">
80
+ </a>
81
+ <a href="https://github.com/Labbeti/pythonwrench/actions">
82
+ <img alt="Build" src="https://img.shields.io/github/actions/workflow/status/Labbeti/pythonwrench/test.yaml?branch=main&style=for-the-badge&logo=github">
83
+ </a>
84
+ <a href='https://pythonwrench.readthedocs.io/en/stable/?badge=stable'>
85
+ <img src='https://readthedocs.org/projects/pythonwrench/badge/?version=stable&style=for-the-badge' alt='Documentation Status' />
86
+ </a>
87
+
88
+ Python library with tools for typing, manipulating collections, and more!
89
+
90
+ </center>
91
+
92
+
93
+ ## Installation
94
+
95
+ With uv:
96
+ ```bash
97
+ uv add pythonwrench
98
+ ```
99
+
100
+ With pip:
101
+ ```bash
102
+ pip install pythonwrench
103
+ ```
104
+
105
+ This library has been tested on all Python versions **3.8 - 3.14**, requires only `typing_extensions>=4.10.0`, and runs on **Linux, Mac and Windows** systems.
106
+
107
+ ## Examples
108
+
109
+ ### Typing
110
+
111
+ Check generic types with `isinstance_generic` :
112
+
113
+ ```python
114
+ >>> import pythonwrench as pw
115
+ >>>
116
+ >>> # Behaves like builtin isinstance() :
117
+ >>> pw.isinstance_generic({"a": 1, "b": 2}, dict)
118
+ ... True
119
+ >>> # But works with generic types !
120
+ >>> pw.isinstance_generic({"a": 1, "b": 2}, dict[str, int])
121
+ ... True
122
+ >>> pw.isinstance_generic({"a": 1, "b": 2}, dict[str, str])
123
+ ... False
124
+ ```
125
+
126
+ ... or check specific methods with protocols classes beginning with `Supports`
127
+ ```python
128
+ >>> import pythonwrench as pw
129
+ >>>
130
+ >>> isinstance({"a": 1, "b": 2}, pw.SupportsIterLen)
131
+ ... True
132
+ >>> isinstance({"a": 1, "b": 2}, pw.SupportsGetitemLen)
133
+ ... True
134
+ ```
135
+
136
+ Finally, you can also force argument type checking with `check_args_types` function :
137
+
138
+ ```python
139
+ >>> import pythonwrench as pw
140
+
141
+ >>> @pw.check_args_types
142
+ >>> def f(a: int, b: str) -> str:
143
+ >>> return a * b
144
+
145
+ >>> f(1, "a") # pass check
146
+ >>> f(1, 2) # raises TypeError from decorator
147
+ ```
148
+
149
+ ### Collections
150
+
151
+ Provides functions to facilitate iterables processing, like `unzip` :
152
+
153
+ ```python
154
+ >>> import pythonwrench as pw
155
+ >>>
156
+ >>> list_of_tuples = [(1, "a"), (2, "b"), (3, "c"), (4, "d")]
157
+ >>> pw.unzip(list_of_tuples)
158
+ ... [1, 2, 3, 4], ["a", "b", "c", "d"]
159
+ >>> pw.flatten(list_of_tuples)
160
+ ... [1, "a", 2, "b", 3, "c", 4, "d"]
161
+ ```
162
+
163
+ ... or mathematical functions like `prod` or `argmax` :
164
+
165
+ ```python
166
+ >>> import pythonwrench as pw
167
+ >>>
168
+ >>> values = [3, 1, 6, 4]
169
+ >>> pw.prod(values)
170
+ ... 72
171
+ >>> pw.argmax(values)
172
+ ... 2
173
+ >>> pw.is_sorted(values)
174
+ ... False
175
+ ```
176
+
177
+ Easely converts common python structures like list of dicts to dict of lists :
178
+
179
+ ```python
180
+ >>> import pythonwrench as pw
181
+ >>>
182
+ >>> list_of_dicts = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
183
+ >>> pw.list_dict_to_dict_list(list_of_dicts)
184
+ ... {"a": [1, 3], "b": [2, 4]}
185
+ ```
186
+
187
+ ... or dict of dicts :
188
+ ```python
189
+ >>> import pythonwrench as pw
190
+ >>>
191
+ >>> dict_of_dicts = {"a": {"x": 1, "y": 2}, "b": {"x": 3, "y": 4}}
192
+ >>> pw.flat_dict_of_dict(dict_of_dicts)
193
+ ... {"a.x": 1, "a.y": 2, "b.x": 3, "b.y": 4}
194
+ ```
195
+
196
+ ### Disk caching (memoize)
197
+
198
+ ```python
199
+ >>> import pythonwrench as pw
200
+ >>>
201
+ >>> @pw.disk_cache_decorator
202
+ >>> def heavy_processing():
203
+ >>> # Lot of stuff here
204
+ >>> ...
205
+ >>>
206
+ >>> data1 = heavy_processing() # first call function is called and the result is stored on disk
207
+ >>> data2 = heavy_processing() # second call result is loaded from disk directly
208
+ ```
209
+
210
+ ### Semantic versionning parsing
211
+
212
+ ```python
213
+ >>> import pythonwrench as pw
214
+ >>> version = pw.Version("1.12.2")
215
+ >>> version.to_tuple()
216
+ ... (1, 12, 2)
217
+ >>> version = pw.Version("0.5.1-beta+linux")
218
+ >>> version.to_tuple()
219
+ ... (0, 5, 1, "beta", "linux")
220
+
221
+ >>> Version("1.3.1") < Version("1.4.0")
222
+ ... True
223
+ ```
224
+
225
+ ### Serialization
226
+
227
+ ```python
228
+ >>> import pythonwrench as pw
229
+ >>>
230
+ >>> list_of_dicts = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
231
+ >>> pw.dump_csv(list_of_dicts, "data.csv")
232
+ >>> pw.dump_json(list_of_dicts, "data.json")
233
+ >>> pw.load_json("data.json") == list_of_dicts
234
+ ... True
235
+ ```
236
+
237
+ ## Lazy loading
238
+ To speed up this package loading, you can install it with `uv add pythonwrench[lazy]`, so `import pythonwrench as pw` will be faster.
239
+
240
+ ## Contact
241
+ Maintainer:
242
+ - [Étienne Labbé](https://labbeti.github.io/) "Labbeti": labbeti.pub@gmail.com
@@ -0,0 +1,52 @@
1
+ pythonwrench/__init__.py,sha256=x-hc6RsMcMvsfZmWSOOPqD1pO7uWDL_tXHtexZn_QZo,14412
2
+ pythonwrench/__main__.py,sha256=-J-4fr1bVi_nkS7UDg41YdzPPKOdFml7lF71OI2cxEM,131
3
+ pythonwrench/_core.py,sha256=W4LcZit_Df1ZHjbHWQtBka2x5EvFun7YSrwgFq4neFU,5690
4
+ pythonwrench/abc.py,sha256=x4VhA3iTqYp2DBt0_eW-regwkYTIAM2aOqOBnEuOymc,751
5
+ pythonwrench/cast.py,sha256=sZUPg90oITHuOZhM2hp4KUVjlMETMheI_uCsUsv_-8s,6187
6
+ pythonwrench/checksum.py,sha256=G6z6L5kYb1aUHe2_iUBo5i5zcvsaj8-ry7RaA4grcrA,12472
7
+ pythonwrench/concurrent.py,sha256=mZQFiy1GBipqB3R-DedQCbPtS8Zt_Re3FYJrxfkRfSM,2281
8
+ pythonwrench/csv.py,sha256=ky7oEtWBosUnJ8yhVz-FaEBcBvgzzRmedrRHNmrWdyM,223
9
+ pythonwrench/dataclasses.py,sha256=-b8F-Xtf_pkNkE7jLYE57WqBUuA8mec_lA_JKFYqd1w,3836
10
+ pythonwrench/datetime.py,sha256=V1TqFrOcmIFTL9E9ZHEBRuD2rwl4x-c4abbNk6VvmbY,490
11
+ pythonwrench/difflib.py,sha256=nSfNpMQQPgHmceA9-IRm8m_MxlK5dmVhOxJooVeie88,1051
12
+ pythonwrench/disk_cache.py,sha256=Uu--EZhBlw5g1c6pLCwdj-zzinOZjtoDx2D7bf3irus,22540
13
+ pythonwrench/enum.py,sha256=DMR7W_6xIyJl3deKJ7C8x_y8nXYd5BE8EtnW4IAMRbE,1758
14
+ pythonwrench/functools.py,sha256=mU-Jc6yliqNGqMRVKWqXHlTYJISUe8ma16A7Py6jlHQ,6060
15
+ pythonwrench/hashlib.py,sha256=1-pt0qv42MUxXoqjiLVhNV9xobznbK0YzYzSZeXl-NQ,2500
16
+ pythonwrench/importlib.py,sha256=8fjjSO6GicWAodJJX0GaSDJGA4au8s8LzsQox1227I4,7163
17
+ pythonwrench/inspect.py,sha256=1T_vIXOgw7QPs6BaoSeIozKfNbut37mO5UNHarb86SY,2033
18
+ pythonwrench/json.py,sha256=lYHXwtuyoAt0gTaURGDX3bOHcrX2GrFh3-JkiG3qNLo,230
19
+ pythonwrench/jsonl.py,sha256=0QFzFyO3rch0RaKAycC1jesO112fZTZSaUMDK-sedEw,237
20
+ pythonwrench/logging.py,sha256=dbD7RxBF6MVhWlsP8wt03Scd2ujQbN3CbZR0fandjo0,7097
21
+ pythonwrench/math.py,sha256=iuZNklFdNXo7Wjh4-KWJvN3kgZQu6Ws3oeygh7AAITE,2580
22
+ pythonwrench/os.py,sha256=vHUcYNzR8LX3mQksfLgxOBPyqvZveb4L7VbmSObhEN4,6690
23
+ pythonwrench/pickle.py,sha256=wXnekklbdyEEkRlMvDuZmZDLCIQaekE359qlup7voUE,244
24
+ pythonwrench/random.py,sha256=604dz2ueMTJDKAoXEM4S-hvp9ypQARBDEgbaRmb_EU0,1252
25
+ pythonwrench/re.py,sha256=34aQ0drOwB65OGpuj1aHz9EF0eMdgvOFxV27HcHGJvc,4046
26
+ pythonwrench/semver.py,sha256=rD4Kz6asC9ZEqhBglCH__EVIckvA1TbDtS_UTYPKu0g,13501
27
+ pythonwrench/time.py,sha256=muyTqTs3_hRdmAE4jErxL_Fio9LrLkEBYHvoTM_BfmA,951
28
+ pythonwrench/warnings.py,sha256=Ue-TlfvywSnbm5MBIu0m6fVYOACHKwYlN3rXdV7OrJg,3340
29
+ pythonwrench/argparse/__init__.py,sha256=CBkoXsq3MbtQt29PkioT4I-_iV4ZkWiIvzpaz7jG4Ys,2153
30
+ pythonwrench/argparse/dataclass_.py,sha256=aFRvOX1gVxly--s9PzcXN59-lRH_xI2OBNom3xGY02I,8663
31
+ pythonwrench/argparse/parsers.py,sha256=we7xoHVCIg5fRf7Ev6M0v3eKn3aoXzwELjZNgTizFJw,17092
32
+ pythonwrench/collections/__init__.py,sha256=mXPH4G2sQwbqeuY2XfdbFrxD7pRXuBWcRSSWM-8YG8E,2421
33
+ pythonwrench/collections/collections.py,sha256=_VrqNRAAN6pVaIt6nrq429p9HkRisVD32iwsDdOOKxo,23331
34
+ pythonwrench/collections/prop.py,sha256=Sg_Av4QCWcLyI1Cu5taG2l2sDS5NE4AfQ_Fz97rINWw,2446
35
+ pythonwrench/collections/reducers.py,sha256=T7al9jmDo7tZUTKX02eSt6CXmb1in34-3ImnfYUst14,6777
36
+ pythonwrench/entrypoints/info.py,sha256=c1WlYYTBrWtqM1zyT55iTmQtujBkFPJRzZeB0TgB9GE,1254
37
+ pythonwrench/entrypoints/safe_rmdir.py,sha256=u1ZrbYToDUuKH_AvYeSkgZWeJPD7qb5BWzdpLL753Y4,2773
38
+ pythonwrench/entrypoints/tree.py,sha256=IKOkiBXtGKc4hoVKAz608oqlNpIWMO25iextJDJ1f-w,2666
39
+ pythonwrench/serialization/__init__.py,sha256=6FwkizzN5dlc9beF6EcsXk83lLXUW59slO8sICRiohQ,1724
40
+ pythonwrench/serialization/_core.py,sha256=I5imPIAmRRNBoVsvqdelZS4QCLjF4wnimVmSOc2g5eQ,1531
41
+ pythonwrench/serialization/csv.py,sha256=eyFuWKnho3Va29uuqSksV5DJIpZLP8v9uH_qpxO6UZ0,15051
42
+ pythonwrench/serialization/json.py,sha256=0wddq-kBluJF_PtIXW5eIVE9-6nu0DoPCEmKm19n7dg,4617
43
+ pythonwrench/serialization/jsonl.py,sha256=R5reMSCgSUkngIm3ENe7KQUEnI06tg5kAVlUs2-bFIE,5506
44
+ pythonwrench/serialization/pickle.py,sha256=MJntjLPwYUJ7PfnhYh_ns15SYEuH0GygXEttWYWvsoU,5051
45
+ pythonwrench/typing/__init__.py,sha256=NO4bVE3AdNQTIr6t4Zkfb219wWsC7nLX8M_LPoATIPc,3406
46
+ pythonwrench/typing/checks.py,sha256=-8Jos3HvzkmH9zfNyfp9k3pAn0x-PD0Kyx5ErTrVw98,16352
47
+ pythonwrench/typing/classes.py,sha256=BWjVjWrFZBKfU7m8dve2EU00KhEnE95_iQfOpGSp0Aw,7234
48
+ pythonwrench-0.6.4.dist-info/METADATA,sha256=g9ez7VaCEDQGVsZDhddckCVam3y1vuXdytHi5XLEoOw,7780
49
+ pythonwrench-0.6.4.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
50
+ pythonwrench-0.6.4.dist-info/entry_points.txt,sha256=cbkAbcRN_B8X6GXLk-fPBXkhpbbEVcnrDGj7GGBEnH4,555
51
+ pythonwrench-0.6.4.dist-info/licenses/LICENSE,sha256=Y0sRu8pOKAbX47H6HaYKhvj1NbuOyzSidGhjhVCzrlg,1064
52
+ pythonwrench-0.6.4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,10 @@
1
+ [console_scripts]
2
+ pw-info = pythonwrench.entrypoints.info:main_info
3
+ pw-safe-rmdir = pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir
4
+ pw-tree = pythonwrench.entrypoints.tree:main_tree
5
+ pythonwrench-info = pythonwrench.entrypoints.info:main_info
6
+ pythonwrench-safe-rmdir = pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir
7
+ pythonwrench-tree = pythonwrench.entrypoints.tree:main_tree
8
+ pyw-info = pythonwrench.entrypoints.info:main_info
9
+ pyw-safe-rmdir = pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir
10
+ pyw-tree = pythonwrench.entrypoints.tree:main_tree