persistent-function-cache 0.2.0__tar.gz → 0.2.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 (35) hide show
  1. {persistent_function_cache-0.2.0/src/persistent_function_cache.egg-info → persistent_function_cache-0.2.2}/PKG-INFO +1 -1
  2. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/pyproject.toml +1 -1
  3. persistent_function_cache-0.2.2/src/persistent_cache/main/cache_slot.py +100 -0
  4. persistent_function_cache-0.2.2/src/persistent_cache/main/decorator.py +91 -0
  5. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/main/hashing.py +6 -5
  6. persistent_function_cache-0.2.2/src/persistent_cache/models/__init__.py +1 -0
  7. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2/src/persistent_function_cache.egg-info}/PKG-INFO +1 -1
  8. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_function_cache.egg-info/SOURCES.txt +1 -2
  9. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/tests/test_cache.py +42 -0
  10. persistent_function_cache-0.2.0/src/persistent_cache/main/cacheslot.py +0 -42
  11. persistent_function_cache-0.2.0/src/persistent_cache/main/decorator.py +0 -122
  12. persistent_function_cache-0.2.0/src/persistent_cache/models/__init__.py +0 -2
  13. persistent_function_cache-0.2.0/src/persistent_cache/models/function.py +0 -4
  14. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/LICENSE +0 -0
  15. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/README.md +0 -0
  16. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/setup.cfg +0 -0
  17. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/__init__.py +0 -0
  18. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/caches/__init__.py +0 -0
  19. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/caches/deep_learning.py +0 -0
  20. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/caches/speedup_deep_learning.py +0 -0
  21. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/cli/__init__.py +0 -0
  22. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/cli/clear_cache.py +0 -0
  23. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/main/__init__.py +0 -0
  24. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/models/path.py +0 -0
  25. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/py.typed +0 -0
  26. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/reducers/__init__.py +0 -0
  27. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/reducers/base.py +0 -0
  28. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/reducers/deep_learning.py +0 -0
  29. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_cache/reducers/speedup_deep_learning.py +0 -0
  30. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_function_cache.egg-info/dependency_links.txt +0 -0
  31. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_function_cache.egg-info/entry_points.txt +0 -0
  32. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_function_cache.egg-info/requires.txt +0 -0
  33. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/src/persistent_function_cache.egg-info/top_level.txt +0 -0
  34. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/tests/test_clear_cache.py +0 -0
  35. {persistent_function_cache-0.2.0 → persistent_function_cache-0.2.2}/tests/test_deep_learning_cache.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: persistent-function-cache
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Persistent cache for expensive functions
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "persistent-function-cache"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Persistent cache for expensive functions"
5
5
  authors = [{name = "Quinten Roets", email = "qdr2104@columbia.edu"}]
6
6
  license = {text = "MIT"}
