tigris-boto3-ext

Extend boto3 with Tigris-specific features — snapshots, forks, soft delete, in-place rename and the Bundle API — while keeping full boto3 compatibility. You keep your boto3.client("s3"); this library adds the Tigris headers to the right requests and otherwise stays out of the way.

Installation

pip install tigris-boto3-ext

Requires Python 3.9+ and boto3 >= 1.26.0.

Quick start

import boto3
from tigris_boto3_ext import (
    create_snapshot,
    create_snapshot_bucket,
    get_object_from_snapshot,
    get_snapshot_version,
)

s3 = boto3.client("s3", endpoint_url="https://t3.storage.dev")

create_snapshot_bucket(s3, "my-bucket")
s3.put_object(Bucket="my-bucket", Key="config.json", Body=b'{"version": 1}')
version = get_snapshot_version(create_snapshot(s3, "my-bucket", snapshot_name="v1"))

s3.put_object(Bucket="my-bucket", Key="config.json", Body=b'{"version": 2}')
old = get_object_from_snapshot(s3, "my-bucket", "config.json", version)["Body"].read()
# b'{"version": 1}'

Credentials come from the usual boto3 sources: environment variables, a profile, or keyword arguments to boto3.client.

Features

Everything is imported from tigris_boto3_ext. Each page lists the feature's helpers, context manager and decorator, the headers it sends, and its caveats.

FeatureWhat it doesStart withDocsExample
Create bucketPublic access, default storage class, region placement and anonymous listing at creationcreate_bucketdocs/create-bucket.mdcreate_bucket_usage.py
Bucket settingsChange access, default storage class, placement, Cache-Control, custom domain, response headers and snapshot type after creationupdate_bucketdocs/settings.mdsettings_usage.py
Object notificationsDeliver object events to a webhook, with optional filter and authenticationset_bucket_notificationsdocs/notifications.mdnotifications_usage.py
CORSboto3's standard CORS calls work on Tigris; the constraints Tigris addss3.put_bucket_corsdocs/cors.md—
Lifecycle rulesboto3's standard lifecycle calls work on Tigris; the constraints Tigris addss3.put_bucket_lifecycle_configurationdocs/lifecycle.md—
Data migrationTake over a bucket from another S3 provider without an upfront copy: configure the source, list either side, schedule and check object copiesset_bucket_migration, migrate_object, is_object_migrated, list_objects_from_sourcedocs/migration.mdmigration_usage.py
Archive restoreThaw objects in the GLACIER storage class and follow the restorerestore_object, get_restore_infodocs/restore.mdrestore_usage.py
Presigned URLsDownload or upload URLs from Tigris's presign endpoint, served from the public or custom domain, optionally pinned to a snapshotpresigndocs/presign.mdpresign_usage.py
SnapshotsPoint-in-time copies of a bucket: create, list, read from, deletecreate_snapshot_bucket, create_snapshot, get_object_from_snapshotdocs/snapshots.mdsnapshots_usage.py
ForksA new bucket from an existing bucket or one of its snapshots, zero-copy; list a bucket's forkscreate_fork, list_forksdocs/forks.mdforks_usage.py
Bucket infoSnapshot and fork flags from HeadBucket; placement, storage class, forks, soft delete and size from the metadata endpointget_bucket_info, get_bucket_metadatadocs/bucket-info.mdbucket_info_usage.py
Bucket listingList buckets with type, visibility, regions, forks and ownerlist_bucketsdocs/listing.mdlisting_usage.py
Storage statsAccount totals: buckets, objects, unique objects, bytesget_statsdocs/stats.mdstats_usage.py
Object renameMove an object to a new key without rewriting its datarename_objectdocs/rename.mdrename_usage.py
Soft deleteDeleted objects and buckets stay recoverable for 7–90 days: enable, list, restore, purgecreate_soft_delete_bucket, set_bucket_soft_delete, restore_deleted_object, restore_bucketdocs/soft-delete.mdsoft_delete_usage.py
Force deleteDelete a bucket that still contains objectsforce_delete_bucketdocs/force-delete.mdforce_delete_usage.py
Bundle APIThousands of objects in one request, as a streaming tar archivebundle_objectsdocs/bundle.mdbundle_usage.py

Usage patterns

