tigris-python-sdk

v2026.09.24

Use when working with Tigris from Python — boto3 setup, Django uploads via django-storages, snapshots, bucket forking, in-place object rename, conditional writes (IfMatch/IfNoneMatch), and the Bundle API for batch ML data fetches. Covers the tigris-boto3-ext extension library (context managers, decorators, helpers) plus framework integration.

GitHub
Install command
npx skhub add tigrisdata/tigris-python-sdk
Markdown
SKILL.md

Tigris Python SDK

Use Tigris from Python via boto3 plus the tigris-boto3-ext extension package. Standard S3 calls work unchanged; the extension layers on Tigris-specific features — snapshots, bucket forking, in-place object rename, and the Bundle API for batch fetches — by injecting headers on activated boto3 events.

For Django uploads via django-storages, see ./resources/django.md.

Quick Start

# 1. Install CLI & authenticate
npm install -g @tigrisdata/cli
tigris login

# 2. Create bucket and access key
tigris buckets create my-bucket
tigris access-keys create "my-bucket-key"
# ⚠ Save the Secret Access Key — shown only once
tigris access-keys assign tid_xxx --bucket my-bucket --role Editor

# 3. Install Python deps
pip install boto3 tigris-boto3-ext
# .env
AWS_ENDPOINT_URL_S3=https://t3.storage.dev
AWS_ACCESS_KEY_ID=tid_xxx
AWS_SECRET_ACCESS_KEY=tsec_yyy
AWS_REGION=auto
import boto3
from tigris_boto3_ext import (
    TigrisSnapshotEnabled, create_snapshot, get_snapshot_version,
)

s3 = boto3.client("s3")  # picks up env vars

# Create a snapshot-enabled bucket (header injection scoped to the block)
with TigrisSnapshotEnabled(s3):
    s3.create_bucket(Bucket="my-bucket")

# Take a snapshot
result = create_snapshot(s3, "my-bucket", snapshot_name="daily-backup")
version = get_snapshot_version(result)

See Getting Started with CLI for detailed setup steps.


Getting Started with CLI

Step 1: Install CLI

npm install -g @tigrisdata/cli
tigris --version

t3 is an alias for tigris — all commands work with either.

Step 2: Authenticate

tigris login    # opens browser for OAuth
tigris whoami   # verify

For CI/CD:

tigris configure --access-key <key> --access-secret <secret>

Step 3: Create Bucket

tigris buckets create my-bucket

Buckets are private and global by default. Use --public for anonymous reads, --locations to pin regions.

Step 4: Create Access Key

tigris access-keys create "my-bucket-key"

Outputs an Access Key ID (tid_xxx) and Secret Access Key (tsec_yyy). The secret is shown only once.

Step 5: Assign Key to Bucket

tigris access-keys assign tid_xxx --bucket my-bucket --role Editor
RolePermissionsUse when
EditorRead + write + deleteApp code that uploads/deletes objects
ReadOnlyRead onlyApps that only download/list

Step 6: Configure Environment

# .env (add to .gitignore)
AWS_ENDPOINT_URL_S3=https://t3.storage.dev
AWS_ACCESS_KEY_ID=tid_xxx
AWS_SECRET_ACCESS_KEY=tsec_yyy
AWS_REGION=auto

boto3 reads AWS_ENDPOINT_URL_S3 natively (boto3 ≥ 1.34) — no client-side overrides needed if you set it.

Step 7: Install Python Dependencies

pip install boto3 tigris-boto3-ext

Requires Python 3.9+ and boto3 ≥ 1.26.0.


boto3 Client Setup

import boto3

# Reads AWS_ENDPOINT_URL_S3, AWS_ACCESS_KEY_ID, etc. from env
s3 = boto3.client("s3")

# Or configure explicitly
s3 = boto3.client(
    "s3",
    endpoint_url="https://t3.storage.dev",
    aws_access_key_id="tid_xxx",
    aws_secret_access_key="tsec_yyy",
    region_name="auto",
)

tigris-boto3-ext works with any boto3 S3 client — it registers handlers on before-sign.s3.* to inject Tigris headers when activated. Calls outside an activated scope pass through unchanged.


Usage Patterns

tigris-boto3-ext exposes three ways to scope Tigris-specific behavior. Pick whichever fits the call site:

