checkpointer 2.11.0__tar.gz → 2.11.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: checkpointer
3
- Version: 2.11.0
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
 
@@ -166,7 +164,7 @@ In this example, the cache key for `numbers` ignores order, `data_file` is hashe
166
164
 
167
165
  For integration with databases, cloud storage, or custom serialization, implement your own storage backend by inheriting from `checkpointer.Storage` and implementing its abstract methods.
168
166
 
169
- Within custom storage methods, `call_id` identifies calls by arguments. Use `self.fn_id()` to get the function's unique identity (name + hash/version), crucial for organizing stored checkpoints (e.g., by function version). Access global `Checkpointer` config via `self.checkpointer`.
167
+ Within custom storage methods, `call_hash` identifies calls by arguments. Use `self.fn_id()` to get the function's unique identity (name + hash/version), crucial for organizing stored checkpoints (e.g., by function version). Access global `Checkpointer` config via `self.checkpointer`.
170
168
 
171
169
  **Example: Custom Storage Backend**
172
170
 
@@ -175,18 +173,18 @@ from checkpointer import checkpoint, Storage
175
173
  from datetime import datetime
176
174
 
177
175
  class MyCustomStorage(Storage):
178
- def exists(self, call_id):
176
+ def exists(self, call_hash):
179
177
  # Example: Constructing a path based on function ID and call ID
180
178
  fn_dir = self.checkpointer.root_path / self.fn_id()
181
- return (fn_dir / call_id).exists()
179
+ return (fn_dir / call_hash).exists()
182
180
 
183
- def store(self, call_id, data):
184
- ... # Store the serialized data for `call_id`
181
+ def store(self, call_hash, data):
182
+ ... # Store the serialized data for `call_hash`
185
183
  return data # This method must return the data back to checkpointer
186
184
 
187
- def checkpoint_date(self, call_id): ...
188
- def load(self, call_id): ...
189
- def delete(self, call_id): ...
185
+ def checkpoint_date(self, call_hash): ...
186
+ def load(self, call_hash): ...
187
+ def delete(self, call_hash): ...
190
188
 
191
189
  @checkpoint(format=MyCustomStorage)
192
190
  def custom_cached_function(x: int):
@@ -1,8 +1,6 @@
1
1
  # 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/)
2
2
 
3
- `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.
4
-
5
- It works with synchronous and asynchronous functions, supports multiple storage backends, and automatically invalidates caches when function code, dependencies, or captured variables change.
3
+ `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.
6
4
 
7
5
  ## 📦 Installation
8
6
 
@@ -146,7 +144,7 @@ In this example, the cache key for `numbers` ignores order, `data_file` is hashe
146
144
 
147
145
  For integration with databases, cloud storage, or custom serialization, implement your own storage backend by inheriting from `checkpointer.Storage` and implementing its abstract methods.
148
146
 
149
- Within custom storage methods, `call_id` identifies calls by arguments. Use `self.fn_id()` to get the function's unique identity (name + hash/version), crucial for organizing stored checkpoints (e.g., by function version). Access global `Checkpointer` config via `self.checkpointer`.
147
+ Within custom storage methods, `call_hash` identifies calls by arguments. Use `self.fn_id()` to get the function's unique identity (name + hash/version), crucial for organizing stored checkpoints (e.g., by function version). Access global `Checkpointer` config via `self.checkpointer`.
150
148
 
151
149
  **Example: Custom Storage Backend**
152
150
 
@@ -155,18 +153,18 @@ from checkpointer import checkpoint, Storage
155
153
  from datetime import datetime
156
154
 
157
155
  class MyCustomStorage(Storage):
158
- def exists(self, call_id):
156
+ def exists(self, call_hash):
159
157
  # Example: Constructing a path based on function ID and call ID
160
158
  fn_dir = self.checkpointer.root_path / self.fn_id()
161
- return (fn_dir / call_id).exists()
159
+ return (fn_dir / call_hash).exists()
162
160
 
163
- def store(self, call_id, data):
164
- ... # Store the serialized data for `call_id`
161
+ def store(self, call_hash, data):
162
+ ... # Store the serialized data for `call_hash`
165
163
  return data # This method must return the data back to checkpointer
166
164
 
167
- def checkpoint_date(self, call_id): ...
168
- def load(self, call_id): ...
169
- def delete(self, call_id): ...
165
+ def checkpoint_date(self, call_hash): ...
166
+ def load(self, call_hash): ...
167
+ def delete(self, call_hash): ...
170
168
 
171
169
  @checkpoint(format=MyCustomStorage)
172
170
  def custom_cached_function(x: int):
@@ -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,11 +130,11 @@ 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
 