@@ -0,0 +1,100 @@
1
+ import inspect
2
+ import pickle
3
+ from collections.abc import Callable, Iterable, Iterator
4
+ from dataclasses import dataclass
5
+ from functools import cached_property
6
+ from inspect import BoundArguments
7
+ from typing import Any
8
+
9
+ from persistent_cache.models import Path
10
+ from persistent_cache.reducers.base import Reducer
11
+
12
+ from . import hashing
13
+
14
+
15
+ @dataclass
16
+ class CacheSlot:
17
+ function: Callable[..., Any]
18
+ args: tuple[Any, ...]
19
+ kwargs: dict[str, Any]
20
+ directory: Path
21
+ key_arguments: Iterable[str] | str | None
22
+ argument_reducers: dict[str, Callable[[Any], Any]] | None
23
+ extra_keys: Any
24
+ key_reducer: type[Reducer] | None
25
+ deep_learning: bool
26
+ speedup_deep_learning: bool
27
+
28
+ @property
29
+ def value(self) -> Any:
30
+ try:
31
+ with self.path.open("rb") as fp:
32
+ return pickle.Unpickler(fp).load() # noqa: S301
33
+ except (pickle.UnpicklingError, EOFError):
34
+ # discard values of corrupted or empty slots
35
+ raise KeyError from None
36
+
37
+ @value.setter
38
+ def value(self, value: Any) -> None:
39
+ self.path.byte_content = pickle.dumps(value)
40
+
41
+ @cached_property
42
+ def path(self) -> Path:
43
+ return (
44
+ self.directory
45
+ / self.function.__module__.replace(".", "_")
46
+ / self.function.__name__
47
+ / hashing.compute_hash(self.reducer, self.keys)
48
+ )
49
+
50
+ @property
51
+ def keys(self) -> Iterator[Any]:
52
+ yield self.function
53
+ is_iterable = isinstance(self.extra_keys, Iterable) and not isinstance(
54
+ self.extra_keys,
55
+ str | bytes | bytearray,
56
+ )
57
+ if is_iterable:
58
+ yield from self.extra_keys
59
+ else:
60
+ yield self.extra_keys
61
+ yield from self.argument_values
62
+
63
+ @property
64
+ def argument_values(self) -> Iterator[Any]:
65
+ if self.key_arguments is None and self.argument_reducers is None:
66
+ yield from self.args
67
+ yield from self.kwargs.values()
68
+ else:
69
+ arguments = inspect.signature(self.function).bind(*self.args, **self.kwargs)
70
+ arguments.apply_defaults()
71
+ yield from self.extract_argument_values(arguments)
72
+
73
+ def extract_argument_values(self, arguments: BoundArguments) -> Iterator[Any]:
74
+ if self.key_arguments is not None:
75
+ if isinstance(self.key_arguments, str):
76
+ yield arguments.arguments.get(self.key_arguments)
77
+ else:
78
+ for name in self.key_arguments:
79
+ yield arguments.arguments.get(name)
80
+ if self.argument_reducers is not None:
81
+ for argument_name, reducer in self.argument_reducers.items():
82
+ argument = arguments.arguments.get(argument_name)
83
+ yield reducer(argument)
84
+
85
+ @property
86
+ def reducer(self) -> type[Reducer]:
87
+ if self.key_reducer is not None:
88
+ return self.key_reducer
89
+ if self.deep_learning:
90
+ from persistent_cache.reducers.deep_learning import Reducer as Reducer_
91
+
92
+ return Reducer_
93
+ if self.speedup_deep_learning:
94
+ from persistent_cache.reducers.speedup_deep_learning import (
95
+ Reducer as Reducer_,
96
+ )
97
+
98
+ return Reducer_
99
+
100
+ return Reducer
@@ -0,0 +1,91 @@
1
+ from collections.abc import Callable, Iterable
2
+ from functools import wraps
3
+ from typing import Any, TypeVar, cast, overload
4
+
5
+ from persistent_cache.models import Path
6
+ from persistent_cache.reducers.base import Reducer
7
+
8
+ from .cache_slot import CacheSlot
9
+
10
+ F = TypeVar("F", bound=Callable[..., Any])
11
+
12
+
13
+ @overload
14
+ def cache(
15
+ function: F,
16
+ *,
17
+ cache_directory: Path = Path.cache,
18
+ cache_key_arguments: Iterable[str] | str | None = None,
19
+ argument_reducers: dict[str, Callable[[Any], Any]] | None = None,
20
+ extra_cache_keys: Iterable[Any] | None = None,
21
+ key_reducer: type[Reducer] = Reducer,
22
+ deep_learning: bool = False,
23
+ speedup_deep_learning: bool = False,
24
+ ) -> F: ...
25
+
26
+
27
+ @overload
28
+ def cache(
29
+ function: None = None,
30
+ *,
31
+ cache_directory: Path = Path.cache,
32
+ cache_key_arguments: Iterable[str] | str | None = None,
33
+ argument_reducers: dict[str, Callable[[Any], Any]] | None = None,
34
+ extra_cache_keys: Any = None,
35
+ key_reducer: type[Reducer] = Reducer,
36
+ deep_learning: bool = False,
37
+ speedup_deep_learning: bool = False,
38
+ ) -> Callable[[F], F]: ...
39
+
40
+
41
+ def cache( # noqa: PLR0913
42
+ function: F | None = None,
43
+ *,
44
+ cache_directory: Path = Path.cache,
45
+ cache_key_arguments: Iterable[str] | str | None = None,
46
+ argument_reducers: dict[str, Callable[[Any], Any]] | None = None,
47
+ extra_cache_keys: Any = None,
48
+ key_reducer: type[Reducer] | None = None,
49
+ deep_learning: bool = False,
50
+ speedup_deep_learning: bool = False,
51
+ ) -> F | Callable[[F], F]:
52
+ """A decorator to cache function results. Decorated functions are only executed if
53
+ result is not present in cache. The arguments of the function can be any nested
54
+ complex object.
55
+
56
+ Use as:
57
+
58
+ from persistent_cache import cache
59
+
60
+ @cache
61
+ def long_function(complex_object):
62
+ ...
63
+ """
64
+
65
+ def cache_decorator(function_: F) -> F:
66
+ @wraps(function_)
67
+ def wrapped_function(*args: Any, **kwargs: Any) -> Any:
68
+ cache_slot = CacheSlot(
69
+ function_,
70
+ args,
71
+ kwargs,
72
+ cache_directory,
73
+ cache_key_arguments,
74
+ argument_reducers,
75
+ extra_cache_keys,
76
+ key_reducer,
77
+ deep_learning,
78
+ speedup_deep_learning,
79
+ )
80
+ try:
81
+ result = cache_slot.value
82
+ except KeyError:
83
+ result = function_(*args, **kwargs)
84
+ cache_slot.value = result
85
+ return result
86
+
87
+ return cast(F, wrapped_function)
88
+
89
+ if function is not None:
90
+ cache_decorator = cache_decorator(function)
91
+ return cache_decorator
@@ -54,7 +54,7 @@ class HashPickler(pickle.Pickler):
54
54
  reduction = NotImplemented
