Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

EvalCache

Decorator-first caching for graphs of expensive Python computations.

CI

EvalCache turns calls to pure functions into deferred expression nodes. The result of every node can be reused from memory or a persistent cache, including intermediate nodes shared by several computations.

Install

python -m pip install evalcache

Basic use

import evalcache

evalcache.configure(
    cache_store=evalcache.DirectoryCacheStore(".evalcache"),
)

@evalcache.operation
def preprocess(source: str) -> list[int]:
    print("preprocessing", source)
    return [1, 2, 3]

@evalcache.operation
def total(values: list[int]) -> int:
    return sum(values)

result = total(preprocess("model.step"))

# Decorated calls build a graph and return Deferred values.
assert isinstance(result, evalcache.Deferred)
print(result.compute())

On the first run, EvalCache evaluates and stores both operations. A fresh process can restore their results from .evalcache when their operation identity and arguments have not changed.

The default serializer uses pickle. Only open cache directories trusted by the current user.

Operation identity

By default, EvalCache derives an operation id from the function's module and qualified name and derives a version from its Python implementation. For long-lived caches, explicit identity makes invalidation intentional:

@evalcache.operation(
    operation_id="my-project.build-mesh",
    operation_version="3",
    result=Mesh,
)
def build_mesh(source: Source) -> Mesh:
    ...

Keep operation_id stable while the operation retains the same meaning. Increment operation_version when old cached results must no longer be used. A return annotation normally supplies the result contract; result= can instead accept a runtime type or a ResultSpec with a validator or custom serializer.

Domain arguments must have deterministic identities. Register an encoder with HashRegistry or implement __evalcache_key__() -> bytes:

hashes = evalcache.HashRegistry()
hashes.register(Source, lambda source: source.digest.encode("ascii"))

@evalcache.operation(hash_registry=hashes)
def convert(source: Source) -> Mesh:
    ...

See advanced_decorators.py for operation versions, validation, domain hashing, progress events, and non-cacheable operations.

Policies and explicit evaluators

Module-level @evalcache.operation uses the default evaluator at call time. configure() replaces its global policies without redecorating functions. For libraries or isolated jobs, own the evaluator explicitly:

evaluator = evalcache.Evaluator(
    cache_store=evalcache.DirectoryCacheStore(".evalcache"),
    cache_policy=evalcache.CachePolicy(namespace="geometry"),
)

@evaluator.operation
def triangulate(shape: Shape) -> Mesh:
    ...

using_evaluator() temporarily changes the default evaluator. Deferred values owned by different evaluators cannot be mixed in one graph.

CachePolicy controls persistent reads, writes, namespaces, and corrupt record recovery. In-memory reuse within an evaluator remains enabled even when CachePolicy.disabled() disables persistent caching.

Composing Deferred values

Arguments may contain other Deferred values inside lists, tuples, sets, frozensets, and mappings. Operators with unambiguous lazy semantics create new expression nodes without computing their operands:

@evalcache.operation
def load_values() -> tuple[int, ...]:
    return (6, -4, 3)

values = load_values()
result = 10 + values[0] * 2 - abs(values[1])
assert result.compute() == 18

Unary +, -, abs, and ~ are supported, together with arithmetic, matrix, bitwise, shift, reflected binary operators, and indexing. Comparisons, truth testing, and implicit iteration require an explicit .compute().

See expression_tree.py and operators.py.

File artifacts

A file-producing operation can return immutable contents while leaving the destination path outside expression identity:

@evalcache.operation(
    operation_id="my-project.render-report",
    operation_version="1",
    result=evalcache.file_artifact_result(),
)
def render_report(value: int) -> evalcache.FileArtifact:
    return evalcache.FileArtifact(
        name="report.txt",
        data=("result={}\n".format(value)).encode("utf-8"),
        media_type="text/plain",
    )

report = render_report(42)
report.materialize("first.txt")
report.materialize("second.txt")

FileArtifact.from_path() snapshots a backend that can only produce a file. materialize() atomically replaces the selected destination. The current serializer stores artifact bytes inside the cache record.

See file_artifact.py.

Low-level API

Decorators are the normal entry point. Applications that need explicit domain handles may construct Expression[T] values and submit or evaluate them with Evaluator. Storage and serialization are replaceable through the CacheStore and Serializer protocols; MemoryCacheStore, DirectoryCacheStore, and MappingCacheStore are included.

Original Lazy API

The original implementation remains available under evalcache.legacy:

from evalcache.legacy import DirCache, Lazy

lazy = Lazy(cache=DirCache(".evalcache"))

@lazy
def calculate(value):
    return value * 2

assert calculate(21).unlazy() == 42

The historical top-level imports remain as compatibility aliases, but new code should use @evalcache.operation. See Legacy API and migration and the legacy examples.

Further reading

License

EvalCache is distributed under the MIT License.

Contact: mirmik (mirmikns@yandex.ru)

Metadata

Release files for evalcache 2.0.0a1

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

Source distribution (sdist)

Source distribution for evalcache 2.0.0a1
File Size Uploaded
evalcache-2.0.0a1.tar.gz 44.8 kB Details

Built distribution (wheel)

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

Total release size: 80.6 kB

Release files / evalcache-2.0.0a1.tar.gz

Download URL evalcache-2.0.0a1.tar.gz
Size 44.8 kB
Tags Source
SHA-256 checksum
How to use checksums
4a1dfb28d3e652454a0feb9e2846472890f84895466c8503a8cef11aa93d1847
BLAKE2b-256 checksum
How to use checksums
fd872e2564fdc56202da738be24424531db69bfa37c3956d17d9b6554edca96e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.19

Release files / evalcache-2.0.0a1-py3-none-any.whl

Download URL evalcache-2.0.0a1-py3-none-any.whl
Size 35.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45eb44cb1eec5e750be8aefb6b1bda9ff0a8a3c73d5441ba420160b58c697be7
BLAKE2b-256 checksum
How to use checksums
8cc5d9d61e1cefc87f68bf8e9767146dfa34fff8a804f40fc66ead031a129c77
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.19
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