Create bucket, snapshots, forks, rename, soft delete and the migration list source each come in three shapes. They do the same thing; pick by how much code the Tigris behaviour should cover. Bucket info, force delete and the Bundle API are single calls and come as helpers only.

Helper functions

One call, one request. The header is registered on the client for the duration of that call and removed before it returns, so sequential code never sees it; concurrent code on the same client can, see Thread safety. The default choice.

from tigris_boto3_ext import rename_object

rename_object(s3, "my-bucket", "old-name.txt", "new-name.txt")

Context managers

Several plain boto3 calls under one Tigris setting. Reach for this when you have a batch to do and the helpers would mean repeating yourself.

from tigris_boto3_ext import TigrisSnapshot

with TigrisSnapshot(s3, "my-bucket", snapshot_version=version):
    config = s3.get_object(Bucket="my-bucket", Key="config.json")
    listing = s3.list_objects_v2(Bucket="my-bucket", Prefix="data/")

Decorators

Wrap an existing function so its boto3 calls run under the setting, without changing its body. The wrapped function must take the client as its first argument.

from tigris_boto3_ext import forked_from

@forked_from("production-data")
def create_dev_environment(s3_client, name):
    return s3_client.create_bucket(Bucket=name)

create_dev_environment(s3, "dev-environment")

Decorators without options (@snapshot_enabled, @with_rename, @with_soft_delete_view) are applied bare. The others are called: @with_snapshot("bucket") and @forked_from("source-bucket") need their bucket, and @soft_delete_enabled(retention_days=30) takes an optional window, so @soft_delete_enabled() with empty parentheses is also valid.

How it works

boto3 lets you hook into a request just before it is signed. This library registers handlers on the client's before-sign.s3.<Operation> events and adds the X-Tigris-* header the feature needs, so the header is covered by the SigV4 signature and the request is otherwise a normal S3 call. When the helper returns, or the context manager or decorated function exits, the handler is removed. Each feature page lists its headers.

Contexts nest. When two active contexts set the same header on the same operation, the inner one wins.

A few features talk to Tigris endpoints that are not S3 operations — the Bundle API (/{bucket}?bundle), bucket metadata (/{bucket}?metadata), bucket settings and soft delete (PATCH /{bucket}), the bucket listing with Tigris details and account stats (GET /?Include…), fork listing (X-Tigris-Fork) and deleted-bucket listing and restore (/?OnlyDeleted=true, /{bucket}?restore). For those the library signs and sends the request itself with the client's credentials, region and endpoint, and a gateway error raises TigrisAPIError (status_code, code, message, request_id).

Thread safety

Header injection is registered on the boto3 client, not on a single request. While a context manager, decorator or helper from this library is active, every matching operation on that client carries the injected headers, including operations issued from other threads. Use a separate client per thread when combining these features with concurrent work.

Helpers whose header changes what a destructive request does go one step further: they raise RuntimeError instead of running while another header injector is active for the same operation on the same client, rather than risk the wrong header on the wrong request. delete_snapshot and force_delete_bucket both send DeleteBucket, so they exclude each other; restore_deleted_object guards RestoreObject. Unrelated operations are not blocked.

Development

Setup

git clone https://github.com/tigrisdata/tigris-boto3-ext.git
cd tigris-boto3-ext

# Install with dev dependencies using uv
uv sync --all-extras

# Or with pip
pip install -e ".[dev]"

Running tests

# Unit tests: mocked clients, no network
uv run pytest tests/ --ignore=tests/integration -v

# Integration tests run against Tigris and are skipped when these are not set
export AWS_ENDPOINT_URL_S3="https://t3.storage.dev"
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
uv run pytest tests/integration/ -v

See tests/integration/README.md for the integration test setup in detail.

Code quality

# Type checking
uv run mypy tigris_boto3_ext

# Linting
uv run ruff check tigris_boto3_ext

# Auto-fix linting issues
uv run ruff check --fix tigris_boto3_ext

# Code formatting
uv run ruff format tigris_boto3_ext

# Check formatting without making changes
uv run ruff format --check tigris_boto3_ext

License

Apache-2.0

Contributing

Contributions welcome! Please open an issue or PR on GitHub.

A feature ships as a set: its module under tigris_boto3_ext/buckets/ or tigris_boto3_ext/objects/, a unit test, an integration test, a page under docs/, an example under examples/, and a row in the table above. CLAUDE.md describes the module layout.

Support

For issues and questions: