fleche.storage.base =================== .. py:module:: fleche.storage.base Attributes ---------- .. autoapisummary:: fleche.storage.base.logger Exceptions ---------- .. autoapisummary:: fleche.storage.base.SaveError fleche.storage.base.AmbiguousDigestError Classes ------- .. autoapisummary:: fleche.storage.base.KeyManagement fleche.storage.base.StorageBackend fleche.storage.base.ValueStorage fleche.storage.base.ValueMixin fleche.storage.base.CallStorage fleche.storage.base.CallMixin Functions --------- .. autoapisummary:: fleche.storage.base._longest_common_prefix_length fleche.storage.base._resolve_prefix Module Contents --------------- .. py:data:: logger .. py:exception:: SaveError Bases: :py:obj:`Exception` Common base class for all non-exit exceptions. .. py:exception:: AmbiguousDigestError Bases: :py:obj:`ValueError` Inappropriate argument value (of correct type). .. py:function:: _longest_common_prefix_length(s1: str, s2: str) -> int .. py:function:: _resolve_prefix(key: str, candidates: list[fleche.digest.Digest]) -> fleche.digest.Digest 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). .. py:class:: KeyManagement Bases: :py:obj:`abc.ABC` 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 :meth:`_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. .. py:method:: _operation_context(key: fleche.digest.Digest | str) 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). **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): with self._lock: # this mixin's resource with super()._operation_context(key): yield .. py:method:: list() -> Iterable[fleche.digest.Digest] :abstractmethod: .. py:method:: _evict(key: fleche.digest.Digest) -> None :abstractmethod: .. py:method:: _contains(key: fleche.digest.Digest) -> bool :abstractmethod: .. py:method:: evict(key: fleche.digest.Digest | str) -> None Removes the entry corresponding to the key from the storage. .. py:method:: contains(key: fleche.digest.Digest | str) -> bool .. py:method:: expand(key: fleche.digest.Digest | str) -> fleche.digest.Digest Expands a short-hand digest to the full length one. .. py:method:: shrink(key: fleche.digest.Digest | str, /) -> fleche.digest.Digest 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 :class:`Digest`. With multiple keys, returns a tuple of :class:`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). .. py:method:: _shrink_one(key: Digest | str, sorted_all: Sequence[str]) -> fleche.digest.Digest .. py:method:: _normalize_key(key: fleche.digest.Digest | str) -> fleche.digest.Digest Expand a short digest prefix to a full key, or wrap a full key as Digest. .. py:class:: StorageBackend Bases: :py:obj:`KeyManagement` Primitive backend interface for key-value storage. Backends implement the low-level ``put``/``get``/``_evict``/``list`` operations. Higher-level classes (:class:`ValueMixin`, :class:`CallMixin`) add domain-specific logic on top. .. py:method:: put(value: Any, key: fleche.digest.Digest) -> fleche.digest.Digest :abstractmethod: .. py:method:: get(key: fleche.digest.Digest) -> Any :abstractmethod: .. py:method:: _contains(key: fleche.digest.Digest) -> bool .. py:class:: ValueStorage Bases: :py:obj:`KeyManagement` Abstract domain interface for value storage. .. py:method:: save(value: Any, key: fleche.digest.Digest | None = None) -> fleche.digest.Digest :abstractmethod: .. py:method:: load(key: fleche.digest.Digest | str) -> Any :abstractmethod: .. py:class:: ValueMixin Bases: :py:obj:`ValueStorage`, :py:obj:`StorageBackend` Bridges :class:`ValueStorage` with :class:`StorageBackend` primitives. Implements ``save`` and ``load`` using ``put`` and ``get``. Concrete classes inherit from this and a :class:`StorageBackend` implementation to get a fully functional value storage. .. py:method:: save(value: Any, key: fleche.digest.Digest | None = None) -> fleche.digest.Digest .. py:method:: load(key: fleche.digest.Digest | str) -> Any .. py:class:: CallStorage Bases: :py:obj:`KeyManagement` Abstract domain interface for call storage. .. py:method:: save(call: fleche.call.DigestedCall) -> fleche.digest.Digest :abstractmethod: .. py:method:: load(key: fleche.digest.Digest | str) -> fleche.call.DigestedCall :abstractmethod: .. py:method:: query(template: fleche.call.QueryCall) -> Iterable[fleche.call.DigestedCall] :abstractmethod: .. py:method:: transform(func: Callable[[fleche.call.DigestedCall], fleche.call.DigestedCall] | None = None) -> None Applies a transformation function to all DigestedCall objects in the storage. :param func: A function that takes a :class:`DigestedCall` and returns a transformed one. If ``None``, the identity is used (useful for re-calculating keys). .. py:class:: CallMixin Bases: :py:obj:`CallStorage`, :py:obj:`StorageBackend` Bridges :class:`CallStorage` with :class:`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 :class:`CallStorage`. Concrete classes inherit from this and a :class:`StorageBackend` implementation to get a fully functional call storage. .. py:method:: save(call: fleche.call.DigestedCall) -> fleche.digest.Digest .. py:method:: load(key: fleche.digest.Digest | str) -> fleche.call.DigestedCall .. py:method:: query(template: fleche.call.QueryCall) -> Iterable[fleche.call.DigestedCall] 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 :class:`Digest`. :param template: specification for calls to return; use `None` as wildcard. :type template: Call :returns: an iterable over all matching digested call objects :rtype: Iterable[DigestedCall]