55
55
  else:
56
56
  mapping = reducer(obj)
57
- str_mapping = str(object_to_bytes(self.reducer, mapping))
57
+ str_mapping = str(item_to_bytes(self.reducer, mapping))
58
58
  reduction = str, (str_mapping,)
59
59
  return reduction
60
60
 
@@ -65,15 +65,16 @@ class HashPickler(pickle.Pickler):
65
65
  yield reducer
66
66
 
67
67
 
68
- def compute_hash(key_reducer: type[Reducer], *args: Any) -> str:
69
- data = object_to_bytes(key_reducer, args)
68
+ def compute_hash(key_reducer: type[Reducer], items: Iterator[Any]) -> str:
69
+ data = item_to_bytes(key_reducer, tuple(items))
70
70
  # use fast hash function because it is not used for security
71
71
  return hashlib.new("sha1", data=data, usedforsecurity=False).hexdigest()
72
72
 
73
73
 
74
- def object_to_bytes(key_reducer: type[Reducer], args: Any) -> bytes:
74
+ def item_to_bytes(key_reducer: type[Reducer], item: Any) -> bytes:
75
75
  with io.BytesIO() as fp:
76
76
  # Use custom pickler to generate bytes from complex structures
77
- HashPickler(fp, key_reducer).dump(args)
77
+ pickler = HashPickler(fp, key_reducer)
78
+ pickler.dump(item)
78
79
  fp.seek(0)
79
80
  return fp.read()
@@ -0,0 +1 @@
1
+ from .path import Path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: persistent-function-cache
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Persistent cache for expensive functions
5
5
  Author-email: Quinten Roets <qdr2104@columbia.edu>
6
6
  License: MIT
@@ -9,11 +9,10 @@ src/persistent_cache/caches/speedup_deep_learning.py
9
9
  src/persistent_cache/cli/__init__.py
10
10
  src/persistent_cache/cli/clear_cache.py
11
11
  src/persistent_cache/main/__init__.py
12
- src/persistent_cache/main/cacheslot.py
12
+ src/persistent_cache/main/cache_slot.py
13
13
  src/persistent_cache/main/decorator.py
14
14
  src/persistent_cache/main/hashing.py
15
15
  src/persistent_cache/models/__init__.py