132
- def get_call_id(self, args: tuple, kw: dict[str, object]) -> str:
137
+ def get_call_hash(self, args: tuple, kw: dict[str, object]) -> str:
133
138
  args = self.bound + args
134
139
  pos_args = args[len(self.arg_names):]
135
140
  named_pos_args = dict(zip(self.arg_names, args))
@@ -143,8 +148,8 @@ class CachedFunction(Generic[Fn]):
143
148
  pos_args = tuple(map(pos_hash_by, pos_args))
144
149
  return str(ObjectHash(named_args, pos_args, self.ident.captured_hash, digest_size=16))
145
150
 
146
- async def _resolve_coroutine(self, call_id: str, coroutine: Coroutine):
147
- return self.storage.store(call_id, AwaitableValue(await coroutine)).value
151
+ async def _resolve_coroutine(self, call_hash: str, coroutine: Coroutine):
152
+ return self.storage.store(call_hash, AwaitableValue(await coroutine)).value
148
153
 
149
154
  def _call(self: CachedFunction[Callable[P, R]], args: tuple, kw: dict, rerun=False) -> R:
150
155
  full_args = self.bound + args
@@ -152,27 +157,26 @@ class CachedFunction(Generic[Fn]):
152
157
  if not params.when:
153
158
  return self.fn(*full_args, **kw)
154
159
 
155
- call_id = self.get_call_id(args, kw)
156
- call_id_long = f"{self.fn_dir}/{self.ident.fn_hash}/{call_id}"
157
-
160
+ call_hash = self.get_call_hash(args, kw)
161
+ call_id = f"{self.storage.fn_id()}/{call_hash}"
158
162
  refresh = rerun \
159
- or not self.storage.exists(call_id) \
160
- or (params.should_expire and params.should_expire(self.storage.checkpoint_date(call_id)))
163
+ or not self.storage.exists(call_hash) \
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_id_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
- return self._resolve_coroutine(call_id, data)
167
- return self.storage.store(call_id, data)
170
+ return self._resolve_coroutine(call_hash, data)
171
+ return self.storage.store(call_hash, data)
168
172
 
169
173
  try:
170
- data = self.storage.load(call_id)
171
- print_checkpoint(params.verbosity >= 2, "REMEMBERED", call_id_long, "green")
174
+ data = self.storage.load(call_hash)
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_id_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:
@@ -181,28 +185,35 @@ class CachedFunction(Generic[Fn]):
181
185
  def rerun(self: CachedFunction[Callable[P, R]], *args: P.args, **kw: P.kwargs) -> R:
182
186
  return self._call(args, kw, True)
183
187
 
188
+ def exists(self: CachedFunction[Callable[P, R]], *args: P.args, **kw: P.kwargs) -> bool:
189
+ return self.storage.exists(self.get_call_hash(args, kw))
190
+
191
+ def delete(self: CachedFunction[Callable[P, R]], *args: P.args, **kw: P.kwargs):
192
+ self.storage.delete(self.get_call_hash(args, kw))
193
+
184
194
  @overload
185
- 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: ...
186
196
  @overload
187
197
  def get(self: Callable[P, R], *args: P.args, **kw: P.kwargs) -> R: ...
188
198
  def get(self, *args, **kw):
189
- call_id = self.get_call_id(args, kw)
199
+ call_hash = self.get_call_hash(args, kw)
190
200
  try:
191
- data = self.storage.load(call_id)
201
+ data = self.storage.load(call_hash)
192
202
  return data.value if isinstance(data, AwaitableValue) else data
193
203
  except Exception as ex:
194
204
  raise CheckpointError("Could not load checkpoint") from ex
195
205
 
196
- def exists(self: CachedFunction[Callable[P, R]], *args: P.args, **kw: P.kwargs) -> bool:
197
- return self.storage.exists(self.get_call_id(args, kw))
198
-
199
- def delete(self: CachedFunction[Callable[P, R]], *args: P.args, **kw: P.kwargs):
200
- self.storage.delete(self.get_call_id(args, kw))
206
+ @overload
207
+ def set(self: Callable[P, Coro[R]], value: AwaitableValue[R], *args: P.args, **kw: P.kwargs): ...
208
+ @overload
209
+ def set(self: Callable[P, R], value: R, *args: P.args, **kw: P.kwargs): ...
210
+ def set(self, value, *args, **kw):
211
+ self.storage.store(self.get_call_hash(args, kw), value)
201
212
 
202
213
  def __repr__(self) -> str:
203
214
  return f"<CachedFunction {self.fn.__name__} {self.ident.fn_hash[:6]}>"
204
215
 
205
- def deep_depends(self, visited: set[CachedFunction] = set()) -> Iterable[CachedFunction]:
216
+ def deep_depends(self, visited: set[CachedFunction] = empty_set) -> Iterable[CachedFunction]:
206
217
  if self not in visited:
