Decorator Helpers
Functions decorated with @fleche are enhanced with several helper methods that allow for manual interaction with the cache and inspection of function calls.
Helper Methods
The following methods are added to the decorated function:
.call(*args, **kwargs)
Returns a Call object corresponding to the provided arguments. This object contains metadata about the call, such as the function name, arguments, and version, but does not execute the function.
.digest(*args, **kwargs)
Returns the unique cache key (a digest string) that would be used for the given call.
.load(*args, **kwargs)
Attempts to load the result of a specific call from the cache. If the result is not cached, it raises a KeyError.
.contains(*args, **kwargs)
Returns True if the result for the given call is already present in the cache, False otherwise.
.query(*args, metadata={}, **kwargs)
Returns matching cached calls from the active cache. Any argument passed as None acts as a wildcard, matching any stored value for that parameter. The metadata keyword argument accepts a dictionary of metadata tags to further filter results (e.g., metadata={"tags": {"project": "alpha"}}).
See Querying cached calls for a detailed guide on querying cached calls.
.rerun(*args, **kwargs)
Forces the function to re-execute, even if its result is already present in the cache, and saves the newly computed result to the cache. This forces reevaluation recursively for any nested @fleche calls as well.
Accessing the Original Function
The original, undecorated function is always accessible via the .__wrapped__ attribute. This is useful if you need to bypass the cache entirely for a specific call.
@fleche
def my_func(x):
return x * 2
# Bypass cache
result = my_func.__wrapped__(10)
Per-Function Static Caching
To keep the cache-hit hot path fast, @fleche memoises three pure-of-func
quantities the first time it sees a given function:
inspect.signature(func)the digest of
func.__code__(included in cache keys only whenhash_code=True; the default isFalse)(qualname, module, version)extracted viapyiron_snippets.versions.VersionInfo
These are keyed on the wrapped function’s identity — i.e. on func itself
as a hashable object — so subsequent calls re-use the cached result instead of
re-introspecting on every invocation.
The caches are bounded LRU maps (max 1000 entries each), scoped to the
Python process; they have no effect on the persistent fleche backends.
Warning
Mutating ``func`` after the first call is not picked up. Changes to
func.__code__, func.__signature__, func.__module__,
func.__qualname__, or func.__version__ made after the wrapper has
already seen func once will not affect subsequent cache keys.
In practice this matters only for code that hot-mutates dunder attributes
on a live function — typically Mock instances in tests, or
monkey-patching experiments. Decorators that return a new wrapped
callable, and importlib.reload (which gives a reloaded module fresh
function identities), are unaffected: each new identity gets its own
cache entry, and old entries LRU-evict naturally.
If you genuinely need to drop the per-function caches in-process, the
helpers in fleche.call expose cache_clear():
from fleche.call import (
_cached_signature,
_cached_code_digest,
_cached_version_info,
)
for c in (_cached_signature, _cached_code_digest, _cached_version_info):
c.cache_clear()
Note
Callables that aren’t python-hashable (__hash__ = None, e.g. some
instances with a custom __call__) bypass the in-process cache
transparently. Correctness is preserved — the wrapper re-introspects
on every call — but the cache-hit path is slower than for plain functions.
Usage with Decorated Methods
Note
When @fleche is applied to a method, the helper methods (.call, .digest, .query,
.load, .contains, .rerun) do not automatically bind self. Python’s bound method
objects delegate custom attribute lookups to the underlying function, so
obj.method.query and MyClass.method.query return the same helper function.
However, this helper is a plain function — not a bound method — so obj is not
pre-applied; you must pass the instance explicitly as the first positional argument.
For fleche to cache calls that include self, the class must be hashable —
i.e. it must implement a __digest__ method (see Customizing Digests).
from fleche import fleche
from fleche.digest import digest, Digest
class MyClass:
def __init__(self, id: int):
self.id = id
def __digest__(self) -> Digest:
return digest((type(self).__name__, self.id))
@fleche
def compute(self, x):
return x ** 2
obj = MyClass(id=1)
# Correct — pass self explicitly
obj.compute.query(obj, x=5)
obj.compute.contains(obj, x=5)
obj.compute.digest(obj, x=5)
# Also works as a keyword argument
obj.compute.query(self=obj, x=5)