objectstore_client package

class objectstore_client.Client(base_url: str, metrics_backend: MetricsBackend | None = None, retries: int | None = None, timeout_ms: float | None = None, connection_kwargs: Mapping[str, Any] | None = None, token: TokenGenerator | str | None = None, spool_dir: str | PathLike[str] | None = None)[source]

Bases: object

A client for Objectstore. Constructing it initializes a connection pool.

Parameters:
  • base_url – The base URL of the Objectstore server (e.g., “http://objectstore:8888”). metrics_backend: Optional metrics backend for tracking storage operations. Defaults to NoOpMetricsBackend if not provided.

  • retries – Number of connection retries for failed requests. Defaults to 3 if not specified. Note: only connection failures are retried, not read failures (as compression streams cannot be rewound).

  • timeout_ms –

    Read timeout in milliseconds for API requests. The read timeout is the maximum time to wait between consecutive read operations on the socket (i.e., between receiving chunks of data). Defaults to no read timeout if not specified. The connection timeout is always 100ms. To override the connection timeout, pass a custom urllib3.Timeout object via connection_kwargs. For example:

    client = Client(
        "http://objectstore:8888", connection_kwargs={
            "timeout": urllib3.Timeout(connect=1.0, read=5.0)
        }
    )
    

  • connection_kwargs – Additional keyword arguments to pass to the underlying urllib3 connection pool (e.g., custom headers, SSL settings, advanced timeouts). By default, requests carry a User-Agent header of objectstore-client/{version}; pass a headers mapping with a User-Agent key here to override it.

  • token – A TokenGenerator that signs a fresh JWT for each request using an EdDSA keypair, or a static pre-signed JWT string used as-is for every request. Use a TokenGenerator for internal services that have access to the signing key, and a string for external services that receive a token from another source.

  • spool_dir – Directory that temporary files are created in when preparing resumable uploads. None uses Python’s default temporary directory.

session(usecase: Usecase, **scopes: str | int | bool) → Session[source]

Create a [Session] with the Objectstore server, tied to a specific [Usecase] and [Scope].

A Scope is a (possibly nested) namespace within a Usecase, given as a sequence of key-value pairs passed as kwargs. IMPORTANT: the order of the kwargs matters!

The admitted characters for keys and values are: A-Za-z0-9_-()$!+’.

Users are free to choose the scope structure that best suits their Usecase. The combination of Usecase and Scope will determine the physical key/path of the blob in the underlying storage backend.

For most usecases, it’s recommended to use the organization and project ID as the first components of the scope, as follows: ` client.session(usecase, org=organization_id, project=project_id, ...) `

Parameters:
  • usecase – The Usecase to scope this session to.

  • **scopes – Key-value pairs defining the scope within the usecase.

exception objectstore_client.ExpiryExtensionRejected(message: str, status: int | None, response: str | None)[source]

Bases: RequestError

The object’s expiration deadline could not be extended.

The object is non-expiring, lacks required creation metadata, or conflicts with a conditional update. A conflict can result from concurrent deletion, so this does not guarantee that the object still exists.

class objectstore_client.GetResponse(metadata, payload)[source]

Bases: NamedTuple

metadata: Metadata

Alias for field number 0

payload: IO[bytes]

Alias for field number 1

class objectstore_client.Metadata(content_type: 'str | None', compression: 'Compression | None', expiration_policy: 'ExpirationPolicy | None', time_created: 'datetime | None', time_expires: 'datetime | None', origin: 'str | None', filename: 'str | None', size: 'int | None', custom: 'dict[str, str]')[source]

Bases: object

compression: Literal['zstd', 'gzip'] | Literal['none'] | None
content_type: str | None
custom: dict[str, str]
expiration_policy: TimeToIdle | TimeToLive | None
filename: str | None

An optional filename associated with this object.

When present, the server includes a Content-Disposition header in GET responses, prompting browsers and download tools to save the file under this name.

classmethod from_headers(headers: Mapping[str, str]) → Metadata[source]
origin: str | None

The origin of the object, typically the IP address of the original source.

This tracks where the payload was originally obtained from (e.g., the IP of a Sentry SDK or CLI).

size: int | None

The size of the complete stored object in bytes.

This is always the size of the whole object, even when only a range of it was requested. For a compressed object it is the compressed size, matching the bytes on the wire.

This field is computed by the server, it cannot be set by clients.

time_created: datetime | None

Timestamp indicating when the object was created or the last time it was replaced.

This means that a PUT request to an existing object causes this value to be bumped. This field is computed by the server, it cannot be set by clients.

time_expires: datetime | None

Timestamp indicating when the object will expire.

When using a Time To Idle expiration policy, this value will reflect the expiration timestamp present prior to the current access to the object.

This field is computed by the server. Use expiration_policy to set the policy on upload, or Session.extend_expiry to extend an existing deadline.

class objectstore_client.MetricsBackend(*args, **kwargs)[source]

Bases: Protocol

An abstract class that defines the interface for metrics backends.

abstractmethod distribution(name: str, value: int | float, tags: Mapping[str, str] | None = None, unit: str | None = None) → None[source]

Records a distribution metric.

abstractmethod gauge(name: str, value: int | float, tags: Mapping[str, str] | None = None) → None[source]

Sets a gauge metric to the given value.

abstractmethod increment(name: str, value: int | float = 1, tags: Mapping[str, str] | None = None) → None[source]

Increments a counter metric by a given value.

class objectstore_client.NoOpMetricsBackend(*args, **kwargs)[source]

Bases: MetricsBackend

Default metrics backend that does not record anything.

distribution(name: str, value: int | float, tags: Mapping[str, str] | None = None, unit: str | None = None) → None[source]

Records a distribution metric.

gauge(name: str, value: int | float, tags: Mapping[str, str] | None = None) → None[source]

Sets a gauge metric to the given value.

increment(name: str, value: int | float = 1, tags: Mapping[str, str] | None = None) → None[source]

Increments a counter metric by a given value.

exception objectstore_client.ObjectNotFound(message: str, status: int | None, response: str | None)[source]

Bases: RequestError

The object was observed to be absent or expired.

class objectstore_client.Permission(value)[source]

Bases: StrEnum

Enum listing permissions that Objectstore tokens may be granted.

OBJECT_DELETE = 'object.delete'
OBJECT_READ = 'object.read'
OBJECT_WRITE = 'object.write'
classmethod max() → list[Self][source]
exception objectstore_client.RequestError(message: str, status: int | None, response: str | None)[source]

Bases: Exception

Exception raised if an API call to Objectstore fails.

class objectstore_client.SecretKey(kid: str, secret_key: str)[source]

Bases: object

An EdDSA keypair used to authenticate with Objectstore.

kid is the key ID that the Objectstore server uses to load the corresponding public key from its configuration, and secret_key is the PEM-encoded EdDSA private key.

A SecretKey only identifies the signer. To authenticate requests, wrap it in a TokenGenerator, which adds the expiry and permissions for the tokens it signs.

signature_for_canonical_form(canonical_form: str) → str[source]

Signs a canonical request form with the Ed25519 private key.

Returns the base64url-encoded (no padding) signature, suitable as the value of the os_sig query parameter.

class objectstore_client.Session(pool: HTTPConnectionPool, base_path: str, metrics_backend: MetricsBackend, usecase: Usecase, scope: Scope, token: TokenGenerator | str | None = None, spool_dir: str | PathLike[str] | None = None)[source]

Bases: object

A session with the Objectstore server, scoped to a specific [Usecase] and Scope.

This should never be constructed directly, use [Client.session].

delete(key: str) → None[source]

Deletes the blob with the given key.

extend_expiry(key: str, at: datetime | None = None, from_creation: timedelta | None = None, from_now: timedelta | None = None) → None[source]

Extend an object’s expiration deadline, preserving its payload.

Supply exactly one target: at is a timezone-aware absolute datetime; from_creation is total lifetime since creation or replacement; and from_now is lifetime from server request start. Relative targets are resolved by the server, not added to the existing deadline. Fractional duration seconds are truncated; absolute deadlines round upward to seconds.

An already-sufficient deadline succeeds without being shortened. Success returns no value and does not indicate whether the deadline changed. Requires object-write permission.

Actual extensions adjust TTL duration to approximately match the total lifetime since creation; TTI duration remains unchanged.

Raises ValueError for missing or multiple targets, naive datetimes, or negative durations. Zero durations are valid. Objects observed absent or expired raise ObjectNotFound. Rejected extensions raise ExpiryExtensionRejected for non-expiring or concurrently changed objects, or missing creation metadata needed for a creation-relative target. Both exceptions subclass RequestError. Rejection does not guarantee that the object still exists, since it may be deleted concurrently. Other HTTP errors propagate normally. Retrying from_now establishes a new server-time anchor and can extend the deadline further.

Example:

from objectstore_client import ExpiryExtensionRejected, ObjectNotFound

try:
    session.extend_expiry(key, from_now=timedelta(days=30))
except ObjectNotFound:
    print("Object is missing or expired")
except ExpiryExtensionRejected:
    print("Extension was rejected")
get(key: str, decompress: bool = True, accept_encoding: Sequence[str] | None = None) → GetResponse | None[source]

This fetches the blob with the given key, returning an IO stream that can be read.

Returns None if the object does not exist.

By default, content that was uploaded compressed will be automatically decompressed, unless decompress=False is passed.

If accept_encoding is provided, any compression algorithm listed there will be passed through compressed instead of decompressed, even when decompress=True. For example, passing accept_encoding=[“zstd”] returns the raw zstd-compressed bytes when the stored object is zstd-compressed.

head(key: str) → Metadata | None[source]

Checks whether an object exists and retrieves its metadata.

Returns the object’s Metadata if it exists, None otherwise.

If the object has a TTI expiration policy, this is considered an access, and therefore bumps its expiration.

mint_token(permissions: list[Permission] | None = None, expiry_seconds: int | None = None) → str[source]

Returns a signed token.

When permissions is None, the generator’s default permissions are used. When provided, they must be a subset of the generator’s permissions.

When expiry_seconds is None, the generator’s default expiry is used.

Raises ValueError if no TokenGenerator is configured or if any requested permission is not granted to the generator.

object_url(key: str, token_validity: timedelta | None = None, response_content_disposition: Literal['inline', 'attachment'] | None = None) → str[source]

Generates a GET url to the object with the given key.

This can then be used by downstream services to fetch the given object. NOTE however that the service does not strictly follow HTTP semantics, in particular in relation to Accept-Encoding.

When token_validity is provided, read-only authorization information is embedded in the returned URL’s query string, valid for the given duration.

response_content_disposition selects attachment (default) or inline for successful GET/HEAD responses, retaining the stored filename.

These options are not bound by the auth token and can be changed by recipients. Use presigned_object_url() to bind the options to a signature.

Raises ValueError if token_validity is provided but no TokenGenerator is configured on this session, or if disposition is invalid.

presigned_object_url(method: Literal['GET', 'HEAD'], key: str, duration: timedelta = datetime.timedelta(seconds=3600), response_content_disposition: Literal['inline', 'attachment'] | None = None) → str[source]

Generates a pre-signed URL authorizing a single method request on the object with the given key, valid for duration.

Warning

Experimental: pre-signed URLs are an experimental feature and this API may change in a future release.

Raises ValueError if no TokenGenerator is configured on this session’s client, if method is not supported, or if duration is above the one-week maximum, or if disposition is invalid.

The returned URL carries a signature that allows the recipient to perform a request without an auth token. It can be handed to any HTTP client; the URL is already percent-encoded, and must be transmitted verbatim.

response_content_disposition selects attachment (default) or inline for successful GET/HEAD responses, retaining the stored filename.

Response options are included in the signature when supplied.

Note that HEAD and GET are considered equivalent from the point of view of signatures, meaning that a signature for GET can also be used for HEAD, and viceversa.

put(contents: bytes | IO[bytes], key: str | None = None, compress: Literal['zstd', 'gzip'] | Literal['none'] | None = None, precompressed: Literal['zstd', 'gzip'] | Literal['none'] | None = None, content_type: str | None = None, metadata: dict[str, str] | None = None, expiration_policy: TimeToIdle | TimeToLive | None = None, origin: str | None = None, filename: str | None = None, resumable_threshold_bytes: int | Literal['none'] | None = None) → str[source]

Uploads the given contents to blob storage.

If no key is provided, one will be automatically generated and returned from this function.

The client will select the Usecase’s configured compression if none is given explicitly. This can be overridden by explicitly giving a compress argument. Providing “none” as the argument will instruct the client to not apply any compression to this upload, which is useful for uncompressible formats.

If contents is already compressed, pass the algorithm as precompressed instead. The payload is then uploaded verbatim and the algorithm is recorded in the object’s metadata, so downloads decompress it transparently.

You can use the utility function objectstore_client.utils.guess_mime_type to attempt to guess a content_type based on magic bytes.

Resumable Uploads

This method uses Objectstore’s Resumable Uploads API when the size of contents is at least resumable_threshold_bytes. Pass resumable_threshold_bytes="none" to disable Resumable Uploads. When not overridden, this threshold defaults to the one set on the Usecase.

Transient failures and writes that make no progress are retried up to twice. The retry count resets whenever the server reports a higher offset.

For seekable binary streams, the remaining size is measured by seeking to the end and restoring the original position. Prefer streams with efficient seeking: transforming streams may read their entire input to find the end. Non-seekable streams and streams whose size cannot be determined use a regular upload without spooling.

Resumable uploads reuse the input unless compression is needed, in which case the compressed payload is spooled in a temporary file (tempfile.TemporaryFile) on disk before sending. Set Client(spool_dir=<path>) if you wish to customize the directory where the temporary files are created in.

class objectstore_client.TimeToIdle(delta: 'timedelta')[source]

Bases: object

delta: timedelta
class objectstore_client.TimeToLive(delta: 'timedelta')[source]

Bases: object

delta: timedelta
objectstore_client.Token

alias of str

class objectstore_client.TokenGenerator(key: SecretKey, expiry_seconds: int = 60, permissions: list[Permission] | None = None)[source]

Bases: object

Generates auth tokens for Objectstore requests, signed with a SecretKey.

expiry_seconds and permissions are the defaults for every token this generator signs. Per-token overrides may shorten the expiry or narrow the permissions, but cannot grant permissions beyond permissions.

Pass a TokenGenerator directly to Client(token=...) and it will sign a fresh JWT for each request. Its key is also used to sign pre-signed URLs.

token_for_scope(usecase: str, scope: Scope, permissions: list[Permission] | None = None, expiry_seconds: int | None = None) → str[source]

Sign a JWT for the passed-in usecase and scope using this generator’s key, expiry, and permissions.

When permissions is None, the default permissions are used. When provided, they override the defaults for this token only.

When expiry_seconds is None, the default expiry is used.

Raises ValueError if any requested permission is not granted to this generator.

class objectstore_client.Usecase(name: str, compression: Literal['zstd', 'gzip', 'none'] = 'zstd', expiration_policy: TimeToIdle | TimeToLive | None = None, resumable_threshold_bytes: int | Literal['none'] = 268435456)[source]

Bases: object

An identifier for a workload in Objectstore, along with defaults to use for all operations within that Usecase.

Usecases need to be statically defined in Objectstore’s configuration server-side. Objectstore can make decisions based on the Usecase. For example, choosing the most suitable storage backend.

Names must be 3–64 ASCII characters from [A-Za-z0-9_-]. Any permitted character may appear in any position. Names are case-sensitive and are never normalized. The name is read-only after construction.

property name: str

The validated usecase name.

objectstore_client.parse_accept_encoding(header: str) → list[str][source]

Parse an Accept-Encoding header value for use in objectstore GET requests.

Returns a list of encoding names, normalized to lowercase and stripped of q-values, per RFC 7231. Encodings explicitly rejected via q=0 are excluded.

Note: identity;q=0 is not supported and will be silently ignored (i.e., treated as acceptable), since objectstore always serves an identity fallback.

Submodules

objectstore_client.auth module

class objectstore_client.auth.Permission(value)[source]

Bases: StrEnum

Enum listing permissions that Objectstore tokens may be granted.

OBJECT_DELETE = 'object.delete'
OBJECT_READ = 'object.read'
OBJECT_WRITE = 'object.write'
classmethod max() → list[Self][source]
class objectstore_client.auth.SecretKey(kid: str, secret_key: str)[source]

Bases: object

An EdDSA keypair used to authenticate with Objectstore.

kid is the key ID that the Objectstore server uses to load the corresponding public key from its configuration, and secret_key is the PEM-encoded EdDSA private key.

A SecretKey only identifies the signer. To authenticate requests, wrap it in a TokenGenerator, which adds the expiry and permissions for the tokens it signs.

signature_for_canonical_form(canonical_form: str) → str[source]

Signs a canonical request form with the Ed25519 private key.

Returns the base64url-encoded (no padding) signature, suitable as the value of the os_sig query parameter.

objectstore_client.auth.Token

A static, pre-signed JWT used as-is for every request.

class objectstore_client.auth.TokenGenerator(key: SecretKey, expiry_seconds: int = 60, permissions: list[Permission] | None = None)[source]

Bases: object

Generates auth tokens for Objectstore requests, signed with a SecretKey.

expiry_seconds and permissions are the defaults for every token this generator signs. Per-token overrides may shorten the expiry or narrow the permissions, but cannot grant permissions beyond permissions.

Pass a TokenGenerator directly to Client(token=...) and it will sign a fresh JWT for each request. Its key is also used to sign pre-signed URLs.

token_for_scope(usecase: str, scope: Scope, permissions: list[Permission] | None = None, expiry_seconds: int | None = None) → str[source]

Sign a JWT for the passed-in usecase and scope using this generator’s key, expiry, and permissions.

When permissions is None, the default permissions are used. When provided, they override the defaults for this token only.

When expiry_seconds is None, the default expiry is used.

Raises ValueError if any requested permission is not granted to this generator.

objectstore_client.auth.TokenProvider = objectstore_client.auth.TokenGenerator | str

Authentication provider for Objectstore requests.

Can be either a TokenGenerator that signs a fresh JWT per request, or a static pre-signed Token.

objectstore_client.client module

class objectstore_client.client.Client(base_url: str, metrics_backend: MetricsBackend | None = None, retries: int | None = None, timeout_ms: float | None = None, connection_kwargs: Mapping[str, Any] | None = None, token: TokenGenerator | str | None = None, spool_dir: str | PathLike[str] | None = None)[source]

Bases: object

A client for Objectstore. Constructing it initializes a connection pool.

Parameters:
  • base_url – The base URL of the Objectstore server (e.g., “http://objectstore:8888”). metrics_backend: Optional metrics backend for tracking storage operations. Defaults to NoOpMetricsBackend if not provided.

  • retries – Number of connection retries for failed requests. Defaults to 3 if not specified. Note: only connection failures are retried, not read failures (as compression streams cannot be rewound).

  • timeout_ms –

    Read timeout in milliseconds for API requests. The read timeout is the maximum time to wait between consecutive read operations on the socket (i.e., between receiving chunks of data). Defaults to no read timeout if not specified. The connection timeout is always 100ms. To override the connection timeout, pass a custom urllib3.Timeout object via connection_kwargs. For example:

    client = Client(
        "http://objectstore:8888", connection_kwargs={
            "timeout": urllib3.Timeout(connect=1.0, read=5.0)
        }
    )
    

  • connection_kwargs – Additional keyword arguments to pass to the underlying urllib3 connection pool (e.g., custom headers, SSL settings, advanced timeouts). By default, requests carry a User-Agent header of objectstore-client/{version}; pass a headers mapping with a User-Agent key here to override it.

  • token – A TokenGenerator that signs a fresh JWT for each request using an EdDSA keypair, or a static pre-signed JWT string used as-is for every request. Use a TokenGenerator for internal services that have access to the signing key, and a string for external services that receive a token from another source.

  • spool_dir – Directory that temporary files are created in when preparing resumable uploads. None uses Python’s default temporary directory.

session(usecase: Usecase, **scopes: str | int | bool) → Session[source]

Create a [Session] with the Objectstore server, tied to a specific [Usecase] and [Scope].

A Scope is a (possibly nested) namespace within a Usecase, given as a sequence of key-value pairs passed as kwargs. IMPORTANT: the order of the kwargs matters!

The admitted characters for keys and values are: A-Za-z0-9_-()$!+’.

Users are free to choose the scope structure that best suits their Usecase. The combination of Usecase and Scope will determine the physical key/path of the blob in the underlying storage backend.

For most usecases, it’s recommended to use the organization and project ID as the first components of the scope, as follows: ` client.session(usecase, org=organization_id, project=project_id, ...) `

Parameters:
  • usecase – The Usecase to scope this session to.

  • **scopes – Key-value pairs defining the scope within the usecase.

class objectstore_client.client.GetResponse(metadata, payload)[source]

Bases: NamedTuple

metadata: Metadata

Alias for field number 0

payload: IO[bytes]

Alias for field number 1

class objectstore_client.client.Session(pool: HTTPConnectionPool, base_path: str, metrics_backend: MetricsBackend, usecase: Usecase, scope: Scope, token: TokenGenerator | str | None = None, spool_dir: str | PathLike[str] | None = None)[source]

Bases: object

A session with the Objectstore server, scoped to a specific [Usecase] and Scope.

This should never be constructed directly, use [Client.session].

delete(key: str) → None[source]

Deletes the blob with the given key.

extend_expiry(key: str, at: datetime | None = None, from_creation: timedelta | None = None, from_now: timedelta | None = None) → None[source]

Extend an object’s expiration deadline, preserving its payload.

Supply exactly one target: at is a timezone-aware absolute datetime; from_creation is total lifetime since creation or replacement; and from_now is lifetime from server request start. Relative targets are resolved by the server, not added to the existing deadline. Fractional duration seconds are truncated; absolute deadlines round upward to seconds.

An already-sufficient deadline succeeds without being shortened. Success returns no value and does not indicate whether the deadline changed. Requires object-write permission.

Actual extensions adjust TTL duration to approximately match the total lifetime since creation; TTI duration remains unchanged.

Raises ValueError for missing or multiple targets, naive datetimes, or negative durations. Zero durations are valid. Objects observed absent or expired raise ObjectNotFound. Rejected extensions raise ExpiryExtensionRejected for non-expiring or concurrently changed objects, or missing creation metadata needed for a creation-relative target. Both exceptions subclass RequestError. Rejection does not guarantee that the object still exists, since it may be deleted concurrently. Other HTTP errors propagate normally. Retrying from_now establishes a new server-time anchor and can extend the deadline further.

Example:

from objectstore_client import ExpiryExtensionRejected, ObjectNotFound

try:
    session.extend_expiry(key, from_now=timedelta(days=30))
except ObjectNotFound:
    print("Object is missing or expired")
except ExpiryExtensionRejected:
    print("Extension was rejected")
get(key: str, decompress: bool = True, accept_encoding: Sequence[str] | None = None) → GetResponse | None[source]

This fetches the blob with the given key, returning an IO stream that can be read.

Returns None if the object does not exist.

By default, content that was uploaded compressed will be automatically decompressed, unless decompress=False is passed.

If accept_encoding is provided, any compression algorithm listed there will be passed through compressed instead of decompressed, even when decompress=True. For example, passing accept_encoding=[“zstd”] returns the raw zstd-compressed bytes when the stored object is zstd-compressed.

head(key: str) → Metadata | None[source]

Checks whether an object exists and retrieves its metadata.

Returns the object’s Metadata if it exists, None otherwise.

If the object has a TTI expiration policy, this is considered an access, and therefore bumps its expiration.

mint_token(permissions: list[Permission] | None = None, expiry_seconds: int | None = None) → str[source]

Returns a signed token.

When permissions is None, the generator’s default permissions are used. When provided, they must be a subset of the generator’s permissions.

When expiry_seconds is None, the generator’s default expiry is used.

Raises ValueError if no TokenGenerator is configured or if any requested permission is not granted to the generator.

object_url(key: str, token_validity: timedelta | None = None, response_content_disposition: Literal['inline', 'attachment'] | None = None) → str[source]

Generates a GET url to the object with the given key.

This can then be used by downstream services to fetch the given object. NOTE however that the service does not strictly follow HTTP semantics, in particular in relation to Accept-Encoding.

When token_validity is provided, read-only authorization information is embedded in the returned URL’s query string, valid for the given duration.

response_content_disposition selects attachment (default) or inline for successful GET/HEAD responses, retaining the stored filename.

These options are not bound by the auth token and can be changed by recipients. Use presigned_object_url() to bind the options to a signature.

Raises ValueError if token_validity is provided but no TokenGenerator is configured on this session, or if disposition is invalid.

presigned_object_url(method: Literal['GET', 'HEAD'], key: str, duration: timedelta = datetime.timedelta(seconds=3600), response_content_disposition: Literal['inline', 'attachment'] | None = None) → str[source]

Generates a pre-signed URL authorizing a single method request on the object with the given key, valid for duration.

Warning

Experimental: pre-signed URLs are an experimental feature and this API may change in a future release.

Raises ValueError if no TokenGenerator is configured on this session’s client, if method is not supported, or if duration is above the one-week maximum, or if disposition is invalid.

The returned URL carries a signature that allows the recipient to perform a request without an auth token. It can be handed to any HTTP client; the URL is already percent-encoded, and must be transmitted verbatim.

response_content_disposition selects attachment (default) or inline for successful GET/HEAD responses, retaining the stored filename.

Response options are included in the signature when supplied.

Note that HEAD and GET are considered equivalent from the point of view of signatures, meaning that a signature for GET can also be used for HEAD, and viceversa.

put(contents: bytes | IO[bytes], key: str | None = None, compress: Literal['zstd', 'gzip'] | Literal['none'] | None = None, precompressed: Literal['zstd', 'gzip'] | Literal['none'] | None = None, content_type: str | None = None, metadata: dict[str, str] | None = None, expiration_policy: TimeToIdle | TimeToLive | None = None, origin: str | None = None, filename: str | None = None, resumable_threshold_bytes: int | Literal['none'] | None = None) → str[source]

Uploads the given contents to blob storage.

If no key is provided, one will be automatically generated and returned from this function.

The client will select the Usecase’s configured compression if none is given explicitly. This can be overridden by explicitly giving a compress argument. Providing “none” as the argument will instruct the client to not apply any compression to this upload, which is useful for uncompressible formats.

If contents is already compressed, pass the algorithm as precompressed instead. The payload is then uploaded verbatim and the algorithm is recorded in the object’s metadata, so downloads decompress it transparently.

You can use the utility function objectstore_client.utils.guess_mime_type to attempt to guess a content_type based on magic bytes.

Resumable Uploads

This method uses Objectstore’s Resumable Uploads API when the size of contents is at least resumable_threshold_bytes. Pass resumable_threshold_bytes="none" to disable Resumable Uploads. When not overridden, this threshold defaults to the one set on the Usecase.

Transient failures and writes that make no progress are retried up to twice. The retry count resets whenever the server reports a higher offset.

For seekable binary streams, the remaining size is measured by seeking to the end and restoring the original position. Prefer streams with efficient seeking: transforming streams may read their entire input to find the end. Non-seekable streams and streams whose size cannot be determined use a regular upload without spooling.

Resumable uploads reuse the input unless compression is needed, in which case the compressed payload is spooled in a temporary file (tempfile.TemporaryFile) on disk before sending. Set Client(spool_dir=<path>) if you wish to customize the directory where the temporary files are created in.

class objectstore_client.client.Usecase(name: str, compression: Literal['zstd', 'gzip', 'none'] = 'zstd', expiration_policy: TimeToIdle | TimeToLive | None = None, resumable_threshold_bytes: int | Literal['none'] = 268435456)[source]

Bases: object

An identifier for a workload in Objectstore, along with defaults to use for all operations within that Usecase.

Usecases need to be statically defined in Objectstore’s configuration server-side. Objectstore can make decisions based on the Usecase. For example, choosing the most suitable storage backend.

Names must be 3–64 ASCII characters from [A-Za-z0-9_-]. Any permitted character may appear in any position. Names are case-sensitive and are never normalized. The name is read-only after construction.

property name: str

The validated usecase name.

objectstore_client.errors module

exception objectstore_client.errors.ExpiryExtensionRejected(message: str, status: int | None, response: str | None)[source]

Bases: RequestError

The object’s expiration deadline could not be extended.

The object is non-expiring, lacks required creation metadata, or conflicts with a conditional update. A conflict can result from concurrent deletion, so this does not guarantee that the object still exists.

exception objectstore_client.errors.ObjectNotFound(message: str, status: int | None, response: str | None)[source]

Bases: RequestError

The object was observed to be absent or expired.

exception objectstore_client.errors.RequestError(message: str, status: int | None, response: str | None)[source]

Bases: Exception

Exception raised if an API call to Objectstore fails.

objectstore_client.errors.raise_for_status(response: BaseHTTPResponse, *, error_type: type[RequestError] = <class 'objectstore_client.errors.RequestError'>) → None[source]

objectstore_client.metadata module

class objectstore_client.metadata.Metadata(content_type: 'str | None', compression: 'Compression | None', expiration_policy: 'ExpirationPolicy | None', time_created: 'datetime | None', time_expires: 'datetime | None', origin: 'str | None', filename: 'str | None', size: 'int | None', custom: 'dict[str, str]')[source]

Bases: object

compression: Literal['zstd', 'gzip'] | Literal['none'] | None
content_type: str | None
custom: dict[str, str]
expiration_policy: TimeToIdle | TimeToLive | None
filename: str | None

An optional filename associated with this object.

When present, the server includes a Content-Disposition header in GET responses, prompting browsers and download tools to save the file under this name.

classmethod from_headers(headers: Mapping[str, str]) → Metadata[source]
origin: str | None

The origin of the object, typically the IP address of the original source.

This tracks where the payload was originally obtained from (e.g., the IP of a Sentry SDK or CLI).

size: int | None

The size of the complete stored object in bytes.

This is always the size of the whole object, even when only a range of it was requested. For a compressed object it is the compressed size, matching the bytes on the wire.

This field is computed by the server, it cannot be set by clients.

time_created: datetime | None

Timestamp indicating when the object was created or the last time it was replaced.

This means that a PUT request to an existing object causes this value to be bumped. This field is computed by the server, it cannot be set by clients.

time_expires: datetime | None

Timestamp indicating when the object will expire.

When using a Time To Idle expiration policy, this value will reflect the expiration timestamp present prior to the current access to the object.

This field is computed by the server. Use expiration_policy to set the policy on upload, or Session.extend_expiry to extend an existing deadline.

class objectstore_client.metadata.TimeToIdle(delta: 'timedelta')[source]

Bases: object

delta: timedelta
class objectstore_client.metadata.TimeToLive(delta: 'timedelta')[source]

Bases: object

delta: timedelta
objectstore_client.metadata.format_expiration(expiration_policy: TimeToIdle | TimeToLive) → str[source]
objectstore_client.metadata.format_timedelta(delta: timedelta) → str[source]

Formats a duration in the wire format, such as 400d 1m 30s.

Days are the largest unit, components that are zero are omitted, and a zero duration is written as 0s. Any sub-second remainder is truncated.

objectstore_client.metadata.itertools_batched(iterable: Iterable[T], n: int, strict: bool = False) → Iterator[tuple[T, ...]][source]

Vendored version of itertools.batched, not available in Python 3.11. Batch data from the iterable into tuples of length n. The last batch may be shorter than n. If strict is true, will raise a ValueError if the final batch is shorter than n. Loops over the input iterable and accumulates data into tuples up to size n. The input is consumed lazily, just enough to fill a batch. The result is yielded as soon as the batch is full or when the input iterable is exhausted:

objectstore_client.metadata.parse_expiration(value: str) → TimeToIdle | TimeToLive | None[source]
objectstore_client.metadata.parse_timedelta(delta: str) → timedelta[source]

Parses a duration in the wire format, such as 400d 1m 30s.

Units are matched exactly, so anything outside the wire format raises a ValueError rather than being mistaken for a unit it merely starts with.

objectstore_client.metrics module

class objectstore_client.metrics.MetricsBackend(*args, **kwargs)[source]

Bases: Protocol

An abstract class that defines the interface for metrics backends.

abstractmethod distribution(name: str, value: int | float, tags: Mapping[str, str] | None = None, unit: str | None = None) → None[source]

Records a distribution metric.

abstractmethod gauge(name: str, value: int | float, tags: Mapping[str, str] | None = None) → None[source]

Sets a gauge metric to the given value.

abstractmethod increment(name: str, value: int | float = 1, tags: Mapping[str, str] | None = None) → None[source]

Increments a counter metric by a given value.

class objectstore_client.metrics.NoOpMetricsBackend(*args, **kwargs)[source]

Bases: MetricsBackend

Default metrics backend that does not record anything.

distribution(name: str, value: int | float, tags: Mapping[str, str] | None = None, unit: str | None = None) → None[source]

Records a distribution metric.

gauge(name: str, value: int | float, tags: Mapping[str, str] | None = None) → None[source]

Sets a gauge metric to the given value.

increment(name: str, value: int | float = 1, tags: Mapping[str, str] | None = None) → None[source]

Increments a counter metric by a given value.

class objectstore_client.metrics.StorageMetricEmitter(backend: MetricsBackend, operation: str, usecase: str)[source]

Bases: object

maybe_record_compression_ratio() → None[source]
maybe_record_throughputs() → None[source]
record_compressed_size(value: int, compression: str = 'unknown') → None[source]
record_latency(elapsed: float) → None[source]
record_size(value: int) → None[source]
record_uncompressed_size(value: int) → None[source]
objectstore_client.metrics.measure_storage_operation(backend: MetricsBackend, operation: str, usecase: str, uncompressed_size: int | None = None, compressed_size: int | None = None, compression: str = 'unknown') → Generator[StorageMetricEmitter][source]

Context manager which records the latency of the enclosed storage operation. Can also record the compressed or uncompressed size of an object, the compression ratio, the throughput, and the inverse throughput.

Yields a StorageMetricEmitter because for some operations (GET) the size is not known until the inside of the enclosed block.

objectstore_client.presign module

Utilities for pre-signed URLs.

A pre-signed URL lets a client that owns an Ed25519 keypair hand out a time-limited URL that authorizes a specific request, without the recipient needing an auth token.

Experimental: pre-signed URLs are an experimental feature and this API may change in a future release.

See objectstore-types/src/presign.rs for details on the scheme.

objectstore_client.presign.build_canonical_form(method: str, encoded_path: str, encoded_query: str) → str[source]

Builds the canonical form of a request to be signed.

encoded_path and encoded_query must already be percent-encoded as they will appear on the wire (see objectstore_client.utils.encode_path() and objectstore_client.utils.encode_query()).

objectstore_client.scope module

class objectstore_client.scope.Scope(**scopes: str | int | bool)[source]

Bases: object

dict() → dict[str, str | int | bool][source]

objectstore_client.tracing module

objectstore_client.tracing.storage_span(operation: str, usecase: Usecase, scope: Scope, **attributes: SpanAttributeValue | None) → Generator[StreamedSpan][source]

Starts a span for a single Objectstore operation.

operation is the same literal already passed to measure_storage_operation, so the span’s op (objectstore.<operation>), its name, and the storage.<operation>.* metrics all derive from one source. Keyword arguments become objectstore.<key> span attributes, with None values dropped so call sites can pass optionals inline; attributes that are only known after the request completes can be set on the yielded span instead.

objectstore_client.utils module

objectstore_client.utils.decode_header_value(value: str) → str[source]

Decodes a percent-encoded HTTP header value into its logical string.

Inverse of encode_header_value(). Values without escape sequences are returned unchanged, so headers written before the encoding existed still read back as-is.

objectstore_client.utils.encode_header_value(value: str) → str[source]

Percent-encodes a free-form metadata string for an HTTP header value.

HTTP header values carry only visible ASCII, so anything outside that range has to be escaped to survive the transport (see _HEADER_VALUE_SAFE). This is the inverse of decode_header_value(); only the transport representation is escaped, never the logical string held in Metadata.

objectstore_client.utils.encode_path(path: str) → str[source]

Percent-encodes a request path as it will appear on the wire.

Leaves the RFC 3986 unreserved set, sub-delims, and : @ / unescaped (see _PATH_SAFE); everything else is percent-encoded. This treats the input as a literal string: a literal % becomes %25.

objectstore_client.utils.encode_query(query: str) → str[source]

Percent-encodes a query string as it will appear on the wire.

Same set as encode_path(), additionally leaving ? unescaped (see _QUERY_SAFE).

objectstore_client.utils.guess_mime_type(contents: bytes | IO[bytes]) → str | None[source]

Guesses the MIME type from the given contents.

Reads up to 261 bytes from the beginning of the content to determine the MIME type using file header signatures.

To guess the MIME type from a filename, use mimetypes.guess_type, which is part of the standard library.

objectstore_client.utils.parse_accept_encoding(header: str) → list[str][source]

Parse an Accept-Encoding header value for use in objectstore GET requests.

Returns a list of encoding names, normalized to lowercase and stripped of q-values, per RFC 7231. Encodings explicitly rejected via q=0 are excluded.

Note: identity;q=0 is not supported and will be silently ignored (i.e., treated as acceptable), since objectstore always serves an identity fallback.