fleche.storage.sql ================== .. py:module:: fleche.storage.sql Attributes ---------- .. autoapisummary:: fleche.storage.sql.logger fleche.storage.sql.Base fleche.storage.sql.SQLITE_FOREIGN_KEYS_ON fleche.storage.sql.SQLITE_WAL_MODE Classes ------- .. autoapisummary:: fleche.storage.sql.Sql Functions --------- .. autoapisummary:: fleche.storage.sql._coerce_sqlite_url fleche.storage.sql._configure_sqlite_pragmas Module Contents --------------- .. py:data:: logger .. py:data:: Base .. py:function:: _coerce_sqlite_url(path_or_url: str | None) -> str Normalise the user-facing ``url=`` argument. Accepts a filesystem path (treated as sqlite), a ``sqlite:`` URL (passed through, parent dir auto-created), or any other SQLAlchemy URL such as ``postgresql://`` / ``mysql+pymysql://`` (passed through verbatim). Only sqlite paths get the parent-dir-create convenience. Leading ``~`` is expanded to the home directory in both bare paths and ``sqlite:///`` URLs. .. py:data:: SQLITE_FOREIGN_KEYS_ON :value: 'PRAGMA foreign_keys=ON' .. py:data:: SQLITE_WAL_MODE :value: 'PRAGMA journal_mode=WAL' .. py:function:: _configure_sqlite_pragmas(engine) -> None .. py:class:: Sql Bases: :py:obj:`fleche.storage.thread_safe.PerKeyLockMixin`, :py:obj:`fleche.storage.base.CallStorage` SQLAlchemy-backed CallStorage with JSON metadata and DB-backed expand(). .. py:attribute:: url :type: str | None :value: None .. py:attribute:: echo :type: bool :value: False .. py:attribute:: engine :type: Any .. py:attribute:: session :type: Any .. py:attribute:: _local :type: threading.local .. py:method:: __post_init__() -> None .. py:method:: __reduce__() .. py:method:: _session_context() .. py:method:: _operation_context(key) 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:: put(call: fleche.call.DigestedCall, key: fleche.digest.Digest) -> fleche.digest.Digest .. py:method:: get(key: fleche.digest.Digest) -> fleche.call.DigestedCall .. py:method:: _contains(key: fleche.digest.Digest) -> bool .. py:method:: list() -> Iterable[fleche.digest.Digest] .. py:method:: expand(key: fleche.digest.Digest | str) -> fleche.digest.Digest Expands a short-hand digest to the full length one. .. py:method:: _evict(key: fleche.digest.Digest) -> None .. py:method:: save(call: fleche.call.DigestedCall) -> fleche.digest.Digest .. py:method:: load(key: fleche.digest.Digest | str) -> fleche.call.DigestedCall .. py:method:: _normalize_value(v: Any) -> str Return the stored form used in SQL for argument/result matching. We must match the generic CallStorage.query semantics which compare digest(template_value) == digest(stored_call_value). In this backend, stored argument/result values are hex-digest strings, and digest(Digest(x)) == x. Therefore we should always compare Arg.value/CallModel.result to str(digest(template_value)). .. py:method:: _build_call_conditions(template: fleche.call.QueryCall) -> List[Any] .. py:method:: _apply_argument_filters(stmt: Any, arguments: dict[str, Any] | None) -> Any .. py:method:: _apply_metadata_filters(stmt: Any, meta_specs: dict[str, dict[str, Any]] | None) -> Any .. py:method:: query(template: fleche.call.QueryCall) -> Iterable[fleche.call.DigestedCall] Find cached calls matching a template using SQL-side filtering. Semantics match CallStorage.query: - Fields set to None are wildcards. - Arguments and result are compared by digest(template_value) == digest(stored_value). - Metadata can be filtered by providing template.metadata as a mapping of metadata name -> dict of key/value filters. An empty dict for a given name means "presence of that metadata name". Filters with simple types (str, bool, int, float) are pushed down to SQL via JSON-extract expressions; other types (e.g., lists) or None values fall back to client-side checks after loading. This method builds a SELECT over calls, joining the arguments table and metadata table as needed to reduce candidate rows, then loads the resulting calls and performs any remaining client-side validation. :param template: A Call used as a template. None-valued fields are wildcards. :Yields: *Call* -- Matching calls including their decoded metadata.