persistent-function-cache 0.2.2__tar.gz → 0.3.1__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 (33) hide show
  1. {persistent_function_cache-0.2.2/src/persistent_function_cache.egg-info → persistent_function_cache-0.3.1}/PKG-INFO +10 -9
  2. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/README.md +1 -1
  3. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/pyproject.toml +14 -8
  4. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/cli/clear_cache.py +2 -4
  5. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/main/cache_slot.py +6 -4
  6. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/main/decorator.py +3 -2
  7. persistent_function_cache-0.3.1/src/persistent_cache/main/hashing.py +55 -0
  8. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/models/path.py +4 -6
  9. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/reducers/base.py +8 -10
  10. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/reducers/deep_learning.py +5 -7
  11. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/reducers/speedup_deep_learning.py +0 -4
  12. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1/src/persistent_function_cache.egg-info}/PKG-INFO +10 -9
  13. persistent_function_cache-0.3.1/src/persistent_function_cache.egg-info/requires.txt +9 -0
  14. persistent_function_cache-0.2.2/src/persistent_cache/main/hashing.py +0 -80
  15. persistent_function_cache-0.2.2/src/persistent_function_cache.egg-info/requires.txt +0 -9
  16. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/LICENSE +0 -0
  17. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/setup.cfg +0 -0
  18. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/__init__.py +0 -0
  19. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/caches/__init__.py +0 -0
  20. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/caches/deep_learning.py +0 -0
  21. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/caches/speedup_deep_learning.py +0 -0
  22. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/cli/__init__.py +0 -0
  23. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/main/__init__.py +0 -0
  24. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/models/__init__.py +0 -0
  25. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/py.typed +0 -0
  26. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_cache/reducers/__init__.py +0 -0
  27. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_function_cache.egg-info/SOURCES.txt +0 -0
  28. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_function_cache.egg-info/dependency_links.txt +0 -0
  29. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_function_cache.egg-info/entry_points.txt +0 -0
  30. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/src/persistent_function_cache.egg-info/top_level.txt +0 -0
  31. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/tests/test_cache.py +0 -0
  32. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/tests/test_clear_cache.py +0 -0
  33. {persistent_function_cache-0.2.2 → persistent_function_cache-0.3.1}/tests/test_deep_learning_cache.py +0 -0
@@ -1,26 +1,27 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: persistent-function-cache
3
- Version: 0.2.2
3
+ Version: 0.3.1
4
4
  Summary: Persistent cache for expensive functions
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Source Code, https://github.com/quintenroets/persistent-cache
8
- Requires-Python: <3.13,>=3.10
8
+ Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
- Requires-Dist: package-utils<1,>=0.6.7
12
- Requires-Dist: powercli<1,>=0.3.2
13
- Requires-Dist: superpathlib<3,>=2.0.9
11
+ Requires-Dist: package-utils<1,>=0.9.1
12
+ Requires-Dist: powercli<1,>=0.4.0
13
+ Requires-Dist: superpathlib<3,>=2.1.0
14
14
  Provides-Extra: dev
15
- Requires-Dist: package-dev-tools<1,>=0.5.11; extra == "dev"
15
+ Requires-Dist: package-dev-tools<1,>=0.8.7; extra == "dev"
16
16
  Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
17
17
  Requires-Dist: numpy<3,>=1.26.0; extra == "dev"
18
18
  Requires-Dist: torch<3,>=1.26.0; extra == "dev"
19
+ Dynamic: license-file
19
20
 
20
21
  # Cache
