pythonwrench 0.4.6__tar.gz → 0.4.8__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.
Files changed (73) hide show
  1. {pythonwrench-0.4.6/src/pythonwrench.egg-info → pythonwrench-0.4.8}/PKG-INFO +1 -1
  2. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/__init__.py +7 -2
  3. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/cast.py +1 -1
  4. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/checksum.py +16 -0
  5. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/disk_cache.py +177 -26
  6. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/entries.py +5 -0
  7. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/functools.py +1 -1
  8. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/pickle.py +32 -0
  9. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/typing/checks.py +12 -6
  10. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/typing/classes.py +35 -3
  11. {pythonwrench-0.4.6 → pythonwrench-0.4.8/src/pythonwrench.egg-info}/PKG-INFO +1 -1
  12. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_checksum.py +17 -0
  13. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_typing.py +8 -0
  14. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/LICENSE +0 -0
  15. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/README.md +0 -0
  16. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/docs/requirements.txt +0 -0
  17. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/pyproject.toml +0 -0
  18. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/setup.cfg +0 -0
  19. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/setup.py +0 -0
  20. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/__main__.py +0 -0
  21. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/_core.py +0 -0
  22. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/abc.py +0 -0
  23. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/argparse.py +0 -0
  24. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/collections/__init__.py +0 -0
  25. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/collections/collections.py +0 -0
  26. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/collections/prop.py +0 -0
  27. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/collections/reducers.py +0 -0
  28. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/concurrent.py +0 -0
  29. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/csv.py +0 -0
  30. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/dataclasses.py +0 -0
  31. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/datetime.py +0 -0
  32. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/difflib.py +0 -0
  33. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/enum.py +0 -0
  34. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/hashlib.py +0 -0
  35. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/importlib.py +0 -0
  36. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/inspect.py +0 -0
  37. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/json.py +0 -0
  38. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/jsonl.py +0 -0
  39. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/logging.py +0 -0
  40. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/math.py +0 -0
  41. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/os.py +0 -0
  42. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/random.py +0 -0
  43. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/re.py +0 -0
  44. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/semver.py +0 -0
  45. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/typing/__init__.py +0 -0
  46. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench/warnings.py +0 -0
  47. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench.egg-info/SOURCES.txt +0 -0
  48. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench.egg-info/dependency_links.txt +0 -0
  49. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench.egg-info/entry_points.txt +0 -0
  50. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench.egg-info/requires.txt +0 -0
  51. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/src/pythonwrench.egg-info/top_level.txt +0 -0
  52. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_abc.py +0 -0
  53. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_argparse.py +0 -0
  54. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_cast.py +0 -0
  55. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_collections.py +0 -0
  56. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_csv.py +0 -0
  57. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_dataclasses.py +0 -0
  58. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_difflib.py +0 -0
  59. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_disk_cache.py +0 -0
  60. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_entries.py +0 -0
  61. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_enum.py +0 -0
  62. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_functools.py +0 -0
  63. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_hashlib.py +0 -0
  64. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_importlib.py +0 -0
  65. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_inspect.py +0 -0
  66. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_json.py +0 -0
  67. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_jsonl.py +0 -0
  68. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_logging.py +0 -0
  69. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_math.py +0 -0
  70. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_os.py +0 -0
  71. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_random.py +0 -0
  72. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_readme.py +0 -0
  73. {pythonwrench-0.4.6 → pythonwrench-0.4.8}/tests/test_semver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pythonwrench
3
- Version: 0.4.6
3
+ Version: 0.4.8
4
4
  Summary: Python library with tools for typing, manipulating collections, and more!
5
5
  Author-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
6
6
  Maintainer-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
@@ -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.6"
12
+ __version__ = "0.4.8"
13
13
 
14
14
 
15
15
  # Re-import for language servers
@@ -54,7 +54,7 @@ from .argparse import (
54
54
  str_to_type,
55
55
  )
56
56
  from .cast import as_builtin, register_as_builtin_fn
