checkpointer 2.11.1__py3-none-any.whl → 2.11.2__py3-none-any.whl
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.
- checkpointer/checkpoint.py +26 -22
- checkpointer/print_checkpoint.py +6 -5
- checkpointer/types.py +6 -2
- checkpointer/utils.py +7 -9
- {checkpointer-2.11.1.dist-info → checkpointer-2.11.2.dist-info}/METADATA +2 -4
- {checkpointer-2.11.1.dist-info → checkpointer-2.11.2.dist-info}/RECORD +8 -8
- {checkpointer-2.11.1.dist-info → checkpointer-2.11.2.dist-info}/WHEEL +0 -0
- {checkpointer-2.11.1.dist-info → checkpointer-2.11.2.dist-info}/licenses/LICENSE +0 -0
checkpointer/checkpoint.py
CHANGED
@@ -6,23 +6,20 @@ from inspect import Parameter, Signature, iscoroutine, signature
|
|
6
6
|
from pathlib import Path
|
7
7
|
from typing import (
|
8
8
|
Annotated, Callable, Concatenate, Coroutine, Generic,
|
9
|
-
Iterable, Literal,
|
10
|
-
|
9
|
+
Iterable, Literal, Self, Type, TypedDict,
|
10
|
+
Unpack, cast, get_args, get_origin, overload,
|
11
11
|
)
|
12
12
|
from .fn_ident import RawFunctionIdent, get_fn_ident
|
13
13
|
from .object_hash import ObjectHash
|
14
14
|
from .print_checkpoint import print_checkpoint
|
15
15
|
from .storages import STORAGE_MAP, Storage
|
16
|
-
from .types import AwaitableValue, HashBy
|
16
|
+
from .types import AwaitableValue, C, Coro, Fn, HashBy, P, R
|
17
17
|
from .utils import unwrap_fn
|
18
18
|
|
19
|
-
Fn = TypeVar("Fn", bound=Callable)
|
20
|
-
P = ParamSpec("P")
|
21
|
-
R = TypeVar("R")
|
22
|
-
C = TypeVar("C")
|
23
|
-
|
24
19
|
DEFAULT_DIR = Path.home() / ".cache/checkpoints"
|
25
20
|
|
21
|
+
empty_set = cast(set, frozenset())
|
22
|
+
|
26
23
|
class CheckpointError(Exception):
|
27
24
|
pass
|
28
25
|
|
@@ -72,8 +69,8 @@ class FunctionIdent:
|
|
72
69
|
|
73
70
|
@cached_property
|
74
71
|
def fn_hash(self) -> str:
|
75
|
-
if self.cached_fn.checkpointer.fn_hash_from is not None:
|
76
|
-
return str(ObjectHash(
|
72
|
+
if (hash_from := self.cached_fn.checkpointer.fn_hash_from) is not None:
|
73
|
+
return str(ObjectHash(hash_from, digest_size=16))
|
77
74
|
deep_hashes = [depend.ident.raw_ident.fn_hash for depend in self.cached_fn.deep_depends()]
|
78
75
|
return str(ObjectHash(digest_size=16).write_text(iter=deep_hashes))
|
79
76
|
|
@@ -82,7 +79,7 @@ class FunctionIdent:
|
|
82
79
|
deep_hashes = [depend.ident.raw_ident.captured_hash for depend in self.cached_fn.deep_depends()]
|
83
80
|
return str(ObjectHash().write_text(iter=deep_hashes))
|
84
81
|
|
85
|
-
def
|
82
|
+
def reset(self):
|
86
83
|
self.__init__(self.cached_fn)
|
87
84
|
|
88
85
|
class CachedFunction(Generic[Fn]):
|
@@ -98,6 +95,7 @@ class CachedFunction(Generic[Fn]):
|
|
98
95
|
self.storage = Storage(self)
|
99
96
|
self.cleanup = self.storage.cleanup
|
100
97
|
self.bound = ()
|
98
|
+
self.attrname: str | None = None
|
101
99
|
|
102
100
|
sig = signature(wrapped)
|
103
101
|
params = list(sig.parameters.items())
|
@@ -107,6 +105,10 @@ class CachedFunction(Generic[Fn]):
|
|
107
105
|
self.hash_by_map = get_hash_by_map(sig)
|
108
106
|
self.ident = FunctionIdent(self)
|
109
107
|
|
108
|
+
def __set_name__(self, _, name: str):
|
109
|
+
assert self.attrname is None
|
110
|
+
self.attrname = name
|
111
|
+
|
110
112
|
@overload
|
111
113
|
def __get__(self: Self, instance: None, owner: Type[C]) -> Self: ...
|
112
114
|
@overload
|
@@ -114,9 +116,12 @@ class CachedFunction(Generic[Fn]):
|
|
114
116
|
def __get__(self, instance, owner):
|
115
117
|
if instance is None:
|
116
118
|
return self
|
119
|
+
assert self.attrname is not None
|
117
120
|
bound_fn = object.__new__(CachedFunction)
|
118
121
|
bound_fn.__dict__ |= self.__dict__
|
119
122
|
bound_fn.bound = (instance,)
|
123
|
+
if hasattr(instance, "__dict__"):
|
124
|
+
setattr(instance, self.attrname, bound_fn)
|
120
125
|
return bound_fn
|
121
126
|
|
122
127
|
@property
|
@@ -125,7 +130,7 @@ class CachedFunction(Generic[Fn]):
|
|
125
130
|
|
126
131
|
def reinit(self, recursive=False) -> CachedFunction[Fn]:
|
127
132
|
depend_idents = [depend.ident for depend in self.deep_depends()] if recursive else [self.ident]
|
128
|
-
for ident in depend_idents: ident.
|
133
|
+
for ident in depend_idents: ident.reset()
|
129
134
|
for ident in depend_idents: ident.fn_hash
|
130
135
|
return self
|
131
136
|
|
@@ -153,14 +158,13 @@ class CachedFunction(Generic[Fn]):
|
|
153
158
|
return self.fn(*full_args, **kw)
|
154
159
|
|
155
160
|
call_hash = self.get_call_hash(args, kw)
|
156
|
-
|
157
|
-
|
161
|
+
call_id = f"{self.storage.fn_id()}/{call_hash}"
|
158
162
|
refresh = rerun \
|
159
163
|
or not self.storage.exists(call_hash) \
|
160
164
|
or (params.should_expire and params.should_expire(self.storage.checkpoint_date(call_hash)))
|
161
165
|
|
162
166
|
if refresh:
|
163
|
-
print_checkpoint(params.verbosity >= 1, "MEMORIZING",
|
167
|
+
print_checkpoint(params.verbosity >= 1, "MEMORIZING", call_id, "blue")
|
164
168
|
data = self.fn(*full_args, **kw)
|
165
169
|
if iscoroutine(data):
|
166
170
|
return self._resolve_coroutine(call_hash, data)
|
@@ -168,11 +172,11 @@ class CachedFunction(Generic[Fn]):
|
|
168
172
|
|
169
173
|
try:
|
170
174
|
data = self.storage.load(call_hash)
|
171
|
-
print_checkpoint(params.verbosity >= 2, "REMEMBERED",
|
175
|
+
print_checkpoint(params.verbosity >= 2, "REMEMBERED", call_id, "green")
|
172
176
|
return data
|
173
177
|
except (EOFError, FileNotFoundError):
|
174
178
|
pass
|
175
|
-
print_checkpoint(params.verbosity >= 1, "CORRUPTED",
|
179
|
+
print_checkpoint(params.verbosity >= 1, "CORRUPTED", call_id, "yellow")
|
176
180
|
return self._call(args, kw, True)
|
177
181
|
|
178
182
|
def __call__(self: CachedFunction[Callable[P, R]], *args: P.args, **kw: P.kwargs) -> R:
|
@@ -188,7 +192,7 @@ class CachedFunction(Generic[Fn]):
|
|
188
192
|
self.storage.delete(self.get_call_hash(args, kw))
|
189
193
|
|
190
194
|
@overload
|
191
|
-
def get(self: Callable[P,
|
195
|
+
def get(self: Callable[P, Coro[R]], *args: P.args, **kw: P.kwargs) -> R: ...
|
192
196
|
@overload
|
193
197
|
def get(self: Callable[P, R], *args: P.args, **kw: P.kwargs) -> R: ...
|
194
198
|
def get(self, *args, **kw):
|
@@ -200,16 +204,16 @@ class CachedFunction(Generic[Fn]):
|
|
200
204
|
raise CheckpointError("Could not load checkpoint") from ex
|
201
205
|
|
202
206
|
@overload
|
203
|
-
def
|
207
|
+
def set(self: Callable[P, Coro[R]], value: AwaitableValue[R], *args: P.args, **kw: P.kwargs): ...
|
204
208
|
@overload
|
205
|
-
def
|
206
|
-
def
|
209
|
+
def set(self: Callable[P, R], value: R, *args: P.args, **kw: P.kwargs): ...
|
210
|
+
def set(self, value, *args, **kw):
|
207
211
|
self.storage.store(self.get_call_hash(args, kw), value)
|
208
212
|
|
209
213
|
def __repr__(self) -> str:
|
210
214
|
return f"<CachedFunction {self.fn.__name__} {self.ident.fn_hash[:6]}>"
|
211
215
|
|
212
|
-
def deep_depends(self, visited: set[CachedFunction] =
|
216
|
+
def deep_depends(self, visited: set[CachedFunction] = empty_set) -> Iterable[CachedFunction]:
|
213
217
|
if self not in visited:
|
214
218
|
yield self
|
215
219
|
visited = visited or set()
|
checkpointer/print_checkpoint.py
CHANGED
@@ -29,7 +29,7 @@ COLOR_MAP: dict[Color, int] = {
|
|
29
29
|
"white": 97,
|
30
30
|
}
|
31
31
|
|
32
|
-
def
|
32
|
+
def _allow_color() -> bool:
|
33
33
|
if "NO_COLOR" in os.environ or os.environ.get("TERM") == "dumb" or not hasattr(sys.stdout, "fileno"):
|
34
34
|
return False
|
35
35
|
try:
|
@@ -37,16 +37,17 @@ def allow_color() -> bool:
|
|
37
37
|
except io.UnsupportedOperation:
|
38
38
|
return sys.stdout.isatty()
|
39
39
|
|
40
|
-
|
40
|
+
allow_color = _allow_color()
|
41
|
+
|
42
|
+
def colored(text: str, color: Color | None = None, on_color: Color | None = None) -> str:
|
43
|
+
if not allow_color:
|
44
|
+
return text
|
41
45
|
if color:
|
42
46
|
text = f"\033[{COLOR_MAP[color]}m{text}"
|
43
47
|
if on_color:
|
44
48
|
text = f"\033[{COLOR_MAP[on_color] + 10}m{text}"
|
45
49
|
return text + "\033[0m"
|
46
50
|
|
47
|
-
noop = lambda text, *a, **k: text
|
48
|
-
colored = colored_ if allow_color() else noop
|
49
|
-
|
50
51
|
def print_checkpoint(should_log: bool, title: str, text: str, color: Color):
|
51
52
|
if should_log:
|
52
53
|
print(f'{colored(f" {title} ", "grey", color)} {colored(text, color)}')
|
checkpointer/types.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
-
from typing import Annotated, Callable, Generic, TypeVar
|
1
|
+
from typing import Annotated, Callable, Coroutine, Generic, ParamSpec, TypeVar
|
2
2
|
|
3
|
-
T = TypeVar("T")
|
4
3
|
Fn = TypeVar("Fn", bound=Callable)
|
4
|
+
P = ParamSpec("P")
|
5
|
+
R = TypeVar("R")
|
6
|
+
C = TypeVar("C")
|
7
|
+
T = TypeVar("T")
|
5
8
|
|
6
9
|
class HashBy(Generic[Fn]):
|
7
10
|
pass
|
8
11
|
|
9
12
|
NoHash = Annotated[T, HashBy[lambda _: None]]
|
13
|
+
Coro = Coroutine[object, object, R]
|
10
14
|
|
11
15
|
class AwaitableValue(Generic[T]):
|
12
16
|
def __init__(self, value: T):
|
checkpointer/utils.py
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
from contextlib import contextmanager
|
2
|
-
from typing import
|
3
|
-
|
4
|
-
T = TypeVar("T")
|
5
|
-
Fn = TypeVar("Fn", bound=Callable)
|
2
|
+
from typing import Callable, Generic, Iterable, cast
|
3
|
+
from .types import Fn, T
|
6
4
|
|
7
5
|
def distinct(seq: Iterable[T]) -> list[T]:
|
8
6
|
return list(dict.fromkeys(seq))
|
9
7
|
|
10
|
-
def get_cell_contents(fn: Callable) -> Iterable[tuple[str,
|
8
|
+
def get_cell_contents(fn: Callable) -> Iterable[tuple[str, object]]:
|
11
9
|
for key, cell in zip(fn.__code__.co_freevars, fn.__closure__ or []):
|
12
10
|
try:
|
13
11
|
yield (key, cell.cell_contents)
|
@@ -26,11 +24,11 @@ class AttrDict(dict):
|
|
26
24
|
super().__init__(*args, **kwargs)
|
27
25
|
self.__dict__ = self
|
28
26
|
|
29
|
-
def __getattribute__(self, name: str)
|
27
|
+
def __getattribute__(self, name: str):
|
30
28
|
return super().__getattribute__(name)
|
31
29
|
|
32
|
-
def __setattr__(self, name: str, value:
|
33
|
-
|
30
|
+
def __setattr__(self, name: str, value: object):
|
31
|
+
super().__setattr__(name, value)
|
34
32
|
|
35
33
|
def set(self, d: dict) -> "AttrDict":
|
36
34
|
if not d:
|
@@ -43,7 +41,7 @@ class AttrDict(dict):
|
|
43
41
|
del d[attr]
|
44
42
|
return d
|
45
43
|
|
46
|
-
def get_at(self, attrs: tuple[str, ...]) ->
|
44
|
+
def get_at(self, attrs: tuple[str, ...]) -> object:
|
47
45
|
d = self
|
48
46
|
for attr in attrs:
|
49
47
|
d = getattr(d, attr, None)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: checkpointer
|
3
|
-
Version: 2.11.
|
3
|
+
Version: 2.11.2
|
4
4
|
Summary: checkpointer adds code-aware caching to Python functions, maintaining correctness and speeding up execution as your code changes.
|
5
5
|
Project-URL: Repository, https://github.com/Reddan/checkpointer.git
|
6
6
|
Author: Hampus Hallman
|
@@ -20,9 +20,7 @@ Description-Content-Type: text/markdown
|
|
20
20
|
|
21
21
|
# checkpointer · [](https://github.com/Reddan/checkpointer/blob/master/LICENSE) [](https://pypi.org/project/checkpointer/) [](https://pypi.org/project/checkpointer/)
|
22
22
|
|
23
|
-
`checkpointer` is a Python library
|
24
|
-
|
25
|
-
It works with synchronous and asynchronous functions, supports multiple storage backends, and automatically invalidates caches when function code, dependencies, or captured variables change.
|
23
|
+
`checkpointer` is a Python library offering a decorator-based API for memoizing (caching) function results with code-aware cache invalidation. It works with sync and async functions, supports multiple storage backends, and refreshes caches automatically when your code or dependencies change - helping you maintain correctness, speed up execution, and smooth out your workflows by skipping redundant, costly operations.
|
26
24
|
|
27
25
|
## 📦 Installation
|
28
26
|
|
@@ -1,16 +1,16 @@
|
|
1
1
|
checkpointer/__init__.py,sha256=ayjFyHwvl_HRHwocY-hOJvAx0Ko5X9IMZrNT4CwfoMU,824
|
2
|
-
checkpointer/checkpoint.py,sha256=
|
2
|
+
checkpointer/checkpoint.py,sha256=jUTImaeAMde2skReH8DxmlTaUe8XzL1uKRSkbS1-N80,9523
|
3
3
|
checkpointer/fn_ident.py,sha256=-5XbovQowVyYCFc7JdT9z1NoIEiL8h9fi7alF_34Ils,4470
|
4
4
|
checkpointer/object_hash.py,sha256=YlyFupQrg3V2mpzTLfOqpqlZWhoSCHliScQ4cKd36T0,8133
|
5
|
-
checkpointer/print_checkpoint.py,sha256=
|
5
|
+
checkpointer/print_checkpoint.py,sha256=uUQ493fJCaB4nhp4Ox60govSCiBTIPbBX15zt2QiRGo,1356
|
6
6
|
checkpointer/test_checkpointer.py,sha256=-EvsMMNOOiIxhTcG97LLX0jUMWp534ko7qCKDSFWiA0,3802
|
7
|
-
checkpointer/types.py,sha256=
|
8
|
-
checkpointer/utils.py,sha256=
|
7
|
+
checkpointer/types.py,sha256=n1CxJsh7c_o72pFEfSbZ8cZgMeSNfAbLWUwrca8-zNo,449
|
8
|
+
checkpointer/utils.py,sha256=CXoofmu6nxY5uW7oPUqdH31qffi3jQn_sc1qXbzxCsU,2137
|
9
9
|
checkpointer/storages/__init__.py,sha256=en32nTUltpCSgz8RVGS_leIHC1Y1G89IqG1ZqAb6qUo,236
|
10
10
|
checkpointer/storages/memory_storage.py,sha256=aQRSOmAfS0UudubCpv8cdfu2ycM8mlsO9tFMcD2kmgo,1133
|
11
11
|
checkpointer/storages/pickle_storage.py,sha256=je1LM2lTSs5yzm25Apg5tJ9jU9T6nXCgD9SlqQRIFaM,1652
|
12
12
|
checkpointer/storages/storage.py,sha256=5Jel7VlmCG9gnIbZFKT1NrEiAePz8ZD8hfsD-tYEiP4,905
|
13
|
-
checkpointer-2.11.
|
14
|
-
checkpointer-2.11.
|
15
|
-
checkpointer-2.11.
|
16
|
-
checkpointer-2.11.
|
13
|
+
checkpointer-2.11.2.dist-info/METADATA,sha256=5nwfJ3M_F-RjJLCr49fMaXdjbl5yk1dKZt6Fk7WCy_k,11630
|
14
|
+
checkpointer-2.11.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
15
|
+
checkpointer-2.11.2.dist-info/licenses/LICENSE,sha256=9xVsdtv_-uSyY9Xl9yujwAPm4-mjcCLeVy-ljwXEWbo,1059
|
16
|
+
checkpointer-2.11.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|