belljar 0.1.0__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.
- belljar-0.1.0/PKG-INFO +67 -0
- belljar-0.1.0/README.md +41 -0
- belljar-0.1.0/pyproject.toml +42 -0
- belljar-0.1.0/src/belljar/__init__.py +88 -0
belljar-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: belljar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Extremely simple to use callable memoization decorator library.
|
|
5
|
+
Keywords: cache,memoization,decorator,dill,persistence,hashing
|
|
6
|
+
Author: Wannes Vantorre
|
|
7
|
+
Author-email: Wannes Vantorre <vantorrewannes@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Dist: dill>=0.4.1
|
|
20
|
+
Requires-Dist: xxhash>=3.6.0
|
|
21
|
+
Requires-Python: >=3.14
|
|
22
|
+
Project-URL: Homepage, https://github.com/VantorreWannes/belljar/belljar
|
|
23
|
+
Project-URL: Repository, https://github.com/VantorreWannes/belljar/belljar
|
|
24
|
+
Project-URL: Issues, https://github.com/VantorreWannes/belljar/issues
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# belljar 🫙
|
|
28
|
+
|
|
29
|
+
**Conditional memoization for complex runtime state.**
|
|
30
|
+
|
|
31
|
+
Standard decorators check the cache *before* execution. `belljar` lets you check the cache *during* execution.
|
|
32
|
+
|
|
33
|
+
By calling `includes()`, you update the hash with runtime state (like file handles or database cursors). If `belljar` detects that this specific state has been processed before, **execution stops immediately** and the cached result is returned.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from belljar import jar, includes
|
|
39
|
+
|
|
40
|
+
@jar
|
|
41
|
+
def parse_log(file_handle):
|
|
42
|
+
# 1. State is initially just the function args.
|
|
43
|
+
|
|
44
|
+
# 2. Add runtime state to the hash (e.g., file cursor position).
|
|
45
|
+
includes(file_handle)
|
|
46
|
+
|
|
47
|
+
# CHECKPOINT:
|
|
48
|
+
# If this exact sequence (args + file state) exists in the cache,
|
|
49
|
+
# execution STOPS here and returns the stored value.
|
|
50
|
+
|
|
51
|
+
print("Heavy processing...")
|
|
52
|
+
return file_handle.read()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
- **Mid-Execution Cache Hits:** Skip the heavy lifting if the intermediate state is recognized.
|
|
58
|
+
- **Complex Serialization:** Uses `dill` instead of `pickle`, supporting lambdas, local classes, and closures.
|
|
59
|
+
- **Zero Config:** caches to `.jar/` by default, or pass a path: `@jar(Path("/tmp/cache"))`.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv add belljar
|
|
65
|
+
# or
|
|
66
|
+
pip install belljar
|
|
67
|
+
```
|
belljar-0.1.0/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# belljar 🫙
|
|
2
|
+
|
|
3
|
+
**Conditional memoization for complex runtime state.**
|
|
4
|
+
|
|
5
|
+
Standard decorators check the cache *before* execution. `belljar` lets you check the cache *during* execution.
|
|
6
|
+
|
|
7
|
+
By calling `includes()`, you update the hash with runtime state (like file handles or database cursors). If `belljar` detects that this specific state has been processed before, **execution stops immediately** and the cached result is returned.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from belljar import jar, includes
|
|
13
|
+
|
|
14
|
+
@jar
|
|
15
|
+
def parse_log(file_handle):
|
|
16
|
+
# 1. State is initially just the function args.
|
|
17
|
+
|
|
18
|
+
# 2. Add runtime state to the hash (e.g., file cursor position).
|
|
19
|
+
includes(file_handle)
|
|
20
|
+
|
|
21
|
+
# CHECKPOINT:
|
|
22
|
+
# If this exact sequence (args + file state) exists in the cache,
|
|
23
|
+
# execution STOPS here and returns the stored value.
|
|
24
|
+
|
|
25
|
+
print("Heavy processing...")
|
|
26
|
+
return file_handle.read()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Mid-Execution Cache Hits:** Skip the heavy lifting if the intermediate state is recognized.
|
|
32
|
+
- **Complex Serialization:** Uses `dill` instead of `pickle`, supporting lambdas, local classes, and closures.
|
|
33
|
+
- **Zero Config:** caches to `.jar/` by default, or pass a path: `@jar(Path("/tmp/cache"))`.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add belljar
|
|
39
|
+
# or
|
|
40
|
+
pip install belljar
|
|
41
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "belljar"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Extremely simple to use callable memoization decorator library."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Wannes Vantorre", email = "vantorrewannes@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["cache", "memoization", "decorator", "dill", "persistence", "hashing"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"dill>=0.4.1",
|
|
27
|
+
"xxhash>=3.6.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/VantorreWannes/belljar/belljar"
|
|
32
|
+
Repository = "https://github.com/VantorreWannes/belljar/belljar"
|
|
33
|
+
Issues = "https://github.com/VantorreWannes/belljar/issues"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["uv_build>=0.9.26,<0.10.0"]
|
|
37
|
+
build-backend = "uv_build"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=9.0.2",
|
|
42
|
+
]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Callable, Union, cast
|
|
7
|
+
|
|
8
|
+
import dill
|
|
9
|
+
import xxhash
|
|
10
|
+
|
|
11
|
+
_context = threading.local()
|
|
12
|
+
_CACHE_PROPERTY_NAME = "cache"
|
|
13
|
+
CACHE_DIR = Path(".jar")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _CacheHit(Exception):
|
|
17
|
+
def __init__(self, fingerprint: xxhash.xxh64) -> None:
|
|
18
|
+
self.fingerprint = fingerprint
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _CacheState:
|
|
22
|
+
def __init__(self, dir: Path) -> None:
|
|
23
|
+
os.makedirs(dir, exist_ok=True)
|
|
24
|
+
self.dir: Path = dir
|
|
25
|
+
self._fingerprint = xxhash.xxh64()
|
|
26
|
+
|
|
27
|
+
def fingerprint(self) -> xxhash.xxh64:
|
|
28
|
+
return self._fingerprint
|
|
29
|
+
|
|
30
|
+
def update(self, value: Any) -> None:
|
|
31
|
+
self._fingerprint.update(dill.dumps(value))
|
|
32
|
+
|
|
33
|
+
def path(self) -> Path:
|
|
34
|
+
digest = self._fingerprint.hexdigest()
|
|
35
|
+
filename = digest + ".dill"
|
|
36
|
+
return self.dir / filename
|
|
37
|
+
|
|
38
|
+
def exists(self) -> bool:
|
|
39
|
+
return os.path.exists(self.path())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def includes(value: Any) -> None:
|
|
43
|
+
if hasattr(_context, _CACHE_PROPERTY_NAME):
|
|
44
|
+
cache = cast(_CacheState, getattr(_context, _CACHE_PROPERTY_NAME))
|
|
45
|
+
cache.update(value)
|
|
46
|
+
if cache.exists():
|
|
47
|
+
raise _CacheHit(cache.fingerprint())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def jar(dir: Union[Path, Callable] = CACHE_DIR) -> Callable:
|
|
51
|
+
def factory(func: Callable) -> Callable:
|
|
52
|
+
@functools.wraps(func)
|
|
53
|
+
def wrapper(*args, **kwargs):
|
|
54
|
+
state = _CacheState(dir if not callable(dir) else CACHE_DIR)
|
|
55
|
+
state.update(func.__module__)
|
|
56
|
+
state.update(func.__name__)
|
|
57
|
+
state.update(inspect.getsource(func))
|
|
58
|
+
state.update(args)
|
|
59
|
+
state.update(kwargs)
|
|
60
|
+
|
|
61
|
+
setattr(_context, _CACHE_PROPERTY_NAME, state)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
if state.exists():
|
|
65
|
+
with open(state.path(), "rb") as f:
|
|
66
|
+
return dill.load(f)
|
|
67
|
+
|
|
68
|
+
output = func(*args, **kwargs)
|
|
69
|
+
|
|
70
|
+
with open(state.path(), "wb") as f:
|
|
71
|
+
dill.dump(output, f)
|
|
72
|
+
return output
|
|
73
|
+
|
|
74
|
+
except _CacheHit:
|
|
75
|
+
with open(state.path(), "rb") as f:
|
|
76
|
+
return dill.load(f)
|
|
77
|
+
finally:
|
|
78
|
+
delattr(_context, _CACHE_PROPERTY_NAME)
|
|
79
|
+
|
|
80
|
+
return wrapper
|
|
81
|
+
|
|
82
|
+
if callable(dir):
|
|
83
|
+
return factory(dir)
|
|
84
|
+
|
|
85
|
+
return factory
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = ["jar", "includes"]
|