persistent-function-cache 0.1.1__tar.gz → 0.2.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 (38) hide show
  1. {persistent_function_cache-0.1.1/src/persistent_function_cache.egg-info → persistent_function_cache-0.2.1}/PKG-INFO +1 -1
  2. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/pyproject.toml +1 -1
  3. persistent_function_cache-0.2.1/src/persistent_cache/caches/deep_learning.py +5 -0
  4. persistent_function_cache-0.2.1/src/persistent_cache/caches/speedup_deep_learning.py +5 -0
  5. persistent_function_cache-0.2.1/src/persistent_cache/main/cache_slot.py +90 -0
  6. persistent_function_cache-0.2.1/src/persistent_cache/main/decorator.py +87 -0
  7. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/main/hashing.py +6 -5
  8. persistent_function_cache-0.2.1/src/persistent_cache/models/__init__.py +1 -0
  9. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1/src/persistent_function_cache.egg-info}/PKG-INFO +1 -1
  10. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_function_cache.egg-info/SOURCES.txt +1 -2
  11. persistent_function_cache-0.2.1/tests/test_cache.py +86 -0
  12. persistent_function_cache-0.1.1/src/persistent_cache/caches/deep_learning.py +0 -18
  13. persistent_function_cache-0.1.1/src/persistent_cache/caches/speedup_deep_learning.py +0 -18
  14. persistent_function_cache-0.1.1/src/persistent_cache/main/cacheslot.py +0 -43
  15. persistent_function_cache-0.1.1/src/persistent_cache/main/decorator.py +0 -63
  16. persistent_function_cache-0.1.1/src/persistent_cache/models/__init__.py +0 -2
  17. persistent_function_cache-0.1.1/src/persistent_cache/models/function.py +0 -4
  18. persistent_function_cache-0.1.1/tests/test_cache.py +0 -33
  19. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/LICENSE +0 -0
  20. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/README.md +0 -0
  21. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/setup.cfg +0 -0
  22. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/__init__.py +0 -0
  23. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/caches/__init__.py +0 -0
  24. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/cli/__init__.py +0 -0
  25. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/cli/clear_cache.py +0 -0
  26. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/main/__init__.py +0 -0
  27. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/models/path.py +0 -0
  28. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/py.typed +0 -0
  29. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/reducers/__init__.py +0 -0
  30. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/reducers/base.py +0 -0
  31. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/reducers/deep_learning.py +0 -0
  32. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_cache/reducers/speedup_deep_learning.py +0 -0
  33. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_function_cache.egg-info/dependency_links.txt +0 -0
  34. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_function_cache.egg-info/entry_points.txt +0 -0
  35. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_function_cache.egg-info/requires.txt +0 -0
  36. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/src/persistent_function_cache.egg-info/top_level.txt +0 -0
  37. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/tests/test_clear_cache.py +0 -0
  38. {persistent_function_cache-0.1.1 → persistent_function_cache-0.2.1}/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.1.1
