Features — remote-store v0.30.0¶
Authoritative snapshot of what remote-store delivers in this version. Updated each release. This is the single reference for the package's feature surface.
What this package does¶
remote-store gives application code a single, portable file-storage API.
Write against Store once; swap the backend by changing one line of config —
the same read(), write(), and list_files() calls work across all supported
backends (see the Backends section).
Three primitives do all the work:
Storewraps a backend and exposes capability-gated methods. An operation is available only if the backend supports it. Capability is queried at runtime withstore.supports(Capability.X), so code can adapt gracefully instead of failing at import time.Backendis the storage-specific adapter. Built-in backends cover the common targets; a public ABC makes it straightforward to add new ones.Registryloads named backend and store definitions from config (TOML, YAML, dict, or Pydantic models), resolves${ENV_VAR}placeholders, and wraps credentials inSecretautomatically.
Store API¶
Methods are grouped by the capability that gates them.
All methods share one invariant: they raise typed errors from remote_store.errors
and never leak backend-native exceptions to the caller.
The table below is generated from the documentation graph (graph.json): each
gating capability maps to the methods it gates. The descriptive per-capability
subsections that follow add return types and behaviour. Quality-flag capabilities
(SEEKABLE_READ, LAZY_READ, ATOMIC_MOVE, WRITE_RESULT_NATIVE,
USER_METADATA) are not gates and so do not appear here — see Capabilities.
Methods by capability gate¶
| Capability | Gated methods |
|---|---|
ATOMIC_WRITE |
open_atomic(), write_atomic() |
COPY |
copy() |
DELETE |
delete(), delete_folder() |
GLOB |
glob() |
LIST |
iter_children(), list_files(), list_folders() |
METADATA |
get_file_info(), get_folder_info()*, head() |
MOVE |
move() |
READ |
read(), read_bytes(), read_seekable(), read_text() |
WRITE |
write(), write_text() |
* get_folder_info() is additionally gated on LIST when called with max_depth (depth-limited traversal).
Ungated (always available)¶
These methods carry no capability gate — they are available on every backend.
The set is derived from the graph (Store method nodes with gated: false).
| Method | Returns | Description |
|---|---|---|
exists(path) |
bool |
Whether a file exists at the path |
is_file(path) |
bool |
Whether the path resolves to a file (not a folder) |
is_folder(path) |
bool |
Whether the path resolves to a folder |
ping() |
None |
Health check — raises BackendUnavailable if unreachable |
close() |
None |
Release backend resources |
child(subpath) |
Store |
Scoped sub-store rooted at subpath |
unwrap(type_hint) |
T |
Extract the underlying backend by type |
resolve(key) |
ResolutionPlan |
Resolution plan for a key (type, resolved path, options) |
native_path(key) |
str |
Backend-native path string for a store key |
to_key(path) |
str |
Convert a native path back to a store key |
supports(capability) |
bool |
Query whether a capability is active |
READ¶
| Method | Returns | Description |
|---|---|---|
read(path) |
BinaryIO |
Open a binary stream for reading; lazy where the backend supports LAZY_READ |
read_bytes(path) |
bytes |
Read the entire file into memory |
read_text(path, *, encoding) |
str |
Read the entire file as a decoded string |
read_seekable(path) |
BinaryIO |
Seekable binary stream; zero-copy where read() is already seekable (SEEKABLE_READ backends), a native HTTP-Range reader on sync Azure, else spooled to a temp file |
WRITE¶
All write methods accept an optional metadata= mapping. If the backend
declares Capability.USER_METADATA, the mapping is persisted alongside the
file; otherwise a non-empty metadata= raises CapabilityNotSupported.
All write methods return a WriteResult (see Data Models).
| Method | Returns | Description |
|---|---|---|
write(path, content, *, overwrite, metadata) |
WriteResult |
Create or overwrite a file from bytes or a binary stream |
write_text(path, text, *, encoding, overwrite, metadata) |
WriteResult |
Write a string as a file |
ATOMIC_WRITE¶
Atomic writes use a temp-and-rename strategy: no reader ever sees a partial file, and a crash mid-write leaves the previous version intact.
| Method | Returns | Description |
|---|---|---|
write_atomic(path, content, *, overwrite, metadata) |
WriteResult |
Atomic write from bytes or a stream |
open_atomic(path, *, overwrite, metadata) |
context manager → WriteResult |
Streaming atomic write; WriteResult returned on __exit__ |
DELETE¶
| Method | Returns | Description |
|---|---|---|
delete(path, *, missing_ok) |
None |
Remove a file; missing_ok=True suppresses NotFound |
delete_folder(path, *, recursive) |
None |
Remove a folder; requires recursive=True if non-empty |
LIST¶
| Method | Returns | Description |
|---|---|---|
list_files(path, *, max_depth, pattern) |
Iterator[FileInfo] |
Enumerate files under a prefix as FileInfo records (use .path for the path); optional pattern= filters basenames via fnmatch |
list_folders(path, *, max_depth, pattern) |
Iterator[FolderEntry] |
Enumerate immediate subfolders as FolderEntry records (.name, .path); optional pattern= filters folder basenames via fnmatch |
iter_children(path) |
Iterator[FileInfo \| FolderEntry] |
Iterate files and folders together (FileInfo for files, FolderEntry for folders) |
GLOB¶
| Method | Returns | Description |
|---|---|---|
glob(pattern) |
Iterator[FileInfo] |
Native glob pattern matching (*, **, ?), yielding matched files as FileInfo records |
Backends without native GLOB capability can use the ext.glob extension as a
portable fallback.
MOVE / COPY¶
| Method | Returns | Description |
|---|---|---|
move(src, dst, *, overwrite) |
None |
Rename or relocate within the same backend |
copy(src, dst, *, overwrite) |
None |
Duplicate a file within the same backend |
METADATA¶
| Method | Returns | Description |
|---|---|---|
head(path) |
WriteResult |
File metadata without reading content (size, etag, last_modified, etc.) |
get_file_info(path) |
FileInfo |
Full file metadata record |
get_folder_info(path) |
FolderInfo |
Folder metadata with aggregate file count and total size |
Data Models¶
All models are importable from remote_store and are frozen dataclasses.
WriteResult¶
Returned by every write method and by head().
| Field | Type | Populated when |
|---|---|---|
path |
str |
Always |
size |
int |
Always |
source |
WriteSource |
Always (NativeSource, BasicSource, or SidecarSource) |
digest |
ContentDigest \| None |
Backend declares WRITE_RESULT_NATIVE |
etag |
str \| None |
Backend declares WRITE_RESULT_NATIVE |
version_id |
str \| None |
Backend declares WRITE_RESULT_NATIVE (S3 versioning, Azure) |
last_modified |
datetime \| None |
Backend declares WRITE_RESULT_NATIVE |
metadata |
dict[str, str] |
Echo of caller's metadata= input |
source signals where the rich fields came from:
NativeSource = populated from the backend's write response;
BasicSource = populated from a post-write stat/head;
SidecarSource = populated by the ext.write hash helper.
FileInfo¶
Returned by get_file_info() and yielded by iter_children().
| Field | Type |
|---|---|
path |
str |
size |
int |
modified_at |
datetime \| None |
etag |
str \| None |
version_id |
str \| None |
content_type |
str \| None |
metadata |
dict[str, str] |
Other models¶
| Model | Description |
|---|---|
FolderInfo |
path, file_count, total_size — returned by get_folder_info() |
FolderEntry |
path, name — folder identity yielded by list_folders() / iter_children() |
ContentDigest |
algorithm (e.g. "crc32", "sha256") + value (hex) |
ResolutionPlan |
Backend type, resolved path, and options — returned by resolve() |
BackendConfig |
type string + options dict — one entry in a RegistryConfig |
RegistryConfig |
Named backends and stores; the entry point for config-driven setup |
Capabilities¶
Capabilities are declared by backends at construction time. Two flavours exist:
- Method gates: the capability is a hard prerequisite for calling the
method.
store.supports()returnsFalseand the method raisesCapabilityNotSupportedif called without it. - Quality flags: the capability signals that a method delivers a stronger guarantee. The method is available regardless; the flag lets callers decide whether to rely on the native behaviour or fall back to an extension.
| Capability | Flavour | Gated / signalled methods | Notes |
|---|---|---|---|
READ |
Gate | read(), read_bytes(), read_text(), read_seekable() |
Declared by all built-in backends |
WRITE |
Gate | write(), write_text() |
Declared by all built-in backends |
DELETE |
Gate | delete(), delete_folder() |
Declared by all built-in backends |
LIST |
Gate | list_files(), list_folders(), iter_children() |
Declared by all built-in backends |
GLOB |
Gate | glob() |
Not declared by memory; use ext.glob as fallback |
MOVE |
Gate | move() |
Declared by all built-in backends |
COPY |
Gate | copy() |
Declared by all built-in backends |
ATOMIC_WRITE |
Gate | write_atomic(), open_atomic() |
Declared by all built-in backends |
METADATA |
Gate | head(), get_file_info(), get_folder_info() |
Declared by all built-in backends |
SEEKABLE_READ |
Quality flag | read() |
read() returns a natively seekable stream. Absence means only that read() is forward-only — the sync Store.read_seekable() still serves it (native range read on sync Azure, else spooled); the async API has no read_seekable() |
LAZY_READ |
Quality flag | read() |
read() fetches data lazily; partial reads avoid loading the full file |
ATOMIC_MOVE |
Quality flag | move() |
move() is crash-safe under concurrent access |
WRITE_RESULT_NATIVE |
Quality flag | write*(), head() |
Rich WriteResult fields (etag, digest, version_id, last_modified) come from the backend's own write response |
USER_METADATA |
Strict gate | metadata= kwarg on write methods |
A non-empty metadata= raises CapabilityNotSupported when unset |
Backends¶
Built-in backends cover the most common targets. All implement the same
Backend ABC; SFTPUtils, S3PyArrowBackend, and the SQL backends expose
additional backend-specific options via BackendConfig.options.
| Type | Class | Extra | Capabilities |
|---|---|---|---|
azure |
AzureBackend |
remote-store[azure] |
All except ATOMIC_MOVE, SEEKABLE_READ |
http |
ReadOnlyHttpBackend |
— (stdlib; requests/httpx optional) |
LAZY_READ, METADATA, READ |
local |
LocalBackend |
— | All |
memory |
MemoryBackend |
— | All except GLOB, LAZY_READ |
s3 |
S3Backend |
remote-store[s3] |
All except ATOMIC_MOVE |
s3-pyarrow |
S3PyArrowBackend |
remote-store[s3-pyarrow] |
All except ATOMIC_MOVE |
sftp |
SFTPBackend |
remote-store[sftp] |
All except ATOMIC_MOVE, GLOB |
sql-blob |
SQLBlobBackend |
remote-store[sql] |
All except LAZY_READ |
sql-query |
SQLQueryBackend |
remote-store[sql-query] |
GLOB, LIST, METADATA, READ, SEEKABLE_READ |
The SEEKABLE_READ exclusions above concern read() only — read_seekable()
remains available on the sync Store, and on Azure it is a native HTTP-Range
reader (no temp-file spill), not a spool. See
Streaming and seekable reads.
Write-result quality flags by backend:
| Backend | WRITE_RESULT_NATIVE |
USER_METADATA |
|---|---|---|
azure |
Yes | Yes |
http |
— | — |
local |
Yes | — |
memory |
Yes | Yes |
s3 |
Yes | Yes |
s3-pyarrow |
Yes | — |
sftp |
Yes | — |
sql-blob |
Yes (requires modified_at column) |
Yes (requires user_metadata column) |
sql-query |
— | — |
Write and move atomicity by backend — whether each mutating operation
completes atomically or via a non-atomic mechanism that can leave partial state
on failure. read, list, and metadata are non-mutating (atomicity N/A);
delete and folder operations carry no atomicity guarantee and are omitted.
| Backend | write |
write_atomic |
move |
copy |
|---|---|---|---|---|
azure |
Atomic§ | Atomic | Copy+delete† | Copy+delete |
http |
— (read-only) | — (read-only) | — (read-only) | — (read-only) |
local |
Direct | Atomic | Atomic* | Copy+delete |
memory |
Atomic | Atomic | Atomic | Atomic |
s3 |
Atomic | Atomic | Copy+delete | Copy+delete |
s3-pyarrow |
Streamed‡ | Atomic | Copy+delete | Copy+delete |
sftp |
Streamed | Atomic | Copy+delete† | Copy+delete |
sql-blob |
Atomic | Atomic | Atomic | Atomic |
sql-query |
— (read-only) | — (read-only) | — (read-only) | — (read-only) |
* local move is atomic within one filesystem (os.rename); a cross-filesystem move falls back to copy-then-delete.
† Azure and SFTP move use a native rename that is atomic (Azure HNS rename_file, SFTP posix_rename), but ATOMIC_MOVE is not advertised because it cannot be guaranteed across all configurations (non-HNS Azure accounts, non-POSIX SFTP servers).
‡ s3-pyarrow plain write streams straight to a multipart upload; PyArrow's stream exposes no abort, so a mid-stream failure finalises a truncated object. write_atomic buffers the body first, so a failure leaves no object.
§ azure write commits atomically on flat (non-HNS) accounts; on hierarchical-namespace accounts use write_atomic for a guaranteed atomic replace.
Read-after-write consistency by backend — whether a read or listing issued
after a write, overwrite, or delete reflects that change. remote-store normalises
to strong read-after-write on every read/write backend: the object you just
wrote or deleted is immediately visible to a subsequent read, head, or
list_files on the same store, with no eventual-consistency window to code
around. This is a per-caller visibility guarantee, not mutual exclusion between
simultaneous writers — overwrite=False remains a TOCTOU convenience guard, and
concurrent writers to one key resolve last-writer-wins. See
Concurrency and atomicity
for the ordering and race semantics.
| Backend | Read-after-write | Listing consistency |
|---|---|---|
azure |
Strong | Strong |
http |
— (read-only) | — (read-only) |
local |
Strong | Strong |
memory |
Strong | Strong |
s3 |
Strong | Strong* |
s3-pyarrow |
Strong | Strong* |
sftp |
Strong | Strong |
sql-blob |
Strong | Strong |
sql-query |
— (read-only) | — (read-only) |
The async-native backends inherit their sync peer's consistency; the async-only GraphBackend is the one distinct case.
| Async backend | Read-after-write | Listing consistency |
|---|---|---|
AsyncAzureBackend |
Strong | Strong |
AsyncMemoryBackend |
Strong | Strong |
GraphBackend |
Strong† | Strong† |
* s3 / s3-pyarrow listings are strongly consistent by default: the backend leaves the s3fs directory cache off (use_listings_cache=False), so a listing taken after a write reflects it. Opting into client_options['use_listings_cache'] trades this for a cache that never expires — a listing can then stay blind to a cross-writer change until the backend is rebuilt.
† GraphBackend read-your-writes holds on one instance (a write is committed to two datacentre regions before it is acknowledged). copy (always) and a large or cross-folder move (sometimes) run server-side and are polled to completion before the call returns, so a read or listing afterwards reflects the result.
Per-operation cost by backend — the structural cost each backend forces
per call: whether read streams (constant memory) or materializes the whole
object, and what a metadata probe and a list cost per invocation. This is the
cost the API shape dictates, distinct from measured latency — see
Performance for
benchmarked overhead. Write-path cost (streaming vs full-buffer, atomic vs
copy+delete) is in the atomicity table above; list cost scales with the number
of entries returned, and enabling hierarchical-write safety
(reject_write_under_file_ancestor, or the native HNS / SFTP / Graph parent
checks) adds one metadata probe per path ancestor on the guarded write path.
| Backend | read |
metadata |
list |
|---|---|---|---|
azure |
Streaming | 1 HEAD | Paginated LIST |
http |
Streaming | 1 HEAD | — (no LIST) |
local |
Streaming | stat syscall |
scandir walk |
memory |
Buffered in memory* | Dict lookup | Dict scan |
s3 |
Streaming | 1 HEAD | Paginated LIST |
s3-pyarrow |
Streaming | 1 HEAD | Paginated LIST |
sftp |
Streaming | 1 stat round-trip |
Directory walk |
sql-blob |
Full BLOB into memory* | 1 SELECT |
1 SELECT |
sql-query |
Query run, buffered* | Registry lookup | Registry scan |
The async-native backends inherit their sync peer's per-op cost; the async-only GraphBackend streams reads but blocks copy (always) and a large or cross-folder move on a server-side monitor polled to completion — a per-call latency the columns below do not show.
| Async backend | read |
metadata |
list |
|---|---|---|---|
AsyncAzureBackend |
Streaming | 1 HEAD | Paginated LIST |
AsyncMemoryBackend |
Streaming | Dict lookup | Dict scan |
GraphBackend |
Streaming | 1 GET | Paginated GETs |
* memory (sync), sql-blob, and sql-query do not stream a read (LAZY_READ absent): each holds the whole object in memory before read() returns — sql-blob loads the full BLOB, sql-query buffers the serialised query result, and sync memory keeps the value resident (its async peer AsyncMemoryBackend yields chunks and does stream). sql-blob likewise buffers the entire body before the write INSERT/UPDATE. For objects larger than process memory use a streaming backend (Local, S3, Azure, SFTP).
Native async backends — constructed directly via AsyncStore(backend=…);
no RegistryConfig type= string (there is no async config registry).
| Class | Extra | Capabilities |
|---|---|---|
AsyncAzureBackend |
remote-store[azure] |
All except ATOMIC_MOVE, SEEKABLE_READ |
AsyncMemoryBackend |
— | All except GLOB |
GraphBackend |
remote-store[graph] |
All except ATOMIC_MOVE, GLOB, SEEKABLE_READ |
The async API has no read_seekable(): an async-native backend that omits
SEEKABLE_READ (AsyncAzureBackend, GraphBackend) has no seekable read until
bridged to sync via AsyncBackendSyncAdapter, which spools.
Write-result quality flags by native async backend:
| Class | WRITE_RESULT_NATIVE |
USER_METADATA |
|---|---|---|
AsyncAzureBackend |
Yes | Yes |
AsyncMemoryBackend |
Yes | Yes |
GraphBackend |
Yes | — |
Configuration¶
RegistryConfig decouples storage topology from application code.
Define named backends and stores in a config file; application code
calls registry.store("name") and never sees connection strings.
Credentials in standard-looking keys (password, secret_key, etc.)
are wrapped in Secret automatically and masked in logs.
[backends.primary]
type = "s3"
options.bucket = "my-bucket"
options.access_key = "${AWS_ACCESS_KEY_ID}"
options.secret_key = "${AWS_SECRET_ACCESS_KEY}"
[stores.data]
backend = "primary"
root = "data/"
| API | Location | Description |
|---|---|---|
RegistryConfig.from_dict(data) |
remote_store |
Construct from a plain dict |
RegistryConfig.from_toml(path, *, table, resolve_env_vars) |
remote_store |
Load from a TOML file |
from_yaml(path, *, resolve_env_vars) |
remote_store.ext.yaml |
Load from a YAML file |
from_pydantic(model) |
remote_store.ext.pydantic |
Convert a Pydantic settings model |
resolve_env(data, *, environ) |
remote_store |
Resolve ${VAR} / ${VAR:-default} placeholders in a config dict |
Extensions¶
Extensions are composable layers on top of Store or utility helpers.
They use only the public Store/Backend API and carry no lifecycle
ownership — they never call store.close().
Always available (base install)¶
| Extension | Problem it solves | Module | Key exports |
|---|---|---|---|
| Batch | Apply copy/delete/exists across many paths in one call with partial-failure reporting | remote_store.ext.batch |
BatchResult, batch_copy, batch_delete, batch_exists |
| Cache | Transparent read-through caching — subsequent reads of the same key go to a fast local store | remote_store.ext.cache |
CachedStore, CacheBackend, CacheStats, MemoryCache, cache |
| Glob | Portable glob for backends that do not declare Capability.GLOB |
remote_store.ext.glob |
glob_files |
| Integrity | Client-side content checksums and verification independent of backend digest support | remote_store.ext.integrity |
checksum, verify, verify_hex, content_digest |
| Observe | Structured event hooks for every store operation — logging, metrics, tracing, audit trails | remote_store.ext.observe |
ObservedStore, StoreEvent, BufferedObserver, observe, set_correlation_id |
| Partition | Parse and construct Hive-style partition paths (key=value/…) |
remote_store.ext.partition |
ParsedPartition, parse_partition, partition_path |
| Streams | Wrap any stream with progress callbacks or rolling checksums without buffering | remote_store.ext.streams |
ChecksumReader, ChecksumWriter, ProgressReader, ProgressWriter, read_with_progress |
| Transfer | High-level upload / download / store-to-store copy with streaming and progress | remote_store.ext.transfer |
upload, download, transfer |
| Write | Guaranteed client-side digest on write regardless of backend capability; atomic write variant | remote_store.ext.write |
write_with_hash, open_atomic_with_hash |
Optional (require extras)¶
| Extension | Problem it solves | Module | Key exports | Extra |
|---|---|---|---|---|
| Arrow | Use any Store as a PyArrow filesystem for Arrow / Parquet tooling |
remote_store.ext.arrow |
StoreFileSystemHandler, pyarrow_fs |
remote-store[arrow] |
| Parquet | Read and write Parquet datasets (single files or partitioned) via PyArrow | remote_store.ext.parquet |
ParquetDatasetStore, DatasetManifest |
remote-store[arrow] |
| OpenTelemetry | Emit distributed tracing spans for every store operation | remote_store.ext.otel |
otel_hooks, otel_observe |
remote-store[otel] |
| Pydantic | Derive RegistryConfig from a Pydantic settings model |
remote_store.ext.pydantic |
from_pydantic |
remote-store[pydantic] |
| YAML | Load RegistryConfig from a YAML file |
remote_store.ext.yaml |
from_yaml |
remote-store[yaml] |
| Dagster | IO manager, config-driven Store resource, and compute log manager for Dagster pipelines | remote_store.ext.dagster |
RemoteStoreIOManager, DagsterStoreResource, RemoteStoreComputeLogManager, dagster_io_manager |
remote-store[dagster] |
Error Model¶
All errors are subclasses of RemoteStoreError (importable from remote_store).
Backend-native exceptions are mapped at the adapter boundary — callers
always receive a typed error, never an S3ServiceError or azure.core.….
| Error | Raised when |
|---|---|
NotFound |
File or folder does not exist |
AlreadyExists |
Target path already exists and overwrite=False |
InvalidPath |
Path is malformed or points at the wrong node type (e.g. a directory where a file is expected) |
CapabilityNotSupported |
A method is called without the required capability |
BackendUnavailable |
The backend is unreachable (network, auth, service down) |
PermissionDenied |
Caller lacks access rights |
DirectoryNotEmpty |
Directory is not empty and the operation requires it to be |
ResourceLocked |
Target resource is held by another session (e.g. an open co-authoring session); maps from Graph 423 Locked |
RemoteStoreError |
Base class for all errors above |
Retryable vs. terminal — the HTTP statuses the HTTP-transport backends
(graph, http) classify, and the typed error each surfaces as. Retried
statuses are re-attempted under the backend's RetryPolicy (default 3 attempts,
1–60 s exponential backoff) and honour Retry-After; the typed error is raised
only once the attempt budget is exhausted. The other backends reach the same
typed-error vocabulary by mapping native SDK/OS exceptions rather than HTTP
status codes, so the outcome is shared — but the status classification and
Retry-After handling shown here are specific to the HTTP transports (s3 and
azure honour only max_attempts; local, memory, and sql-* make no remote
calls and do not retry).
| Status | Disposition | Surfaced as |
|---|---|---|
429 |
Retried — honours Retry-After |
BackendUnavailable |
500 |
Retried | BackendUnavailable |
502 |
Retried | BackendUnavailable |
503 |
Retried | BackendUnavailable |
504 |
Retried | BackendUnavailable |
403 |
Not retried | PermissionDenied |
404 |
Not retried | NotFound |
409 |
Not retried | AlreadyExists |
423 |
Not retried | ResourceLocked |
507 |
Not retried | BackendUnavailable |
| Backend | Transport retry mechanism |
|---|---|
azure |
Azure SDK ExponentialRetry (all five RetryPolicy fields) |
http |
Hand-rolled loop over the shared backoff helpers† |
local |
— (no retry parameter) |
memory |
— (no retry parameter) |
s3 |
botocore standard mode — honours max_attempts only |
s3-pyarrow |
AwsStandardS3RetryStrategy — honours max_attempts only |
sftp |
tenacity — connection-scope only (reconnect, not per-request) |
sql-blob |
— (errors mapped, not retried) |
sql-query |
— (errors mapped, not retried) |
† http additionally retries 408 Request Timeout (classified as BackendUnavailable) — a transport-local extension of the shared retryable set above.
Native async backends inherit their sync peer's mechanism; the async-only
GraphBackend runs hand-rolled retry loops over the shared backoff helpers,
honouring all five RetryPolicy fields. local, memory, sftp, and the SQL
backends leave a closed backend reusable; azure, s3, and graph treat use
after close() as terminal (close_is_terminal).
Async API¶
remote_store.aio provides native async/await support and two bridge
adapters for mixing sync and async backends.
Native async classes:
| Class | Description |
|---|---|
AsyncStore |
Async counterpart to Store; coroutine methods for all operations; write*() returns WriteResult and accepts metadata= |
AsyncBackend |
ABC for native async backends |
AsyncMemoryBackend |
In-memory async backend (for testing) |
AsyncAzureBackend |
Native async Azure backend via Azure SDK async clients |
GraphBackend |
Native async Microsoft Graph backend (OneDrive / SharePoint / Teams) via httpx + msal; companions GraphAuth, GraphUtils |
AsyncWritableContent |
Type alias: bytes \| AsyncIterator[bytes] |
Sync ↔ async backend equivalence — generated from the graph's mirrors
edges. The capability delta names capabilities one side declares that its peer
does not. Async-only backends (GraphBackend) have no sync mirror and are
listed above only.
| Sync backend | Async backend | Capability delta |
|---|---|---|
AzureBackend |
AsyncAzureBackend |
— |
MemoryBackend |
AsyncMemoryBackend |
async adds LAZY_READ |
Bridge adapters — when you need to cross the sync/async boundary:
| Class | Direction | Mechanism |
|---|---|---|
SyncBackendAdapter |
sync Backend → usable in async code |
Dispatches each call via asyncio.to_thread (thread-pool executor) |
AsyncBackendSyncAdapter |
async AsyncBackend → usable in sync code |
Runs the async backend on a dedicated daemon-thread event loop |
Both adapters translate capabilities faithfully and are covered by the same conformance suite as native backends.
Async extensions:
| Module | Description |
|---|---|
aio.ext.write |
write_with_hash — client-side SHA-256 checksumming on async write paths |
Install extras¶
pip install remote-store[arrow] # PyArrow filesystem bridge + Parquet extension
pip install remote-store[azure] # Azure ADLS Gen2 via Azure SDK
pip install remote-store[dagster] # Dagster IO manager
pip install remote-store[graph] # Microsoft Graph (OneDrive / SharePoint / Teams) via httpx + msal
pip install remote-store[httpx] # httpx HTTP adapter for ReadOnlyHttpBackend
pip install remote-store[otel] # OpenTelemetry distributed tracing
pip install remote-store[pydantic] # Pydantic settings integration
pip install remote-store[requests] # requests HTTP adapter for ReadOnlyHttpBackend
pip install remote-store[s3] # S3 via s3fs
pip install remote-store[s3-pyarrow] # S3 via PyArrow C++ filesystem
pip install remote-store[sftp] # SFTP via paramiko
pip install remote-store[sql] # SQL blob store via SQLAlchemy
pip install remote-store[sql-query] # SQL query store via SQLAlchemy + PyArrow
pip install remote-store[toml] # TOML config (stdlib on Python 3.11+)
pip install remote-store[yaml] # YAML config loading
Each extra's version range — a floor always, plus a ceiling only where a
known-incompatible major looms — is declared in pyproject.toml, the
source of truth for per-extra pins. For the exact upper-bound versions CI
was last green against, see Tested upper-bound versions.