pythonwrench 0.4.1__tar.gz → 0.4.2__tar.gz
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-0.4.1/src/pythonwrench.egg-info → pythonwrench-0.4.2}/PKG-INFO +2 -1
- pythonwrench-0.4.2/docs/requirements.txt +3 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/pyproject.toml +4 -1
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/__init__.py +1 -2
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/_core.py +56 -2
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/abc.py +4 -3
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/cast.py +3 -4
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/checksum.py +3 -4
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/collections.py +25 -30
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/csv.py +9 -9
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/disk_cache.py +77 -47
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/functools.py +25 -10
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/importlib.py +2 -2
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/inspect.py +28 -25
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/json.py +3 -3
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/jsonl.py +3 -3
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/pickle.py +3 -3
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/re.py +3 -4
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/semver.py +2 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/typing/__init__.py +1 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/typing/checks.py +13 -11
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/typing/classes.py +3 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2/src/pythonwrench.egg-info}/PKG-INFO +2 -1
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/SOURCES.txt +1 -1
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/requires.txt +2 -0
- pythonwrench-0.4.2/tests/test_functools.py +32 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_inspect.py +17 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_typing.py +7 -0
- pythonwrench-0.4.1/docs/requirements.txt +0 -3
- pythonwrench-0.4.1/src/pythonwrench/io.py +0 -59
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/LICENSE +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/README.md +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/setup.cfg +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/setup.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/__main__.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/argparse.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/__init__.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/prop.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/reducers.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/dataclasses.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/datetime.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/difflib.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/entries.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/enum.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/hashlib.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/logging.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/math.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/os.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/random.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/warnings.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/dependency_links.txt +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/entry_points.txt +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/top_level.txt +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_abc.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_argparse.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_cast.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_checksum.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_collections.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_csv.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_dataclasses.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_difflib.py +0 -0
- /pythonwrench-0.4.1/tests/test_functools.py → /pythonwrench-0.4.2/tests/test_disk_cache.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_entries.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_enum.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_hashlib.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_importlib.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_json.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_jsonl.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_logging.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_math.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_os.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_random.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_readme.py +0 -0
- {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_semver.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pythonwrench
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Set of tools for Python that could be in the standard library.
|
|
5
5
|
Author-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
6
6
|
Maintainer-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
@@ -47,6 +47,7 @@ Requires-Python: >=3.8
|
|
|
47
47
|
Description-Content-Type: text/markdown
|
|
48
48
|
License-File: LICENSE
|
|
49
49
|
Requires-Dist: typing-extensions>=4.10.0
|
|
50
|
+
Provides-Extra: dev
|
|
50
51
|
|
|
51
52
|
# pythonwrench
|
|
52
53
|
|
|
@@ -45,6 +45,9 @@ pyw-info = "pythonwrench.entries:print_install_info"
|
|
|
45
45
|
pyw-tree = "pythonwrench.entries:main_tree"
|
|
46
46
|
pyw-safe-rmdir = "pythonwrench.entries:main_safe_rmdir"
|
|
47
47
|
|
|
48
|
+
[project.optional-dependencies]
|
|
49
|
+
dev = []
|
|
50
|
+
|
|
48
51
|
[build-system]
|
|
49
52
|
requires = ["setuptools >= 61.0"]
|
|
50
53
|
build-backend = "setuptools.build_meta"
|
|
@@ -87,6 +90,6 @@ dev = [
|
|
|
87
90
|
"ruff>=0.12.0",
|
|
88
91
|
"setuptools",
|
|
89
92
|
"sphinx",
|
|
90
|
-
"sphinx-
|
|
93
|
+
"sphinx-immaterial>=0.11.14",
|
|
91
94
|
"twine",
|
|
92
95
|
]
|
|
@@ -9,7 +9,7 @@ __author_email__ = "labbeti.pub@gmail.com"
|
|
|
9
9
|
__license__ = "MIT"
|
|
10
10
|
__maintainer__ = "Étienne Labbé (Labbeti)"
|
|
11
11
|
__status__ = "Development"
|
|
12
|
-
__version__ = "0.4.
|
|
12
|
+
__version__ = "0.4.2"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# Re-import for language servers
|
|
@@ -29,7 +29,6 @@ from . import functools as functools
|
|
|
29
29
|
from . import hashlib as hashlib
|
|
30
30
|
from . import importlib as importlib
|
|
31
31
|
from . import inspect as inspect
|
|
32
|
-
from . import io as io
|
|
33
32
|
from . import json as json
|
|
34
33
|
from . import jsonl as jsonl
|
|
35
34
|
from . import logging as logging
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
+
import os
|
|
4
5
|
from functools import wraps
|
|
6
|
+
from io import TextIOWrapper
|
|
7
|
+
from pathlib import Path
|
|
5
8
|
from typing import (
|
|
6
9
|
Any,
|
|
7
10
|
Callable,
|
|
@@ -13,13 +16,14 @@ from typing import (
|
|
|
13
16
|
Tuple,
|
|
14
17
|
Union,
|
|
15
18
|
get_args,
|
|
19
|
+
overload,
|
|
16
20
|
)
|
|
17
21
|
|
|
18
22
|
from typing_extensions import ParamSpec, TypeVar
|
|
19
23
|
|
|
20
24
|
P = ParamSpec("P")
|
|
21
|
-
T = TypeVar("T")
|
|
22
|
-
U = TypeVar("U")
|
|
25
|
+
T = TypeVar("T", covariant=True)
|
|
26
|
+
U = TypeVar("U", covariant=True)
|
|
23
27
|
T_Output = TypeVar("T_Output")
|
|
24
28
|
T_Any = TypeVar("T_Any", contravariant=True, default=Any)
|
|
25
29
|
|
|
@@ -165,3 +169,53 @@ class _FunctionRegistry(Generic[T_Output]):
|
|
|
165
169
|
else:
|
|
166
170
|
msg = f"Invalid argument {unk_mode=}. (expected one of {get_args(UnkMode)})"
|
|
167
171
|
raise ValueError(msg)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@overload
|
|
175
|
+
def _setup_output_fpath(
|
|
176
|
+
fpath: Union[str, Path, os.PathLike],
|
|
177
|
+
overwrite: bool,
|
|
178
|
+
make_parents: bool,
|
|
179
|
+
absolute: bool = True,
|
|
180
|
+
) -> Path: ...
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@overload
|
|
184
|
+
def _setup_output_fpath(
|
|
185
|
+
fpath: TextIOWrapper,
|
|
186
|
+
overwrite: bool,
|
|
187
|
+
make_parents: bool,
|
|
188
|
+
absolute: bool = True,
|
|
189
|
+
) -> TextIOWrapper: ...
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@overload
|
|
193
|
+
def _setup_output_fpath(
|
|
194
|
+
fpath: None,
|
|
195
|
+
overwrite: bool,
|
|
196
|
+
make_parents: bool,
|
|
197
|
+
absolute: bool = True,
|
|
198
|
+
) -> None: ...
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _setup_output_fpath(
|
|
202
|
+
fpath: Union[str, Path, os.PathLike, TextIOWrapper, None],
|
|
203
|
+
overwrite: bool,
|
|
204
|
+
make_parents: bool,
|
|
205
|
+
absolute: bool = True,
|
|
206
|
+
) -> Union[Path, None, TextIOWrapper]:
|
|
207
|
+
"""Resolve path, expand path and create intermediate parents."""
|
|
208
|
+
if not isinstance(fpath, (str, Path, os.PathLike)):
|
|
209
|
+
return fpath
|
|
210
|
+
|
|
211
|
+
fpath = Path(fpath)
|
|
212
|
+
if absolute:
|
|
213
|
+
fpath = fpath.resolve().expanduser()
|
|
214
|
+
|
|
215
|
+
if not overwrite and fpath.exists():
|
|
216
|
+
msg = f"File {fpath} already exists."
|
|
217
|
+
raise FileExistsError(msg)
|
|
218
|
+
elif make_parents:
|
|
219
|
+
fpath.parent.mkdir(parents=True, exist_ok=True)
|
|
220
|
+
|
|
221
|
+
return fpath
|
|
@@ -7,14 +7,15 @@ from typing import Any, ClassVar, Dict, Type
|
|
|
7
7
|
class Singleton(type):
|
|
8
8
|
"""Singleton metaclass.
|
|
9
9
|
|
|
10
|
-
To use it, just inherit from metaclass
|
|
11
|
-
|
|
10
|
+
To use it, just inherit from metaclass.
|
|
11
|
+
|
|
12
|
+
Example
|
|
13
|
+
-------
|
|
12
14
|
>>> class MyClass(metaclass=Singleton):
|
|
13
15
|
>>> pass
|
|
14
16
|
>>> a1 = MyClass()
|
|
15
17
|
>>> a2 = MyClass()
|
|
16
18
|
>>> # a1 and a2 are exactly the same instance, i.e. id(a1) == id(a2)
|
|
17
|
-
```
|
|
18
19
|
"""
|
|
19
20
|
|
|
20
21
|
_instances: ClassVar[Dict[Type, Any]] = {}
|
|
@@ -63,16 +63,15 @@ def register_as_builtin_fn(
|
|
|
63
63
|
priority: int = 0,
|
|
64
64
|
) -> Callable:
|
|
65
65
|
"""Decorator to add an as_builtin function.
|
|
66
|
-
```
|
|
67
|
-
>>> import numpy as np
|
|
68
66
|
|
|
67
|
+
Example
|
|
68
|
+
-------
|
|
69
|
+
>>> import numpy as np
|
|
69
70
|
>>> @register_as_builtin_fn(np.ndarray)
|
|
70
71
|
>>> def my_checksum_for_numpy(x: np.ndarray):
|
|
71
72
|
>>> return x.tolist()
|
|
72
|
-
|
|
73
73
|
>>> pw.as_builtin([np.array([1, 2]), [3, 4]])
|
|
74
74
|
... [[1, 2], [3, 4]]
|
|
75
|
-
```
|
|
76
75
|
"""
|
|
77
76
|
return _AS_BUILTIN_REGISTRY.register_decorator(
|
|
78
77
|
class_or_tuple,
|
|
@@ -60,15 +60,14 @@ def register_checksum_fn(
|
|
|
60
60
|
priority: int = 0,
|
|
61
61
|
) -> Callable:
|
|
62
62
|
"""Decorator to add a checksum function.
|
|
63
|
-
```
|
|
64
|
-
>>> import numpy as np
|
|
65
63
|
|
|
64
|
+
Example
|
|
65
|
+
-------
|
|
66
|
+
>>> import numpy as np
|
|
66
67
|
>>> @register_checksum_fn(np.ndarray)
|
|
67
68
|
>>> def my_checksum_for_numpy(x: np.ndarray):
|
|
68
69
|
>>> return int(x.sum())
|
|
69
|
-
|
|
70
70
|
>>> pw.checksum_any(np.array([1, 2])) # calls my_checksum_for_numpy internally, even if array in nested inside a list, dict, etc.
|
|
71
|
-
```
|
|
72
71
|
"""
|
|
73
72
|
return _CHECKSUM_REGISTRY.register_decorator(
|
|
74
73
|
class_or_tuple,
|
|
@@ -95,20 +95,16 @@ def dict_list_to_list_dict(
|
|
|
95
95
|
"""Convert dict of lists with same sizes to list of dicts.
|
|
96
96
|
|
|
97
97
|
Example 1
|
|
98
|
-
|
|
99
|
-
```
|
|
98
|
+
---------
|
|
100
99
|
>>> dic = {"a": [1, 2], "b": [3, 4]}
|
|
101
100
|
>>> dict_list_to_list_dict(dic)
|
|
102
101
|
... [{"a": 1, "b": 3}, {"a": 2, "b": 4}]
|
|
103
|
-
```
|
|
104
102
|
|
|
105
103
|
Example 2
|
|
106
|
-
|
|
107
|
-
```
|
|
104
|
+
---------
|
|
108
105
|
>>> dic = {"a": [1, 2, 3], "b": [4], "c": [5, 6]}
|
|
109
106
|
>>> dict_list_to_list_dict(dic, key_mode="union", default=-1)
|
|
110
107
|
... [{"a": 1, "b": 4, "c": 5}, {"a": 2, "b": -1, "c": 6}, {"a": 3, "b": -1, "c": -1}]
|
|
111
|
-
```
|
|
112
108
|
"""
|
|
113
109
|
if len(dic) == 0:
|
|
114
110
|
return []
|
|
@@ -151,11 +147,9 @@ def dump_dict(
|
|
|
151
147
|
|
|
152
148
|
Example 1:
|
|
153
149
|
----------
|
|
154
|
-
```
|
|
155
150
|
>>> d = {"a": 1, "b": 2}
|
|
156
151
|
>>> dump_dict(d)
|
|
157
152
|
... 'a=1, b=2'
|
|
158
|
-
```
|
|
159
153
|
"""
|
|
160
154
|
if dic is None:
|
|
161
155
|
dic = {}
|
|
@@ -360,7 +354,6 @@ def flat_dict_of_dict(
|
|
|
360
354
|
|
|
361
355
|
Example 1
|
|
362
356
|
---------
|
|
363
|
-
```
|
|
364
357
|
>>> dic = {
|
|
365
358
|
... "a": 1,
|
|
366
359
|
... "b": {
|
|
@@ -370,15 +363,12 @@ def flat_dict_of_dict(
|
|
|
370
363
|
... }
|
|
371
364
|
>>> flat_dict_of_dict(dic)
|
|
372
365
|
... {"a": 1, "b.a": 2, "b.b": 10}
|
|
373
|
-
```
|
|
374
366
|
|
|
375
367
|
Example 2
|
|
376
368
|
---------
|
|
377
|
-
```
|
|
378
369
|
>>> dic = {"a": ["hello", "world"], "b": 3}
|
|
379
370
|
>>> flat_dict_of_dict(dic, flat_iterables=True)
|
|
380
371
|
... {"a.0": "hello", "a.1": "world", "b": 3}
|
|
381
|
-
```
|
|
382
372
|
|
|
383
373
|
Args:
|
|
384
374
|
nested_dic: Nested mapping containing sub-mappings or iterables.
|
|
@@ -508,10 +498,11 @@ def list_dict_to_dict_list(
|
|
|
508
498
|
|
|
509
499
|
Args:
|
|
510
500
|
lst: The list of dict to merge.
|
|
511
|
-
key_mode: Can be "same" or "intersect".
|
|
512
|
-
If "same", all the dictionaries must contains the same keys otherwise a ValueError will be raised.
|
|
513
|
-
If "intersect", only the intersection of all keys will be used in output.
|
|
514
|
-
If "union", the output dict will contains the union of all keys, and the missing value will use the argument default_val.
|
|
501
|
+
key_mode: Can be "same" or "intersect". \
|
|
502
|
+
- If "same", all the dictionaries must contains the same keys otherwise a ValueError will be raised. \
|
|
503
|
+
- If "intersect", only the intersection of all keys will be used in output. \
|
|
504
|
+
- If "union", the output dict will contains the union of all keys, and the missing value will use the argument default_val. \
|
|
505
|
+
- If an iterable of elements, use them as keys for output dict.
|
|
515
506
|
default_val: Default value of an element when key_mode is "union". defaults to None.
|
|
516
507
|
default_val_fn: Function to return the default value according to a specific key. defaults to None.
|
|
517
508
|
list_fn: Optional function to build the values. defaults to identity.
|
|
@@ -622,7 +613,6 @@ def unflat_dict_of_dict(dic: Mapping[str, Any], *, sep: str = ".") -> Dict[str,
|
|
|
622
613
|
|
|
623
614
|
Example 1
|
|
624
615
|
----------
|
|
625
|
-
```
|
|
626
616
|
>>> dic = {
|
|
627
617
|
"a.a": 1,
|
|
628
618
|
"b.a": 2,
|
|
@@ -631,7 +621,6 @@ def unflat_dict_of_dict(dic: Mapping[str, Any], *, sep: str = ".") -> Dict[str,
|
|
|
631
621
|
}
|
|
632
622
|
>>> unflat_dict_of_dict(dic)
|
|
633
623
|
... {"a": {"a": 1}, "b": {"a": 2, "b": 3}, "c": 4}
|
|
634
|
-
```
|
|
635
624
|
"""
|
|
636
625
|
output = {}
|
|
637
626
|
for k, v in dic.items():
|
|
@@ -671,6 +660,7 @@ def unflat_list_of_list(
|
|
|
671
660
|
|
|
672
661
|
|
|
673
662
|
def union_dicts(dicts: Iterable[Dict[K, V]]) -> Dict[K, V]:
|
|
663
|
+
"""Performs union of dictionaries."""
|
|
674
664
|
if Version.python() >= Version("3.9.0"):
|
|
675
665
|
return reduce_or(*dicts)
|
|
676
666
|
|
|
@@ -721,19 +711,24 @@ def unzip(
|
|
|
721
711
|
) -> Tuple[List[T], List[U], List[V], List[W], List[X]]: ...
|
|
722
712
|
|
|
723
713
|
|
|
714
|
+
@overload
|
|
715
|
+
def unzip(
|
|
716
|
+
lst: Iterable[Tuple[T, ...]],
|
|
717
|
+
) -> Tuple[List[T], ...]: ...
|
|
718
|
+
|
|
719
|
+
|
|
724
720
|
def unzip(lst):
|
|
725
721
|
"""Invert function of builtin zip().
|
|
726
722
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
... [1, 2, 3, 4], [5, 6, 7, 8]
|
|
723
|
+
Example
|
|
724
|
+
-------
|
|
725
|
+
>>> lst1 = [1, 2, 3, 4]
|
|
726
|
+
>>> lst2 = [5, 6, 7, 8]
|
|
727
|
+
>>> zipped_list = list(zip(lst1, lst2))
|
|
728
|
+
>>> zipped_list
|
|
729
|
+
... [(1, 5), (2, 6), (3, 7), (4, 8)]
|
|
730
|
+
>>> unzip(zipped_list)
|
|
731
|
+
... [1, 2, 3, 4], [5, 6, 7, 8]
|
|
737
732
|
"""
|
|
738
733
|
return tuple(map(list, zip(*lst)))
|
|
739
734
|
|
|
@@ -741,8 +736,8 @@ def unzip(lst):
|
|
|
741
736
|
def duplicate_list(lst: List[T], sizes: List[int]) -> List[T]:
|
|
742
737
|
"""Duplicate elements elements of a list with the corresponding sizes.
|
|
743
738
|
|
|
744
|
-
Example
|
|
745
|
-
|
|
739
|
+
Example
|
|
740
|
+
-------
|
|
746
741
|
>>> lst = ["a", "b", "c", "d", "e"]
|
|
747
742
|
>>> sizes = [1, 0, 2, 1, 3]
|
|
748
743
|
>>> duplicate_list(lst, sizes)
|
|
@@ -21,10 +21,10 @@ from typing import (
|
|
|
21
21
|
overload,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
+
from pythonwrench._core import _setup_output_fpath
|
|
24
25
|
from pythonwrench.cast import as_builtin
|
|
25
26
|
from pythonwrench.collections import dict_list_to_list_dict, list_dict_to_dict_list
|
|
26
27
|
from pythonwrench.functools import function_alias
|
|
27
|
-
from pythonwrench.io import _setup_output_fpath
|
|
28
28
|
from pythonwrench.typing import isinstance_generic
|
|
29
29
|
|
|
30
30
|
T = TypeVar("T")
|
|
@@ -48,7 +48,7 @@ def dump_csv(
|
|
|
48
48
|
replace_newline_by: Optional[str] = "\\n",
|
|
49
49
|
**csv_writer_kwds,
|
|
50
50
|
) -> str:
|
|
51
|
-
"""Dump content to CSV format into string and/or file.
|
|
51
|
+
r"""Dump content to CSV format into string and/or file.
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
54
|
data: Data to serialize. Can be a list of dicts, dicts of lists or list of lists.
|
|
@@ -59,7 +59,7 @@ def dump_csv(
|
|
|
59
59
|
header: Indicates if CSV must have header. If "auto", an header is added when a dict of list or list of dicts is passed. defaults to "auto".
|
|
60
60
|
align_content: If True, center content at the middle of each row for better visualization. defaults to False.
|
|
61
61
|
replace_newline_by: Replace newline character to avoid newline in CSV content. defaults to "\\n".
|
|
62
|
-
|
|
62
|
+
\*\*csv_writer_kwds: Others optional arguments passed to CSV writer object.
|
|
63
63
|
|
|
64
64
|
Returns:
|
|
65
65
|
Dumped content as string.
|
|
@@ -98,7 +98,7 @@ def dumps_csv(
|
|
|
98
98
|
replace_newline_by: Optional[str] = "\\n",
|
|
99
99
|
**csv_writer_kwds,
|
|
100
100
|
) -> str:
|
|
101
|
-
"""Dump content to CSV format into string.
|
|
101
|
+
r"""Dump content to CSV format into string.
|
|
102
102
|
|
|
103
103
|
Args:
|
|
104
104
|
data: Data to serialize. Can be a list of dicts, dicts of lists or list of lists.
|
|
@@ -108,7 +108,7 @@ def dumps_csv(
|
|
|
108
108
|
header: Indicates if CSV must have header. If "auto", an header is added when a dict of list or list of dicts is passed. defaults to "auto".
|
|
109
109
|
align_content: If True, center content at the middle of each row for better visualization. defaults to False.
|
|
110
110
|
replace_newline_by: Replace newline character to avoid newline in CSV content. defaults to "\\n".
|
|
111
|
-
|
|
111
|
+
\*\*csv_writer_kwds: Others optional arguments passed to CSV writer object.
|
|
112
112
|
|
|
113
113
|
Returns:
|
|
114
114
|
Dumped content as string.
|
|
@@ -140,7 +140,7 @@ def save_csv(
|
|
|
140
140
|
replace_newline_by: Optional[str] = "\\n",
|
|
141
141
|
**csv_writer_kwds,
|
|
142
142
|
) -> None:
|
|
143
|
-
"""Save content to CSV format into a file or buffer.
|
|
143
|
+
r"""Save content to CSV format into a file or buffer.
|
|
144
144
|
|
|
145
145
|
Args:
|
|
146
146
|
data: Data to serialize. Can be a list of dicts, dicts of lists or list of lists.
|
|
@@ -150,7 +150,7 @@ def save_csv(
|
|
|
150
150
|
header: Indicates if CSV must have header. If "auto", an header is added when a dict of list or list of dicts is passed. defaults to "auto".
|
|
151
151
|
align_content: If True, center content at the middle of each row for better visualization. defaults to False.
|
|
152
152
|
replace_newline_by: Replace newline character to avoid newline in CSV content. defaults to "\\n".
|
|
153
|
-
|
|
153
|
+
\*\*csv_writer_kwds: Others optional arguments passed to CSV writer object.
|
|
154
154
|
"""
|
|
155
155
|
if isinstance(file, (str, Path, PathLike)):
|
|
156
156
|
file = _setup_output_fpath(file, overwrite=overwrite, make_parents=make_parents)
|
|
@@ -320,14 +320,14 @@ def load_csv(
|
|
|
320
320
|
delimiter: Optional[str] = ",",
|
|
321
321
|
**csv_reader_kwds,
|
|
322
322
|
) -> Union[List[Dict[str, Any]], Dict[str, List[Any]]]:
|
|
323
|
-
"""Load content from csv filepath.
|
|
323
|
+
r"""Load content from csv filepath.
|
|
324
324
|
|
|
325
325
|
Args:
|
|
326
326
|
orient: Orientation of the output value. Can be "list" or "dict". defaults to "list".
|
|
327
327
|
header: Specify if CSV has header column. defaults to True.
|
|
328
328
|
comment_start: If this string is not None and a line starts with this string, the line will be ignored. defaults to None.
|
|
329
329
|
delimiter: Value delimiter. defaults to ",".
|
|
330
|
-
|
|
330
|
+
\*\*csv_reader_kwds: Other optional csv arguments.
|
|
331
331
|
|
|
332
332
|
Returns:
|
|
333
333
|
The loaded values as dict of lists, list of dicts or list of lists.
|
|
@@ -37,7 +37,7 @@ SavingBackend = Literal["csv", "json", "pickle"]
|
|
|
37
37
|
StoreMode = Literal["outputs_only", "outputs_metadata", "outputs_metadata_inputs"]
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class
|
|
40
|
+
class _CacheMeta(TypedDict):
|
|
41
41
|
datetime: str
|
|
42
42
|
duration: float
|
|
43
43
|
checksum: int
|
|
@@ -52,49 +52,6 @@ _DEFAULT_CACHE_DPATH = Path.home().joinpath(".cache", "disk_cache")
|
|
|
52
52
|
logger = logging.getLogger(__name__)
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def disk_cache_call(
|
|
56
|
-
fn: Callable[..., T],
|
|
57
|
-
*args,
|
|
58
|
-
cache_dpath: Union[str, Path, None] = None,
|
|
59
|
-
cache_force: bool = False,
|
|
60
|
-
cache_verbose: int = 0,
|
|
61
|
-
cache_checksum_fn: ChecksumFn = checksum_any,
|
|
62
|
-
cache_saving_backend: Optional[SavingBackend] = "pickle",
|
|
63
|
-
cache_fname_fmt: str = "{fn_name}_{csum}{suffix}",
|
|
64
|
-
cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
|
|
65
|
-
cache_load_fn: Optional[Callable[[Path], Any]] = None,
|
|
66
|
-
cache_enable: bool = True,
|
|
67
|
-
cache_store_mode: StoreMode = "outputs_metadata",
|
|
68
|
-
**kwargs,
|
|
69
|
-
) -> T:
|
|
70
|
-
"""Call function and store output in a cache file.
|
|
71
|
-
|
|
72
|
-
Cache file is identified by the checksum of the function arguments, and stored by default in `~/.cache/disk_cache/<Function_name>/` directory.
|
|
73
|
-
|
|
74
|
-
```python
|
|
75
|
-
>>> import pythonwrench as pw
|
|
76
|
-
>>> def heavy_processing():
|
|
77
|
-
>>> # Lot of stuff here
|
|
78
|
-
>>> ...
|
|
79
|
-
>>> outputs = pw.disk_cache_call(heavy_processing) # first time function is called
|
|
80
|
-
>>> outputs = pw.disk_cache_call(heavy_processing) # second time outputs is loaded from disk
|
|
81
|
-
```
|
|
82
|
-
"""
|
|
83
|
-
wrapped_fn = _disk_cache_impl(
|
|
84
|
-
cache_dpath=cache_dpath,
|
|
85
|
-
cache_force=cache_force,
|
|
86
|
-
cache_verbose=cache_verbose,
|
|
87
|
-
cache_checksum_fn=cache_checksum_fn,
|
|
88
|
-
cache_saving_backend=cache_saving_backend,
|
|
89
|
-
cache_fname_fmt=cache_fname_fmt,
|
|
90
|
-
cache_dump_fn=cache_dump_fn,
|
|
91
|
-
cache_load_fn=cache_load_fn,
|
|
92
|
-
cache_enable=cache_enable,
|
|
93
|
-
cache_store_mode=cache_store_mode,
|
|
94
|
-
)
|
|
95
|
-
return wrapped_fn(fn)(*args, **kwargs)
|
|
96
|
-
|
|
97
|
-
|
|
98
55
|
@overload
|
|
99
56
|
def disk_cache_decorator(
|
|
100
57
|
fn: None = None,
|
|
@@ -145,9 +102,10 @@ def disk_cache_decorator(
|
|
|
145
102
|
) -> Callable:
|
|
146
103
|
"""Decorator to store function output in a cache file.
|
|
147
104
|
|
|
148
|
-
Cache file is identified by the checksum of the function arguments, and stored by default in
|
|
105
|
+
Cache file is identified by the checksum of the function arguments, and stored by default in `"~/.cache/disk_cache/<Function_name>/"` directory.
|
|
149
106
|
|
|
150
|
-
|
|
107
|
+
Example
|
|
108
|
+
-------
|
|
151
109
|
>>> import pythonwrench as pw
|
|
152
110
|
>>> @pw.disk_cache_decorator
|
|
153
111
|
>>> def heavy_processing():
|
|
@@ -155,7 +113,19 @@ def disk_cache_decorator(
|
|
|
155
113
|
>>> ...
|
|
156
114
|
>>> outputs = heavy_processing() # first time function is called
|
|
157
115
|
>>> outputs = heavy_processing() # second time outputs is loaded from disk
|
|
158
|
-
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
fn: Function to store its output. By default, it must be a callable that returns a pickable object.
|
|
119
|
+
cache_dpath: Cache directory path. defaults to `"~/.cache/disk_cache"`.
|
|
120
|
+
cache_force: Force function call and overwrite cache. defaults to False.
|
|
121
|
+
cache_verbose: Set verbose logging level. Higher means more verbose. defaults to 0.
|
|
122
|
+
cache_checksum_fn: Checksum function to identify input arguments. defaults to ``pythonwrench.checksum_any``.
|
|
123
|
+
cache_saving_backend: Optional saving backend. Can be one of ('csv', 'json', 'pickle'). defaults to 'pickle'.
|
|
124
|
+
cache_fname_fmt: Cache filename format. defaults to "{fn_name}_{csum}{suffix}".
|
|
125
|
+
cache_dump_fn: Dump/save function to store outputs and overwrite saving backend. defaults to None.
|
|
126
|
+
cache_load_fn: Load function to store outputs and overwrite saving backend. defaults to None.
|
|
127
|
+
cache_enable: Enable disk cache. If False, the function has no effect. defaults to True.
|
|
128
|
+
cache_store_mode: Disk cache storage mode. By default, it store function output and saved date into the cache file. defaults to 'outputs_metadata'.
|
|
159
129
|
"""
|
|
160
130
|
impl_fn = _disk_cache_impl(
|
|
161
131
|
cache_dpath=cache_dpath,
|
|
@@ -175,6 +145,64 @@ def disk_cache_decorator(
|
|
|
175
145
|
return impl_fn
|
|
176
146
|
|
|
177
147
|
|
|
148
|
+
def disk_cache_call(
|
|
149
|
+
fn: Callable[..., T],
|
|
150
|
+
*args,
|
|
151
|
+
cache_dpath: Union[str, Path, None] = None,
|
|
152
|
+
cache_force: bool = False,
|
|
153
|
+
cache_verbose: int = 0,
|
|
154
|
+
cache_checksum_fn: ChecksumFn = checksum_any,
|
|
155
|
+
cache_saving_backend: Optional[SavingBackend] = "pickle",
|
|
156
|
+
cache_fname_fmt: str = "{fn_name}_{csum}{suffix}",
|
|
157
|
+
cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
|
|
158
|
+
cache_load_fn: Optional[Callable[[Path], Any]] = None,
|
|
159
|
+
cache_enable: bool = True,
|
|
160
|
+
cache_store_mode: StoreMode = "outputs_metadata",
|
|
161
|
+
**kwargs,
|
|
162
|
+
) -> T:
|
|
163
|
+
r"""Call function and store output in a cache file.
|
|
164
|
+
|
|
165
|
+
Cache file is identified by the checksum of the function arguments, and stored by default in '~/.cache/disk_cache/<Function_name>/' directory.
|
|
166
|
+
|
|
167
|
+
Example
|
|
168
|
+
-------
|
|
169
|
+
>>> import pythonwrench as pw
|
|
170
|
+
>>> def heavy_processing():
|
|
171
|
+
>>> # Lot of stuff here
|
|
172
|
+
>>> ...
|
|
173
|
+
>>> outputs = pw.disk_cache_call(heavy_processing) # first time function is called
|
|
174
|
+
>>> outputs = pw.disk_cache_call(heavy_processing) # second time outputs is loaded from disk
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
fn: Function to store its output. By default, it must be a callable that returns a pickable object.
|
|
178
|
+
cache_dpath: Cache directory path. defaults to '~/.cache/disk_cache'.
|
|
179
|
+
cache_force: Force function call and overwrite cache. defaults to False.
|
|
180
|
+
cache_verbose: Set verbose logging level. Higher means more verbose. defaults to 0.
|
|
181
|
+
cache_checksum_fn: Checksum function to identify input arguments. defaults to ``pythonwrench.checksum_any``.
|
|
182
|
+
cache_saving_backend: Optional saving backend. Can be one of ('csv', 'json', 'pickle'). defaults to 'pickle'.
|
|
183
|
+
cache_fname_fmt: Cache filename format. defaults to '{fn_name}_{csum}{suffix}'.
|
|
184
|
+
cache_dump_fn: Dump/save function to store outputs and overwrite saving backend. defaults to None.
|
|
185
|
+
cache_load_fn: Load function to store outputs and overwrite saving backend. defaults to None.
|
|
186
|
+
cache_enable: Enable disk cache. If False, the function has no effect. defaults to True.
|
|
187
|
+
cache_store_mode: Disk cache storage mode. By default, it store function output and saved date into the cache file. defaults to 'outputs_metadata'.
|
|
188
|
+
\*args: Positional arguments passed to the function.
|
|
189
|
+
\*\*kwargs: Keywords arguments passed to the function.
|
|
190
|
+
"""
|
|
191
|
+
wrapped_fn = _disk_cache_impl(
|
|
192
|
+
cache_dpath=cache_dpath,
|
|
193
|
+
cache_force=cache_force,
|
|
194
|
+
cache_verbose=cache_verbose,
|
|
195
|
+
cache_checksum_fn=cache_checksum_fn,
|
|
196
|
+
cache_saving_backend=cache_saving_backend,
|
|
197
|
+
cache_fname_fmt=cache_fname_fmt,
|
|
198
|
+
cache_dump_fn=cache_dump_fn,
|
|
199
|
+
cache_load_fn=cache_load_fn,
|
|
200
|
+
cache_enable=cache_enable,
|
|
201
|
+
cache_store_mode=cache_store_mode,
|
|
202
|
+
)
|
|
203
|
+
return wrapped_fn(fn)(*args, **kwargs)
|
|
204
|
+
|
|
205
|
+
|
|
178
206
|
def _disk_cache_impl(
|
|
179
207
|
*,
|
|
180
208
|
cache_dpath: Union[str, Path, None] = None,
|
|
@@ -336,6 +364,7 @@ def _disk_cache_impl(
|
|
|
336
364
|
|
|
337
365
|
|
|
338
366
|
def get_cache_dpath(cache_dpath: Union[str, Path, None] = None) -> Path:
|
|
367
|
+
"""Returns defaults disk cache directory path, which is `~/.cache/disk_cache`."""
|
|
339
368
|
if cache_dpath is None:
|
|
340
369
|
cache_dpath = _DEFAULT_CACHE_DPATH
|
|
341
370
|
else:
|
|
@@ -348,6 +377,7 @@ def remove_fn_cache(
|
|
|
348
377
|
*,
|
|
349
378
|
cache_dpath: Union[str, Path, None] = None,
|
|
350
379
|
) -> None:
|
|
380
|
+
"""Removes all caches for a specific function."""
|
|
351
381
|
cache_fn_dpath = _get_fn_cache_dpath(fn, cache_dpath=cache_dpath)
|
|
352
382
|
if cache_fn_dpath.is_dir():
|
|
353
383
|
shutil.rmtree(cache_fn_dpath)
|
|
@@ -14,7 +14,7 @@ from typing import (
|
|
|
14
14
|
from typing_extensions import ParamSpec
|
|
15
15
|
|
|
16
16
|
from pythonwrench._core import _decorator_factory, return_none # noqa: F401
|
|
17
|
-
from pythonwrench.inspect import get_argnames
|
|
17
|
+
from pythonwrench.inspect import _get_code_and_start, get_argnames
|
|
18
18
|
from pythonwrench.typing import isinstance_generic
|
|
19
19
|
|
|
20
20
|
T = TypeVar("T")
|
|
@@ -111,30 +111,45 @@ compose = Compose # type: ignore
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
def filter_and_call(fn: Callable[..., T], **kwargs: Any) -> T:
|
|
114
|
-
"""
|
|
114
|
+
"""Call object only with the valid keyword arguments. Non-valid arguments are ignored.
|
|
115
|
+
|
|
116
|
+
Examples
|
|
117
|
+
--------
|
|
118
|
+
>>> def f(x, y):
|
|
119
|
+
>>> return x + y
|
|
120
|
+
>>> filter_and_call(f, y=2, x=1)
|
|
121
|
+
... 3
|
|
122
|
+
>>> filter_and_call(f, y=2, x=1, z=0) # z is ignored
|
|
123
|
+
... 3
|
|
124
|
+
"""
|
|
115
125
|
argnames = get_argnames(fn)
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
code, start = _get_code_and_start(fn)
|
|
127
|
+
|
|
128
|
+
pos_argnames = argnames[: code.co_posonlyargcount]
|
|
129
|
+
other_argnames = argnames[code.co_posonlyargcount :]
|
|
130
|
+
|
|
131
|
+
posonly_args = [value for name, value in kwargs.items() if name in pos_argnames]
|
|
132
|
+
other_kwds = {
|
|
133
|
+
name: value for name, value in kwargs.items() if name in other_argnames
|
|
118
134
|
}
|
|
119
|
-
|
|
135
|
+
result = fn(*posonly_args, **other_kwds)
|
|
136
|
+
return result
|
|
120
137
|
|
|
121
138
|
|
|
122
139
|
def function_alias(alternative: Callable[P, U]) -> Callable[..., Callable[P, U]]:
|
|
123
140
|
"""Decorator to wrap function aliases.
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
|
|
142
|
+
Example
|
|
143
|
+
-------
|
|
127
144
|
>>> def f(a: int, b: str) -> str:
|
|
128
145
|
>>> return a * b
|
|
129
|
-
|
|
130
146
|
>>> @function_alias(f)
|
|
131
147
|
>>> def g(*args, **kwargs): ...
|
|
132
|
-
|
|
133
148
|
>>> f(2, "a")
|
|
134
149
|
... "aa"
|
|
135
150
|
>>> g(3, "b") # calls function f() internally.
|
|
136
151
|
... "bbb"
|
|
137
|
-
|
|
152
|
+
|
|
138
153
|
"""
|
|
139
154
|
return _decorator_factory(alternative)
|
|
140
155
|
|
|
@@ -181,12 +181,12 @@ def requires_packages(
|
|
|
181
181
|
) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
|
182
182
|
"""Decorator to wrap a function and raises an error if the function is called.
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
Example
|
|
185
|
+
-------
|
|
185
186
|
>>> @requires_packages("pandas")
|
|
186
187
|
>>> def f(x):
|
|
187
188
|
>>> return x
|
|
188
189
|
>>> f(1) # raises ImportError if pandas is not installed
|
|
189
|
-
```
|
|
190
190
|
"""
|
|
191
191
|
if isinstance(arg0, str):
|
|
192
192
|
packages = [arg0] + list(args)
|