3
+ Version: 0.2.1
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.1.1"
3
+ version = "0.2.1"
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,5 @@
1
+ import functools
2
+
3
+ from persistent_cache.main import decorator
4
+
5
+ cache = functools.partial(decorator.cache, deep_learning=True)
@@ -0,0 +1,5 @@
1
+ import functools
2
+
3
+ from persistent_cache.main import decorator
4
+
5
+ cache = functools.partial(decorator.cache, speedup_deep_learning=True)
@@ -0,0 +1,90 @@
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 typing import Any
7
+
8
+ from persistent_cache.models import Path
9
+ from persistent_cache.reducers.base import Reducer
10
+
11
+ from . import hashing
12
+
13
+
14
+ @dataclass
15
+ class CacheSlot:
16
+ function: Callable[..., Any]
17
+ args: tuple[Any, ...]
18
+ kwargs: dict[str, Any]
19
+ directory: Path
20
+ key_arguments: Iterable[str] | str | None
21
+ extra_keys: Any
22
+ key_reducer: type[Reducer] | None
23
+ deep_learning: bool
24
+ speedup_deep_learning: bool
25
+
26
+ @property
27
+ def value(self) -> Any:
28
+ try:
29
+ with self.path.open("rb") as fp:
30
+ return pickle.Unpickler(fp).load() # noqa: S301
31
+ except (pickle.UnpicklingError, EOFError):
32
+ # discard values of corrupted or empty slots
33
+ raise KeyError from None
34
+
35
+ @value.setter
36
+ def value(self, value: Any) -> None:
37
+ self.path.byte_content = pickle.dumps(value)
38
+
39
+ @cached_property
40
+ def path(self) -> Path:
41
+ return (
42
+ self.directory
43
+ / self.function.__module__.replace(".", "_")
44
+ / self.function.__name__
45
+ / hashing.compute_hash(self.reducer, self.keys)
46
+ )
47
+
48
+ @property
49
+ def keys(self) -> Iterator[Any]:
50
+ yield self.function
51
+ is_iterable = isinstance(self.extra_keys, Iterable) and not isinstance(
52
+ self.extra_keys,
53
+ str | bytes | bytearray,
54
+ )
55
+ if is_iterable:
56
+ yield from self.extra_keys
57
+ else:
58
+ yield self.extra_keys
59
+ yield from self.argument_values
60
+
61
+ @property
62
+ def argument_values(self) -> Iterator[Any]:
63
+ if self.key_arguments is None:
64
+ yield from self.args
65
+ yield from self.kwargs.values()
66
+ else:
67
+ arguments = inspect.signature(self.function).bind(*self.args, **self.kwargs)
68
+ arguments.apply_defaults()
69
+ if isinstance(self.key_arguments, str):
70
+ yield arguments.arguments.get(self.key_arguments)
71
+ else:
72
+ for name in self.key_arguments:
73
+ yield arguments.arguments.get(name)
74
+
75
+ @property
76
+ def reducer(self) -> type[Reducer]:
77
+ if self.key_reducer is not None:
78
+ return self.key_reducer
79
+ if self.deep_learning:
80
+ from persistent_cache.reducers.deep_learning import Reducer as Reducer_
81
+
82
+ return Reducer_
83
+ if self.speedup_deep_learning:
84
+ from persistent_cache.reducers.speedup_deep_learning import (
85
+ Reducer as Reducer_,
86
+ )
87
+
88
+ return Reducer_
89
+
90
+ return Reducer
@@ -0,0 +1,87 @@
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
+ extra_cache_keys: Iterable[Any] | None = None,
20
+ key_reducer: type[Reducer] = Reducer,
21
+ deep_learning: bool = False,
22
+ speedup_deep_learning: bool = False,
23
+ ) -> F: ...
24
+
25
+
26
+ @overload
27
+ def cache(
28
+ function: None = None,
29
+ *,
30
+ cache_directory: Path = Path.cache,
31
+ cache_key_arguments: Iterable[str] | str | None = None,
32
+ extra_cache_keys: Any = None,
33
+ key_reducer: type[Reducer] = Reducer,
34
+ deep_learning: bool = False,
35
+ speedup_deep_learning: bool = False,
36
+ ) -> Callable[[F], F]: ...
37
+
38
+
39
+ def cache( # noqa: PLR0913
40
+ function: F | None = None,
41
+ *,
42
+ cache_directory: Path = Path.cache,
43
+ cache_key_arguments: Iterable[str] | str | None = None,
44
+ extra_cache_keys: Any = None,
45
+ key_reducer: type[Reducer] | None = None,
46
+ deep_learning: bool = False,
47
+ speedup_deep_learning: bool = False,
48
+ ) -> F | Callable[[F], F]:
49
+ """A decorator to cache function results. Decorated functions are only executed if
50
+ result is not present in cache. The arguments of the function can be any nested
51
+ complex object.
52
+
53
+ Use as:
54
+
55
+ from persistent_cache import cache
56
+
57
+ @cache
58
+ def long_function(complex_object):
59
+ ...
60
+ """
61
+
62
+ def cache_decorator(function_: F) -> F:
63
+ @wraps(function_)
64
+ def wrapped_function(*args: Any, **kwargs: Any) -> Any:
65
+ cache_slot = CacheSlot(
66
+ function_,
67
+ args,
68
+ kwargs,
69
+ cache_directory,
70
+ cache_key_arguments,
71
+ extra_cache_keys,
72
+ key_reducer,
73
+ deep_learning,
74
+ speedup_deep_learning,
75
+ )
76
+ try:
77
+ result = cache_slot.value
78
+ except KeyError:
79
+ result = function_(*args, **kwargs)
80
+ cache_slot.value = result
81
+ return result
82
+
83
+ return cast(F, wrapped_function)
84
+
85
+ if function is not None:
86
+ cache_decorator = cache_decorator(function)
87
+ 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.1.1
3
+ Version: 0.2.1
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
@@ -0,0 +1,86 @@
1
+ import io
2
+ import math
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ import cli
7
+ import pytest
8
+
9
+ from persistent_cache import cache, deep_learning, speedup_deep_learning
10
+ from persistent_cache.reducers import Reducer
11
+
12
+ caches = [cache, deep_learning.cache, speedup_deep_learning.cache]
13
+
14
+
15
+ def calculate_with_name(value: str) -> None:
16
+ cli.console.print(value)
17
+
18
+
19
+ def calculate(*args: Any, **kwargs: Any) -> None:
20
+ cli.console.print("calculation started")
21
+ cli.console.print(args, kwargs)
22
+
23
+
24
+ def verify_cached_function(function: Callable[..., Any]) -> None:
25
+ with io.BytesIO() as fp:
26
+ function(fp, lambda x: x, math, {})
27
+
28
+
29
+ @pytest.mark.parametrize("cache_decorator", caches)
30
+ def test_cache_with_argument_combination(cache_decorator: Callable[..., Any]) -> None:
31
+ cached_function = cache_decorator(calculate)
32
+ verify_cached_function(cached_function)
33
+
34
+
35
+ @pytest.mark.parametrize("cache_decorator", caches)
36
+ def test_cache_as_function(cache_decorator: Callable[..., Any]) -> None:
37
+ cached_function = cache_decorator()(calculate)
38
+ verify_cached_function(cached_function)
39
+
40
+
41
+ @pytest.mark.parametrize("cache_decorator", caches)
42
+ def test_cache_with_argument_name(
43
+ cache_decorator: Callable[..., Any],
44
+ ) -> None:
45
+ cached_function = cache_decorator(calculate_with_name, cache_key_arguments="value")
46
+ cached_function("test")
47
+
48
+
49
+ @pytest.mark.parametrize("cache_decorator", caches)
50
+ def test_cache_with_argument_names(cache_decorator: Callable[..., Any]) -> None:
51
+ cache_key_arguments = ("value",)
52
+ cached_function = cache_decorator(
53
+ calculate_with_name,
54
+ cache_key_arguments=cache_key_arguments,
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")
@@ -1,18 +0,0 @@
1
- from collections.abc import Callable
2
-
3
- from persistent_cache.main import decorator
4
- from persistent_cache.models import F, Path
5
- from persistent_cache.reducers import Reducer
6
-
7
-
8
- def cache(
9
- function: F | None = None,
10
- *,
11
- key_reducer: type[Reducer] | None = None,
12
- cache_path: Path = Path.cache,
13
- ) -> Callable[[F], F]:
14
- if key_reducer is None:
15
- from persistent_cache.reducers import deep_learning
16
-
17
- key_reducer = deep_learning.Reducer
18
- return decorator.cache(function, key_reducer=key_reducer, cache_path=cache_path)
@@ -1,18 +0,0 @@
1
- from collections.abc import Callable
2
-
3
- from persistent_cache.main import decorator
4
- from persistent_cache.models import F, Path
5
- from persistent_cache.reducers import Reducer
6
-
7
-
8
- def cache(
9
- function: F | None = None,
10
- *,
11
- key_reducer: type[Reducer] | None = None,
12
- cache_path: Path = Path.cache,
13
- ) -> Callable[[F], F]:
14
- if key_reducer is None:
15
- from persistent_cache.reducers import speedup_deep_learning
16
-
17
- key_reducer = speedup_deep_learning.Reducer
18
- return decorator.cache(function, key_reducer=key_reducer, cache_path=cache_path)
@@ -1,43 +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
- kwargs: Any,
19
- key_reducer: type[Reducer] = Reducer,
20
- cache_path: Path = Path.cache,
21
- ) -> None:
22
- # change cache key when implementation changes
23
- cache_keys = (function, args, kwargs)
24
- self.location = (
25
- cache_path
26
- / function.__module__.replace(".", "_")
27
- / function.__name__
28
- / hashing.compute_hash(key_reducer, cache_keys)
29
- )
30
-
31
- @property
32
- def value(self) -> T:
33
- try:
34
- with self.location.open("rb") as fp:
35
- value = pickle.Unpickler(fp).load() # noqa: S301
36
- except (pickle.UnpicklingError, EOFError):
37
- # discard values of corrupted or empty slots
38
- raise KeyError from None
39
- return cast(T, value)
40
-
41
- @value.setter
42
- def value(self, value: T) -> None:
43
- self.location.byte_content = pickle.dumps(value)
@@ -1,63 +0,0 @@
1
- from collections.abc import Callable
2
- from functools import wraps
3
- from typing import Any, cast, overload
4
-
5
- from persistent_cache.models import F, Path
6
- from persistent_cache.reducers.base import Reducer
7
-
8
- from .cacheslot import CacheSlot
9
-
10
-
11
- @overload
12
- def cache(
13
- function: F,
14
- *,
15
- key_reducer: type[Reducer] = Reducer,
16
- cache_path: Path = Path.cache,
17
- ) -> F: ...
18
-
19
-
20
- @overload
21
- def cache(
22
- function: None = None,
23
- *,
24
- key_reducer: type[Reducer] = Reducer,
25
- cache_path: Path = Path.cache,
26
- ) -> Callable[[F], F]: ...
27
-
28
-
29
- def cache(
30
- function: F | None = None,
31
- *,
32
- key_reducer: type[Reducer] = Reducer,
33
- cache_path: Path = Path.cache,
34
- ) -> F | Callable[[F], F]:
35
- """A decorator to cache function results. Decorated functions are only executed if
36
- result is not present in cache. The arguments of the function can be any nested
37
- complex object.
38
-
39
- Use as:
40
-
41
- from persistent_cache import cache
42
-
43
- @cache
44
- def long_function(complex_object):
45
- ...
46
- """
47
-
48
- def cache_decorator(function: F) -> F:
49
- @wraps(function)
50
- def wrapped_function(*args: Any, **kwargs: Any) -> Any:
51
- cache_slot = CacheSlot(function, args, kwargs, key_reducer, cache_path)
52
- try:
53
- value = cache_slot.value
54
- except KeyError:
55
- value = function(*args, **kwargs)
56
- cache_slot.value = value
57
- return value
58
-
59
- return cast(F, wrapped_function)
60
-
61
- if function is not None:
62
- cache_decorator = cache_decorator(function)
63
- return cache_decorator
@@ -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])
@@ -1,33 +0,0 @@
1
- import io
2
- import math
3
- from collections.abc import Callable
4
- from typing import Any
5
-
6
- import cli
7
- import pytest
8
-
9
- from persistent_cache import cache, deep_learning, speedup_deep_learning
10
-
11
- caches = [cache, deep_learning.cache, speedup_deep_learning.cache]
12
-
13
-
14
- def calculate(*args: Any, **kwargs: Any) -> None:
15
- cli.console.print("calculation started")
16
- cli.console.print(args, kwargs)
17
-
18
-
19
- def verify_cached_function(function: Callable[..., Any]) -> None:
20
- with io.BytesIO() as fp:
21
- function(fp, lambda x: x, math, {})
22
-
23
-
24
- @pytest.mark.parametrize("cache_decorator", caches)
25
- def test_cache_with_argument_combination(cache_decorator: Callable[..., Any]) -> None:
26
- cached_function = cache_decorator(calculate)
27
- verify_cached_function(cached_function)
28
-
29
-
30
- @pytest.mark.parametrize("cache_decorator", caches)
31
- def test_cache_as_function(cache_decorator: Callable[..., Any]) -> None:
32
- cached_function = cache_decorator()(calculate)
33
- verify_cached_function(cached_function)