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 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
+ ```
@@ -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"]