207
218
  yield self
208
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)}')
@@ -9,21 +9,21 @@ class MemoryStorage(Storage):
9
9
  def get_dict(self):
10
10
  return item_map.setdefault(self.fn_dir(), {})
11
11
 
12
- def store(self, call_id, data):
13
- self.get_dict()[call_id] = (datetime.now(), data)
12
+ def store(self, call_hash, data):
13
+ self.get_dict()[call_hash] = (datetime.now(), data)
14
14
  return data
15
15
 
16
- def exists(self, call_id):
17
- return call_id in self.get_dict()
16
+ def exists(self, call_hash):
17
+ return call_hash in self.get_dict()
18
18
 
19
- def checkpoint_date(self, call_id):
20
- return self.get_dict()[call_id][0]
19
+ def checkpoint_date(self, call_hash):
20
+ return self.get_dict()[call_hash][0]
21
21
 
22
- def load(self, call_id):
23
- return self.get_dict()[call_id][1]
22
+ def load(self, call_hash):
23
+ return self.get_dict()[call_hash][1]
24
24
 
25
- def delete(self, call_id):
26
- self.get_dict().pop(call_id, None)
25
+ def delete(self, call_hash):
26
+ self.get_dict().pop(call_hash, None)
27
27
 
28
28
  def cleanup(self, invalidated=True, expired=True):
29
29
  curr_key = self.fn_dir()
@@ -32,6 +32,6 @@ class MemoryStorage(Storage):
32
32
  if invalidated and key != curr_key:
33
33
  del item_map[key]
34
34
  elif expired and self.checkpointer.should_expire:
35
- for call_id, (date, _) in list(calldict.items()):
35
+ for call_hash, (date, _) in list(calldict.items()):
36
36
  if self.checkpointer.should_expire(date):
37
- del calldict[call_id]
37
+ del calldict[call_hash]
@@ -8,29 +8,29 @@ def filedate(path: Path) -> datetime:
8
8
  return datetime.fromtimestamp(path.stat().st_mtime)
9
9
 
10
10
  class PickleStorage(Storage):
11
- def get_path(self, call_id: str):
12
- return self.fn_dir() / f"{call_id}.pkl"
11
+ def get_path(self, call_hash: str):
12
+ return self.fn_dir() / f"{call_hash}.pkl"
13
13
 
14
- def store(self, call_id, data):
15
- path = self.get_path(call_id)
14
+ def store(self, call_hash, data):
15
+ path = self.get_path(call_hash)
16
16
  path.parent.mkdir(parents=True, exist_ok=True)
17
17
  with path.open("wb") as file:
18
18
  pickle.dump(data, file, -1)
19
19
  return data
20
20
 
21
- def exists(self, call_id):
22
- return self.get_path(call_id).exists()
21
+ def exists(self, call_hash):
22
+ return self.get_path(call_hash).exists()
23
23
 
24
- def checkpoint_date(self, call_id):
24
+ def checkpoint_date(self, call_hash):
25
25
  # Should use st_atime/access time?
26
- return filedate(self.get_path(call_id))
26
+ return filedate(self.get_path(call_hash))
27
27
 
28
- def load(self, call_id):
29
- with self.get_path(call_id).open("rb") as file:
28
+ def load(self, call_hash):
29
+ with self.get_path(call_hash).open("rb") as file:
30
30
  return pickle.load(file)
31
31
 
32
- def delete(self, call_id):
33
- self.get_path(call_id).unlink(missing_ok=True)
32
+ def delete(self, call_hash):
33
+ self.get_path(call_hash).unlink(missing_ok=True)
34
34
 
35
35
  def cleanup(self, invalidated=True, expired=True):
36
36
  version_path = self.fn_dir()
@@ -20,14 +20,14 @@ class Storage:
20
20
  def fn_dir(self) -> Path:
21
21
  return self.checkpointer.root_path / self.fn_id()
22
22
 
23
- def store(self, call_id: str, data: Any) -> Any: ...
23
+ def store(self, call_hash: str, data: Any) -> Any: ...
24
24
 
25
- def exists(self, call_id: str) -> bool: ...
25
+ def exists(self, call_hash: str) -> bool: ...
26
26
 
27
- def checkpoint_date(self, call_id: str) -> datetime: ...
27
+ def checkpoint_date(self, call_hash: str) -> datetime: ...
28
28
 
29
- def load(self, call_id: str) -> Any: ...
29
+ def load(self, call_hash: str) -> Any: ...
30
30
 
31
- def delete(self, call_id: str) -> None: ...
31
+ def delete(self, call_hash: str) -> None: ...
32
32
 
33
33
  def cleanup(self, invalidated=True, expired=True): ...
