Skip to content

Store

persista.store

Contain stores.

persista.store.OnConflict module-attribute

OnConflict = Literal['raise', 'skip', 'overwrite', 'merge']

Strategy for handling keys that already exist in the store.

Used by :meth:BaseStore.set, :meth:BaseStore.set_many, and :meth:BaseStore.set_batches to control what happens when a key being written already has a value in the store.

persista.store.AsyncBasePostgresStore

Bases: AsyncBaseStore, MultilineDisplayMixin

Define a base class for Postgres-backed asynchronous key-value stores.

Mirrors :class:~persista.store.postgres.BasePostgresStore, but runs every query through :class:psycopg.AsyncConnection instead of the blocking :class:psycopg.Connection, so it can be awaited from an async application without stalling the event loop on network I/O.

A single table (named by the table argument, "store" by default) backs every value; the primary key column is named by :attr:_key_column. :meth:get, :meth:get_many, :meth:filter, and :meth:iter_batches all query the full row and hand it to :meth:_row_to_value to turn it back into a value dict, which is what lets subclasses differ in how a value is laid out across columns (a single JSONB column vs. typed columns plus a JSONB overflow column) without duplicating any of the surrounding query logic.

Subclasses only need to implement :meth:_create_table_sql, :meth:_row_to_value, :meth:_build_filter_condition, and :meth:_set_many (see :class:AsyncPostgresStore for a JSONB-only layout and :class:AsyncTypedPostgresStore for an optionally typed one).

The table schema is created lazily, on the first query, rather than in the constructor -- __init__ cannot await, so there's no synchronous point at which the schema could be created eagerly.

Parameters:

Name Type Description Default
conninfo str

The connection string/DSN passed to psycopg.AsyncConnection.connect (e.g. "postgresql://user:pass@localhost/dbname").

required
table str

The name of the table backing this store. Must be a valid SQL identifier (letters, digits, underscores, not starting with a digit).

'store'
**kwargs Any

Additional keyword arguments to pass to psycopg.AsyncConnection.connect.

{}

persista.store.AsyncBaseRedisStore

Bases: AsyncBaseStore, MultilineDisplayMixin

Define a base class for asynchronous Redis-backed key-value stores.

Mirrors :class:~persista.store.redis.BaseRedisStore, but runs every command through :mod:redis.asyncio instead of the blocking :mod:redis client, so it can be awaited from an async application without stalling the event loop on network I/O. A Redis set at __keys__ tracks the keys currently in the store, which allows :meth:count, :meth:keys, and :meth:contains_many to avoid scanning the whole keyspace. Unlike the SQL-backed stores, Redis has no query language for matching on the content of a value, so :meth:filter is implemented client-side by scanning every value in the store.

Subclasses only need to implement :meth:_encode and :meth:_decode, which control how a value is serialized to and from what is stored in Redis (see :class:AsyncRedisStore for a JSON encoding and :class:AsyncPickleRedisStore for a pickle encoding).

Parameters:

Name Type Description Default
url str

The Redis connection URL passed to redis.asyncio.Redis.from_url (e.g. "redis://localhost:6379/0").

'redis://localhost:6379/0'
**kwargs Any

Additional keyword arguments to pass to redis.asyncio.Redis.from_url.

{}

persista.store.AsyncBaseSQLiteStore

Bases: AsyncBaseStore, MultilineDisplayMixin

Define a base class for SQLite-backed asynchronous key-value stores.

Mirrors :class:~persista.store.sqlite.BaseSQLiteStore, but runs every query through :mod:aiosqlite instead of the blocking :mod:sqlite3 driver, so it can be awaited from an async application without stalling the event loop on disk I/O.

A single store table backs every value; the primary key column is named by :attr:_key_column. :meth:get, :meth:get_many, :meth:filter, and :meth:iter_batches all query the full row and hand it to :meth:_row_to_value to turn it back into a value dict, which is what lets subclasses differ in how a value is laid out across columns without duplicating any of the surrounding query logic.

Subclasses only need to implement :meth:_create_table_sql, :meth:_row_to_value, :meth:_build_filter_condition, and :meth:_set_many (see :class:AsyncSQLiteStore for a JSON-only layout).

The table schema is created lazily, on the first query, rather than in the constructor -- __init__ cannot await, and :func:aiosqlite.connect itself connects lazily on first use, so there's no synchronous point at which the schema could be created eagerly.

The constructor mirrors :func:aiosqlite.connect: the first positional argument is the database argument (a path, ":memory:", or a file: URI when uri=True is passed), and any additional keyword arguments are forwarded as-is. Use :meth:from_path for a more convenient constructor that builds the appropriate URI for you, including read-only access.

Requires the optional aiosqlite dependency (pip install aiosqlite).

Parameters:

Name Type Description Default
database Path | str

The database argument passed to aiosqlite.connect (path, ":memory:", or file: URI).

required
**kwargs Any

Additional keyword arguments to pass to aiosqlite.connect (e.g. uri=True, timeout).

{}

persista.store.AsyncBaseSQLiteStore.from_path classmethod

from_path(
    path: Path | str,
    *,
    read_only: bool = False,
    **kwargs: Any
) -> Self

Construct a store from a file path.

Builds the appropriate file: URI for aiosqlite.connect, including read-only access, so callers don't need to construct SQLite URIs themselves.

Parameters:

Name Type Description Default
path Path | str

Path to the SQLite file, or ":memory:" for an in-memory database (useful for testing).

required
read_only bool

If True, open the database in read-only mode. The database file must already exist.

False
**kwargs Any

Additional keyword arguments to pass to the constructor (and, from there, to aiosqlite.connect).

{}

Returns:

Type Description
Self

A new store connected to path.

persista.store.AsyncBaseSQLiteStore.get_columns_info async

get_columns_info() -> dict[str, str]

Return the column names and types of the store's table.

Returns:

Type Description
dict[str, str]

A mapping of column name to SQLite declared type.

persista.store.AsyncBaseSQLiteStore.show_columns_info async

show_columns_info() -> None

Print the store's table column names and types to stdout.

This is a convenience wrapper around :meth:get_columns_info for interactive/debugging use. For programmatic access, use :meth:get_columns_info instead.

persista.store.AsyncBaseStore

Bases: ABC

Abstract base class for asynchronous key-value stores.

Mirrors :class:BaseStore, but every method that touches the underlying store is a coroutine (or an async generator), for implementations backed by an async driver (e.g. an async DB client).

Implementations are expected to support use as an async context manager (async with SomeStore(...) as store: ...), which calls :meth:close automatically on exit.

persista.store.AsyncBaseStore.closed abstractmethod property

closed: bool

Indicate whether the store is closed.

Returns:

Type Description
bool

True if the store has been closed, False if it is

bool

open and ready to use.

persista.store.AsyncBaseStore.clear abstractmethod async

