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.
Files changed (74) hide show
  1. {pythonwrench-0.4.1/src/pythonwrench.egg-info → pythonwrench-0.4.2}/PKG-INFO +2 -1
  2. pythonwrench-0.4.2/docs/requirements.txt +3 -0
  3. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/pyproject.toml +4 -1
  4. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/__init__.py +1 -2
  5. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/_core.py +56 -2
  6. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/abc.py +4 -3
  7. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/cast.py +3 -4
  8. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/checksum.py +3 -4
  9. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/collections.py +25 -30
  10. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/csv.py +9 -9
  11. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/disk_cache.py +77 -47
  12. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/functools.py +25 -10
  13. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/importlib.py +2 -2
  14. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/inspect.py +28 -25
  15. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/json.py +3 -3
  16. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/jsonl.py +3 -3
  17. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/pickle.py +3 -3
  18. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/re.py +3 -4
  19. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/semver.py +2 -0
  20. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/typing/__init__.py +1 -0
  21. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/typing/checks.py +13 -11
  22. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/typing/classes.py +3 -0
  23. {pythonwrench-0.4.1 → pythonwrench-0.4.2/src/pythonwrench.egg-info}/PKG-INFO +2 -1
  24. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/SOURCES.txt +1 -1
  25. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/requires.txt +2 -0
  26. pythonwrench-0.4.2/tests/test_functools.py +32 -0
  27. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_inspect.py +17 -0
  28. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_typing.py +7 -0
  29. pythonwrench-0.4.1/docs/requirements.txt +0 -3
  30. pythonwrench-0.4.1/src/pythonwrench/io.py +0 -59
  31. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/LICENSE +0 -0
  32. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/README.md +0 -0
  33. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/setup.cfg +0 -0
  34. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/setup.py +0 -0
  35. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/__main__.py +0 -0
  36. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/argparse.py +0 -0
  37. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/__init__.py +0 -0
  38. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/prop.py +0 -0
  39. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/collections/reducers.py +0 -0
  40. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/dataclasses.py +0 -0
  41. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/datetime.py +0 -0
  42. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/difflib.py +0 -0
  43. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/entries.py +0 -0
  44. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/enum.py +0 -0
  45. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/hashlib.py +0 -0
  46. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/logging.py +0 -0
  47. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/math.py +0 -0
  48. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/os.py +0 -0
  49. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/random.py +0 -0
  50. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench/warnings.py +0 -0
  51. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/dependency_links.txt +0 -0
  52. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/entry_points.txt +0 -0
  53. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/src/pythonwrench.egg-info/top_level.txt +0 -0
  54. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_abc.py +0 -0
  55. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_argparse.py +0 -0
  56. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_cast.py +0 -0
  57. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_checksum.py +0 -0
  58. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_collections.py +0 -0
  59. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_csv.py +0 -0
  60. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_dataclasses.py +0 -0
  61. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_difflib.py +0 -0
  62. /pythonwrench-0.4.1/tests/test_functools.py → /pythonwrench-0.4.2/tests/test_disk_cache.py +0 -0
  63. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_entries.py +0 -0
  64. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_enum.py +0 -0
  65. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_hashlib.py +0 -0
  66. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_importlib.py +0 -0
  67. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_json.py +0 -0
  68. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_jsonl.py +0 -0
  69. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_logging.py +0 -0
  70. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_math.py +0 -0
  71. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_os.py +0 -0
  72. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_random.py +0 -0
  73. {pythonwrench-0.4.1 → pythonwrench-0.4.2}/tests/test_readme.py +0 -0
  74. {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.1
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
 
@@ -0,0 +1,3 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ sphinx-immaterial>=0.11.14
@@ -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-press-theme",
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.1"
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
- .. code-block:: python
728
- :caption: Example
729
-
730
- >>> lst1 = [1, 2, 3, 4]
731
- >>> lst2 = [5, 6, 7, 8]
732
- >>> lst_zipped = list(zip(lst1, lst2))
733
- >>> lst_zipped
734
- ... [(1, 5), (2, 6), (3, 7), (4, 8)]
735
- >>> unzip(lst_zipped)
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 1
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
- **csv_writer_kwds: Others optional arguments passed to CSV writer object.
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
- **csv_writer_kwds: Others optional arguments passed to CSV writer object.
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
- **csv_writer_kwds: Others optional arguments passed to CSV writer object.
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
- **csv_reader_kwds: Other optional csv arguments.
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 CacheMeta(TypedDict):
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 `~/.cache/disk_cache/<Function_name>/` directory.
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
- ```python
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
- """Filter kwargs with function arg names and call function."""
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
- kwargs_filtered = {
117
- name: value for name, value in kwargs.items() if name in argnames
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
- return fn(**kwargs_filtered)
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
- Usage:
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)