fleche.storage.base

Attributes

logger

Exceptions

SaveError

Common base class for all non-exit exceptions.

AmbiguousDigestError

Inappropriate argument value (of correct type).

Classes

Intent

Describes the kind of operation being performed on storage.

OperationContext

Minimal base that exposes the _operation_context() hook.

KeyManagement

Abstract base providing key-management helpers for any keyed storage.

StorageBackend

Primitive backend interface for key-value storage.

ValueStorage

Abstract domain interface for value storage.

ValueMixin

Bridges ValueStorage with StorageBackend primitives.

CallStorage

Abstract domain interface for call storage.

CallMixin

Bridges CallStorage with StorageBackend primitives.

Functions

_longest_common_prefix_length(→ int)

_resolve_prefix(→ fleche.digest.Digest)

Return the unique Digest for key prefix, or raise KeyError / AmbiguousDigestError.

Module Contents

fleche.storage.base.logger[source]
exception fleche.storage.base.SaveError[source]

Bases: Exception

Common base class for all non-exit exceptions.

exception fleche.storage.base.AmbiguousDigestError[source]

Bases: ValueError

Inappropriate argument value (of correct type).

class fleche.storage.base.Intent[source]

Bases: enum.StrEnum

Describes the kind of operation being performed on storage.

Mixins may use this to choose between exclusive and shared locks.

WRITE = 'write'[source]
fleche.storage.base._longest_common_prefix_length(s1: str, s2: str) int[source]
fleche.storage.base._resolve_prefix(key: str, candidates: list[fleche.digest.Digest]) fleche.digest.Digest[source]

Return the unique Digest for key prefix, or raise KeyError / AmbiguousDigestError.

candidates must contain at most two entries (the two lexicographically smallest keys that start with key); callers are responsible for fetching them efficiently (e.g. via a LIKE LIMIT 2 query for SQL backends).

class fleche.storage.base.OperationContext[source]

Bases: abc.ABC

Minimal base that exposes the _operation_context() hook.

Both KeyManagement (storage layer) and BaseCache (cache layer) inherit from this class so that the same thread-safety mixins (SerializingMixin, PerKeyLockMixin) can attach to either layer without duplication.

_operation_context(key: fleche.digest.Digest | str, *, intent: Intent = Intent.WRITE)[source]

Context manager entered around every operation on key.

The base implementation is a no-op. Override in a mixin to inject any resource scoped to the operation — a threading lock, a SQLAlchemy session, an open file handle, a decompression stream, etc.

Receiving key lets implementations choose between a single global resource (ignore the key) or per-key resources (e.g. a striped lock table or a key-specific file handle).

intent describes the kind of operation being performed. Mixins may use it to choose between exclusive and shared locks. Currently the only defined value is Intent.WRITE (the default).

Composing multiple mixins: use super() to chain so that every mixin in the MRO gets to wrap the operation:

@contextlib.contextmanager
def _operation_context(self, key, *, intent=Intent.WRITE):
    with self._lock:                   # this mixin's resource
        with super()._operation_context(key, intent=intent):
            yield
class fleche.storage.base.KeyManagement[source]

Bases: OperationContext

Abstract base providing key-management helpers for any keyed storage.

Subclasses must implement list, _evict, and _contains. The concrete helpers evict, contains, expand, and shrink are implemented here once and inherited by all storage classes.

Every public operation enters _operation_context() around the compound work it performs, so mixins can inject an operation-scoped resource (e.g. a threading lock, a SQLAlchemy session, a file handle) without overriding every method individually.

abstractmethod list() Iterable[fleche.digest.Digest][source]
abstractmethod _evict(key: fleche.digest.Digest) None[source]
abstractmethod _contains(key: fleche.digest.Digest) bool[source]
evict(key: fleche.digest.Digest | str) None[source]

Removes the entry corresponding to the key from the storage.

contains(key: fleche.digest.Digest | str) bool[source]
expand(key: fleche.digest.Digest | str) fleche.digest.Digest[source]

Expands a short-hand digest to the full length one.

shrink(key: fleche.digest.Digest | str, /) fleche.digest.Digest[source]
shrink(key: fleche.digest.Digest | str, /, *keys: fleche.digest.Digest | str) tuple[Digest, ...]

Find the shortest substring(s) that unambiguously reference each key.

With a single key, returns one Digest. With multiple keys, returns a tuple of Digest in the same order as the inputs; the batched form fetches list() once instead of per-key, which matters on backends where listing is expensive (e.g. SQL, filesystem).

_shrink_one(key: Digest | str, sorted_all: Sequence[str]) fleche.digest.Digest[source]
_normalize_key(key: fleche.digest.Digest | str) fleche.digest.Digest[source]

Expand a short digest prefix to a full key, or wrap a full key as Digest.

class fleche.storage.base.StorageBackend[source]

Bases: KeyManagement

Primitive backend interface for key-value storage.

Backends implement the low-level put/get/_evict/list operations. Higher-level classes (ValueMixin, CallMixin) add domain-specific logic on top.

abstractmethod put(value: Any, key: fleche.digest.Digest) fleche.digest.Digest[source]
abstractmethod get(key: fleche.digest.Digest) Any[source]
_contains(key: fleche.digest.Digest) bool[source]
class fleche.storage.base.ValueStorage[source]

Bases: KeyManagement

Abstract domain interface for value storage.

abstractmethod save(value: Any, key: fleche.digest.Digest | None = None) fleche.digest.Digest[source]
abstractmethod load(key: fleche.digest.Digest | str) Any[source]
class fleche.storage.base.ValueMixin[source]

Bases: ValueStorage, StorageBackend

Bridges ValueStorage with StorageBackend primitives.

Implements save and load using put and get. Concrete classes inherit from this and a StorageBackend implementation to get a fully functional value storage.

save(value: Any, key: fleche.digest.Digest | None = None) fleche.digest.Digest[source]
load(key: fleche.digest.Digest | str) Any[source]
class fleche.storage.base.CallStorage[source]

Bases: KeyManagement

Abstract domain interface for call storage.

abstractmethod save(call: fleche.call.DigestedCall) fleche.digest.Digest[source]
abstractmethod load(key: fleche.digest.Digest | str) fleche.call.DigestedCall[source]
abstractmethod query(template: fleche.call.QueryCall) Iterable[fleche.call.DigestedCall][source]
transform(func: Callable[[fleche.call.DigestedCall], fleche.call.DigestedCall] | None = None) None[source]

Applies a transformation function to all DigestedCall objects in the storage.

Parameters:

func – A function that takes a DigestedCall and returns a transformed one. If None, the identity is used (useful for re-calculating keys).

class fleche.storage.base.CallMixin[source]

Bases: CallStorage, StorageBackend

Bridges CallStorage with StorageBackend primitives.

Implements save, load, and query using put and get, deriving the storage key from the call’s lookup key. transform is inherited from CallStorage.

Concrete classes inherit from this and a StorageBackend implementation to get a fully functional call storage.

save(call: fleche.call.DigestedCall) fleche.digest.Digest[source]
load(key: fleche.digest.Digest | str) fleche.call.DigestedCall[source]
query(template: fleche.call.QueryCall) Iterable[fleche.call.DigestedCall][source]

Find cached calls that ‘match’ the template.

Returns all calls where the given arguments, results or metadata match exactly the stored ones. Values may be given either as they are or as Digest.

Parameters:

template (Call) – specification for calls to return; use None as wildcard.

Returns:

an iterable over all matching digested call objects

Return type:

Iterable[DigestedCall]