fleche.storage.destructuring ============================ .. py:module:: fleche.storage.destructuring Classes ------- .. autoapisummary:: fleche.storage.destructuring.Digested fleche.storage.destructuring.DigestedIterable fleche.storage.destructuring.DigestedDict fleche.storage.destructuring.DigestedFields fleche.storage.destructuring.DigestedDataclass fleche.storage.destructuring.DigestedAttrs fleche.storage.destructuring.HasChildDigests fleche.storage.destructuring.DestructuringMixin Module Contents --------------- .. py:class:: Digested Bases: :py:obj:`abc.ABC` Helper class that provides a standard way to create an ABC using inheritance. .. py:method:: underlying() :abstractmethod: Return plain underlying value, ie. list/dict/etc of nested values or their partial digests .. py:method:: __digest__() .. py:method:: mend(storage: DestructuringMixin) :abstractmethod: .. py:method:: sunder(intern: Callable[[Any], tuple[Any, int | float]], value: Any) :classmethod: :abstractmethod: .. py:method:: get(storage, key) :staticmethod: .. py:class:: DigestedIterable Bases: :py:obj:`Digested` Helper class that provides a standard way to create an ABC using inheritance. .. py:attribute:: items :type: list | tuple .. py:method:: underlying() Return plain underlying value, ie. list/dict/etc of nested values or their partial digests .. py:method:: mend(storage: DestructuringMixin) -> list | tuple .. py:method:: sunder(intern: Callable[[Any], tuple[Any, int | float]], value: list | tuple) :classmethod: .. py:class:: DigestedDict Bases: :py:obj:`Digested` Helper class that provides a standard way to create an ABC using inheritance. .. py:attribute:: items :type: dict .. py:method:: underlying() Return plain underlying value, ie. list/dict/etc of nested values or their partial digests .. py:method:: mend(storage: DestructuringMixin) -> dict .. py:method:: sunder(intern: Callable[[Any], tuple[Any, int | float]], value: dict) :classmethod: .. py:class:: DigestedFields Bases: :py:obj:`Digested` Common base for record-shaped value markers (dataclasses, attrs). Subclasses provide :meth:`_field_items` to enumerate ``(name, value)`` pairs from a live instance; :meth:`sunder`, :meth:`mend`, and :meth:`__digest__` are shared. Field values may be replaced by :class:`~fleche.digest.Digest` back-references when they are stored independently. :meth:`mend` reconstructs the original instance by bypassing ``__init__`` / ``__post_init__``, so ``InitVar`` and ``init=False`` fields (and attrs slots / frozen instances) are all handled uniformly. .. py:attribute:: cls :type: type .. py:attribute:: fields :type: dict .. py:method:: underlying() Return plain underlying value, ie. list/dict/etc of nested values or their partial digests .. py:method:: __digest__() .. py:method:: mend(storage: DestructuringMixin) .. py:method:: _field_items(value: Any) -> list[tuple[str, Any]] :staticmethod: :abstractmethod: .. py:method:: sunder(intern: Callable[[Any], tuple[Any, int | float]], value: Any) :classmethod: .. py:class:: DigestedDataclass Bases: :py:obj:`DigestedFields` Per-field marker for stdlib :mod:`dataclasses` instances. .. py:method:: _field_items(value: Any) -> list[tuple[str, Any]] :staticmethod: .. py:class:: DigestedAttrs Bases: :py:obj:`DigestedFields` Per-field marker for ``attrs``-decorated instances. .. py:method:: _field_items(value: Any) -> list[tuple[str, Any]] :staticmethod: .. py:class:: HasChildDigests Bases: :py:obj:`Protocol` Structural protocol for value storages that expose a reference-graph edge query. Any :class:`~fleche.storage.base.ValueStorage` that implements :meth:`child_digests` satisfies this protocol automatically — no explicit registration or inheritance required. Use ``isinstance(storage, HasChildDigests)`` to check at runtime whether the storage supports transitive reachability walks. .. py:method:: child_digests(key: fleche.digest.Digest) -> set[fleche.digest.Digest] Return the set of digests directly referenced by the entry at *key*. :raises KeyError: if *key* is not present in the storage. .. py:class:: DestructuringMixin Bases: :py:obj:`fleche.storage.base.ValueStorage` Mixin that recursively destructures collections on save/load. Place before a :class:`ValueMixin` in the MRO to add destructuring behavior. Lists, tuples, and dicts are broken apart so each element is stored independently; on load the original structure is reassembled. .. rubric:: Example >>> from fleche.storage.base import ValueMixin >>> from fleche.storage.memory import MemoryBackend >>> @dataclass(frozen=True) ... class MyValueStorage(DestructuringMixin, ValueMixin, MemoryBackend): ... >>> vm = MyValueStorage(storage={}) >>> key = vm.save([1, [2, 3]]) >>> vm.load(key) == [1, [2, 3]] True .. py:attribute:: remaining_depth :type: int :value: 0 .. py:method:: _is_trojan_tuple(value) :staticmethod: .. py:method:: _intern_rec(value: Any, key: fleche.digest.Digest | None = None) -> tuple[Any, int | float] Post-order traversal: recurse to leaves, decide inline-vs-store on the way back up. Returns ``(result, depth)`` where *result* is the plain value when ``depth < remaining_depth`` (the element is inlined in its parent's :class:`Digested` wrapper) or a :class:`Digest` when the element was written to storage separately. Every node in the structure is visited exactly once (O(n)), unlike a separate depth-counting pass. .. py:method:: save(value: Any, key: fleche.digest.Digest | None = None) -> fleche.digest.Digest .. py:method:: load(key: fleche.digest.Digest | str) -> Any .. py:method:: _raw_sub_digests(raw: Any) -> set[fleche.digest.Digest] Direct digest children of a raw stored entry. A *raw* entry is what ``super().load`` returns — i.e. what was written to the underlying backend before :meth:`mend` rewires sub-digests back into their parent container. Only :class:`Digested` wrappers carry child references; scalars and plain (non-destructured) containers return an empty set. .. py:method:: child_digests(key: fleche.digest.Digest | str) -> set[fleche.digest.Digest] Direct digest children of the raw entry stored at *key*. Bypasses :meth:`mend`, so destructured sub-references are returned as opaque :class:`~fleche.digest.Digest` keys rather than being followed. Intended for reference-graph traversals (GC, debugging) where loading the mended value would flatten the structure we need to inspect. :raises KeyError: if *key* is not present in the underlying backend. .. py:method:: count_reuses() -> collections.Counter[fleche.digest.Digest] Return a counter of how many times each stored key is referenced as a sub-component. Scans every raw entry and tallies ``Digest`` back-references found inside :class:`DigestedIterable` and :class:`DigestedDict` wrappers. A count of ``0`` means the key is not pointed to by any other stored value (i.e. a top-level entry). A count greater than ``1`` indicates a sub-value shared between multiple parent containers. :returns: A :class:`~collections.Counter` mapping each :class:`~fleche.digest.Digest` key to the number of times it is referenced by other stored entries. .. rubric:: Example >>> from fleche.storage.memory import ValueMemory >>> ds = ValueMemory(storage={}) >>> shared = [2, 3] >>> _ = ds.save([1, shared]) >>> _ = ds.save([4, shared]) >>> hits = ds.count_reuses() >>> hits[ds.save(shared)] # [2, 3] is referenced by both outer lists 2