PatternBest for
Context managersOne-off blocks with a clear scope
DecoratorsFunctions that always need a Tigris feature
Helper functionsDirect calls without scoping ceremony

Context Managers

from tigris_boto3_ext import (
    TigrisSnapshotEnabled, TigrisSnapshot, TigrisFork, TigrisRename,
)

# Enable snapshots on bucket creation
with TigrisSnapshotEnabled(s3):
    s3.create_bucket(Bucket="my-bucket")

# List snapshots for a bucket
with TigrisSnapshot(s3, "my-bucket"):
    snapshots = s3.list_buckets()

# Read from a specific snapshot version
with TigrisSnapshot(s3, "my-bucket", snapshot_version="12345"):
    obj = s3.get_object(Bucket="my-bucket", Key="file.txt")

# Fork a bucket (current state or from a snapshot)
with TigrisFork(s3, "source-bucket", snapshot_version="12345"):
    s3.create_bucket(Bucket="forked-bucket")

# Rename in place — keep scope tight
with TigrisRename(s3):
    s3.copy_object(
        Bucket="my-bucket",
        CopySource="my-bucket/old.txt",
        Key="new.txt",
    )

Decorators

from tigris_boto3_ext import snapshot_enabled, with_snapshot, forked_from, with_rename

@snapshot_enabled
def make_snapshot_bucket(s3, name):
    return s3.create_bucket(Bucket=name)

@with_snapshot("my-bucket", snapshot_version="12345")
def read_old_config(s3):
    return s3.get_object(Bucket="my-bucket", Key="config.json")

@forked_from("source-bucket", snapshot_version="12345")
def make_fork(s3, name):
    return s3.create_bucket(Bucket=name)

@with_rename
def rename(s3, bucket, old, new):
    return s3.copy_object(Bucket=bucket, CopySource=f"{bucket}/{old}", Key=new)

Helper Functions

from tigris_boto3_ext import (
    create_snapshot_bucket,
    create_snapshot,
    list_snapshots,
    create_fork,
    get_object_from_snapshot,
    get_snapshot_version,
    list_objects_from_snapshot,
    head_object_from_snapshot,
    has_snapshot_enabled,
    get_bucket_info,
    rename_object,
)

create_snapshot_bucket(s3, "my-bucket")

result = create_snapshot(s3, "my-bucket", snapshot_name="backup-1")
version = get_snapshot_version(result)

snapshots = list_snapshots(s3, "my-bucket")
create_fork(s3, "fork-bucket", "my-bucket", snapshot_version=version)

obj = get_object_from_snapshot(s3, "my-bucket", "file.txt", version)
objects = list_objects_from_snapshot(s3, "my-bucket", version, Prefix="data/")
metadata = head_object_from_snapshot(s3, "my-bucket", "file.txt", version)

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

# Inspect bucket
if has_snapshot_enabled(s3, "my-bucket"):
    info = get_bucket_info(s3, "my-bucket")
    # info: { snapshot_enabled, fork_source_bucket, fork_source_snapshot }

Snapshots

Point-in-time bucket copies. Tigris stores only deltas, so they're cheap.

# 1. Enable snapshots when creating the bucket
create_snapshot_bucket(s3, "production-data")

# 2. Take a named snapshot
result = create_snapshot(s3, "production-data", snapshot_name="daily-2026-05-07")
version = get_snapshot_version(result)

# 3. Read from the snapshot — historical view
with TigrisSnapshot(s3, "production-data", snapshot_version=version):
    obj = s3.get_object(Bucket="production-data", Key="config.json")

# 4. List all snapshots
snaps = list_snapshots(s3, "production-data")
for b in snaps.get("Buckets", []):
    print(b["Name"])

Snapshots must be enabled at bucket creation — there is no retroactive enable.

Bucket Forking

Forks are independent buckets created from the live state or a snapshot of a source bucket. Storage is shared until divergence, so forks are essentially free.

# Fork from current state
with TigrisFork(s3, "source-bucket"):
    s3.create_bucket(Bucket="forked-bucket")

# Fork from a snapshot — common for testing against prod data
create_fork(s3, "test-bucket", "production-data", snapshot_version=version)

# Inspect lineage
info = get_bucket_info(s3, "test-bucket")
print(info["fork_source_bucket"], info["fork_source_snapshot"])

