Skip to main content

belljar 🫙

Mid-execution memoization for dynamic state.

Standard caching fails when your function relies on hidden or changing state (like a database cursor or an open file). belljar solves this by letting you build the cache key while your code runs — and keep it, as a value, for later.

uv add belljar

The Difference

Concept functools.lru_cache belljar
When is it checked? Before the function runs. Mid-execution, exactly when you tell it to.
What defines identity? Static function arguments. Whatever runtime state you fold in, in order.
Handling Mutable State Fails. Returns stale/wrong data. Succeeds. Hashes current state dynamically.
Where can you retrieve? Only by calling again. Anywhere that folds the same trace.

Usage

A Jar is a persistent store plus a cursor: it opens at a fresh identity, and include() advances it by folding runtime state in. get() and set() read and write the value at the current position (get() returns None when nothing is sealed there).

from belljar import Jar
import io

# A simulated file. The object stays the same, but its internal cursor moves.
log_file = io.StringIO("chunk1 chunk2 chunk3")

def process_chunk(file_handle) -> str:
    jar = Jar[str]()  # caches under ./.jar by default; pass any path to move it
    jar.include(process_chunk.__code__)    # fold this function's code: edits invalidate
    jar.include(file_handle.tell())        # fold the file's exact runtime cursor position

    # If we've processed from this exact position before, skip the work
    if (cached := jar.get()) is not None:
        return cached

    # Otherwise, do the heavy processing and seal the result
    print("Doing heavy work...")
    return jar.set(file_handle.read(6))

process_chunk(log_file)  # Reads "chunk1", saves to disk. (Takes time)
process_chunk(log_file)  # Reads "chunk2", saves to disk. (Takes time)

log_file.seek(0)
process_chunk(log_file)  # Cursor is back at 0 → instantly returns cached "chunk1"

No decorators, no changed return types — a cache hit is a plain if.

Identity Is Reached, Not Given

Identities are deterministic: folding the same state in the same order always arrives at the same position. So the place that seals a value and the place that retrieves it don't have to share anything but the trace — fold both functions' code on both sides and they reach the same entry:

class KVStore:
    def add(self, key: str, value: int) -> None:
        jar = Jar[int]("./cache")
        jar.include(KVStore.add.__code__)
        jar.include(KVStore.get.__code__)
        jar.include(key)
        jar.set(value)

    def get(self, key: str) -> int | None:
        jar = Jar[int]("./cache")
        jar.include(KVStore.add.__code__)
        jar.include(KVStore.get.__code__)
        jar.include(key)
        return jar.get()

Core Mechanics

  • Identity is a trace. The cursor is a running hash: every include chains onto everything folded before it, in order. include(a); include(b) and include(b); include(a) are different identities — the key mirrors the path your execution took.
  • Invalidation is inclusion. Fold whatever governs a value's lifetime: func.__code__ to invalidate on edit, a schema version, a mtime. If it's part of the identity, changing it is invalidation.
  • Disk Persistent: Caches survive restarts. Identities are deterministic, so a fresh process folding the same state finds the same entries.
  • Type Safe: Jar is generic — Jar[str]() seals and returns str, so set and get type-check end to end (Jar() alone defaults to object).
  • Deep Serialization: Powered by dill (not pickle), meaning it safely handles lambdas, nested classes, and complex closures — both as folded state and as sealed values.
  • Concurrency: A Jar is a lightweight cursor over a shared store — construction is just a path and a hash seed, so make one per call or task instead of sharing a cursor.

Release files for belljar 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for belljar 0.4.1
File Size Uploaded
belljar-0.4.1.tar.gz 3.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for belljar 0.4.1
File Interpreter ABI Platform
belljar-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 7.7 kB

Release files / belljar-0.4.1.tar.gz

Download URL belljar-0.4.1.tar.gz
Size 3.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c7a0fc5b03db703bbde27a3b28d0ab1e0a42b916e76b016be77bda705e090e56
BLAKE2b-256 checksum
How to use checksums
a4c9924f078895dbf1fc95cd292b7218c591cb828bdbff61199296982df85d2f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / belljar-0.4.1-py3-none-any.whl

Download URL belljar-0.4.1-py3-none-any.whl
Size 4.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
218396136eb4ff0e5c8ef9efad7e80574bee77084c7e125b5aeb902133d446cd
BLAKE2b-256 checksum
How to use checksums
4ea826e4680d28d3361a869ea38789c6fc7709309b32ec1fe6a702c39f55ec13
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"CachyOS Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page