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.
@@ -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, ParamSpec, Self, Type, TypedDict,
10
- TypeVar, Unpack, cast, get_args, get_origin, overload,
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(self.cached_fn.checkpointer.fn_hash_from, digest_size=16))
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 clear(self):
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.clear()
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
- call_hash_long = f"{self.fn_dir}/{self.ident.fn_hash}/{call_hash}"
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", call_hash_long, "blue")
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", call_hash_long, "green")
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", call_hash_long, "yellow")
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, Coroutine[object, object, R]], *args: P.args, **kw: P.kwargs) -> R: ...
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 _set(self: Callable[P, Coroutine[object, object, R]], value: AwaitableValue[R], *args: P.args, **kw: P.kwargs): ...
207
+ def set(self: Callable[P, Coro[R]], value: AwaitableValue[R], *args: P.args, **kw: P.kwargs): ...
204
208
  @overload
205
- def _set(self: Callable[P, R], value: R, *args: P.args, **kw: P.kwargs): ...
206
- def _set(self, value, *args, **kw):
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] = set()) -> Iterable[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()
@@ -29,7 +29,7 @@ COLOR_MAP: dict[Color, int] = {
29
29
  "white": 97,
30
30
  }
31
31
 
32
- def allow_color() -> bool:
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
- def colored_(text: str, color: Color | None = None, on_color: Color | None = None) -> str:
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 Any, Callable, Generic, Iterable, TypeVar, cast
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, Any]]:
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) -> Any:
27
+ def __getattribute__(self, name: str):
30
28
  return super().__getattribute__(name)
31
29
 
32
- def __setattr__(self, name: str, value: Any) -> None:
33
- return super().__setattr__(name, value)
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, ...]) -> Any:
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.1
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 · [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Reddan/checkpointer/blob/master/LICENSE) [![pypi](https://img.shields.io/pypi/v/checkpointer)](https://pypi.org/project/checkpointer/) [![pypi](https://img.shields.io/pypi/pyversions/checkpointer)](https://pypi.org/project/checkpointer/)
22
22
 
23
- `checkpointer` is a Python library providing a decorator-based API for memoizing (caching) function results. It helps you skip redundant, computationally expensive operations, saving execution time and streamlining your workflows.
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=fSIgZATbTMBwZWLeWH3wIzHoPRQ5LySeb9Ygi30rCg8,9415
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=aJCeWMRJiIR3KpyPk_UOKTaD906kArGrmLGQ3LqcVgo,1369
5
+ checkpointer/print_checkpoint.py,sha256=uUQ493fJCaB4nhp4Ox60govSCiBTIPbBX15zt2QiRGo,1356
6
6
  checkpointer/test_checkpointer.py,sha256=-EvsMMNOOiIxhTcG97LLX0jUMWp534ko7qCKDSFWiA0,3802
7
- checkpointer/types.py,sha256=_dxYqzqzV8GB_g-MQlN_Voie32syKy8u7RHbc0i4upY,338
8
- checkpointer/utils.py,sha256=0cGVSlTnABgs3jI1uHoTfz353kkGa-qtTfe7jG4NCr0,2192
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.1.dist-info/METADATA,sha256=vpXrjRj8-yid19Lh_tU45cNdVkYalFj_RP3pRCm3S8A,11633
14
- checkpointer-2.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- checkpointer-2.11.1.dist-info/licenses/LICENSE,sha256=9xVsdtv_-uSyY9Xl9yujwAPm4-mjcCLeVy-ljwXEWbo,1059
16
- checkpointer-2.11.1.dist-info/RECORD,,
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,,