fleche.storage.sql

Attributes

logger

Base

SQLITE_FOREIGN_KEYS_ON

SQLITE_WAL_MODE

Classes

Sql

SQLAlchemy-backed CallStorage with JSON metadata and DB-backed expand().

Functions

_coerce_sqlite_url(→ str)

Normalise the user-facing url= argument.

_configure_sqlite_pragmas(→ None)

Module Contents

fleche.storage.sql.logger[source]
fleche.storage.sql.Base[source]
fleche.storage.sql._coerce_sqlite_url(path_or_url: str | None) str[source]

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.

fleche.storage.sql.SQLITE_FOREIGN_KEYS_ON = 'PRAGMA foreign_keys=ON'[source]
fleche.storage.sql.SQLITE_WAL_MODE = 'PRAGMA journal_mode=WAL'[source]
fleche.storage.sql._configure_sqlite_pragmas(engine) None[source]
class fleche.storage.sql.Sql[source]

Bases: fleche.storage.thread_safe.PerKeyLockMixin, fleche.storage.base.CallStorage

SQLAlchemy-backed CallStorage with JSON metadata and DB-backed expand().

url: str | None = None[source]
echo: bool = False[source]
engine: Any[source]
session: Any[source]
_local: threading.local[source]
__post_init__() None[source]
__reduce__()[source]
_session_context()[source]
_operation_context(key, *, intent: fleche.storage.base.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
_persist_call(call: fleche.call.DigestedCall, key: fleche.digest.Digest) fleche.digest.Digest[source]
_fetch_call(key: fleche.digest.Digest) fleche.call.DigestedCall[source]
_contains(key: fleche.digest.Digest) bool[source]
list() Iterable[fleche.digest.Digest][source]
expand(key: fleche.digest.Digest | str) fleche.digest.Digest[source]

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

_evict(key: fleche.digest.Digest) None[source]
save(call: fleche.call.DigestedCall) fleche.digest.Digest[source]
load(key: fleche.digest.Digest | str) fleche.call.DigestedCall[source]
_normalize_value(v: Any) str[source]

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)).

_build_call_conditions(template: fleche.call.QueryCall) List[Any][source]
_apply_argument_filters(stmt: Any, arguments: dict[str, Any] | None) Any[source]
_apply_metadata_filters(stmt: Any, meta_specs: dict[str, dict[str, Any]] | None) Any[source]
query(template: fleche.call.QueryCall) Iterable[fleche.call.DigestedCall][source]

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.

Parameters:

template – A Call used as a template. None-valued fields are wildcards.

Yields:

Call – Matching calls including their decoded metadata.