checkpointer 2.9.1__tar.gz → 2.9.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.
- {checkpointer-2.9.1 → checkpointer-2.9.2}/PKG-INFO +2 -2
- {checkpointer-2.9.1 → checkpointer-2.9.2}/README.md +1 -1
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/__init__.py +3 -3
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/checkpoint.py +13 -14
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/fn_ident.py +6 -6
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/storages/pickle_storage.py +3 -3
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/storages/storage.py +6 -6
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/utils.py +3 -3
- {checkpointer-2.9.1 → checkpointer-2.9.2}/pyproject.toml +1 -1
- {checkpointer-2.9.1 → checkpointer-2.9.2}/uv.lock +1 -1
- {checkpointer-2.9.1 → checkpointer-2.9.2}/.gitignore +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/.python-version +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/LICENSE +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/object_hash.py +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/print_checkpoint.py +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/storages/__init__.py +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/storages/bcolz_storage.py +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/storages/memory_storage.py +0 -0
- {checkpointer-2.9.1 → checkpointer-2.9.2}/checkpointer/test_checkpointer.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: checkpointer
|
3
|
-
Version: 2.9.
|
3
|
+
Version: 2.9.2
|
4
4
|
Summary: A Python library for memoizing function results with support for multiple storage backends, async runtimes, and automatic cache invalidation
|
5
5
|
Project-URL: Repository, https://github.com/Reddan/checkpointer.git
|
6
6
|
Author: Hampus Hallman
|
@@ -76,7 +76,7 @@ def helper(x):
|
|
76
76
|
|
77
77
|
@checkpoint
|
78
78
|
def compute(a, b):
|
79
|
-
# Depends on `helper`
|
79
|
+
# Depends on `helper` and `multiply`
|
80
80
|
return helper(a) + helper(b)
|
81
81
|
```
|
82
82
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import gc
|
2
2
|
import tempfile
|
3
3
|
from typing import Callable
|
4
|
-
from .checkpoint import Checkpointer, CheckpointError
|
4
|
+
from .checkpoint import CachedFunction, Checkpointer, CheckpointError
|
5
5
|
from .object_hash import ObjectHash
|
6
6
|
from .storages import MemoryStorage, PickleStorage, Storage
|
7
7
|
|
@@ -14,8 +14,8 @@ static_checkpoint = Checkpointer(fn_hash=ObjectHash())
|
|
14
14
|
|
15
15
|
def cleanup_all(invalidated=True, expired=True):
|
16
16
|
for obj in gc.get_objects():
|
17
|
-
if isinstance(obj,
|
17
|
+
if isinstance(obj, CachedFunction):
|
18
18
|
obj.cleanup(invalidated=invalidated, expired=expired)
|
19
19
|
|
20
20
|
def get_function_hash(fn: Callable, capture=False) -> str:
|
21
|
-
return
|
21
|
+
return CachedFunction(Checkpointer(capture=capture), fn).fn_hash
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import inspect
|
3
3
|
import re
|
4
|
-
from contextlib import suppress
|
5
4
|
from datetime import datetime
|
6
5
|
from functools import cached_property, update_wrapper
|
7
6
|
from pathlib import Path
|
@@ -43,17 +42,17 @@ class Checkpointer:
|
|
43
42
|
self.fn_hash = opts.get("fn_hash")
|
44
43
|
|
45
44
|
@overload
|
46
|
-
def __call__(self, fn: Fn, **override_opts: Unpack[CheckpointerOpts]) ->
|
45
|
+
def __call__(self, fn: Fn, **override_opts: Unpack[CheckpointerOpts]) -> CachedFunction[Fn]: ...
|
47
46
|
@overload
|
48
47
|
def __call__(self, fn: None=None, **override_opts: Unpack[CheckpointerOpts]) -> Checkpointer: ...
|
49
|
-
def __call__(self, fn: Fn | None=None, **override_opts: Unpack[CheckpointerOpts]) -> Checkpointer |
|
48
|
+
def __call__(self, fn: Fn | None=None, **override_opts: Unpack[CheckpointerOpts]) -> Checkpointer | CachedFunction[Fn]:
|
50
49
|
if override_opts:
|
51
50
|
opts = CheckpointerOpts(**{**self.__dict__, **override_opts})
|
52
51
|
return Checkpointer(**opts)(fn)
|
53
52
|
|
54
|
-
return
|
53
|
+
return CachedFunction(self, fn) if callable(fn) else self
|
55
54
|
|
56
|
-
class
|
55
|
+
class CachedFunction(Generic[Fn]):
|
57
56
|
def __init__(self, checkpointer: Checkpointer, fn: Fn):
|
58
57
|
wrapped = unwrap_fn(fn)
|
59
58
|
fn_file = Path(wrapped.__code__.co_filename).name
|
@@ -80,15 +79,15 @@ class CheckpointFn(Generic[Fn]):
|
|
80
79
|
|
81
80
|
@cached_property
|
82
81
|
def fn_hash(self) -> str:
|
83
|
-
fn_hash = self.checkpointer.fn_hash
|
84
82
|
deep_hashes = [depend.fn_hash_raw for depend in self.deep_depends()]
|
85
|
-
|
83
|
+
fn_hash = ObjectHash(digest_size=16).write_text(self.fn_hash_raw, *deep_hashes)
|
84
|
+
return str(self.checkpointer.fn_hash or fn_hash)[:32]
|
86
85
|
|
87
|
-
def reinit(self, recursive=False) ->
|
86
|
+
def reinit(self, recursive=False) -> CachedFunction[Fn]:
|
88
87
|
depends = list(self.deep_depends()) if recursive else [self]
|
89
88
|
for depend in depends:
|
90
|
-
|
91
|
-
|
89
|
+
self.__dict__.pop("fn_hash", None)
|
90
|
+
self.__dict__.pop("ident_tuple", None)
|
92
91
|
for depend in depends:
|
93
92
|
depend.fn_hash
|
94
93
|
return self
|
@@ -150,21 +149,21 @@ class CheckpointFn(Generic[Fn]):
|
|
150
149
|
raise CheckpointError("Could not load checkpoint") from ex
|
151
150
|
|
152
151
|
def exists(self: Callable[P, R], *args: P.args, **kw: P.kwargs) -> bool: # type: ignore
|
153
|
-
self = cast(
|
152
|
+
self = cast(CachedFunction, self)
|
154
153
|
return self.storage.exists(self.get_call_id(args, kw))
|
155
154
|
|
156
155
|
def delete(self: Callable[P, R], *args: P.args, **kw: P.kwargs): # type: ignore
|
157
|
-
self = cast(
|
156
|
+
self = cast(CachedFunction, self)
|
158
157
|
self.storage.delete(self.get_call_id(args, kw))
|
159
158
|
|
160
159
|
def __repr__(self) -> str:
|
161
160
|
return f"<CheckpointFn {self.fn.__name__} {self.fn_hash[:6]}>"
|
162
161
|
|
163
|
-
def deep_depends(self, visited: set[
|
162
|
+
def deep_depends(self, visited: set[CachedFunction] = set()) -> Iterable[CachedFunction]:
|
164
163
|
if self not in visited:
|
165
164
|
yield self
|
166
165
|
visited = visited or set()
|
167
166
|
visited.add(self)
|
168
167
|
for depend in self.depends:
|
169
|
-
if isinstance(depend,
|
168
|
+
if isinstance(depend, CachedFunction):
|
170
169
|
yield from depend.deep_depends(visited)
|
@@ -8,7 +8,7 @@ from typing import Any, Iterable, Type, TypeGuard
|
|
8
8
|
from .object_hash import ObjectHash
|
9
9
|
from .utils import AttrDict, distinct, get_cell_contents, iterate_and_upcoming, transpose, unwrap_fn
|
10
10
|
|
11
|
-
cwd = Path.cwd()
|
11
|
+
cwd = Path.cwd().resolve()
|
12
12
|
|
13
13
|
def is_class(obj) -> TypeGuard[Type]:
|
14
14
|
# isinstance works too, but needlessly triggers _lazyinit()
|
@@ -72,23 +72,23 @@ def is_user_fn(candidate_fn) -> TypeGuard[Callable]:
|
|
72
72
|
return cwd in fn_path.parents and ".venv" not in fn_path.parts
|
73
73
|
|
74
74
|
def get_depend_fns(fn: Callable, capture: bool, captured_vals_by_fn: dict[Callable, list[Any]] = {}) -> dict[Callable, list[Any]]:
|
75
|
-
from .checkpoint import
|
75
|
+
from .checkpoint import CachedFunction
|
76
76
|
captured_vals_by_fn = captured_vals_by_fn or {}
|
77
77
|
captured_vals = get_fn_captured_vals(fn)
|
78
78
|
captured_vals_by_fn[fn] = [val for val in captured_vals if not callable(val)] * capture
|
79
|
-
child_fns = (unwrap_fn(val,
|
79
|
+
child_fns = (unwrap_fn(val, cached_fn=True) for val in captured_vals if callable(val))
|
80
80
|
for child_fn in child_fns:
|
81
|
-
if isinstance(child_fn,
|
81
|
+
if isinstance(child_fn, CachedFunction):
|
82
82
|
captured_vals_by_fn[child_fn] = []
|
83
83
|
elif child_fn not in captured_vals_by_fn and is_user_fn(child_fn):
|
84
84
|
get_depend_fns(child_fn, capture, captured_vals_by_fn)
|
85
85
|
return captured_vals_by_fn
|
86
86
|
|
87
87
|
def get_fn_ident(fn: Callable, capture: bool) -> tuple[str, list[Callable]]:
|
88
|
-
from .checkpoint import
|
88
|
+
from .checkpoint import CachedFunction
|
89
89
|
captured_vals_by_fn = get_depend_fns(fn, capture)
|
90
90
|
depends, depend_captured_vals = transpose(captured_vals_by_fn.items(), 2)
|
91
91
|
depends = distinct(fn.__func__ if isinstance(fn, MethodType) else fn for fn in depends)
|
92
|
-
unwrapped_depends = [fn for fn in depends if not isinstance(fn,
|
92
|
+
unwrapped_depends = [fn for fn in depends if not isinstance(fn, CachedFunction)]
|
93
93
|
fn_hash = str(ObjectHash(fn, unwrapped_depends).update(depend_captured_vals, tolerate_errors=True))
|
94
94
|
return fn_hash, depends
|
@@ -34,11 +34,11 @@ class PickleStorage(Storage):
|
|
34
34
|
old_dirs = [path for path in fn_path.iterdir() if path.is_dir() and path != version_path]
|
35
35
|
for path in old_dirs:
|
36
36
|
shutil.rmtree(path)
|
37
|
-
print(f"Removed {len(old_dirs)} invalidated directories for {self.
|
37
|
+
print(f"Removed {len(old_dirs)} invalidated directories for {self.cached_fn.__qualname__}")
|
38
38
|
if expired and self.checkpointer.should_expire:
|
39
39
|
count = 0
|
40
|
-
for pkl_path in fn_path.
|
40
|
+
for pkl_path in fn_path.glob("**/*.pkl"):
|
41
41
|
if self.checkpointer.should_expire(self.checkpoint_date(pkl_path.stem)):
|
42
42
|
count += 1
|
43
43
|
self.delete(pkl_path.stem)
|
44
|
-
print(f"Removed {count} expired checkpoints for {self.
|
44
|
+
print(f"Removed {count} expired checkpoints for {self.cached_fn.__qualname__}")
|
@@ -4,18 +4,18 @@ from pathlib import Path
|
|
4
4
|
from datetime import datetime
|
5
5
|
|
6
6
|
if TYPE_CHECKING:
|
7
|
-
from ..checkpoint import Checkpointer,
|
7
|
+
from ..checkpoint import Checkpointer, CachedFunction
|
8
8
|
|
9
9
|
class Storage:
|
10
10
|
checkpointer: Checkpointer
|
11
|
-
|
11
|
+
cached_fn: CachedFunction
|
12
12
|
|
13
|
-
def __init__(self,
|
14
|
-
self.checkpointer =
|
15
|
-
self.
|
13
|
+
def __init__(self, cached_fn: CachedFunction):
|
14
|
+
self.checkpointer = cached_fn.checkpointer
|
15
|
+
self.cached_fn = cached_fn
|
16
16
|
|
17
17
|
def fn_id(self) -> str:
|
18
|
-
return f"{self.
|
18
|
+
return f"{self.cached_fn.fn_dir}/{self.cached_fn.fn_hash}"
|
19
19
|
|
20
20
|
def fn_dir(self) -> Path:
|
21
21
|
return self.checkpointer.root_path / self.fn_id()
|
@@ -32,10 +32,10 @@ def get_cell_contents(fn: Callable) -> Iterable[tuple[str, Any]]:
|
|
32
32
|
except ValueError:
|
33
33
|
pass
|
34
34
|
|
35
|
-
def unwrap_fn(fn: Fn,
|
36
|
-
from .checkpoint import
|
35
|
+
def unwrap_fn(fn: Fn, cached_fn=False) -> Fn:
|
36
|
+
from .checkpoint import CachedFunction
|
37
37
|
while True:
|
38
|
-
if (
|
38
|
+
if (cached_fn and isinstance(fn, CachedFunction)) or not hasattr(fn, "__wrapped__"):
|
39
39
|
return cast(Fn, fn)
|
40
40
|
fn = getattr(fn, "__wrapped__")
|
41
41
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|