ditto.backends¶
The backend registry and helpers for writing a backend.
BACKEND_REGISTRY = BackendRegistry()
module-attribute
¶
The registered backends: URI schemes mapped to their backend factories.
Read-only. It holds the backends installed through the ditto_backends
entry-point group and those set in code on BACKEND_REGISTRY.overrides. ditto
handles file:// targets and fsspec protocols itself, so those schemes aren't
in it unless a package registers one of the fsspec protocols.
BackendFactory = Callable[..., MutableMapping[str, bytes]]
module-attribute
¶
A function that builds the storage for one target.
It is called as factory(uri, **storage_options) and returns the mapping of
snapshot names to bytes that ditto reads and writes. See the Writing a Backend
guide.
PrefixedMapping(store, prefix)
¶
Bases: MutableMapping[str, bytes]
Scopes all operations on a flat MutableMapping under a fixed key prefix.
Use for flat key-value stores (Redis, DynamoDB, etc.) where namespace isolation must come from the key rather than a directory/root path.
The prefix is prepended to keys on write and stripped on read. Keys exposed to callers are always prefix-free.
iter filters by prefix. For backends where full-keyspace iteration is expensive (large shared Redis DB), either: - Use a dedicated DB instance, OR - Configure the inner mapping to do prefix-scoped scanning natively, OR - Raise NotImplementedError on iter to opt out of pruning.
Context management is propagated to the inner store when it implements enter/exit. ditto enters the backend via the session ExitStack (in plugin.py) and closes it in pytest_unconfigure.
Source code in ditto/backends/_prefix.py
FsspecMapping(fs, root)
¶
Bases: MutableMapping[str, bytes]
MutableMapping backed by any fsspec AbstractFileSystem.
Uses fs.find() for iteration and direct fs.open / fs.rm /
fs.isfile for individual key access — never fs.glob(). This
avoids fsspec's fnmatch-based glob expansion, which silently drops
keys whose names contain bracket characters (e.g. parametrised test
names like test_foo[bar]).
Works with any fsspec backend: local, S3 (s3fs), GCS
(gcsfs), Azure (adlfs), in-memory, etc.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fs
|
AbstractFileSystem
|
An initialised |
required |
root
|
str
|
Root path within the filesystem (e.g. |
required |
Source code in ditto/backends/_fsspec.py
__setitem__(key, value)
¶
Store value under key, replacing any previous value whole.
On the local filesystem, opening the destination for writing would truncate it before any new bytes arrive, so the value is written atomically instead: a failed write leaves the previous value intact. Object stores (S3, GCS, Azure) already replace an object whole.
Source code in ditto/backends/_fsspec.py
stat_entries()
¶
Yield (key, size_bytes, modified) for each stored snapshot.
Uses one fs.find(detail=True) listing, so size and mtime come from a
single round trip — no per-key reads. modified is None when the
filesystem reports no mtime. Mirrors __iter__'s root-prefix stripping.
Source code in ditto/backends/_fsspec.py
BackendOverrides()
¶
Bases: MutableMapping[str, BackendFactory]
Backend factories set in code, which take precedence over installed ones.
Behaves like a dict, except that setting a scheme checks the factory and
the scheme first. Nothing here loads or depends on an installed backend, so
monkeypatch.setitem works for any scheme, including one whose installed
backend fails to load or conflicts.
Source code in ditto/backends/_plugins.py
__setitem__(scheme, factory)
¶
Handle scheme with factory, overriding any installed factory.
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
DittoBackendConflictError
|
If no target URI can reach |
Source code in ditto/backends/_plugins.py
BackendRegistry(entry_points=None)
¶
Bases: Mapping[str, BackendFactory]
Backend factories keyed by URI scheme, imported only when first looked up.
Each factory has the signature
factory(uri: str, **storage_options) -> MutableMapping[str, bytes].
Schemes come from ditto_backends entry-point metadata, which needs no
import, so membership tests, iteration and problems never import a
plugin. Every registration is checked against the plugin contract when the
registry is built. Looking up a scheme a problem affects raises
DittoBackendConflictError, so the registry never picks one of the
conflicting registrations.
The registry is read-only. Set a scheme on overrides to handle it in
code: an override takes precedence over any installed factory for the
scheme, and resolves its conflict. Deleting the override reveals the
installed factory again. Iteration preserves discovery order, followed by
schemes set only on overrides.
Looking up values (including through get, items or values) can load
plugins and raise DittoBackendLoadError or DittoBackendConflictError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entry_points
|
Iterable[EntryPoint]
|
Entry points to serve. Defaults to the installed |
None
|