57
- from .checksum import checksum_any, register_checksum_fn
57
+ from .checksum import checksum_any, checksum_object, register_checksum_fn
58
58
  from .collections import (
59
59
  all_eq,
60
60
  all_ne,
@@ -165,6 +165,7 @@ from .typing import (
165
165
  BuiltinScalar,
166
166
  DataclassInstance,
167
167
  EllipsisType,
168
+ ListOrTuple,
168
169
  NamedTupleInstance,
169
170
  NoneType,
170
171
  SupportsAdd,
@@ -172,10 +173,14 @@ from .typing import (
172
173
  SupportsBool,
173
174
  SupportsDiv,
174
175
  SupportsGetitem,
176
+ SupportsGetitem2,
175
177
  SupportsGetitemIterLen,
178
+ SupportsGetitemIterLen2,
176
179
  SupportsGetitemLen,
180
+ SupportsGetitemLen2,
177
181
  SupportsIterLen,
178
182
  SupportsLen,
183
+ SupportsMatmul,
179
184
  SupportsMul,
180
185
  SupportsOr,
181
186
  T_BuiltinNumber,
@@ -182,7 +182,7 @@ def as_builtin(x: Any, **kwargs) -> Any: ...
182
182
 
183
183
 
184
184
  def as_builtin(x: Any, **kwargs) -> Any:
185
- """Convert an object to a sanitized python builtin equivalent.
185
+ """Convert an object to a sanitized python builtin equivalent recursively.
186
186
 
187
187
  This function can be used to sanitize data before saving to a JSON, YAML or CSV file.
188
188
 
@@ -6,6 +6,7 @@ import re
6
6
  import struct
7
7
  import zlib
8
8
  from dataclasses import asdict
9
+ from enum import Enum
9
10
  from functools import lru_cache
10
11
  from pathlib import Path
11
12
  from types import FunctionType, MethodType
@@ -22,6 +23,7 @@ from typing import (
22
23
  )
23
24
 
24
25
  from pythonwrench._core import ClassOrTuple, Predicate, _FunctionRegistry
26
+ from pythonwrench.functools import function_alias
25
27
  from pythonwrench.inspect import get_fullname
26
28
  from pythonwrench.typing import (
27
29
  DataclassInstance,
@@ -85,10 +87,15 @@ def checksum_any(
85
87
  """Compute checksum integer value from an arbitrary object.
86
88
 
87
89
  Supports most builtin types. Checksum can be used to compare objects.
90
+ Not meant for security/cryptography.
88
91
  """
89
92
  return _CHECKSUM_REGISTRY.apply(x, isinstance_fn=isinstance_fn, **kwargs)
90
93
 
91
94
 
95
+ @function_alias(checksum_any)
96
+ def checksum_object(*args, **kwargs): ...
97
+
98
+
92
99
  # Terminate functions
93
100
  @register_checksum_fn(bool)
94
101
  def checksum_bool(x: bool, **kwargs) -> int:
@@ -185,6 +192,11 @@ def checksum_dict(x: dict, **kwargs) -> int:
185
192
  return _checksum_mapping(x, **kwargs)
186
193
 
187
194
 
195
+ @register_checksum_fn(Enum)
196
+ def checksum_enum(x: Enum, **kwargs) -> int:
197
+ return _checksum_iterable((x.__class__, x.name, x.value), **kwargs)
198
+
199
+
188
200
  @register_checksum_fn((list, tuple))
189
201
  def checksum_list_tuple(x: Union[list, tuple], **kwargs) -> int:
190
202
  return _checksum_iterable(x, **kwargs)
@@ -253,6 +265,10 @@ def checksum_path(x: Path, **kwargs) -> int:
253
265
  kwargs["accumulator"] = kwargs.get("accumulator", 0) + _cached_checksum_str(
254
266
  get_fullname(x)
255
267
  )
268
+ resolve_path = kwargs.get("resolve_path", False)
269
+ if isinstance(resolve_path, bool) and resolve_path:
270
+ x = x.expanduser().resolve()
271
+
256
272
  return checksum_str(str(x), **kwargs)
257
273
 
258
274
 
@@ -12,6 +12,7 @@ from typing import (
12
12
  Any,
13
13
  Callable,
14
14
  Dict,
15
+ Iterable,
15
16
  Literal,
16
17
  Optional,
17
18
  Tuple,
@@ -37,6 +38,8 @@ ChecksumFn = Callable[[Tuple[Callable[P, T], Tuple, Dict[str, Any]]], int]
37
38
  SavingBackend = Literal["csv", "json", "pickle"]
38
39
  StoreMode = Literal["outputs_only", "outputs_metadata", "outputs_metadata_inputs"]
39
40
 
41
+ _DEFAULT_CACHE_STORE_MODE: StoreMode = "outputs_metadata"
42
+
40
43
 
41
44
  class _CacheMeta(TypedDict):
42
45
  datetime: str
@@ -61,12 +64,49 @@ def disk_cache_decorator(
61
64
  cache_force: bool = False,
62
65
  cache_verbose: int = 0,
63
66
  cache_checksum_fn: ChecksumFn = checksum_any,
64
- cache_saving_backend: Optional[SavingBackend] = "pickle",
67
+ cache_saving_backend: Literal["custom"],
68
+ cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
69
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
70
+ cache_dump_fn: Callable[[Any, Path], Any],
71
+ cache_load_fn: Callable[[Path], Any],
72
+ cache_enable: bool = True,
73
+ cache_store_mode: StoreMode,
74
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
75
+
76
+
77
+ @overload
78
+ def disk_cache_decorator(
79
+ fn: None = None,
80
+ *,
81
+ cache_dpath: Union[str, Path, None] = None,
82
+ cache_force: bool = False,
83
+ cache_verbose: int = 0,
84
+ cache_checksum_fn: ChecksumFn = checksum_any,
85
+ cache_saving_backend: SavingBackend,
86
+ cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
87
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
88
+ cache_dump_fn: None = None,
89
+ cache_load_fn: None = None,
90
+ cache_enable: bool = True,
91
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
92
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
93
+
94
+
95
+ @overload
96
+ def disk_cache_decorator(
97
+ fn: None = None,
98
+ *,
99
+ cache_dpath: Union[str, Path, None] = None,
100
+ cache_force: bool = False,
101
+ cache_verbose: int = 0,
102
+ cache_checksum_fn: ChecksumFn = checksum_any,
103
+ cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto",
65
104
  cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
105
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
66
106
  cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
67
107
  cache_load_fn: Optional[Callable[[Path], Any]] = None,
68
108
  cache_enable: bool = True,
69
- cache_store_mode: StoreMode = "outputs_metadata",
109
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
70
110
  ) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
71
111
 
72
112
 
@@ -78,12 +118,31 @@ def disk_cache_decorator(
78
118
  cache_force: bool = False,
79
119
  cache_verbose: int = 0,
80
120
  cache_checksum_fn: ChecksumFn = checksum_any,
81
- cache_saving_backend: Optional[SavingBackend] = "pickle",
121
+ cache_saving_backend: Literal["custom"],
122
+ cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
123
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
124
+ cache_dump_fn: Callable[[Any, Path], Any],
125
+ cache_load_fn: Callable[[Path], Any],
126
+ cache_enable: bool = True,
127
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
128
+ ) -> Callable[P, T]: ...
129
+
130
+
131
+ @overload
132
+ def disk_cache_decorator(
133
+ fn: Callable[P, T],
134
+ *,
135
+ cache_dpath: Union[str, Path, None] = None,
136
+ cache_force: bool = False,
137
+ cache_verbose: int = 0,
138
+ cache_checksum_fn: ChecksumFn = checksum_any,
139
+ cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto",
82
140
  cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
141
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
83
142
  cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
84
143
  cache_load_fn: Optional[Callable[[Path], Any]] = None,
85
144
  cache_enable: bool = True,
86
- cache_store_mode: StoreMode = "outputs_metadata",
145
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
87
146
  ) -> Callable[P, T]: ...
88
147
 
89
148
 
@@ -94,12 +153,13 @@ def disk_cache_decorator(
94
153
  cache_force: bool = False,
95
154
  cache_verbose: int = 0,
96
155
  cache_checksum_fn: ChecksumFn = checksum_any,
97
- cache_saving_backend: Optional[SavingBackend] = "pickle",
156
+ cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto",
98
157
  cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
158
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
99
159
  cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
100
160
  cache_load_fn: Optional[Callable[[Path], Any]] = None,
101
161
  cache_enable: bool = True,
102
- cache_store_mode: StoreMode = "outputs_metadata",
162
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
103
163
  ) -> Callable:
104
164
  """Decorator to store function output in a cache file.
105
165
 
@@ -121,7 +181,7 @@ def disk_cache_decorator(
121
181
  cache_force: Force function call and overwrite cache. defaults to False.
122
182
  cache_verbose: Set verbose logging level. Higher means more verbose. defaults to 0.
123
183
  cache_checksum_fn: Checksum function to identify input arguments. defaults to ``pythonwrench.checksum_any``.
124
- cache_saving_backend: Optional saving backend. Can be one of ('csv', 'json', 'pickle'). defaults to 'pickle'.
184
+ cache_saving_backend: Optional saving backend. Can be one of ('csv', 'json', 'pickle', 'custom', 'auto'). defaults to 'auto'.
125
185
  cache_fname_fmt: Cache filename format. defaults to "{fn_name}_{csum}{suffix}".
126
186
  cache_dump_fn: Dump/save function to store outputs and overwrite saving backend. defaults to None.
127
187
  cache_load_fn: Load function to store outputs and overwrite saving backend. defaults to None.
@@ -135,6 +195,7 @@ def disk_cache_decorator(
135
195
  cache_checksum_fn=cache_checksum_fn,
136
196
  cache_saving_backend=cache_saving_backend,
137
197
  cache_fname_fmt=cache_fname_fmt,
198
+ cache_fname_fmt_args=cache_fname_fmt_args,
138
199
  cache_dump_fn=cache_dump_fn,
139
200
  cache_load_fn=cache_load_fn,
140
201
  cache_enable=cache_enable,
@@ -146,6 +207,7 @@ def disk_cache_decorator(
146
207
  return impl_fn
147
208
 
148
209
 
210
+ @overload
149
211
  def disk_cache_call(
150
212
  fn: Callable[..., T],
151
213
  *args,
@@ -153,12 +215,69 @@ def disk_cache_call(
153
215
  cache_force: bool = False,
154
216
  cache_verbose: int = 0,
155
217
  cache_checksum_fn: ChecksumFn = checksum_any,
156
- cache_saving_backend: Optional[SavingBackend] = "pickle",
218
+ cache_saving_backend: Literal["custom"],
157
219
  cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
220
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
221
+ cache_dump_fn: Callable[[Any, Path], Any],
222
+ cache_load_fn: Callable[[Path], Any],
223
+ cache_enable: bool = True,
224
+ cache_store_mode: StoreMode,
225
+ **kwargs,
226
+ ) -> T: ...
227
+
228
+
229
+ @overload
230
+ def disk_cache_call(
231
+ fn: Callable[..., T],
232
+ *args,
233
+ cache_dpath: Union[str, Path, None] = None,
234
+ cache_force: bool = False,
235
+ cache_verbose: int = 0,
236
+ cache_checksum_fn: ChecksumFn = checksum_any,
237
+ cache_saving_backend: SavingBackend,
238
+ cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
239
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
240
+ cache_dump_fn: None = None,
241
+ cache_load_fn: None = None,
242
+ cache_enable: bool = True,
243
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
244
+ **kwargs,
245
+ ) -> T: ...
246
+
247
+
248
+ @overload
249
+ def disk_cache_call(
250
+ fn: Callable[..., T],
251
+ *args,
252
+ cache_dpath: Union[str, Path, None] = None,
253
+ cache_force: bool = False,
254
+ cache_verbose: int = 0,
255
+ cache_checksum_fn: ChecksumFn = checksum_any,
256
+ cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto",
257
+ cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
258
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
158
259
  cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
159
260
  cache_load_fn: Optional[Callable[[Path], Any]] = None,
160
261
  cache_enable: bool = True,
161
- cache_store_mode: StoreMode = "outputs_metadata",
262
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
263
+ **kwargs,
264
+ ) -> T: ...
265
+
266
+
267
+ def disk_cache_call(
268
+ fn: Callable[..., T],
269
+ *args,
270
+ cache_dpath: Union[str, Path, None] = None,
271
+ cache_force: bool = False,
272
+ cache_verbose: int = 0,
273
+ cache_checksum_fn: ChecksumFn = checksum_any,
274
+ cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto",
275
+ cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
276
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
277
+ cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
278
+ cache_load_fn: Optional[Callable[[Path], Any]] = None,
279
+ cache_enable: bool = True,
280
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
162
281
  **kwargs,
163
282
  ) -> T:
164
283
  r"""Call function and store output in a cache file.
@@ -180,7 +299,7 @@ def disk_cache_call(
180
299
  cache_force: Force function call and overwrite cache. defaults to False.
181
300
  cache_verbose: Set verbose logging level. Higher means more verbose. defaults to 0.
182
301
  cache_checksum_fn: Checksum function to identify input arguments. defaults to ``pythonwrench.checksum_any``.
183
- cache_saving_backend: Optional saving backend. Can be one of ('csv', 'json', 'pickle'). defaults to 'pickle'.
302
+ cache_saving_backend: Optional saving backend. Can be one of ('csv', 'json', 'pickle', 'custom', 'auto'). defaults to 'auto'.
184
303
  cache_fname_fmt: Cache filename format. defaults to '{fn_name}_{csum}{suffix}'.
185
304
  cache_dump_fn: Dump/save function to store outputs and overwrite saving backend. defaults to None.
186
305
  cache_load_fn: Load function to store outputs and overwrite saving backend. defaults to None.
@@ -196,6 +315,7 @@ def disk_cache_call(
196
315
  cache_checksum_fn=cache_checksum_fn,
197
316
  cache_saving_backend=cache_saving_backend,
198
317
  cache_fname_fmt=cache_fname_fmt,
318
+ cache_fname_fmt_args=cache_fname_fmt_args,
199
319
  cache_dump_fn=cache_dump_fn,
200
320
  cache_load_fn=cache_load_fn,
201
321
  cache_enable=cache_enable,
@@ -210,20 +330,36 @@ def _disk_cache_impl(
210
330
  cache_force: bool = False,
211
331
  cache_verbose: int = 0,
212
332
  cache_checksum_fn: ChecksumFn = checksum_any,
213
- cache_saving_backend: Optional[SavingBackend] = "pickle",
333
+ cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto",
214
334
  cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}",
335
+ cache_fname_fmt_args: Optional[Iterable[str]] = None,
215
336
  cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None,
216
337
  cache_load_fn: Optional[Callable[[Path], Any]] = None,
217
338
  cache_enable: bool = True,
218
- cache_store_mode: StoreMode = "outputs_metadata",
339
+ cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE,
219
340
  ) -> Callable[[Callable[P, T]], Callable[P, T]]:
220
341
  # for backward compatibility
221
342
  if cache_fname_fmt is None:
343
+ expected = "{fn_name}_{csum}{suffix}"
222
344
  warnings.warn(
223
- f"Deprecated argument value {cache_fname_fmt=}. (use default instead)",
345
+ f"Deprecated argument value {cache_fname_fmt=}. (use {expected} instead)",
224
346
  DeprecationWarning,
225
347
  )
226
- cache_fname_fmt = "{fn_name}_{csum}{suffix}"
348
+ cache_fname_fmt = expected
349
+
350
+ if cache_saving_backend is None:
351
+ expected = "auto"
352
+ warnings.warn(
353
+ f"Deprecated argument value {cache_saving_backend=}. (use {expected} instead)",
354
+ DeprecationWarning,
355
+ )
356
+ cache_saving_backend = expected
357
+
358
+ if cache_saving_backend == "auto":
359
+ if cache_dump_fn is not None and cache_load_fn is not None:
360
+ cache_saving_backend = "custom"
361
+ else:
362
+ cache_saving_backend = "pickle"
227
363
 
228
364
  if cache_saving_backend == "pickle":
229
365
  from pythonwrench.pickle import dump_pickle, load_pickle
@@ -250,9 +386,9 @@ def _disk_cache_impl(
250
386
  cache_dump_fn = dump_csv
251
387
  cache_load_fn = load_csv
252
388
 
253
- elif cache_saving_backend is None:
254
- if cache_fname_fmt is None or cache_dump_fn is None or cache_load_fn is None:
255
- msg = f"If {cache_saving_backend=}, arguments cache_fname_fmt, cache_dump_fn and cache_load_fn cannot be None. (found {cache_fname_fmt=}, {cache_dump_fn=} {cache_load_fn=})"
389
+ elif cache_saving_backend == "custom":
390
+ if cache_dump_fn is None or cache_load_fn is None:
391
+ msg = f"If {cache_saving_backend=}, arguments cache_dump_fn and cache_load_fn cannot be None. (found {cache_dump_fn=} {cache_load_fn=})"
256
392
  raise ValueError(msg)
257
393
 
258
394
  suffix = ""
@@ -283,15 +419,30 @@ def _disk_cache_impl(
283
419
  @wraps(fn)
284
420
  def _disk_cache_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
285
421
  checksum_args = fn, args, kwargs
286
- csum = cache_checksum_fn(checksum_args)
287
- inputs = dict(zip(argnames, args))
288
- inputs.update(kwargs)
289
- cache_fname = cache_fname_fmt(
290
- fn_name=fn_name,
291
- csum=csum,
292
- suffix=suffix,
293
- **inputs,
294
- )
422
+
423
+ kwds = {}
424
+
425
+ if cache_fname_fmt_args is None or "fn_name" in cache_fname_fmt_args:
426
+ kwds["fn_name"] = fn_name
427
+
428
+ if cache_fname_fmt_args is None or "suffix" in cache_fname_fmt_args:
429
+ kwds["suffix"] = suffix
430
+
431
+ if cache_fname_fmt_args is None or "csum" in cache_fname_fmt_args:
432
+ csum = cache_checksum_fn(checksum_args)
433
+ kwds["checksum"] = csum
434
+ kwds["csum"] = csum
435
+ else:
436
+ csum = None
437
+
438
+ inputs_kwds = {
439
+ argname: argval
440
+ for argname, argval in zip(argnames, args)
441
+ if cache_fname_fmt_args is None or argname in cache_fname_fmt_args
442
+ }
443
+ kwds.update(inputs_kwds)
444
+
445
+ cache_fname = cache_fname_fmt(**kwds)
295
446
  cache_fpath = cache_fn_dpath.joinpath(cache_fname)
296
447
 
297
448
  if not cache_enable:
@@ -175,24 +175,29 @@ def main_safe_rmdir() -> None:
175
175
  )
176
176
  parser.add_argument(
177
177
  "--rm_root",
178
+ "--rm-root",
178
179
  type=str_to_bool,
179
180
  default=True,
180
181
  help="If True, remove the root directory too if it is empty at the end. defaults to True.",
181
182
  )
182
183
  parser.add_argument(
183
184
  "--error_on_non_empty_dir",
185
+ "--error-on-non-empty-dir",
184
186
  type=str_to_bool,
185
187
  default=True,
186
188
  help="If True, raises a RuntimeError if a subdirectory contains at least 1 file. Otherwise it will ignore non-empty directories. defaults to True.",
187
189
  )
188
190
  parser.add_argument(
189
191
  "--followlinks",
192
+ "--follow_links",
193
+ "--follow-links",
190
194
  type=str_to_bool,
191
195
  default=False,
192
196
  help="Indicates whether or not symbolic links shound be followed. defaults to False.",
193
197
  )
194
198
  parser.add_argument(
195
199
  "--dry_run",
200
+ "--dry-run",
196
201
  type=str_to_bool,
197
202
  default=False,
198
203
  help="If True, does not remove any directory and just output the list of directories which could be deleted. defaults to False.",
@@ -89,7 +89,7 @@ class Compose(Generic[T, U]):
89
89
  elif isinstance_generic(fns, Tuple[Callable, ...]):
90
90
  pass
91
91
  else:
92
- msg = f"Invalid argument types {type(fns)=}."
92
+ msg = f"Invalid argument types {type(fns)=}. (with {fns=})"
93
93
  raise TypeError(msg)
94
94
 
95
95
  super().__init__()
@@ -66,6 +66,16 @@ def dumps_pickle(
66
66
  to_builtins: bool = False,
67
67
  **pkl_dumps_kwds,
68
68
  ) -> bytes:
69
+ r"""Dump content to PICKLE format into bytes.
70
+
71
+ Args:
72
+ data: Data to dump to PICKLE.
73
+ to_builtins: If True, converts data to builtin equivalent before saving. defaults to False.
74
+ \*\*pkl_dumps_kwds: Other args passed to `pickle.dumps`.
75
+
76
+ Returns:
77
+ Dumped content as bytes.
78
+ """
69
79
  with BytesIO() as buffer:
70
80
  _serialize_pickle(
71
81
  data,
@@ -87,6 +97,16 @@ def save_pickle(
87
97
  to_builtins: bool = False,
88
98
  **pkl_dumps_kwds,
89
99
  ) -> None:
100
+ r"""Dump content to PICKLE format into file.
101
+
102
+ Args:
103
+ data: Data to dump to PICKLE.
104
+ file: Filepath to save dumped data.
105
+ overwrite: If True, overwrite target filepath. defaults to True.
106
+ make_parents: Build intermediate directories to filepath. defaults to True.
107
+ to_builtins: If True, converts data to builtin equivalent before saving. defaults to False.
108
+ \*\*pkl_dumps_kwds: Other args passed to `pickle.dumps`.
109
+ """
90
110
  if isinstance(file, (str, Path, PathLike)):
91
111
  file = _setup_output_fpath(file, overwrite=overwrite, make_parents=make_parents)
92
112
  file = open(file, "wb")
@@ -125,6 +145,12 @@ def _serialize_pickle(
125
145
 
126
146
 
127
147
  def load_pickle(file: Union[str, Path, BinaryIO], /, **pkl_loads_kwds) -> Any:
148
+ r"""Load content from PICKLE file.
149
+
150
+ Args:
151
+ file: Filepath file path.
152
+ \*\*pkl_loads_kwds: Other args passed to `pickle.loads`.
153
+ """
128
154
  if isinstance(file, (str, Path, PathLike)):
129
155
  file = open(file, "rb")
130
156
  close = True
@@ -138,6 +164,12 @@ def load_pickle(file: Union[str, Path, BinaryIO], /, **pkl_loads_kwds) -> Any:
138
164
 
139
165
 
140
166
  def loads_pickle(content: bytes, /, **pkl_loads_kwds) -> Any:
167
+ r"""Load content from raw bytes.
168
+
169
+ Args:
170
+ content: Encoded elements bytes.
171
+ \*\*pkl_loads_kwds: Other args passed to `pickle.loads`.
172
+ """
141
173
  with BytesIO(content) as buffer:
142
174
  return _parse_pickle(buffer, **pkl_loads_kwds)
143
175
 
@@ -99,7 +99,7 @@ def check_args_types(fn: Callable[P, T]) -> Callable[P, T]:
99
99
 
100
100
  def isinstance_generic(
101
101
  obj: Any,
102
- class_or_tuple: Union[Type[T], None, Tuple[Type[T], ...]],
102
+ class_or_tuple: Union[Type[T], None, Tuple[Type[T], ...], Any],
103
103
  *,
104
104
  check_only_first: bool = False,
105
105
  ) -> TypeIs[T]:
@@ -123,8 +123,6 @@ def isinstance_generic(
123
123
  ... True
124
124
 
125
125
  """
126
- if isinstance(obj, type):
127
- return False
128
126
  if class_or_tuple is Any or class_or_tuple is typing_extensions.Any:
129
127
  return True
130
128
  if class_or_tuple is None:
@@ -139,7 +137,7 @@ def isinstance_generic(
139
137
 
140
138
  origin = get_origin(class_or_tuple)
141
139
  if origin is None:
142
- return isinstance(obj, class_or_tuple)
140
+ return isinstance(obj, class_or_tuple) # type: ignore
143
141
 
144
142
  # Special case for empty tuple because get_args(Tuple[()]) returns () and not ((),) in python >= 3.11
145
143
  # More info at https://github.com/python/cpython/issues/91137
@@ -147,6 +145,14 @@ def isinstance_generic(
147
145
  return obj == ()
148
146
 
149
147
  args = get_args(class_or_tuple)
148
+ if origin is Callable:
149
+ if len(args) == 0:
150
+ return callable(obj)
151
+ else:
152
+ # TODO: impl
153
+ msg = "Function `isinstance_generic` currently does not support parametrized Callable."
154
+ raise NotImplementedError(msg)
155
+
150
156
  if len(args) == 0:
151
157
  return isinstance_generic(obj, origin)
152
158
 
@@ -298,7 +304,7 @@ def is_dataclass_instance(x: Any) -> TypeIs[DataclassInstance]:
298
304
 
299
305
  Unlike function `dataclasses.is_dataclass`, this function returns False for a dataclass type.
300
306
  """
301
- return isinstance_generic(x, DataclassInstance)
307
+ return not isinstance(x, type) and isinstance_generic(x, DataclassInstance)
302
308
 
303
309
 
304
310
  def is_iterable_bool(
@@ -369,7 +375,7 @@ def is_iterable_str(
369
375
 
370
376
  def is_namedtuple_instance(x: Any) -> TypeIs[NamedTupleInstance]:
371
377
  """Returns True if argument is a NamedTuple."""
372
- return isinstance_generic(x, NamedTupleInstance)
378
+ return not isinstance(x, type) and isinstance_generic(x, NamedTupleInstance)
373
379
 
374
380
 
375
381
  def is_sequence_str(
@@ -67,37 +67,50 @@ class NamedTupleInstance(Protocol):
67
67
 
68
68
  @runtime_checkable
69
69
  class SupportsAdd(Protocol[_T_Other]):
70
+ """Protocol that support `__add__` (+) method."""
71
+
70
72
  def __add__(self, other: _T_Other, /):
71
73
  raise NotImplementedError
72
74
 
73
75
 
74
76
  @runtime_checkable
75
77
  class SupportsAnd(Protocol[_T_Other]):
78
+ """Protocol that support `__and__` (&) method."""
79
+
76
80
  def __and__(self, other: _T_Other, /):
77
81
  raise NotImplementedError
78
82
 
79
83
 
80
84
  @runtime_checkable
81
85
  class SupportsBool(Protocol):
86
+ """Protocol that support `__bool__` method."""
87
+
82
88
  def __bool__(self) -> bool:
83
89
  raise NotImplementedError
84
90
 
85
91
 
86
92
  @runtime_checkable
87
93
  class SupportsDiv(Protocol[_T_Other]):
94
+ """Protocol that support `__div__` (/) method."""
95
+
88
96
  def __div__(self, other: _T_Other, /):
89
97
  raise NotImplementedError
90
98
 
91
99
 
92
100
  @runtime_checkable
93
101
  class SupportsGetitem(Protocol[_T_Item, _T_Index]):
102
+ """Protocol that support `__getitem__` method."""
103
+
94
104
  def __getitem__(self, idx: _T_Index, /) -> _T_Item:
95
105
  raise NotImplementedError
96
106
 
97
107
 
98
108
  @runtime_checkable
99
109
  class SupportsGetitem2(Protocol[_T_Index2, _T_Item]):
100
- """Same than `SupportsGetitem` except that generic parameters are in reversed order: [T_Index, T_Item]."""
110
+ """Protocol that support `__getitem__` method.
111
+
112
+ Same than `SupportsGetitem` except that generic parameters are in reversed order: [T_Index, T_Item].
113
+ """
101
114
 
102
115
  def __getitem__(self, idx: _T_Index2, /) -> _T_Item:
103
116
  raise NotImplementedError
@@ -105,6 +118,8 @@ class SupportsGetitem2(Protocol[_T_Index2, _T_Item]):
105
118
 
106
119
  @runtime_checkable
107
120
  class SupportsGetitemLen(Protocol[_T_Item, _T_Index]):
121
+ """Protocol that support `__getitem__` and `__len__` methods."""
122
+
108
123
  def __getitem__(self, idx: _T_Index, /) -> _T_Item:
109
124
  raise NotImplementedError
110
125
 
@@ -114,7 +129,9 @@ class SupportsGetitemLen(Protocol[_T_Item, _T_Index]):
114
129
 
115
130
  @runtime_checkable
116
131
  class SupportsGetitemLen2(Protocol[_T_Index2, _T_Item]):
117
- """Same than `SupportsGetitemLen` except that generic parameters are in reversed order: [T_Index, T_Item]."""
132
+ """Protocol that support `__getitem__` and `__len__` methods.
133
+
134
+ Same than `SupportsGetitemLen` except that generic parameters are in reversed order: [T_Index, T_Item]."""
118
135
 
119
136
  def __getitem__(self, idx: _T_Index2, /) -> _T_Item:
120
137
  raise NotImplementedError
@@ -125,6 +142,8 @@ class SupportsGetitemLen2(Protocol[_T_Index2, _T_Item]):
125
142
 
126
143
  @runtime_checkable
127
144
  class SupportsGetitemIterLen(Protocol[_T_Item, _T_Index]):
145
+ """Protocol that support `__getitem__`, `__iter__` and `__len__` methods."""
146
+
128
147
  def __getitem__(self, idx: _T_Index, /) -> _T_Item:
129
148
  raise NotImplementedError
130
149
 
@@ -137,7 +156,10 @@ class SupportsGetitemIterLen(Protocol[_T_Item, _T_Index]):
137
156
 
138
157
  @runtime_checkable
139
158
  class SupportsGetitemIterLen2(Protocol[_T_Index2, _T_Item]):
140
- """Same than `SupportsGetitemIterLen` except that generic parameters are in reversed order: [T_Index, T_Item]."""
159
+ """Protocol that support `__getitem__`, `__iter__` and `__len__` methods.
160
+
161
+ Same than `SupportsGetitemIterLen` except that generic parameters are in reversed order: [T_Index, T_Item].
162
+ """
141
163
 
142
164
  def __getitem__(self, idx: _T_Index2, /) -> _T_Item:
143
165
  raise NotImplementedError
@@ -151,6 +173,8 @@ class SupportsGetitemIterLen2(Protocol[_T_Index2, _T_Item]):
151
173
 
152
174
  @runtime_checkable
153
175
  class SupportsIterLen(Protocol[_T_Item]):
176
+ """Protocol that support `__iter__` and `__len__` methods."""
177
+
154
178
  def __iter__(self) -> Iterator[_T_Item]:
155
179
  raise NotImplementedError
156
180
 
@@ -160,23 +184,31 @@ class SupportsIterLen(Protocol[_T_Item]):
160
184
 
161
185
  @runtime_checkable
162
186
  class SupportsLen(Protocol):
187
+ """Protocol that support `__len__` method."""
188
+
163
189
  def __len__(self) -> int:
164
190
  raise NotImplementedError
165
191
 
166
192
 
167
193
  @runtime_checkable
168
194
  class SupportsMul(Protocol[_T_Other]):
195
+ """Protocol that support `__mul__` (*) method."""
196
+
169
197
  def __mul__(self, other: _T_Other, /):
170
198
  raise NotImplementedError
171
199
 
172
200
 
173
201
  @runtime_checkable
174
202
  class SupportsOr(Protocol[_T_Other]):
203
+ """Protocol that support `__or__` (|) method."""
204
+
175
205
  def __or__(self, other: _T_Other, /):
176
206
  raise NotImplementedError
177
207
 
178
208
 
179
209
  @runtime_checkable
180
210
  class SupportsMatmul(Protocol[_T_Other]):
211
+ """Protocol that support `__matmul__` (@) method."""
212
+
181
213
  def __matmul__(self, other: _T_Other, /):
182
214
  raise NotImplementedError
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pythonwrench
3
- Version: 0.4.6
3
+ Version: 0.4.8
4
4
  Summary: Python library with tools for typing, manipulating collections, and more!
5
5
  Author-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
6
6
  Maintainer-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
@@ -57,6 +57,23 @@ class TestChecksum(TestCase):
57
57
  assert s1 == s2
58
58
  assert checksum_any(s1) == checksum_any(s2)
59
59
 
60
+ def test_enums(self) -> None:
61
+ from enum import Enum
62
+
63
+ class Color(Enum):
64
+ RED = 1
65
+ GREEN = 2
66
+ BLUE = 3
67
+ ROUGE = RED
68
+
69
+ c1 = Color.RED
70
+ c2 = Color.GREEN
71
+ assert c1 != c2
72
+ assert checksum_any(c1) != checksum_any(c2)
73
+
74
+ assert Color.RED == Color.ROUGE
75
+ assert checksum_any(Color.RED) == checksum_any(Color.ROUGE)
76
+
60
77
 
61
78
  if __name__ == "__main__":
62
79
  unittest.main()
@@ -4,6 +4,7 @@
4
4
  import unittest
5
5
  from dataclasses import dataclass
6
6
  from numbers import Number
7
+ from pathlib import Path
7
8
  from typing import (
8
9
  Any,
9
10
  Callable,
@@ -280,6 +281,13 @@ class TestIsInstanceGuard(TestCase):
280
281
  with self.assertRaises(TypeError):
281
282
  assert not isinstance_generic(1, Generator[int, None, None])
282
283
 
284
+ def test_callable(self) -> None:
285
+ assert isinstance_generic(lambda x: x, Callable)
286
+ assert isinstance_generic(Path, Callable)
287
+
288
+ with self.assertRaises(NotImplementedError):
289
+ assert isinstance_generic(Path, Callable[[str], Path])
290
+
283
291
 
284
292
  class TestCheckArgsType(TestCase):
285
293
  def test_example_1(self) -> None:
File without changes
File without changes
File without changes
File without changes