16
- src/persistent_cache/models/function.py
17
16
  src/persistent_cache/models/path.py
18
17
  src/persistent_cache/reducers/__init__.py
19
18
  src/persistent_cache/reducers/base.py
@@ -7,6 +7,7 @@ import cli
7
7
  import pytest
8
8
 
9
9
  from persistent_cache import cache, deep_learning, speedup_deep_learning
10
+ from persistent_cache.reducers import Reducer
10
11
 
11
12
  caches = [cache, deep_learning.cache, speedup_deep_learning.cache]
12
13
 
@@ -53,3 +54,44 @@ def test_cache_with_argument_names(cache_decorator: Callable[..., Any]) -> None:
53
54
  cache_key_arguments=cache_key_arguments,
54
55
  )
55
56
  cached_function("test")
57
+
58
+
59
+ @pytest.mark.parametrize("cache_decorator", caches)
60
+ def test_cache_with_extra_cache_key(
61
+ cache_decorator: Callable[..., Any],
62
+ ) -> None:
63
+ cached_function = cache_decorator(calculate_with_name, extra_cache_keys="value")
64
+ cached_function("test")
65
+
66
+
67
+ @pytest.mark.parametrize("cache_decorator", caches)
68
+ def test_cache_with_extra_cache_keys(
69
+ cache_decorator: Callable[..., Any],
70
+ ) -> None:
71
+ cached_function = cache_decorator(
72
+ calculate_with_name,
73
+ extra_cache_keys=("value",),
74
+ )
75
+ cached_function("test")
76
+
77
+
78
+ @pytest.mark.parametrize("cache_decorator", caches)
79
+ def test_cache_with_reducer(
80
+ cache_decorator: Callable[..., Any],
81
+ ) -> None:
82
+ cached_function = cache_decorator(
83
+ calculate_with_name,
84
+ key_reducer=Reducer,
85
+ )
86
+ cached_function("test")
87
+
88
+
89
+ @pytest.mark.parametrize("cache_decorator", caches)
90
+ def test_cache_with_argument_reducer(
91
+ cache_decorator: Callable[..., Any],
92
+ ) -> None:
93
+ cached_function = cache_decorator(
94
+ calculate_with_name,
95
+ argument_reducers={"value": lambda x: x.strip()},
96
+ )
97
+ cached_function("test")
@@ -1,42 +0,0 @@
1
- import pickle
2
- from collections.abc import Callable
3
- from typing import Any, Generic, TypeVar, cast
4
-
5
- from persistent_cache.models import Path
6
- from persistent_cache.reducers.base import Reducer
7
-
8
- from . import hashing
9
-
10
- T = TypeVar("T")
11
-
12
-
13
- class CacheSlot(Generic[T]):
14
- def __init__(
15
- self,
16
- function: Callable[..., T],
17
- args: tuple[Any, ...],
18
- key_reducer: type[Reducer] = Reducer,
19
- cache_path: Path = Path.cache,
20
- ) -> None:
21
- # change cache key when implementation changes
22
- cache_keys = (function, args)
23
- self.location = (
24
- cache_path
25
- / function.__module__.replace(".", "_")
26
- / function.__name__
27
- / hashing.compute_hash(key_reducer, cache_keys)
28
- )
29
-
30
- @property
31
- def value(self) -> T:
32
- try:
33
- with self.location.open("rb") as fp:
34
- value = pickle.Unpickler(fp).load() # noqa: S301
35
- except (pickle.UnpicklingError, EOFError):
36
- # discard values of corrupted or empty slots
37
- raise KeyError from None
38
- return cast(T, value)
39
-
40
- @value.setter
41
- def value(self, value: T) -> None:
42
- self.location.byte_content = pickle.dumps(value)
@@ -1,122 +0,0 @@
1
- import inspect
2
- from collections.abc import Callable, Iterator
3
- from functools import wraps
4
- from typing import Any, cast, overload
5
-
6
- from persistent_cache.models import F, Path
7
- from persistent_cache.reducers.base import Reducer
8
-
9
- from .cacheslot import CacheSlot
10
-
11
-
12
- @overload
13
- def cache(
14
- function: F,
15
- *,
16
- cache_path: Path = Path.cache,
17
- cache_key_arguments: tuple[str, ...] | str | None = None,
18
- key_reducer: type[Reducer] = Reducer,
19
- deep_learning: bool = False,
20
- speedup_deep_learning: bool = False,
21
- ) -> F: ...
22
-
23
-
24
- @overload
25
- def cache(
26
- function: None = None,
27
- *,
28
- cache_path: Path = Path.cache,
29
- cache_key_arguments: tuple[str, ...] | str | None = None,
30
- key_reducer: type[Reducer] = Reducer,
31
- deep_learning: bool = False,
32
- speedup_deep_learning: bool = False,
33
- ) -> Callable[[F], F]: ...
34
-
35
-
36
- def cache( # noqa: PLR0913
37
- function: F | None = None,
38
- *,
39
- cache_path: Path = Path.cache,
40
- cache_key_arguments: tuple[str, ...] | str | None = None,
41
- key_reducer: type[Reducer] | None = None,
42
- deep_learning: bool = False,
43
- speedup_deep_learning: bool = False,
44
- ) -> F | Callable[[F], F]:
45
- """A decorator to cache function results. Decorated functions are only executed if
46
- result is not present in cache. The arguments of the function can be any nested
47
- complex object.
48
-
49
- Use as:
50
-
51
- from persistent_cache import cache
52
-
53
- @cache
54
- def long_function(complex_object):
55
- ...
56
- """
57
-
58
- reducer = extract_reducer(
59
- key_reducer,
60
- deep_learning=deep_learning,
61
- speedup_deep_learning=speedup_deep_learning,
62
- )
63
-
64
- def cache_decorator(function: F) -> F:
65
- @wraps(function)
66
- def wrapped_function(*args: Any, **kwargs: Any) -> Any:
67
- arguments = tuple(
68
- extract_argument_values(function, args, kwargs, cache_key_arguments),
69
- )
70
- cache_slot = CacheSlot(function, arguments, reducer, cache_path)
71
- try:
72
- value = cache_slot.value
73
- except KeyError:
74
- value = function(*args, **kwargs)
75
- cache_slot.value = value
76
- return value
77
-
78
- return cast(F, wrapped_function)
79
-
80
- if function is not None:
81
- cache_decorator = cache_decorator(function)
82
- return cache_decorator
83
-
84
-
85
- def extract_argument_values(
86
- function: F,
87
- args: tuple[Any, ...],
88
- kwargs: dict[str, Any],
89
- cache_key_arguments: tuple[str, ...] | str | None = None,
90
- ) -> Iterator[Any]:
91
- if cache_key_arguments is None:
92
- yield from (args, kwargs)
93
- else:
94
- arguments = inspect.signature(function).bind(*args, **kwargs)
95
- arguments.apply_defaults()
96
- if isinstance(cache_key_arguments, str):
97
- yield arguments.arguments.get(cache_key_arguments)
98
- else:
99
- for name in cache_key_arguments:
100
- yield arguments.arguments.get(name)
101
-
102
-
103
- def extract_reducer(
104
- reducer: type[Reducer] | None,
105
- *,
106
- deep_learning: bool,
107
- speedup_deep_learning: bool,
108
- ) -> type[Reducer]:
109
- if reducer is None:
110
- if deep_learning:
111
- from persistent_cache.reducers.deep_learning import Reducer as Reducer_
112
-
113
- reducer = Reducer_
114
- elif speedup_deep_learning:
115
- from persistent_cache.reducers.speedup_deep_learning import (
116
- Reducer as Reducer_,
117
- )
118
-
119
- reducer = Reducer_
120
- else:
121
- reducer = Reducer
122
- return reducer
@@ -1,2 +0,0 @@
1
- from .function import F
2
- from .path import Path
@@ -1,4 +0,0 @@
1
- from collections.abc import Callable
2
- from typing import Any, TypeVar
3
-
4
- F = TypeVar("F", bound=Callable[..., Any])