Skip to content

Storix session

Everything you call on a session. A Storix is created over a backend (or built by get_storage), and every path argument is resolved the unix way (~, .., and the current working directory) before it reaches the backend. Any operation raises a typed error on failure; nothing returns a boolean you have to check.

Unless noted, every method has an awaitable twin under storix.aio with the same name and signature.

Session and navigation

pwd

pwd() -> StorixPath

The current working directory.

cd

cd(path: StrPathLike | None = None) -> Self

Change the working directory. No argument returns to home. Returns the session for chaining.

ls

ls(path: StrPathLike | None = None, *, all: bool = False, abs: bool = False) -> list[StorixPath]

List a directory (the cwd by default). Dotfiles are hidden unless all=True. abs=True returns absolute paths instead of names. The eager, names-only member of the listing family below.

scandir / iterdir / is_empty / empty_children

scandir(path=None, *, all=False) -> Iterator[DirEntry]   # lazy, rich (name, path, kind, size)
iterdir(path=None, *, all=False) -> Iterator[StorixPath]  # lazy names (pathlib-shaped)
is_empty(path=None) -> bool                               # one round trip; counts hidden entries
empty_children(path=None, *, names=None) -> dict[str, bool]

scandir streams one directory as DirEntry objects carrying the kind and any size the listing produced for free, so a consumer never stats every entry. iterdir is its names-only sibling. is_empty answers whether a directory holds anything (a dotfile-only directory is not empty). empty_children answers the same question for every immediate child directory. On a backend with bulk listing it groups one recursive listing; otherwise it probes the children concurrently. Pass names when you already have the child directory names from scandir, as the CLI does, to avoid repeating that listing.

walk / find / glob

walk(path=None, *, all=False, top_down=True) -> Iterator[DirEntry]  # recursive scandir
find(path=None, *, name=None, kind=None) -> Iterator[DirEntry]      # walk filtered by glob/kind
glob(pattern, path=None) -> Iterator[StorixPath]                    # *, ?, ** patterns

The recursive family, after os.walk / unix find / pathlib.glob. walk streams every descendant lazily (top_down=False for post-order, e.g. size accumulation). find filters it: name is a basename glob ('*.py'), kind restricts to files or directories. glob matches a path pattern. All exclude hidden entries by default, like the rest of the family.

resolve

resolve(path: StrPathLike | None = None) -> StorixPath

Resolve a path to its absolute, normalized port path (applying cwd, ~, ..), without touching the backend. Useful for logging and bookmarking.

locate

locate(path: StrPathLike | None = None) -> str

