Skip to content

Object-store paths

S3Path, GSPath and AZPath are pathapi.Path classes for the objects of an Amazon S3 bucket, a Google Cloud Storage bucket and an Azure Blob Storage container. They support what pathlib.Path does on a file system (open, read_text, write_bytes, iterdir, glob, stat, mkdir, unlink, rmdir, rename, ...) and what pathapi adds (walk, copy, move, rm, PathSyncer).

Naming the bucket

The bucket (the container, for Azure) is a keyword and the segments are the key:

from objpath import AZPath, GSPath, S3Path

S3Path("reports/2026.csv", bucket="data")
GSPath("models", "latest", bucket="ml")                      # the key models/latest
AZPath("logs/app.log", container="ops", account="mycompany")

A path joined from another keeps its bucket and its backend, and so do parent, with_name, iterdir and relative_to; bucket= on a joined path moves it to another bucket of the same backend. reports/2026.csv and /reports/2026.csv are one key. A trailing / is dropped, and an empty segment inside a key (a//b) is part of it.

Backends and credentials

A backend is the SDK client; paths that hold the same backend share its connection pool. Client options build the path's own backend, backend= shares one, and a path with neither uses the default backend of the process (AZPath: of the account):

from objpath import AZBackend, S3Backend, S3Path

S3Path("x", bucket="b", region_name="eu-west-1")                 # its own S3Backend
backend = S3Backend(endpoint_url="http://127.0.0.1:9000")        # MinIO, say
S3Path("x", bucket="b", backend=backend)                         # a shared one
AZBackend(connection_string="...")                               # names its account itself

Giving backend= together with a client option is a TypeError. Credentials are whatever the SDK finds, or the options you pass; a pickled path carries none of them, so the receiving process builds the default backend. A backend is a context manager: leaving the block closes the SDK client, and a later use builds another.

Timeouts and retries are the SDK's own: botocore's config=Config(...), timeout= and retry= on GSBackend, and the retry_*, connection_timeout= and read_timeout= options of the Azure client.

Directories

An object store has none. is_dir() is true when some key starts with "<path>/", mkdir() writes a zero-byte "<path>/" marker, and rmdir() needs an empty prefix. A listing is one level at a time with a delimiter and carries each object's size and time, so is_dir() and stat() of a listed child cost nothing more.

Some keys cannot be reached by a walk: one with an empty or ./.. segment, the subtree under a key that is also an object. copy(recursive=True), move() and rm(recursive=True) of a prefix that holds one raise UnreachableKeysError before they change anything, naming the keys.

Reading and writing

A read streams one version of the object; an object replaced while it is being read raises ObjectChangedError. A write spools in memory (then a temporary file) and uploads on close(); if the with block raised, nothing is uploaded. open("x") is a conditional create. A file copy() within one store is one request of the store, and rm(recursive=True) deletes in batches.

Errors

A failed request is the OSError pathlib raises for it, with the path as filename: a missing key or bucket is FileNotFoundError, a refusal PermissionError, a request that ran out of time TimeoutError, an unreachable endpoint ConnectionError. Nothing of the SDKs crosses the API, and the message carries the SDK's exception type but not its text (which holds the request URL).