clear() -> None

Remove every key-value pair from the store.

This is equivalent to resetting the store to empty, without closing it.

persista.store.AsyncBaseStore.close abstractmethod async

close() -> None

Close the store and release any underlying resources (e.g. database connections, file handles).

Implementations should make repeated calls to close() safe (i.e. idempotent), since :meth:__aexit__ calls it unconditionally and callers may also close a store manually before using it as a context manager.

persista.store.AsyncBaseStore.contains_many abstractmethod async

contains_many(
    keys: list[str],
) -> tuple[list[str], list[str]]

Check which keys exist in the store.

Parameters:

Name Type Description Default
keys list[str]

The keys to check.

required

Returns:

Type Description
list[str]

A tuple of two lists: (found, missing) where found

list[str]

contains the keys that exist in the store and missing

tuple[list[str], list[str]]

contains the keys that do not.

persista.store.AsyncBaseStore.count abstractmethod async

count() -> int

Return the total number of key-value pairs in the store.

Returns:

Type Description
int

The number of key-value pairs currently stored.

persista.store.AsyncBaseStore.delete abstractmethod async

delete(key: str) -> None

Delete a value by its key.

Keys that do not exist should be silently ignored.

Parameters:

Name Type Description Default
key str

The key of the value to delete.

required

persista.store.AsyncBaseStore.delete_many abstractmethod async

delete_many(keys: list[str]) -> None

Delete multiple values by their keys.

Keys that do not exist should be silently ignored.

Parameters:

Name Type Description Default
keys list[str]

The keys of the values to delete.

required

persista.store.AsyncBaseStore.filter abstractmethod async

filter(**field_filters: Any) -> list[dict[str, Any]]

Retrieve values whose content matches all provided field filters.

All filters should be combined with AND. Each keyword argument matches the corresponding key in the stored value exactly.

Parameters:

Name Type Description Default
**field_filters Any

Key-value pairs where each key is a field name within a stored value and the value is the exact value to match. Calling with no arguments should return every value in the store.

{}

Returns:

Type Description
list[dict[str, Any]]

A list of matching values.

persista.store.AsyncBaseStore.get abstractmethod async

get(key: str) -> dict[str, Any] | None

Retrieve a single value by its key.

Parameters:

Name Type Description Default
key str

The key to look up.

required

Returns:

Type Description
dict[str, Any] | None

The value associated with key, or None if the

dict[str, Any] | None

key is not found.

persista.store.AsyncBaseStore.get_many abstractmethod async

get_many(keys: list[str]) -> list[dict[str, Any] | None]

Retrieve multiple values by their keys.

Parameters:

Name Type Description Default
keys list[str]

The keys to look up.

required

Returns:

Type Description
list[dict[str, Any] | None]

A list the same length as keys, with the

list[dict[str, Any] | None]

corresponding value for each key that exists, or

list[dict[str, Any] | None]

None for keys that are not found.

persista.store.AsyncBaseStore.iter_batches abstractmethod

iter_batches(
    batch_size: int = 32,
) -> AsyncGenerator[dict[str, dict[str, Any]], None]

Yield key-value pairs in batches, avoiding loading the whole store into memory at once.

This is the scalable equivalent of :meth:values: instead of materializing every value as a single mapping, it streams them from the underlying store in chunks of batch_size.

Parameters:

Name Type Description Default
batch_size int

The maximum number of pairs to yield per batch. Must be a positive integer.

32

Yields:

Type Description
AsyncGenerator[dict[str, dict[str, Any]], None]

Dicts mapping key to value, each with at most

AsyncGenerator[dict[str, dict[str, Any]], None]

batch_size entries, in the same order as

AsyncGenerator[dict[str, dict[str, Any]], None]

meth:values. The last batch may contain fewer than

AsyncGenerator[dict[str, dict[str, Any]], None]

batch_size entries.

persista.store.AsyncBaseStore.keys abstractmethod

keys() -> AsyncIterator[str]

Iterate over all keys in the store.

Yields:

Type Description
AsyncIterator[str]

Every key currently in the store.

persista.store.AsyncBaseStore.set abstractmethod async

set(
    key: str,
    value: dict[str, Any],
    on_conflict: OnConflict = "overwrite",
) -> None

Add a single key-value pair to the store.

Parameters:

Name Type Description Default
key str

The key to set.

required
value dict[str, Any]

The value to associate with key.

required
on_conflict OnConflict

The strategy to use if key already exists in the store:

  • "raise": raise a :class:KeyError and leave the existing value unchanged.
  • "skip": leave the existing value unchanged.
  • "overwrite": replace the existing value with value.
  • "merge": shallow-merge value into the existing value, with fields from value taking precedence on overlapping keys.
'overwrite'

Raises:

Type Description
KeyError

If on_conflict is "raise" and key already exists.

persista.store.AsyncBaseStore.set_batches async

set_batches(
    items: Iterable[tuple[str, dict[str, Any]]],
    batch_size: int = 32,
    on_conflict: OnConflict = "overwrite",
) -> None

Add key-value pairs from an iterable, writing them to the store in mini-batches.

This is the streaming equivalent of :meth:set_many: instead of requiring every key-value pair to be materialized into a single mapping upfront, it consumes items lazily and writes at most batch_size pairs at a time. This keeps memory usage bounded when items comes from a generator over a large or unbounded source.

Parameters:

Name Type Description Default
items Iterable[tuple[str, dict[str, Any]]]

An iterable of (key, value) pairs to add.

required
batch_size int

The maximum number of pairs to write to the store per underlying :meth:set_many call. Must be a positive integer.

32
on_conflict OnConflict

The strategy to use for keys that already exist in the store. See :meth:set for the meaning of each option. Applied independently per batch, so with "raise" a conflict is only detected once the offending batch is written, not upfront.

'overwrite'

Raises:

Type Description
KeyError

If on_conflict is "raise" and any key already exists.

persista.store.AsyncBaseStore.set_many abstractmethod async

set_many(
    items: Mapping[str, dict[str, Any]],
    on_conflict: OnConflict = "overwrite",
) -> None

Add multiple key-value pairs to the store.

Parameters:

Name Type Description Default
items Mapping[str, dict[str, Any]]

The values to add, keyed by their unique key.

required
on_conflict OnConflict

The strategy to use for keys in items that already exist in the store. See :meth:set for the meaning of each option.

'overwrite'

Raises:

Type Description
KeyError

If on_conflict is "raise" and any key in items already exists.

persista.store.AsyncBaseStore.values async

values(
    batch_size: int = 32,
) -> AsyncIterator[dict[str, Any]]

Iterate over all values without loading them all into memory at once.

Parameters:

Name Type Description Default
batch_size int

The batch size used internally when pulling values from the underlying store. Does not affect the granularity of what is yielded — values are always yielded one at a time.

32

Yields:

Type Description
AsyncIterator[dict[str, Any]]

One value at a time, in the same order as

AsyncIterator[dict[str, Any]]

meth:iter_batches.

persista.store.AsyncInMemoryStore

Bases: AsyncBaseStore, InlineDisplayMixin

An :class:~persista.store.AsyncBaseStore implementation backed by a plain dict.

Values are held entirely in process memory -- nothing is persisted to disk. This is primarily useful for testing, small-scale exploration, or async pipelines that don't need durability.

Values are deep-copied on both write and read so that mutating a value returned by this store (or a value passed into :meth:set / :meth:set_many) never affects the store's internal state. This trades some performance for isolation; for very large values or hot loops, consider a store that doesn't copy on every access.

Example
>>> import asyncio
>>> from persista.store import AsyncInMemoryStore
>>> async def main():
...     store = AsyncInMemoryStore()
...     await store.set("1", {"text": "hello"})
...     print(await store.count())
...     print(await store.get("1"))
...
>>> asyncio.run(main())
1
{'text': 'hello'}

persista.store.AsyncPickleRedisStore

Bases: AsyncBaseRedisStore

An asynchronous Redis-backed key-value store that serializes values with pickle instead of JSON.

Unlike :class:AsyncRedisStore, this store can persist arbitrary Python objects within a value's fields (tuples, sets, custom classes, etc.), not just JSON-compatible types. The tradeoff is that values are opaque binary blobs from outside Python (not human-readable, not inspectable from non-Python Redis clients), and, since :func:pickle.loads can execute arbitrary code, this store must never be pointed at a Redis instance that isn't fully trusted. Mirrors :class:~persista.store.redis.PickleRedisStore, but every method is a coroutine, backed by :mod:redis.asyncio instead of the blocking :mod:redis client.

Parameters:

Name Type Description Default
url str

The Redis connection URL passed to redis.asyncio.Redis.from_url (e.g. "redis://localhost:6379/0").

'redis://localhost:6379/0'
**kwargs Any

Additional keyword arguments to pass to redis.asyncio.Redis.from_url.

{}
Example
>>> import asyncio
>>> from persista.store import AsyncPickleRedisStore
>>> async def main():
...     store = AsyncPickleRedisStore("redis://localhost:6379/0")
...     await store.set("1", {"title": "Intro to Python", "tags": {"python", "intro"}})
...     print(await store.get("1"))
...     await store.close()
...
>>> asyncio.run(main())  # doctest: +SKIP
{'title': 'Intro to Python', 'tags': {'python', 'intro'}}

persista.store.AsyncPostgresStore

Bases: AsyncBasePostgresStore

An asynchronous Postgres-backed key-value store.

Persists values to a Postgres database and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSONB column, which provides flexibility for arbitrary value fields without requiring a fixed schema. Mirrors :class:~persista.store.postgres.PostgresStore, but every method is a coroutine, backed by :class:psycopg.AsyncConnection instead of the blocking :class:psycopg.Connection.

Parameters:

Name Type Description Default
conninfo str

The connection string/DSN passed to psycopg.AsyncConnection.connect (e.g. "postgresql://user:pass@localhost/dbname").

required
table str

The name of the table backing this store.

'store'
**kwargs Any

Additional keyword arguments to pass to psycopg.AsyncConnection.connect.

{}
Example
>>> import asyncio
>>> from persista.store import AsyncPostgresStore
>>> async def main():
...     store = AsyncPostgresStore("postgresql://user:pass@localhost/dbname")
...     await store.set_many(
...         {
...             "1": {"title": "Intro to Python", "author": "Alice"},
...             "2": {"title": "Advanced Python", "author": "Alice"},
...         }
...     )
...     result = await store.filter(author="Alice")
...     print(len(result))
...     await store.close()
...
>>> asyncio.run(main())  # doctest: +SKIP
2

persista.store.AsyncRedisStore

Bases: AsyncBaseRedisStore

An asynchronous Redis-backed key-value store.

Persists values to Redis and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSON string, which provides flexibility for arbitrary value fields without requiring a fixed schema, is human-readable directly from Redis, and can be read by any Redis client regardless of language. This means only JSON-compatible value fields (str, int, float, bool, None, list, dict) are supported; use :class:AsyncPickleRedisStore if you need to persist arbitrary Python objects. Mirrors :class:~persista.store.redis.RedisStore, but every method is a coroutine, backed by :mod:redis.asyncio instead of the blocking :mod:redis client.

Parameters:

Name Type Description Default
url str

The Redis connection URL passed to redis.asyncio.Redis.from_url (e.g. "redis://localhost:6379/0").

'redis://localhost:6379/0'
**kwargs Any

Additional keyword arguments to pass to redis.asyncio.Redis.from_url.

{}
Example
>>> import asyncio
>>> from persista.store import AsyncRedisStore
>>> async def main():
...     store = AsyncRedisStore("redis://localhost:6379/0")
...     await store.set_many(
...         {
...             "1": {
...                 "title": "Intro to Python",
...                 "author": "Alice",
...                 "category": "Programming",
...             },
...             "2": {
...                 "title": "Advanced Python",
...                 "author": "Alice",
...                 "category": "Programming",
...             },
...             "3": {"title": "History of Rome", "author": "Bob", "category": "History"},
...         }
...     )
...     result = await store.filter(author="Alice")
...     print(len(result))
...     await store.close()
...
>>> asyncio.run(main())  # doctest: +SKIP
2

persista.store.AsyncSQLiteStore

Bases: AsyncBaseSQLiteStore

An asynchronous SQLite-backed key-value store.