The physical URI of a path (file://..., abfss://...), resolved through any sandbox. Use it for audit and cross-system references.

Properties

backend      -> StorageBackend         # the raw port, for backend-specific calls
base_backend -> StorageBackend         # the real provider, under any layers
layers       -> list[StorageBackend]   # the active layers, outermost first
root         -> StorixPath             # always '/'
home         -> StorixPath             # the '~' anchor

backend hands back the outermost object, which is whatever layer wraps the session; base_backend walks past the layers to the thing that really talks to storage, and layers reports what sits in between:

from storix import CacheLayer

fs = get_storage("azure").with_layer(CacheLayer, du=True)
type(fs.base_backend).__name__                            # 'AzureBackend'
[type(layer).__name__ for layer in fs.layers]             # ['CacheLayer']
any(isinstance(layer, CacheLayer) for layer in fs.layers) # True

Layers are identified structurally, so your own show up beside the built-ins. The list is empty on an unwrapped session.

Reading

cat

cat(path: StrPathLike, /, *paths: StrPathLike) -> bytes

Read one or more files fully and concatenate them into bytes. Use for small, known-size content. For large files, prefer stream.

stream

stream(
    path: StrPathLike,
    /,
    *paths: StrPathLike,
    chunk_size: int | None = None,
) -> Iterator[bytes]

Read files back in chunks. chunk_size is the maximum yielded size; None uses the backend's preferred default. Smaller provider chunks pass through promptly. Zero or negative values raise ValueError. Streaming-native backends keep memory bounded; a whole-object custom backend's compatibility fallback does not. See Reading and writing.

download

download(
    path: StrPathLike,
    dest: BinarySink,
    /,
    *,
    ranges: int | None = None,
    chunk_size: int | None = None,
) -> int

Read one file into an open binary sink and return the bytes written. The parallel counterpart of stream: where stream yields chunks in order for any consumer, download writes a destination that accepts out-of-order writes, so it can fetch several byte ranges of the same file at once. That is what makes a single large file transfer faster than one connection on a high-latency link.

dest is anything that accepts bytes - BinarySink is structural, so open(path, "wb"), an io.BytesIO, a gzip.GzipFile, a SpooledTemporaryFile and a socket stream all qualify. A text stream deliberately does not: storage yields bytes, and a parallel download splits a file at byte offsets that can fall inside a multi-byte character. The local write is a call the core performs directly, not an operation on the storage port.

Ranges are written with os.pwrite only when the sink is a standard-library file writer, whose contract is that the bytes it is handed reach its descriptor unchanged. Any other sink streams sequentially and gets identical bytes - that includes sinks that transform what they are given, such as gzip.GzipFile, which reports the underlying file's descriptor as its own. ranges=None splits a file at or above 64 MiB into up to eight ranges on a backend that advertises ranged_reads, and ranges=1 forces the sequential path. A sink without a usable file descriptor (a BytesIO, a pipe) streams sequentially; the bytes written are identical either way. See Tune transfers.

stat

stat(path: StrPathLike | None = None) -> FileProperties

Facts about a path: kind, size, and (where the backend supports it) content type and custom metadata.

du

du(path: StrPathLike | None = None) -> int

Total size in bytes of a tree (apparent content bytes).

exists, isfile, isdir

exists(path: StrPathLike | None = None) -> bool
isfile(path: StrPathLike | None = None) -> bool
isdir(path: StrPathLike | None = None) -> bool

Existence and kind checks.

Writing and creating

echo

echo(
    data: DataBuffer[str] | DataBuffer[bytes],
    path: StrPathLike,
    /,
    *,
    chunk_size: int | None = None,
    mode: EchoMode = "w",
    content_type: str | None = None,
    metadata: Mapping[str, str] | None = None,
) -> None

Write data to path. data accepts native Python: bytes, str, a Buffer, an open file (IO), or an iterable of chunks (and an async iterable under storix.aio). mode="a" appends. content_type and metadata are applied where the backend supports them. chunk_size is the maximum target backend write batch; None uses its preferred default. Tiny iterator yields are combined and oversized values are split. Zero or negative values raise ValueError.

touch

touch(path: StrPathLike, /, *paths: StrPathLike) -> None

Create empty files, or refresh their modification time if they exist.

mkdir

mkdir(path: StrPathLike, /, *paths: StrPathLike, parents: bool = False) -> None

Create directories. parents=True creates missing parents and does not error if the directory already exists (unix mkdir -p).

set_metadata

set_metadata(path: StrPathLike, metadata: Mapping[str, str], /, *, merge: bool = False) -> None

Replace a file's custom metadata ({} clears it). merge=True does a non-atomic read-modify-write to add keys instead of replacing. Requires the custom_metadata capability.

Moving, copying, removing

Move and copy are variadic; the last argument is the destination, like unix.

mv

mv(source: StrPathLike, /, *paths: StrPathLike) -> None

Move or rename. With more than two paths, all sources move into the final directory.

cp

cp(source: StrPathLike, /, *paths: StrPathLike, recursive: bool = False) -> None

Copy. recursive=True to copy directories.

rm

rm(path: StrPathLike, /, *paths: StrPathLike, recursive: bool = False) -> None

Remove files. recursive=True removes directories and their contents (unix rm -r).

rmdir

rmdir(path: StrPathLike, /, *paths: StrPathLike) -> None

Remove strictly empty directories.

URLs and sharing

url

url(path: StrPathLike | None = None, *, expires_in: int | None = None) -> str

A presigned URL (an Azure SAS link). Requires the presigned_urls capability; raises UnsupportedOperationError otherwise. Use DataUrlLayer to get one on any backend.

data_url

data_url(path: StrPathLike | None = None) -> str

A base64 data: URL, generated by the core for any backend.

Composition

with_layer, with_layer_missing

with_layer(layer, /, *args, **kwargs) -> Self
with_layer_missing(layer, /, *args, **kwargs) -> Self

Return a new session with layer wrapping this backend. with_layer_missing skips the layer when the backend already provides its capability. See Layers.

without_layer, uncached

without_layer(*layers: type) -> Self
uncached -> Self          # property; sugar for without_layer(CacheLayer)

Return a session with the given layer types bypassed. Absent layers are a no-op; a SandboxLayer cannot be stripped.

chroot, scratch

chroot(path: StrPathLike, /) -> Self
scratch(*, root: StrPathLike | None = None, prefix: str = "scratch-") -> ContextManager[Storix]

chroot returns a sandboxed session rooted at path. scratch is a context manager giving a disposable (or pinned, with root) workspace on the same backend.

Lifecycle

close() -> None

Release the backend's resources (idempotent). In async code, use the session as a context manager: async with get_storage("azure") as fs: ....