@@ -0,0 +1,21 @@
1
+ from typing import Annotated, Callable, Coroutine, Generic, ParamSpec, TypeVar
2
+
3
+ Fn = TypeVar("Fn", bound=Callable)
4
+ P = ParamSpec("P")
5
+ R = TypeVar("R")
6
+ C = TypeVar("C")
7
+ T = TypeVar("T")
8
+
9
+ class HashBy(Generic[Fn]):
10
+ pass
11
+
12
+ NoHash = Annotated[T, HashBy[lambda _: None]]
13
+ Coro = Coroutine[object, object, R]
14
+
15
+ class AwaitableValue(Generic[T]):
16
+ def __init__(self, value: T):
17
+ self.value = value
18
+
19
+ def __await__(self):
20
+ yield
21
+ return self.value
@@ -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
  [project]
2
2
  name = "checkpointer"
3
- version = "2.11.0"
3
+ version = "2.11.2"
4
4
  requires-python = ">=3.11"
5
5
  dependencies = []
6
6
  authors = [
@@ -21,7 +21,7 @@ Repository = "https://github.com/Reddan/checkpointer.git"
21
21
  [dependency-groups]
22
22
  dev = [
23
23
  "numpy>=2.2.1",
24
- "omg>=1.3.5",
24
+ "omg>=1.3.6",
25
25
  "poethepoet>=0.30.0",
26
26
  "pytest>=8.3.5",
27
27
  "pytest-asyncio>=0.26.0",
@@ -8,7 +8,7 @@ resolution-markers = [
8
8
 
9
9
  [[package]]
10
10
  name = "checkpointer"
11
- version = "2.11.0"
11
+ version = "2.11.2"
12
12
  source = { editable = "." }
13
13
 
14
14
  [package.dev-dependencies]
@@ -27,7 +27,7 @@ dev = [
27
27
  [package.metadata.requires-dev]
28
28
  dev = [
29
29
  { name = "numpy", specifier = ">=2.2.1" },
30
- { name = "omg", specifier = ">=1.3.5" },
30
+ { name = "omg", specifier = ">=1.3.6" },
31
31
  { name = "poethepoet", specifier = ">=0.30.0" },
32
32
  { name = "pytest", specifier = ">=8.3.5" },
33
33
  { name = "pytest-asyncio", specifier = ">=0.26.0" },
@@ -330,14 +330,14 @@ wheels = [
330
330
 
331
331
  [[package]]
332
332
  name = "omg"
333
- version = "1.3.5"
333
+ version = "1.3.6"
334
334
  source = { registry = "https://pypi.org/simple" }
335
335
  dependencies = [
336
336
  { name = "watchdog" },
337
337
  ]
338
- sdist = { url = "https://files.pythonhosted.org/packages/29/6b/0ef0bf8cbd936d1ffb9b0b8f5904ef9f98bc965aeb211bb1a8f6139d401c/omg-1.3.5.tar.gz", hash = "sha256:1b105041c28c4278f7c6631f294b60574baa3d4af0c1892643f0f925aa718454", size = 14260 }
338
+ sdist = { url = "https://files.pythonhosted.org/packages/65/06/da0a3778b7ff8f1333ed7ddc0931ffff3c86ab5cb8bc4a96a1d0edb8671b/omg-1.3.6.tar.gz", hash = "sha256:465a51b7576fa31ef313e2b9a77d57f5d4816fb0a14dca0fc5c09ff471074fe6", size = 14268 }
339
339
  wheels = [
340
- { url = "https://files.pythonhosted.org/packages/0e/6e/6f7577de8246b963ea1ad9da62e3ea8ab0a2502c21b153319b856ce19f34/omg-1.3.5-py3-none-any.whl", hash = "sha256:b67f8d13ad55370e5a488f714d9676bc319aa353f676309485d55acaa190540c", size = 7990 },
340
+ { url = "https://files.pythonhosted.org/packages/dd/d2/87346e94dbecd3a65a09e2156c1adf30c162f31e69d0936343c3eff53e7a/omg-1.3.6-py3-none-any.whl", hash = "sha256:8e3ac99a18d5284ceef2ed98492d288d5f22ee2bb417591654a7d2433e196607", size = 7988 },
341
341
  ]
342
342
 
343
343
  [[package]]
@@ -1,17 +0,0 @@
1
- from typing import Annotated, Callable, Generic, TypeVar
2
-
3
- T = TypeVar("T")
4
- Fn = TypeVar("Fn", bound=Callable)
5
-
6
- class HashBy(Generic[Fn]):
7
- pass
8
-
9
- NoHash = Annotated[T, HashBy[lambda _: None]]
10
-
11
- class AwaitableValue:
12
- def __init__(self, value):
13
- self.value = value
14
-
15
- def __await__(self):
16
- yield
17
- return self.value
File without changes
File without changes