Persists values to a SQLite database and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSON column (using SQLite's built-in json1 functions), which provides flexibility for arbitrary value fields without requiring a fixed schema. Mirrors :class:~persista.store.sqlite.SQLiteStore, but every method is a coroutine, backed by :mod:aiosqlite instead of the blocking :mod:sqlite3 driver.

The constructor mirrors :func:aiosqlite.connect directly. For the common case of opening a file by path (optionally read-only), use :meth:~AsyncBaseSQLiteStore.from_path instead.

Requires the optional aiosqlite dependency (pip install aiosqlite).

Parameters:

Name Type Description Default
database Path | str

The database argument passed to aiosqlite.connect (path, ":memory:", or file: URI).

':memory:'
**kwargs Any

Additional keyword arguments to pass to aiosqlite.connect.

{}
Example
>>> import asyncio
>>> from persista.store import AsyncSQLiteStore
>>> async def main():
...     store = AsyncSQLiteStore(":memory:")
...     await store.set_many(
...         {
...             "1": {"title": "Intro to Python", "author": "Alice"},
...             "2": {"title": "Advanced Python", "author": "Alice"},
...             "3": {"title": "History of Rome", "author": "Bob"},
...         }
...     )
...     result = await store.filter(author="Alice")
...     print(len(result))
...     await store.close()
...
>>> asyncio.run(main())
2

persista.store.AsyncTypedPostgresStore

Bases: AsyncBasePostgresStore

An asynchronous Postgres-backed key-value store with an optional typed value schema.

Persists values to a Postgres database and supports adding, retrieving, and filtering by value fields. An optional value_schema maps known value field names to Postgres types. Known fields are stored as typed columns for fast, index-friendly queries. Any value fields not in the schema are stored in an extra JSONB overflow column, so nothing is lost. Mirrors :class:~persista.store.postgres.TypedPostgresStore, but every method is a coroutine, backed by :class:psycopg.AsyncConnection instead of the blocking :class:psycopg.Connection.

Parameters:

Name Type Description Default
conninfo str

The connection string/DSN passed to psycopg.AsyncConnection.connect.

required
table str

The name of the table backing this store.

'store'
value_schema dict[str, str] | None

Optional mapping of value field names to Postgres type strings (e.g. {"author": "TEXT", "year": "INTEGER"}). Fields in the schema get native typed columns; all other value fields go into the extra JSONB overflow column. Defaults to None, which stores every value field as JSONB only.

None
**kwargs Any

Additional keyword arguments to pass to psycopg.AsyncConnection.connect.

{}
Example
>>> import asyncio
>>> from persista.store import AsyncTypedPostgresStore
>>> async def main():
...     schema = {"author": "TEXT", "year": "INTEGER"}
...     store = AsyncTypedPostgresStore(
...         "postgresql://user:pass@localhost/dbname", value_schema=schema
...     )
...     await store.set_many(
...         {
...             "1": {"title": "Intro to Python", "author": "Alice", "year": 2022},
...             "2": {"title": "History of Rome", "author": "Bob", "year": 2021},
...         }
...     )
...     result = await store.filter(author="Alice")
...     print(len(result))
...     await store.close()
...
>>> asyncio.run(main())  # doctest: +SKIP
1

persista.store.AsyncTypedSQLiteStore

Bases: AsyncBaseSQLiteStore

An asynchronous SQLite-backed key-value store with an optional typed value schema.

Persists values to a SQLite database and supports adding, retrieving, and filtering by value fields. An optional value_schema maps known value field names to SQLite types. Known fields are stored as typed columns for fast, index-friendly queries. Any value fields not in the schema are stored in an extra JSON overflow column, so nothing is lost. Mirrors :class:~persista.store.sqlite.TypedSQLiteStore, but every method is a coroutine, backed by :mod:aiosqlite instead of the blocking :mod:sqlite3 driver.

The constructor mirrors :func:aiosqlite.connect directly (plus the value_schema argument). For the common case of opening a file by path (optionally read-only), use :meth:~AsyncBaseSQLiteStore.from_path instead.

Requires the optional aiosqlite dependency (pip install aiosqlite).

Parameters:

Name Type Description Default
database Path | str

The database argument passed to aiosqlite.connect (path, ":memory:", or file: URI).

':memory:'
value_schema dict[str, str] | None

Optional mapping of value field names to SQLite type strings (e.g. {"author": "TEXT", "year": "INTEGER"}). Fields in the schema get native typed columns; all other value fields go into the extra JSON overflow column. Defaults to None, which stores every value field as JSON only.

None
**kwargs Any

Additional keyword arguments to pass to aiosqlite.connect.

{}
Example
>>> import asyncio
>>> from persista.store import AsyncTypedSQLiteStore
>>> async def main():
...     schema = {"author": "TEXT", "year": "INTEGER", "category": "TEXT"}
...     store = AsyncTypedSQLiteStore(":memory:", value_schema=schema)
...     await store.set_many(
...         {
...             "1": {"title": "Intro to Python", "author": "Alice"},
...             "2": {"title": "Advanced Python", "author": "Alice"},
...             "3": {"title": "History of Rome", "author": "Bob"},
...         }
...     )
...     result = await store.filter(author="Alice")
...     print(len(result))
...     await store.close()
...
>>> asyncio.run(main())
2

persista.store.BaseDuckDBStore

Bases: BaseStore, MultilineDisplayMixin

Define a base class for DuckDB-backed key-value stores.

Subclasses only need to implement :meth:_select_columns, :meth:_row_to_kv, :meth:_set_many, and :meth:filter, which control how a value is laid out across the table's columns (see :class:DuckDBStore for a single JSON column layout and :class:~persista.store.duckdb_typed.TypedDuckDBStore for a typed column layout with a JSON overflow column).

Parameters:

Name Type Description Default
path Path | str

Path to the DuckDB file, or ":memory:" for an in-memory database (useful for testing).

required
**kwargs Any

Additional keyword arguments to pass to duckdb.connect.

{}

persista.store.BaseDuckDBStore.get_columns_info

get_columns_info() -> dict[str, str]

Return the column names and types of the store's table.

Returns:

Type Description
dict[str, str]

A mapping of column name to DuckDB type name.

persista.store.BaseDuckDBStore.show_columns_info

show_columns_info() -> None

Print the store's table column names and types to stdout.

This is a convenience wrapper around :meth:get_columns_info for interactive/debugging use. For programmatic access, use :meth:get_columns_info instead.

persista.store.BaseLmdbStore

Bases: BaseStore, MultilineDisplayMixin

Define a base class for LMDB-backed key-value stores.

LMDB is an embedded, memory-mapped key-value store backed by a single-directory environment on disk, so unlike Redis this store needs no separate server process and no explicit tracking of keys: the environment's own B+tree already provides ordered iteration, membership checks, and a cheap entry count. :meth:filter is still implemented client-side by scanning every value in the store, since LMDB has no query language for matching on the content of a value.

Subclasses only need to implement :meth:_encode and :meth:_decode, which control how a value is serialized to and from what is stored in LMDB (see :class:LmdbStore for a JSON encoding and :class:PickleLmdbStore for a pickle encoding).

Parameters:

Name Type Description Default
path str | PathLike[str]

The directory where the LMDB environment is stored. Created automatically if it does not already exist.

required
map_size int

The maximum size in bytes of the memory map, i.e. the upper bound on the total size of the environment (keys and values combined). Passed to lmdb.open.

_DEFAULT_MAP_SIZE
**kwargs Any

Additional keyword arguments to pass to lmdb.open.

{}

persista.store.BasePostgresStore

Bases: BaseStore, MultilineDisplayMixin

Define a base class for Postgres-backed key-value stores.

A single table (named by the table argument, "store" by default) backs every value; the primary key column is named by :attr:_key_column. :meth:get, :meth:get_many, :meth:filter, and :meth:iter_batches all query the full row and hand it to :meth:_row_to_value to turn it back into a value dict, which is what lets subclasses differ in how a value is laid out across columns (a single JSONB column vs. typed columns plus a JSONB overflow column) without duplicating any of the surrounding query logic. This mirrors :class:~persista.store.sqlite.BaseSQLiteStore.

Subclasses only need to implement :meth:_create_table_sql, :meth:_row_to_value, :meth:_build_filter_condition, and :meth:_set_many (see :class:PostgresStore for a JSONB-only layout and :class:~persista.store.postgres.TypedPostgresStore for an optionally typed one).

Parameters:

Name Type Description Default
conninfo str

The connection string/DSN passed to psycopg.connect (e.g. "postgresql://user:pass@localhost/dbname").

required
table str

The name of the table backing this store. Must be a valid SQL identifier (letters, digits, underscores, not starting with a digit).

'store'
**kwargs Any

Additional keyword arguments to pass to psycopg.connect.

{}

persista.store.BaseRedisStore

Bases: BaseStore, MultilineDisplayMixin

Define a base class for Redis-backed key-value stores.

A Redis set at __keys__ tracks the keys currently in the store, which allows :meth:count, :meth:keys, and :meth:contains_many to avoid scanning the whole keyspace. Unlike the SQL-backed stores, Redis has no query language for matching on the content of a value, so :meth:filter is implemented client-side by scanning every value in the store.

Subclasses only need to implement :meth:_encode and :meth:_decode, which control how a value is serialized to and from what is stored in Redis (see :class:RedisStore for a JSON encoding and :class:~persista.store.redis_pickle.PickleRedisStore for a pickle encoding).

Parameters:

Name Type Description Default
url str

The Redis connection URL passed to redis.Redis.from_url (e.g. "redis://localhost:6379/0").

'redis://localhost:6379/0'
**kwargs Any

Additional keyword arguments to pass to redis.Redis.from_url.

{}

persista.store.BaseSQLiteStore

Bases: BaseStore, MultilineDisplayMixin

Define a base class for SQLite-backed key-value stores.

A single store table backs every value; the primary key column is named by :attr:_key_column. :meth:get, :meth:get_many, :meth:filter, and :meth:iter_batches all query the full row and hand it to :meth:_row_to_value to turn it back into a value dict, which is what lets subclasses differ in how a value is laid out across columns (a single JSON column vs. typed columns plus a JSON overflow column) without duplicating any of the surrounding query logic.

Subclasses only need to implement :meth:_create_table_sql, :meth:_row_to_value, :meth:_build_filter_condition, and :meth:_set_many (see :class:SQLiteStore for a JSON-only layout and :class:~persista.store.sqlite_typed.TypedSQLiteStore for an optionally typed one).

The constructor mirrors :func:sqlite3.connect: the first positional argument is the database argument accepted by sqlite3.connect (a path, ":memory:", or a file: URI when uri=True is passed), and any additional keyword arguments are forwarded as-is. Use :meth:from_path for a more convenient constructor that builds the appropriate URI for you, including read-only access.

Parameters:

Name Type Description Default
database Path | str

The database argument passed to sqlite3.connect (path, ":memory:", or file: URI).

required
**kwargs Any

Additional keyword arguments to pass to sqlite3.connect (e.g. uri=True, timeout, check_same_thread).

{}

persista.store.BaseSQLiteStore.from_path classmethod

from_path(
    path: Path | str,
    *,
    read_only: bool = False,
    **kwargs: Any
) -> Self

Construct a store from a file path.

Builds the appropriate file: URI for sqlite3.connect, including read-only access, so callers don't need to construct SQLite URIs themselves.

Parameters:

Name Type Description Default
path Path | str

Path to the SQLite file, or ":memory:" for an in-memory database (useful for testing).

required
read_only bool

If True, open the database in read-only mode. The database file must already exist.

False
**kwargs Any

Additional keyword arguments to pass to the constructor (and, from there, to sqlite3.connect).

{}

Returns:

Type Description
Self

A new store connected to path.

persista.store.BaseSQLiteStore.get_columns_info

get_columns_info() -> dict[str, str]

Return the column names and types of the store's table.

Returns:

Type Description
dict[str, str]

A mapping of column name to SQLite declared type.

persista.store.BaseSQLiteStore.show_columns_info

show_columns_info() -> None

Print the store's table column names and types to stdout.

This is a convenience wrapper around :meth:get_columns_info for interactive/debugging use. For programmatic access, use :meth:get_columns_info instead.

persista.store.BaseStore

Bases: ABC

Abstract base class for key-value stores.

Defines the common interface that all key-value store implementations must provide. Values are stored as dicts, which allows :meth:filter to match on the content of a value.

To implement a custom store, subclass :class:BaseStore and implement all abstract methods.

Implementations are expected to support use as a context manager (with SomeStore(...) as store: ...), which calls :meth:close automatically on exit.

persista.store.BaseStore.closed abstractmethod property

closed: bool

Indicate whether the store is closed.

Returns:

Type Description
bool

True if the store has been closed, False if it is

bool

open and ready to use.

persista.store.BaseStore.clear abstractmethod

clear() -> None

Remove every key-value pair from the store.

This is equivalent to resetting the store to empty, without closing it.

persista.store.BaseStore.close abstractmethod

close() -> None

Close the store and release any underlying resources (e.g. database connections, file handles).

Implementations should make repeated calls to close() safe (i.e. idempotent), since :meth:__exit__ calls it unconditionally and callers may also close a store manually before using it as a context manager.

persista.store.BaseStore.contains_many abstractmethod

contains_many(
    keys: list[str],
) -> tuple[list[str], list[str]]

Check which keys exist in the store.

Parameters:

Name Type Description Default
keys list[str]

The keys to check.

required

Returns:

Type Description
list[str]

A tuple of two lists: (found, missing) where found

list[str]

contains the keys that exist in the store and missing

tuple[list[str], list[str]]

contains the keys that do not.

persista.store.BaseStore.count abstractmethod

count() -> int

Return the total number of key-value pairs in the store.

Returns:

Type Description
int

The number of key-value pairs currently stored.

persista.store.BaseStore.delete abstractmethod

delete(key: str) -> None

Delete a value by its key.

Keys that do not exist should be silently ignored.

Parameters:

Name Type Description Default
key str

The key of the value to delete.

required

persista.store.BaseStore.delete_many abstractmethod

delete_many(keys: list[str]) -> None

Delete multiple values by their keys.

Keys that do not exist should be silently ignored.

Parameters:

Name Type Description Default
keys list[str]

The keys of the values to delete.

required

persista.store.BaseStore.filter abstractmethod

filter(**field_filters: Any) -> list[dict[str, Any]]

Retrieve values whose content matches all provided field filters.

All filters should be combined with AND. Each keyword argument matches the corresponding key in the stored value exactly.

Parameters:

Name Type Description Default
**field_filters Any

Key-value pairs where each key is a field name within a stored value and the value is the exact value to match. Calling with no arguments should return every value in the store.

{}

Returns:

Type Description
list[dict[str, Any]]

A list of matching values.

persista.store.BaseStore.get abstractmethod

get(key: str) -> dict[str, Any] | None

Retrieve a single value by its key.

Parameters:

Name Type Description Default
key str

The key to look up.

required

Returns:

Type Description
dict[str, Any] | None

The value associated with key, or None if the

dict[str, Any] | None

key is not found.

persista.store.BaseStore.get_many abstractmethod

get_many(keys: list[str]) -> list[dict[str, Any] | None]

Retrieve multiple values by their keys.

Parameters:

Name Type Description Default
keys list[str]

The keys to look up.

required

Returns:

Type Description
list[dict[str, Any] | None]

A list the same length as keys, with the

list[dict[str, Any] | None]

corresponding value for each key that exists, or

list[dict[str, Any] | None]

None for keys that are not found.

persista.store.BaseStore.iter_batches abstractmethod

iter_batches(
    batch_size: int = 32,
) -> Generator[dict[str, dict[str, Any]], None, None]

Yield key-value pairs in batches, avoiding loading the whole store into memory at once.

This is the scalable equivalent of :meth:values: instead of materializing every value as a single mapping, it streams them from the underlying store in chunks of batch_size.

Parameters:

Name Type Description Default
batch_size int

The maximum number of pairs to yield per batch. Must be a positive integer.

32

Yields:

Type Description
dict[str, dict[str, Any]]

Dicts mapping key to value, each with at most

dict[str, dict[str, Any]]

batch_size entries, in the same order as

dict[str, dict[str, Any]]

meth:values. The last batch may contain fewer than

dict[str, dict[str, Any]]

batch_size entries.

persista.store.BaseStore.keys abstractmethod

keys() -> Iterator[str]

Iterate over all keys in the store.

Yields:

Type Description
str

Every key currently in the store.

persista.store.BaseStore.set abstractmethod

set(
    key: str,
    value: dict[str, Any],
    on_conflict: OnConflict = "overwrite",
) -> None

Add a single key-value pair to the store.

Parameters:

Name Type Description Default
key str

The key to set.

required
value dict[str, Any]

The value to associate with key.

required
on_conflict OnConflict

The strategy to use if key already exists in the store:

  • "raise": raise a :class:KeyError and leave the existing value unchanged.
  • "skip": leave the existing value unchanged.
  • "overwrite": replace the existing value with value.
  • "merge": shallow-merge value into the existing value, with fields from value taking precedence on overlapping keys.
'overwrite'

Raises:

Type Description
KeyError

If on_conflict is "raise" and key already exists.

persista.store.BaseStore.set_batches

set_batches(
    items: Iterable[tuple[str, dict[str, Any]]],
    batch_size: int = 32,
    on_conflict: OnConflict = "overwrite",
) -> None

Add key-value pairs from an iterable, writing them to the store in mini-batches.

This is the streaming equivalent of :meth:set_many: instead of requiring every key-value pair to be materialized into a single mapping upfront, it consumes items lazily and writes at most batch_size pairs at a time. This keeps memory usage bounded when items comes from a generator over a large or unbounded source.

Parameters:

Name Type Description Default
items Iterable[tuple[str, dict[str, Any]]]

An iterable of (key, value) pairs to add.

required
batch_size int

The maximum number of pairs to write to the store per underlying :meth:set_many call. Must be a positive integer.

32
on_conflict OnConflict

The strategy to use for keys that already exist in the store. See :meth:set for the meaning of each option. Applied independently per batch, so with "raise" a conflict is only detected once the offending batch is written, not upfront.

'overwrite'

Raises:

Type Description
KeyError

If on_conflict is "raise" and any key already exists.

persista.store.BaseStore.set_many abstractmethod

set_many(
    items: Mapping[str, dict[str, Any]],
    on_conflict: OnConflict = "overwrite",
) -> None

Add multiple key-value pairs to the store.

Parameters:

Name Type Description Default
items Mapping[str, dict[str, Any]]

The values to add, keyed by their unique key.

required
on_conflict OnConflict

The strategy to use for keys in items that already exist in the store. See :meth:set for the meaning of each option.

'overwrite'

Raises:

Type Description
KeyError

If on_conflict is "raise" and any key in items already exists.

persista.store.BaseStore.values

values(batch_size: int = 32) -> Iterator[dict[str, Any]]

Iterate over all values without loading them all into memory at once.

Parameters:

Name Type Description Default
batch_size int

The batch size used internally when pulling values from the underlying store. Does not affect the granularity of what is yielded — values are always yielded one at a time.

32

Yields:

Type Description
dict[str, Any]

One value at a time, in the same order as

dict[str, Any]

meth:iter_batches.

persista.store.DuckDBStore

Bases: BaseDuckDBStore

A DuckDB-backed key-value store.

Persists values to a DuckDB database and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSON column, which provides flexibility for arbitrary value fields without requiring a fixed schema.

Parameters:

Name Type Description Default
path Path | str

Path to the DuckDB file, or ":memory:" for an in-memory database (useful for testing).

':memory:'
**kwargs Any

Additional keyword arguments to pass to duckdb.connect.

{}
Example
>>> from persista.store import DuckDBStore
>>> store = DuckDBStore(":memory:")
>>> store.set_many(
...     {
...         "1": {"title": "Intro to Python", "author": "Alice", "category": "Programming"},
...         "2": {"title": "Advanced Python", "author": "Alice", "category": "Programming"},
...         "3": {"title": "History of Rome", "author": "Bob", "category": "History"},
...     }
... )
>>> len(store.filter(author="Alice"))
2
>>> len(store.filter(author="Alice", category="Programming"))
2
>>> len(store.filter(category="History"))
1

persista.store.InMemoryStore

Bases: BaseStore, InlineDisplayMixin

A :class:~persista.store.BaseStore implementation backed by a plain dict.

Values are held entirely in process memory -- nothing is persisted to disk. This is primarily useful for testing, small-scale exploration, or pipelines that don't need durability.

Values are deep-copied on both write and read so that mutating a value returned by this store (or a value passed into :meth:set / :meth:set_many) never affects the store's internal state. This trades some performance for isolation; for very large values or hot loops, consider a store that doesn't copy on every access.

Example
>>> from persista.store import InMemoryStore
>>> store = InMemoryStore()
>>> store.set("1", {"text": "hello"})
>>> store.count()
1
>>> store.get("1")
{'text': 'hello'}

persista.store.LmdbStore

Bases: BaseLmdbStore

An LMDB-backed key-value store.

Persists values to an LMDB environment and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSON-encoded string, which provides flexibility for arbitrary value fields without requiring a fixed schema and can be read back by any LMDB client regardless of language. This means only JSON-compatible value fields (str, int, float, bool, None, list, dict) are supported; use :class:PickleLmdbStore if you need to persist arbitrary Python objects.

Parameters:

Name Type Description Default
path str | PathLike[str]

The directory where the LMDB environment is stored. Created automatically if it does not already exist.

required
map_size int

The maximum size in bytes of the memory map, i.e. the upper bound on the total size of the environment (keys and values combined). Passed to lmdb.open.

_DEFAULT_MAP_SIZE
**kwargs Any

Additional keyword arguments to pass to lmdb.open.

{}
Example
>>> from persista.store import LmdbStore
>>> store = LmdbStore("/tmp/lmdb_store")  # doctest: +SKIP
>>> store.set_many(
...     {
...         "1": {"title": "Intro to Python", "author": "Alice", "category": "Programming"},
...         "2": {"title": "Advanced Python", "author": "Alice", "category": "Programming"},
...         "3": {"title": "History of Rome", "author": "Bob", "category": "History"},
...     }
... )  # doctest: +SKIP
>>> len(store.filter(author="Alice"))  # doctest: +SKIP
2

persista.store.PickleLmdbStore

Bases: BaseLmdbStore

An LMDB-backed key-value store that serializes values with pickle instead of JSON.

Unlike :class:LmdbStore, this store can persist arbitrary Python objects within a value's fields (tuples, sets, custom classes, etc.), not just JSON-compatible types. The tradeoff is that values are opaque binary blobs from outside Python (not human-readable, not inspectable from non-Python LMDB clients), and, since :func:pickle.loads can execute arbitrary code, this store must never be pointed at an LMDB environment that isn't fully trusted.

Parameters:

Name Type Description Default
path str | PathLike[str]

The directory where the LMDB environment is stored. Created automatically if it does not already exist.

required
map_size int

The maximum size in bytes of the memory map, i.e. the upper bound on the total size of the environment (keys and values combined). Passed to lmdb.open.

_DEFAULT_MAP_SIZE
**kwargs Any

Additional keyword arguments to pass to lmdb.open.

{}
Example
>>> from persista.store import PickleLmdbStore
>>> store = PickleLmdbStore("/tmp/lmdb_store")  # doctest: +SKIP
>>> store.set(
...     "1", {"title": "Intro to Python", "tags": {"python", "intro"}}
... )  # doctest: +SKIP
>>> store.get("1")  # doctest: +SKIP
{'title': 'Intro to Python', 'tags': {'python', 'intro'}}

persista.store.PickleRedisStore

Bases: BaseRedisStore

A Redis-backed key-value store that serializes values with pickle instead of JSON.

Unlike :class:~persista.store.RedisStore, this store can persist arbitrary Python objects within a value's fields (tuples, sets, custom classes, etc.), not just JSON-compatible types. The tradeoff is that values are opaque binary blobs from outside Python (not human-readable, not inspectable from non-Python Redis clients), and, since :func:pickle.loads can execute arbitrary code, this store must never be pointed at a Redis instance that isn't fully trusted.

Parameters:

Name Type Description Default
url str

The Redis connection URL passed to redis.Redis.from_url (e.g. "redis://localhost:6379/0").

'redis://localhost:6379/0'
**kwargs Any

Additional keyword arguments to pass to redis.Redis.from_url.

{}
Example
>>> from persista.store import PickleRedisStore
>>> store = PickleRedisStore("redis://localhost:6379/0")  # doctest: +SKIP
>>> store.set(
...     "1", {"title": "Intro to Python", "tags": {"python", "intro"}}
... )  # doctest: +SKIP
>>> store.get("1")  # doctest: +SKIP
{'title': 'Intro to Python', 'tags': {'python', 'intro'}}

persista.store.PostgresStore

Bases: BasePostgresStore

A Postgres-backed key-value store.

Persists values to a Postgres database and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSONB column, which provides flexibility for arbitrary value fields without requiring a fixed schema.

Parameters:

Name Type Description Default
conninfo str

The connection string/DSN passed to psycopg.connect (e.g. "postgresql://user:pass@localhost/dbname").

required
table str

The name of the table backing this store.

'store'
**kwargs Any

Additional keyword arguments to pass to psycopg.connect.

{}
Example
>>> from persista.store import PostgresStore
>>> store = PostgresStore("postgresql://user:pass@localhost/dbname")  # doctest: +SKIP
>>> store.set_many(  # doctest: +SKIP
...     {
...         "1": {"title": "Intro to Python", "author": "Alice"},
...         "2": {"title": "Advanced Python", "author": "Alice"},
...     }
... )
>>> len(store.filter(author="Alice"))  # doctest: +SKIP
2

persista.store.RedisStore

Bases: BaseRedisStore

A Redis-backed key-value store.

Persists values to Redis and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSON string, which provides flexibility for arbitrary value fields without requiring a fixed schema, is human-readable directly from Redis, and can be read by any Redis client regardless of language. This means only JSON-compatible value fields (str, int, float, bool, None, list, dict) are supported; use :class:~persista.store.redis_pickle.PickleRedisStore if you need to persist arbitrary Python objects.

Parameters:

Name Type Description Default
url str

The Redis connection URL passed to redis.Redis.from_url (e.g. "redis://localhost:6379/0").

'redis://localhost:6379/0'
**kwargs Any

Additional keyword arguments to pass to redis.Redis.from_url.

{}
Example
>>> from persista.store import RedisStore
>>> store = RedisStore("redis://localhost:6379/0")  # doctest: +SKIP
>>> store.set_many(
...     {
...         "1": {"title": "Intro to Python", "author": "Alice", "category": "Programming"},
...         "2": {"title": "Advanced Python", "author": "Alice", "category": "Programming"},
...         "3": {"title": "History of Rome", "author": "Bob", "category": "History"},
...     }
... )  # doctest: +SKIP
>>> len(store.filter(author="Alice"))  # doctest: +SKIP
2

persista.store.SQLiteStore

Bases: BaseSQLiteStore

A SQLite-backed key-value store.

Persists values to a SQLite database and supports adding, retrieving, filtering, and deleting key-value pairs. Each value is stored as a JSON column (using SQLite's built-in json1 functions), which provides flexibility for arbitrary value fields without requiring a fixed schema.

The constructor mirrors :func:sqlite3.connect directly. For the common case of opening a file by path (optionally read-only), use :meth:from_path instead.

Parameters:

Name Type Description Default
database Path | str

The database argument passed to sqlite3.connect (path, ":memory:", or file: URI).

':memory:'
**kwargs Any

Additional keyword arguments to pass to sqlite3.connect.

{}
Example
>>> from persista.store import SQLiteStore
>>> store = SQLiteStore(":memory:")
>>> store.set_many(
...     {
...         "1": {"title": "Intro to Python", "author": "Alice", "category": "Programming"},
...         "2": {"title": "Advanced Python", "author": "Alice", "category": "Programming"},
...         "3": {"title": "History of Rome", "author": "Bob", "category": "History"},
...     }
... )
>>> len(store.filter(author="Alice"))
2
>>> len(store.filter(author="Alice", category="Programming"))
2
>>> len(store.filter(category="History"))
1

persista.store.TypedDuckDBStore

Bases: BaseDuckDBStore

A DuckDB-backed key-value store with an optional typed value schema.

Persists values to a DuckDB database and supports adding, retrieving, and filtering by value fields. An optional value_schema maps known value field names to DuckDB types. Known fields are stored as typed columns for fast, index-friendly queries. Any value fields not in the schema are stored in an extra JSON overflow column, so nothing is lost.

Parameters:

Name Type Description Default
path Path | str

Path to the DuckDB file, or ":memory:" for an in-memory database (useful for testing).

':memory:'
value_schema dict[str, str] | None

Optional mapping of value field names to DuckDB type strings (e.g. {"author": "VARCHAR", "year": "INTEGER"}). Fields in the schema get native typed columns; all other value fields go into the extra JSON overflow column. Defaults to None, which stores every value field as JSON only.

None
**kwargs Any

Additional keyword arguments to pass to duckdb.connect.

{}
Example
>>> from persista.store import TypedDuckDBStore
>>> schema = {"author": "VARCHAR", "year": "INTEGER", "category": "VARCHAR"}
>>> store = TypedDuckDBStore(":memory:", value_schema=schema)
>>> store.set_many(
...     {
...         "1": {"title": "Intro to Python", "author": "Alice", "category": "Programming"},
...         "2": {"title": "Advanced Python", "author": "Alice", "category": "Programming"},
...         "3": {"title": "History of Rome", "author": "Bob", "category": "History"},
...     }
... )
>>> len(store.filter(author="Alice"))
2
>>> len(store.filter(author="Alice", category="Programming"))
2
>>> len(store.filter(category="History"))
1

persista.store.TypedPostgresStore

Bases: BasePostgresStore

A Postgres-backed key-value store with an optional typed value schema.

Persists values to a Postgres database and supports adding, retrieving, and filtering by value fields. An optional value_schema maps known value field names to Postgres types. Known fields are stored as typed columns for fast, index-friendly queries. Any value fields not in the schema are stored in an extra JSONB overflow column, so nothing is lost. Mirrors :class:~persista.store.sqlite.TypedSQLiteStore.

Parameters:

Name Type Description Default
conninfo str

The connection string/DSN passed to psycopg.connect.

required
table str

The name of the table backing this store.

'store'
value_schema dict[str, str] | None

Optional mapping of value field names to Postgres type strings (e.g. {"author": "TEXT", "year": "INTEGER"}). Fields in the schema get native typed columns; all other value fields go into the extra JSONB overflow column. Defaults to None, which stores every value field as JSONB only.

None
**kwargs Any

Additional keyword arguments to pass to psycopg.connect.

{}
Example
>>> from persista.store import TypedPostgresStore
>>> schema = {"author": "TEXT", "year": "INTEGER"}
>>> store = TypedPostgresStore(  # doctest: +SKIP
...     "postgresql://user:pass@localhost/dbname", value_schema=schema
... )
>>> store.set_many(  # doctest: +SKIP
...     {
...         "1": {"title": "Intro to Python", "author": "Alice", "year": 2022},
...         "2": {"title": "History of Rome", "author": "Bob", "year": 2021},
...     }
... )
>>> len(store.filter(author="Alice"))  # doctest: +SKIP
1

persista.store.TypedSQLiteStore

Bases: BaseSQLiteStore

A SQLite-backed key-value store with an optional typed value schema.

Persists values to a SQLite database and supports adding, retrieving, and filtering by value fields. An optional value_schema maps known value field names to SQLite types. Known fields are stored as typed columns for fast, index-friendly queries. Any value fields not in the schema are stored in an extra JSON overflow column, so nothing is lost.

The constructor mirrors :func:sqlite3.connect directly (plus the value_schema argument). For the common case of opening a file by path (optionally read-only), use :meth:from_path instead.

Parameters:

Name Type Description Default
database Path | str

The database argument passed to sqlite3.connect (path, ":memory:", or file: URI).

':memory:'
value_schema dict[str, str] | None

Optional mapping of value field names to SQLite type strings (e.g. {"author": "TEXT", "year": "INTEGER"}). Fields in the schema get native typed columns; all other value fields go into the extra JSON overflow column. Defaults to None, which stores every value field as JSON only.

None
**kwargs Any

Additional keyword arguments to pass to sqlite3.connect.

{}
Example
>>> from persista.store import TypedSQLiteStore
>>> schema = {"author": "TEXT", "year": "INTEGER", "category": "TEXT"}
>>> store = TypedSQLiteStore(":memory:", value_schema=schema)
>>> store.set_many(
...     {
...         "1": {"title": "Intro to Python", "author": "Alice", "category": "Programming"},
...         "2": {"title": "Advanced Python", "author": "Alice", "category": "Programming"},
...         "3": {"title": "History of Rome", "author": "Bob", "category": "History"},
...     }
... )
>>> len(store.filter(author="Alice"))
2
>>> len(store.filter(author="Alice", category="Programming"))
2
>>> len(store.filter(category="History"))
1

persista.store.normalize_on_conflict

normalize_on_conflict(on_conflict: str) -> OnConflict

Normalize and validate an on_conflict value.

Parameters:

Name Type Description Default
on_conflict str

The value to normalize. Matched case-insensitively, with leading/trailing whitespace stripped.

required

Returns:

Type Description
OnConflict

The normalized value, one of :data:ON_CONFLICT_VALUES.

Raises:

Type Description
ValueError

If the normalized value is not one of :data:ON_CONFLICT_VALUES.

persista.store.validate_batch_size

validate_batch_size(batch_size: int) -> None

Validate that a value is a valid batch_size strategy.

Parameters:

Name Type Description Default
batch_size int

The value to validate.

required

Raises:

Type Description
ValueError

If batch_size is invalid.

persista.store.validate_field_name

validate_field_name(name: str) -> None

Validate that a value filter field name is safe to interpolate into a SQL fragment.

BaseStore.filter implementations build SQL by interpolating the field name directly (only the filter value is passed as a bound parameter), so an unrestricted field name is a SQL injection vector. Restricting it to a simple identifier shape closes that off.

Parameters:

Name Type Description Default
name str

The field name to validate.

required

Raises:

Type Description
ValueError

If name is not a valid identifier (letters, digits, underscores, not starting with a digit).

persista.store.validate_on_conflict

validate_on_conflict(on_conflict: str) -> None

Validate that a value is a valid on_conflict strategy.

Parameters:

Name Type Description Default
on_conflict str

The value to validate.

required

Raises:

Type Description
ValueError

If on_conflict is not one of :data:ON_CONFLICT_VALUES.