21
22
  [![PyPI version](https://badge.fury.io/py/persistent-function-cache.svg)](https://badge.fury.io/py/persistent-function-cache)
22
23
  ![PyPI downloads](https://img.shields.io/pypi/dm/persistent-function-cache)
23
- ![Python version](https://img.shields.io/badge/python-3.10--3.12-brightgreen)
24
+ ![Python version](https://img.shields.io/badge/python-3.11+-brightgreen)
24
25
  ![Operating system](https://img.shields.io/badge/os-linux%20%7c%20macOS%20%7c%20windows-brightgreen)
25
26
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
26
27
 
@@ -1,7 +1,7 @@
1
1
  # Cache
2
2
  [![PyPI version](https://badge.fury.io/py/persistent-function-cache.svg)](https://badge.fury.io/py/persistent-function-cache)
3
3
  ![PyPI downloads](https://img.shields.io/pypi/dm/persistent-function-cache)
4
- ![Python version](https://img.shields.io/badge/python-3.10--3.12-brightgreen)
4
+ ![Python version](https://img.shields.io/badge/python-3.11+-brightgreen)
5
5
  ![Operating system](https://img.shields.io/badge/os-linux%20%7c%20macOS%20%7c%20windows-brightgreen)
6
6
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
7
7
 
@@ -1,20 +1,20 @@
1
1
  [project]
2
2
  name = "persistent-function-cache"
3
- version = "0.2.2"
3
+ version = "0.3.1"
4
4
  description = "Persistent cache for expensive functions"
5
5
  authors = [{name = "Quinten Roets", email = "qdr2104@columbia.edu"}]
6
- license = {text = "MIT"}
6
+ license = "MIT"
7
7
  readme = "README.md"
8
- requires-python = ">=3.10, <3.13"
8
+ requires-python = ">=3.11"
9
9
  dependencies = [
10
- "package-utils >=0.6.7, <1",
11
- "powercli >=0.3.2, <1",
12
- "superpathlib >=2.0.9, <3",
10
+ "package-utils >=0.9.1, <1",
11
+ "powercli >=0.4.0, <1",
12
+ "superpathlib >=2.1.0, <3",
13
13
  ]
14
14
 
15
15
  [project.optional-dependencies]
16
16
  dev = [
17
- "package-dev-tools >=0.5.11, <1",
17
+ "package-dev-tools >=0.8.7, <1",
18
18
  "package-dev-utils >=0.1.6, <1",
19
19
  "numpy >=1.26.0, <3",
20
20
  "torch >=1.26.0, <3",
@@ -53,7 +53,13 @@ fix = true
53
53
  select = ["ALL"]
54
54
  ignore = [
55
55
  "ANN401", # annotated with Any
56
- "D", # docstrings
56
+ "CPY001", # missing copyright notice
57
+ "D1", # missing docstrings
58
+ "D200", # one-line docstring
59
+ "D203", # conflicts with D211
60
+ "D205", # blank line between summary and description
61
+ "D212", # conflicts with D213
62
+ "D401", # imperative first line
57
63
  "G004", # logging f-string
58
64
  ]
59
65
 
@@ -1,6 +1,6 @@
1
1
  import time
2
2
  from dataclasses import dataclass
3
- from datetime import datetime, timezone
3
+ from datetime import UTC, datetime
4
4
  from functools import cached_property
5
5
  from typing import Annotated
6
6
 
@@ -42,9 +42,7 @@ def main(options: Options) -> None:
42
42
  for path in Options.cache_path.find(should_remove, recurse_on_match=True):
43
43
  if options.verbose:
44
44
  relative_path = path.relative_to(Options.cache_path)
45
- timestamp = datetime.fromtimestamp(path.mtime).astimezone(
46
- tz=timezone.utc,
47
- )
45
+ timestamp = datetime.fromtimestamp(path.mtime).astimezone(tz=UTC)
48
46
  message = f"{relative_path} ({timestamp})"
49
47
  cli.console.print(message)
50
48
  path.unlink()
@@ -30,8 +30,8 @@ class CacheSlot:
30
30
  try:
31
31
  with self.path.open("rb") as fp:
32
32
  return pickle.Unpickler(fp).load() # noqa: S301
33
- except (pickle.UnpicklingError, EOFError):
34
- # discard values of corrupted or empty slots
33
+ except (FileNotFoundError, pickle.UnpicklingError, EOFError):
34
+ # discard values of missing, corrupted or empty slots
35
35
  raise KeyError from None
36
36
 
37
37
  @value.setter
@@ -87,11 +87,13 @@ class CacheSlot:
87
87
  if self.key_reducer is not None:
88
88
  return self.key_reducer
89
89
  if self.deep_learning:
90
- from persistent_cache.reducers.deep_learning import Reducer as Reducer_
90
+ from persistent_cache.reducers.deep_learning import ( # noqa: PLC0415
91
+ Reducer as Reducer_,
92
+ )
91
93
 
92
94
  return Reducer_
93
95
  if self.speedup_deep_learning:
94
- from persistent_cache.reducers.speedup_deep_learning import (
96
+ from persistent_cache.reducers.speedup_deep_learning import ( # noqa: PLC0415
95
97
  Reducer as Reducer_,
96
98
  )
97
99
 
@@ -49,7 +49,8 @@ def cache( # noqa: PLR0913
49
49
  deep_learning: bool = False,
50
50
  speedup_deep_learning: bool = False,
51
51
  ) -> F | Callable[[F], F]:
52
- """A decorator to cache function results. Decorated functions are only executed if
52
+ """
53
+ A decorator to cache function results. Decorated functions are only executed if
53
54
  result is not present in cache. The arguments of the function can be any nested
54
55
  complex object.
55
56
 
@@ -84,7 +85,7 @@ def cache( # noqa: PLR0913
84
85
  cache_slot.value = result
85
86
  return result
86
87
 
87
- return cast(F, wrapped_function)
88
+ return cast("F", wrapped_function)
88
89
 
89
90
  if function is not None:
90
91
  cache_decorator = cache_decorator(function)
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import inspect
5
+ import io
6
+ import pickle
7
+ from functools import cache
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from package_utils.annotations import first_parameter_types
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Callable, Iterator # pragma: nocover
14
+ from typing import BinaryIO # pragma: nocover
15
+
16
+ from persistent_cache.reducers.base import Reducer # pragma: nocover
17
+
18
+
19
+ def compute_hash(key_reducer: type[Reducer], items: Iterator[Any]) -> str:
20
+ with io.BytesIO() as fp:
21
+ HashPickler(fp, key_reducer).dump(tuple(items))
22
+ data = fp.getvalue()
23
+ # use fast hash function because it is not used for security
24
+ return hashlib.new("sha1", data=data, usedforsecurity=False).hexdigest()
25
+
26
+
27
+ class HashPickler(pickle.Pickler):
28
+ def __init__(self, file_pointer: BinaryIO, reducer: type[Reducer]) -> None:
29
+ super().__init__(file_pointer)
30
+ self.reducers = load_reducers(reducer) # type: ignore[arg-type]
31
+
32
+ def reducer_override(self, obj: Any) -> Any:
33
+ """
34
+ The goal of this pickler is to create hashes of complex objects, not to
35
+ reconstruct complex objects.
36
+
37
+ So mapping does not need to be reversible.
38
+ """
39
+ reducer = next(self.determine_reducer(obj), None)
40
+ return NotImplemented if reducer is None else (tuple, (reducer(obj),))
41
+
42
+ def determine_reducer(self, obj: Any) -> Iterator[Callable[[Any], Any]]:
43
+ if obj is not tuple:
44
+ for obj_type, reducer in self.reducers.items():
45
+ if isinstance(obj, obj_type):
46
+ yield reducer
47
+
48
+
49
+ @cache
50
+ def load_reducers(reducer: type[Reducer]) -> dict[type, Callable[[Any], Any]]:
51
+ return {
52
+ parameter_type: method
53
+ for _, method in inspect.getmembers(reducer, predicate=inspect.ismethod)
54
+ for parameter_type in first_parameter_types(method)
55
+ }
@@ -1,19 +1,17 @@
1
- from typing import TypeVar, cast
1
+ from typing import Self, cast
2
2
 
3
3
  import superpathlib
4
4
  from simple_classproperty import classproperty
5
5
 
6
- T = TypeVar("T", bound="Path")
7
-
8
6
 
9
7
  class Path(superpathlib.Path):
10
8
  @classmethod
11
9
  @classproperty
12
- def source_root(cls: type[T]) -> T:
10
+ def source_root(cls) -> Self:
13
11
  return cls(__file__).parent.parent
14
12
 
15
13
  @classmethod
16
14
  @classproperty
17
- def cache(cls: type[T]) -> T:
15
+ def cache(cls) -> Self:
18
16
  path = cls.script_assets / cls.source_root.name
19
- return cast(T, path)
17
+ return cast("Self", path)
@@ -4,7 +4,8 @@ from types import FunctionType, ModuleType
4
4
 
5
5
 
6
6
  class Reducer:
7
- """Inherit from this class to implement own custom pickler.
7
+ """
8
+ Inherit from this class to implement own custom pickler.
8
9
 
9
10
  The result of each function are pickled further with their custom
10
11
  pickling function, so make sure to reduce each object to a new
@@ -14,15 +15,12 @@ class Reducer:
14
15
 
15
16
  @classmethod
16
17
  def reduce_code(cls, code_object: FunctionType | ModuleType | type) -> str:
17
- """
18
- custom lambda reduction needed:
19
- https://www.pythonpool.com/cant-pickle-local-object/
20
- custom module reduction needed:
21
- https://stackoverflow.com/questions/2790828/python-cant-pickle-module-objects-error
22
- name reduction for function/module/class is not enough because we assume
23
- cache result can change when function/module/class implementation changes
24
- """
25
-
18
+ # name reduction for function/module/class is not enough because we assume
19
+ # cache result can change when function/module/class implementation changes
20
+ # custom lambda reduction needed:
21
+ # https://www.pythonpool.com/cant-pickle-local-object/
22
+ # custom module reduction needed:
23
+ # https://stackoverflow.com/questions/2790828/python-cant-pickle-module-objects-error
26
24
  try:
27
25
  reduction = inspect.getsource(code_object)
28
26
  except (TypeError, OSError):
@@ -9,13 +9,11 @@ from . import base
9
9
  class Reducer(base.Reducer):
10
10
  @classmethod
11
11
  def reduce_model(cls, model: torch.nn.Module) -> tuple[dict[str, Any], Any]:
12
- """
13
- Avoid pickling _forward_hooks of model:
14
- implemented as OrderedDict with nondeterministic keys
15
- Model outputs determined by:
16
- - model weights
17
- - class implementation (forward method)
18
- """
12
+ # avoid pickling _forward_hooks of model:
13
+ # implemented as OrderedDict with nondeterministic keys
14
+ # model outputs determined by:
15
+ # - model weights
16
+ # - class implementation (forward method)
19
17
  return model.state_dict(), model.__class__
20
18
 
21
19
  @classmethod
@@ -57,7 +57,3 @@ class Reducer(deep_learning.Reducer):
57
57
  label = None
58
58
 
59
59
  return length, data, label
60
-
61
- @classmethod
62
- def reduce_tensor(cls, tensor: torch.Tensor) -> NDArray[Any]:
63
- return tensor.detach().cpu().numpy()
@@ -1,26 +1,27 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: persistent-function-cache
3
- Version: 0.2.2
3
+ Version: 0.3.1
4
4
  Summary: Persistent cache for expensive functions
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Source Code, https://github.com/quintenroets/persistent-cache
8
- Requires-Python: <3.13,>=3.10
8
+ Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
- Requires-Dist: package-utils<1,>=0.6.7
12
- Requires-Dist: powercli<1,>=0.3.2
13
- Requires-Dist: superpathlib<3,>=2.0.9
11
+ Requires-Dist: package-utils<1,>=0.9.1
12
+ Requires-Dist: powercli<1,>=0.4.0
13
+ Requires-Dist: superpathlib<3,>=2.1.0
14
14
  Provides-Extra: dev
15
- Requires-Dist: package-dev-tools<1,>=0.5.11; extra == "dev"
15
+ Requires-Dist: package-dev-tools<1,>=0.8.7; extra == "dev"
16
16
  Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
17
17
  Requires-Dist: numpy<3,>=1.26.0; extra == "dev"
18
18
  Requires-Dist: torch<3,>=1.26.0; extra == "dev"
19
+ Dynamic: license-file
19
20
 
20
21
  # Cache
21
22
  [![PyPI version](https://badge.fury.io/py/persistent-function-cache.svg)](https://badge.fury.io/py/persistent-function-cache)
22
23
  ![PyPI downloads](https://img.shields.io/pypi/dm/persistent-function-cache)
23
- ![Python version](https://img.shields.io/badge/python-3.10--3.12-brightgreen)
24
+ ![Python version](https://img.shields.io/badge/python-3.11+-brightgreen)
24
25
  ![Operating system](https://img.shields.io/badge/os-linux%20%7c%20macOS%20%7c%20windows-brightgreen)
25
26
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
26
27
 
@@ -0,0 +1,9 @@
1
+ package-utils<1,>=0.9.1
2
+ powercli<1,>=0.4.0
3
+ superpathlib<3,>=2.1.0
4
+
5
+ [dev]
6
+ package-dev-tools<1,>=0.8.7
7
+ package-dev-utils<1,>=0.1.6
8
+ numpy<3,>=1.26.0
9
+ torch<3,>=1.26.0
@@ -1,80 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- import inspect
5
- import io
6
- import pickle
7
- from types import UnionType
8
- from typing import TYPE_CHECKING, Any, get_args, get_origin, get_type_hints
9
-
10
- from persistent_cache.reducers.base import Reducer
11
-
12
- if TYPE_CHECKING:
13
- from collections.abc import Callable, Iterator # pragma: nocover
14
- from typing import BinaryIO # pragma: nocover
15
-
16
-
17
- def extract_types(method: Callable[[Any], Any]) -> Iterator[type]:
18
- type_hints = get_type_hints(method).values()
19
- if type_hints:
20
- argument_type = next(iter(type_hints))
21
- origin = get_origin(argument_type)
22
- arguments = get_args(argument_type)
23
- if origin is UnionType:
24
- yield from arguments
25
- elif origin is not None:
26
- yield origin
27
- else:
28
- yield argument_type
29
-
30
-
31
- class HashPickler(pickle.Pickler):
32
- def __init__(
33
- self,
34
- file_pointer: BinaryIO,
35
- reducer: type[Reducer] = Reducer,
36
- ) -> None:
37
- super().__init__(file_pointer)
38
- self.reducer = reducer
39
- self.reducers = {}
40
- for _, method in inspect.getmembers(reducer, predicate=inspect.ismethod):
41
- argument_types = extract_types(method)
42
- for argument_type in argument_types:
43
- self.reducers[argument_type] = method
44
-
45
- def reducer_override(self, obj: Any) -> Any:
46
- """The goal of this pickler is to create hashes of complex objects, not to
47
- reconstruct complex objects.
48
-
49
- So mapping does not need to be reversible.
50
- """
51
- reducer = next(self.determine_reducer(obj), None)
52
- reduction: Any
53
- if reducer is None:
54
- reduction = NotImplemented
55
- else:
56
- mapping = reducer(obj)
57
- str_mapping = str(item_to_bytes(self.reducer, mapping))
58
- reduction = str, (str_mapping,)
59
- return reduction
60
-
61
- def determine_reducer(self, obj: Any) -> Iterator[Callable[[Any], Any]]:
62
- if obj is not str:
63
- for obj_type, reducer in self.reducers.items():
64
- if isinstance(obj, obj_type):
65
- yield reducer
66
-
67
-
68
- def compute_hash(key_reducer: type[Reducer], items: Iterator[Any]) -> str:
69
- data = item_to_bytes(key_reducer, tuple(items))
70
- # use fast hash function because it is not used for security
71
- return hashlib.new("sha1", data=data, usedforsecurity=False).hexdigest()
72
-
73
-
74
- def item_to_bytes(key_reducer: type[Reducer], item: Any) -> bytes:
75
- with io.BytesIO() as fp:
76
- # Use custom pickler to generate bytes from complex structures
77
- pickler = HashPickler(fp, key_reducer)
78
- pickler.dump(item)
79
- fp.seek(0)
80
- return fp.read()
@@ -1,9 +0,0 @@
1
- package-utils<1,>=0.6.7
2
- powercli<1,>=0.3.2
3
- superpathlib<3,>=2.0.9
4
-
5
- [dev]
6
- package-dev-tools<1,>=0.5.11
7
- package-dev-utils<1,>=0.1.6
8
- numpy<3,>=1.26.0
9
- torch<3,>=1.26.0