Install
$ agentstack add skill-tigrisdata-skills-tigris-python-sdk ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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](./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 --access-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
| Role | Permissions | Use when | | ---------- | ----------------------------- | ------------------------------------- | | Editor | Read + write + delete | App code that uploads/deletes objects | | ReadOnly | Read only | Apps 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:
| Pattern | Best for | | ----------------- | -------------------------------------------------- | | Context managers | One-off blocks with a clear scope | | Decorators | Functions that always need a Tigris feature | | Helper functions | Direct 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 parameter | Behavior | Common use | | -------------------- | ------------------------------------------------------------------- | --------------------------------------- | | IfMatch= | Write only if current ETag matches | Optimistic concurrency / lost-update | | IfNoneMatch="*" | Write only if the object does not exist | Create-once / idempotent writes | | IfNoneMatch= | Write only if current ETag differs | Skip identical re-uploads | | IfModifiedSince | Proceed only if modified after the given RFC 1123 timestamp | Conditional reads / cache validation | | IfUnmodifiedSince | Proceed only if not modified after the given RFC 1123 timestamp | Time-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.jsoninside the tar; partial success is OK. on_error=BUNDLE_ON_ERROR_FAIL: any failure raisesBundleErrorwithstatus_codeandbody. 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:
| Header | Purpose | | --------------------------------------------- | ----------------------------------------------- | | X-Tigris-Enable-Snapshot: true | Enable snapshots on bucket creation | | X-Tigris-Snapshot: true; name= | Create a named snapshot | | X-Tigris-Snapshot: | List snapshots for a bucket | | X-Tigris-Snapshot-Version: | Read from a specific snapshot version | | X-Tigris-Fork-Source-Bucket: | Fork from this bucket | | X-Tigris-Fork-Source-Bucket-Snapshot: | Fork from a specific snapshot | | X-Tigris-Rename: true | Turn 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 aftercreate_snapshot— you need it to read or fork - Stream Bundle API responses (
mode="r|") — never load the whole tar in memory - Skip
__bundle_errors.jsonwhen iterating tar members - Keep
TigrisRename/with_renamescope tight — everycopy_objectinside becomes a rename
Never:
- Hard-code
region_nameto a real AWS region — useauto - 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
| Problem | Cause & Fix | | ------------------------------------ | ------
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: tigrisdata
- Source: tigrisdata/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.