Custom Backends¶
Non-fsspec backends (Redis, PostgreSQL, DuckDB, etc.) are registered via the
ditto_backends entry-point group.
Backend Factory¶
A backend factory receives the full URI string plus any matching kwargs from
ditto_storage_options, and returns a MutableMapping[str, bytes]:
from collections.abc import MutableMapping
def create_my_backend(uri: str, **storage_options) -> MutableMapping[str, bytes]:
"""Create a backend mapping for the given URI.
Parameters
----------
uri : str
The full target URI (e.g., "myscheme://host/path").
**storage_options
Kwargs from ditto_storage_options for this scheme.
Returns
-------
MutableMapping[str, bytes]
A mapping that stores snapshot data as bytes keyed by snapshot name.
"""
...
Registration¶
Register the factory in pyproject.toml:
Once registered, target="myscheme://..." works in any mark:
import ditto
@ditto.record("json", target="myscheme://my-host/snapshots")
def test_something(snapshot): ...
Context Manager Support¶
If your backend needs setup/teardown (connection pools, transactions), return a context manager. pytest-ditto will enter it at session start and exit it at session end:
from contextlib import contextmanager
from collections.abc import MutableMapping
@contextmanager
def create_db_backend(uri: str, **opts) -> MutableMapping[str, bytes]:
conn = connect(uri, **opts)
try:
yield DbMapping(conn)
finally:
conn.close()
Example: Redis Backend¶
import redis
from collections.abc import MutableMapping, Iterator
class RedisMapping(MutableMapping[str, bytes]):
def __init__(self, client: redis.Redis, prefix: str):
self._client = client
self._prefix = prefix
def _key(self, k: str) -> str:
return f"{self._prefix}:{k}"
def __getitem__(self, key: str) -> bytes:
value = self._client.get(self._key(key))
if value is None:
raise KeyError(key)
return value
def __setitem__(self, key: str, value: bytes) -> None:
self._client.set(self._key(key), value)
def __delitem__(self, key: str) -> None:
self._client.delete(self._key(key))
def __iter__(self) -> Iterator[str]:
prefix = self._prefix + ":"
for key in self._client.scan_iter(f"{prefix}*"):
yield key.decode().removeprefix(prefix)
def __len__(self) -> int:
return sum(1 for _ in self)
def create_redis_backend(uri: str, **storage_options) -> MutableMapping[str, bytes]:
from urllib.parse import urlparse
parsed = urlparse(uri)
client = redis.Redis(
host=parsed.hostname or "localhost",
port=parsed.port or 6379,
db=int(parsed.path.lstrip("/") or "0"),
**storage_options,
)
return RedisMapping(client, prefix="ditto")
Register it:
Runnable Examples¶
See the examples directory for self-contained PostgreSQL, Redis, and DuckDB examples.