Common uses: test/staging environments off prod data, time-travel debugging from a pre-incident snapshot, per-experiment workspaces in ML pipelines.

In-Place Object Rename

Tigris implements rename as a copy_object plus the X-Tigris-Rename: true header — only the key changes, no data rewrite.

from tigris_boto3_ext import TigrisRename, rename_object

# Helper form (recommended)
rename_object(s3, "my-bucket", "old-name.txt", "new-name.txt")

# Context manager — every copy_object inside the block becomes a rename
with TigrisRename(s3):
    s3.copy_object(
        Bucket="my-bucket",
        CopySource="my-bucket/old.txt",
        Key="new.txt",
    )

Keep TigrisRename / with_rename scope tight. Any copy_object inside the block becomes a rename — unrelated copies will not behave as expected.

Conditional Writes

Tigris supports the standard S3 precondition headers — If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since — for optimistic concurrency and create-if-not-exists semantics. These are pure boto3 parameters; tigris-boto3-ext adds nothing on top because boto3 already passes them through.

from botocore.exceptions import ClientError

# Create-only (fail if the key already exists)
try:
    s3.put_object(
        Bucket="my-bucket",
        Key="config.json",
        Body=b'{"version": 1}',
        IfNoneMatch="*",
    )
except ClientError as e:
    if e.response["Error"]["Code"] == "PreconditionFailed":
        print("Object already exists")
    else:
        raise

# Optimistic concurrency — read, modify, write only if unchanged
head = s3.head_object(Bucket="my-bucket", Key="config.json")
etag = head["ETag"]

new_body = b'{"version": 2}'
try:
    s3.put_object(
        Bucket="my-bucket",
        Key="config.json",
        Body=new_body,
        IfMatch=etag,
    )
except ClientError as e:
    if e.response["Error"]["Code"] == "PreconditionFailed":
        # Someone else wrote the object since we read it — retry the read-modify-write
        ...
    else:
        raise
boto3 parameterBehaviorCommon use
IfMatch=<etag>Write only if current ETag matchesOptimistic concurrency / lost-update
IfNoneMatch="*"Write only if the object does not existCreate-once / idempotent writes
IfNoneMatch=<etag>Write only if current ETag differsSkip identical re-uploads
IfModifiedSinceProceed only if modified after the given RFC 1123 timestampConditional reads / cache validation
IfUnmodifiedSinceProceed only if not modified after the given RFC 1123 timestampTime-bounded optimistic concurrency

Failed preconditions surface as a ClientError with Error.Code == "PreconditionFailed" (HTTP 412). Catch that specific code — don't swallow all ClientErrors, since the same exception type covers NoSuchKey, throttling, etc.

IfMatch / IfNoneMatch work on put_object, copy_object, get_object, head_object, and delete_object.

Bundle API

Fetch many objects in a single streaming tar archive. Designed for ML training where per-object roundtrips dominate latency.

import tarfile
from tigris_boto3_ext import bundle_objects, BundleError, BUNDLE_ON_ERROR_FAIL

keys = [f"dataset/train/img_{i:05d}.jpg" for i in range(1000)]
response = bundle_objects(s3, "my-dataset-bucket", keys)

with tarfile.open(fileobj=response, mode="r|") as tar:
    for member in tar:
        if member.name == "__bundle_errors.json":
            continue  # error manifest — skip
        f = tar.extractfile(member)
        if f is not None:
            data = f.read()
            # feed to training pipeline

Error modes:

  • Default: missing/failed objects are reported in __bundle_errors.json inside the tar; partial success is OK.
  • on_error=BUNDLE_ON_ERROR_FAIL: any failure raises BundleError with status_code and body. Use for inference where every key must be present.
try:
    response = bundle_objects(s3, "my-bucket", keys, on_error=BUNDLE_ON_ERROR_FAIL)
except BundleError as e:
    print(f"Bundle failed (HTTP {e.status_code}): {e.body}")

Always open the tar in streaming mode (mode="r|"). Don't materialize the whole archive in memory.


Common Workflows

Backup and Restore

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

s3 = boto3.client("s3")

create_snapshot_bucket(s3, "production-data")

# Daily snapshot
result = create_snapshot(s3, "production-data", snapshot_name="daily-backup")
version = get_snapshot_version(result)

