Skip to content

ext.transfer - Upload, Download, Cross-Store Transfer Specification

Overview

ext.transfer provides three standalone functions for moving data between local files and Stores, or between two Stores: upload, download, and transfer. All functions stream data via BinaryIO — the extension never buffers the full file content. End-to-end memory behavior depends on the backend's write() implementation (see SIO-003). An optional on_progress callback fires per chunk.

Module: src/remote_store/ext/transfer.py Dependencies: None (pure Python, always available) Related: 001-store-api.md (Store API), ID-023 (unifies ID-001 cross-store transfer and ID-009 upload/download).


Requirements

XFER-001: upload Signature

Invariant: upload(store, local_path, remote_path, *, overwrite=False, on_progress=None) -> None. store is a Store instance. local_path is a str | os.PathLike[str]. remote_path is a str. on_progress is Callable[[int], None] | None.

XFER-002: upload Streaming

Invariant: upload opens the local file in binary read mode, wraps it in a ProgressReader (if on_progress is provided), and passes the file handle directly to store.write(). The extension never buffers the full file content in memory.

XFER-003: upload overwrite

Invariant: The overwrite parameter is forwarded to store.write(). When False (default), writing to an existing remote path raises AlreadyExists.

XFER-004: upload FileNotFoundError

Invariant: If the local file does not exist, upload raises FileNotFoundError (stdlib). This error is raised before any Store interaction.

XFER-005: upload on_progress

Invariant: When on_progress is not None, the callback fires once per read() call with the number of bytes read in that call (not cumulative). When on_progress is None, no wrapping occurs.

XFER-006: download Signature

Invariant: download(store, remote_path, local_path, *, overwrite=False, on_progress=None) -> None. store is a Store instance. remote_path is a str. local_path is a str | os.PathLike[str]. on_progress is Callable[[int], None] | None.

XFER-007: download Streaming

Invariant: download calls store.read() to get a stream, then reads chunks of 1 MiB (1_048_576 bytes) and writes each chunk to the local file. The extension never buffers the full file content in memory. The remote stream is always closed via try/finally.

XFER-008: download overwrite Guard

Invariant: When overwrite=False (default) and the local file already exists, download raises FileExistsError (stdlib). This check happens before calling store.read().

XFER-009: download on_progress

Invariant: When on_progress is not None, the callback fires once per read() call on the remote stream, with the number of bytes read in that chunk (not cumulative). Progress is tracked via a ProgressReader wrapper around the remote stream.

XFER-010: download Stream Cleanup

Invariant: The remote stream returned by store.read() is always closed, even if an error occurs during the download. Implemented via try/finally.

XFER-011: transfer Signature

Invariant: transfer(src_store, src_path, dst_store, dst_path, *, overwrite=False, on_progress=None) -> None. src_store and dst_store are Store instances (may be the same). src_path and dst_path are str. on_progress is Callable[[int], None] | None.

XFER-012: transfer Streaming

Invariant: transfer calls src_store.read() to get a stream, wraps it in a ProgressReader (if on_progress is provided), and passes the wrapped stream to dst_store.write(). The extension never buffers the full file content in memory.

XFER-013: transfer overwrite

Invariant: The overwrite parameter is forwarded to dst_store.write(). When False (default), writing to an existing destination raises AlreadyExists.

XFER-014: transfer on_progress

Invariant: When on_progress is not None, the callback fires once per read() call on the source stream, with the number of bytes read (not cumulative). Progress is tracked via a ProgressReader wrapper around the source stream.

XFER-015: transfer Stream Cleanup

Invariant: The source stream returned by src_store.read() is always closed, even if an error occurs during the transfer. Implemented via try/finally.

XFER-016: No Backend Coupling

Invariant: All transfer functions operate exclusively through the public Store API (read, write). They never access store._backend or any backend internals. This ensures they work correctly with Store.child(), capability gating, path rebasing, and any future Store wrapper.

XFER-017: Capability Gating Propagation

Invariant: Capability errors (CapabilityNotSupported) raised by Store methods propagate immediately. Transfer functions do not catch or wrap these errors.