# Restore: fork the snapshot into a recovered bucket
create_fork(s3, "restored-data", "production-data", snapshot_version=version)

Testing Against Production Data

result = create_snapshot(s3, "production-data", snapshot_name="for-tests")
version = get_snapshot_version(result)
create_fork(s3, "test-data", "production-data", snapshot_version=version)

try:
    # Writes to test-data don't touch production
    s3.put_object(Bucket="test-data", Key="experiment.txt", Body=b"...")
finally:
    s3.delete_bucket(Bucket="test-data")

Time-Travel Queries

from tigris_boto3_ext import get_object_from_snapshot, list_objects_from_snapshot

old = get_object_from_snapshot(s3, "my-bucket", "config.json", "12345")
config = old["Body"].read()

old_logs = list_objects_from_snapshot(
    s3, "my-bucket", "12345", Prefix="logs/2025/12/",
)
for obj in old_logs.get("Contents", []):
    print(obj["Key"])

How It Works

tigris-boto3-ext registers handlers on boto3's before-sign.s3.* event to inject Tigris headers when a context/decorator is active:

HeaderPurpose
X-Tigris-Enable-Snapshot: trueEnable snapshots on bucket creation
X-Tigris-Snapshot: true; name=<name>Create a named snapshot
X-Tigris-Snapshot: <bucket>List snapshots for a bucket
X-Tigris-Snapshot-Version: <version>Read from a specific snapshot version
X-Tigris-Fork-Source-Bucket: <bucket>Fork from this bucket
X-Tigris-Fork-Source-Bucket-Snapshot: <ver>Fork from a specific snapshot
X-Tigris-Rename: trueTurn CopyObject into in-place rename

HeadBucket responses include X-Tigris-Enable-Snapshot, X-Tigris-Fork-Source-Bucket, and X-Tigris-Fork-Source-Bucket-Snapshot — surfaced via get_bucket_info() and has_snapshot_enabled().


Critical Rules

Always:

  • Enable snapshots at bucket creation — there is no retroactive enable
  • Capture get_snapshot_version(result) immediately after create_snapshot — you need it to read or fork
  • Stream Bundle API responses (mode="r|") — never load the whole tar in memory
  • Skip __bundle_errors.json when iterating tar members
  • Keep TigrisRename / with_rename scope tight — every copy_object inside becomes a rename

Never:

  • Hard-code region_name to a real AWS region — use auto
  • Use the production bucket directly for tests when a fork costs nothing
  • Forget AWS_ENDPOINT_URL_S3 — without it boto3 hits AWS S3, not Tigris

Known Issues

ProblemCause & Fix
Snapshot calls fail with 404/409Snapshots not enabled on the bucket. Recreate with create_snapshot_bucket.
copy_object renamed unrelated fileTigrisRename block too broad. Wrap only the rename call.
Bundle hangs on iterationTar opened in non-streaming mode. Use mode="r|", not "r".
BundleError raised unexpectedlyBUNDLE_ON_ERROR_FAIL was passed. Default mode reports errors in the tar.
Calls go to AWS, not TigrisAWS_ENDPOINT_URL_S3 unset. Export it or pass endpoint_url= explicitly.
Fork has no snapshots enabledForks don't inherit snapshot capability — enable on the fork separately.

CLI Quick Reference

t3 is an alias for tigris.

# Auth
tigris login
tigris whoami

# Buckets
tigris buckets create <name> [--public] [--locations <region>]
tigris buckets list
tigris buckets delete <name>

# Access keys
tigris access-keys create "<name>"
tigris access-keys assign <tid_xxx> --bucket <name> --role Editor

# Snapshots & forks (also doable from Python via this skill)
tigris buckets snapshot <bucket> --name <snapshot-name>
tigris buckets snapshots <bucket>
tigris buckets fork <new> --source <bucket> [--snapshot <version>]

Related Skills

  • file-storage — General Tigris setup and the JS/TS @tigrisdata/storage SDK
  • tigris-snapshots-forking — CLI-side snapshot/fork workflows
  • tigris-agent-kit — Agent storage workflows (forks for sandboxes, checkpoints)
  • tigris-sdk-guide — Picking SDKs across languages

Official Documentation

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/tigris-python-sdk

Default branch

main

Latest commit

668ea42

Tree SHA

0e26190