Skip to main content

Official Python SDK for Tilde: an agent-native data platform that combines versioned object storage with sandboxed compute

Project description

Tilde Python SDK

Python SDK for the Tilde data versioning API.

Installation

pip install tilde-sdk

Or with uv:

uv add tilde-sdk

Quick Start

export TILDE_API_KEY="your-api-key"
import tilde

repo = tilde.repository("my-org/my-repo")

# Run commands in an interactive sandbox
with repo.shell(image="python:3.12") as sh:
    sh.run("pip install pandas")
    result = sh.run("python train.py")
    print(result.stdout.text())

    # Stream output line by line
    result = sh.run("cat /sandbox/results.csv")
    for line in result.stdout.iter_lines():
        print(line)

[!IMPORTANT] Transactional by default. All filesystem modifications made in a sandbox happen in the context of a transactional session. If anything fails midway or is aborted, changes don't take effect. Only successful sandboxes' changes are committed atomically to the repository -- so your data is always in a consistent state. See Sessions for more details.

One-shot execution

For a single command that doesn't need an interactive session:

result = repo.execute("python train.py", image="python:3.12")
print(result.stdout.text())

# check=False to handle errors yourself
result = repo.execute("might-fail", check=False)
if result.exit_code != 0:
    print(result.stdout.text())

Output streams

Both execute() and shell.run() return a RunResult whose stdout holds the merged stdout+stderr stream as an OutputStream:

Method Returns Description
.read() bytes Full output as raw bytes
.text(encoding='utf-8') str Full output decoded as a string
.iter_bytes(chunk_size) Iterator[bytes] Yield byte chunks
.iter_text(chunk_size) Iterator[str] Yield text chunks
.iter_lines() Iterator[str] Yield lines (no trailing newlines)

Configuration

Option Environment Variable Default
api_key TILDE_API_KEY required
endpoint_url TILDE_ENDPOINT_URL https://tilde.run

Resolution order: explicit parameter > environment variable > default.

A missing API key is not an error at construction time; a ConfigurationError is raised when the first request is made.

Explicit configuration

import tilde

tilde.configure(api_key="your-api-key", endpoint_url="https://custom.endpoint")
repo = tilde.repository("my-org/my-repo")

Explicit client

from tilde import Client

with Client(api_key="your-api-key") as client:
    repo = client.repository("my-org/my-repo")

Usage

Repositories

repo = tilde.repository("my-org/my-repo")

# Lazy-loaded properties
print(repo.id, repo.description, repo.visibility)

# Update
repo.update(description="New description", visibility="public")

# Delete
repo.delete()

Timeline (Commit History)

for commit in repo.timeline():
    print(commit.id, commit.message)

    # View changes introduced by this commit
    for change in commit.diff():
        print(f"object {change.path} was {change.status} in this commit!")

Organizations

orgs = tilde.Client(api_key="key").organizations

# Create
org = orgs.create("my-org", "My Organization")

# List
for org in orgs.list():
    print(org.name)

# Members
for member in orgs.members("my-org").list():
    print(member.username, member.role)

orgs.members("my-org").add(user_id="user-uuid", role="member")

Agents

Manage agents and their API keys using the fluent organization resource:

org = tilde.organization("my-org")

# Create an agent
agent = org.agents.create("my-agent", description="CI bot", metadata={"env": "prod"})
print(agent.name, agent.id)

# List agents
for agent in org.agents.list():
    print(agent.name)

# Get a specific agent
agent = org.agents.get("my-agent")

# Update an agent
agent = org.agents.update("my-agent", description="Updated description")

# Delete an agent
org.agents.delete("my-agent")

Agent API Keys

agent = org.agents.get("my-agent")

# Create a key (token is only shown once)
created = agent.api_keys.create("dev-key")
print(created.token)  # cak-... full token

# List keys
for key in agent.api_keys.list():
    print(key.name, key.token_hint)

# Get by ID and revoke a key
key = agent.api_keys.get(key_id)
key.revoke()

Organization Sub-resources

The organization resource also provides access to repositories, members, groups, policies, and connectors:

org = tilde.organization("my-org")

for repo in org.repositories.list():
    print(repo.name)

for member in org.members.list():
    print(member.username, member.role)

Groups

groups = client.organizations.groups("my-org")

group = groups.create("engineers", description="Engineering team")
detail = groups.get(group.id)  # includes members and attachments

groups.add_member(group.id, "user", "user-uuid")
groups.remove_member(group.id, "user", "user-uuid")

Policies

policies = client.organizations.policies("my-org")

# Create and validate
result = policies.validate("package tilde.authz\ndefault allow = true")
policy = policies.create("allow-all", rego="...", description="Allow everything")

# Attach/detach
policies.attach(policy.id, "group", "group-uuid")
policies.detach(policy.id, "group", "group-uuid")

# Effective policies for a user
for ep in policies.effective_policies("user-uuid"):
    print(ep.policy_name, ep.source)

Connectors and Imports

# Org-level connectors
connectors = client.organizations.connectors("my-org")
conn = connectors.create("my-s3", "s3", {"bucket": "my-bucket", "region": "us-east-1"})

# Attach to repo
repo.connectors.attach(conn.id)

# Import
job_id = repo.imports.start(
    connector_id=conn.id,
    source_path="s3://my-bucket/data/",
    destination_path="imported/",
)
status = repo.imports.status(job_id)
print(status.status, status.objects_imported)

Advanced Usage

Sessions

Sessions provide direct transactional access to objects in a repository. They act like transactions: stage changes, then commit or rollback.

# Context manager (recommended) — rolls back on error
with repo.session() as session:
    objects = session.objects.list(prefix="data/", delimiter="/")
    session.objects.put("data/report.csv", b"content")
    session.commit("update CSV files")

# Explicit control
session = repo.session()
print(session.session_id)
session.objects.put("data/file.csv", b"content")
session.commit("modifying data in parallel")
# or: session.rollback()

Attaching to an Existing Session

Resume a session from another thread, process, or machine:

# In another thread/process/machine:
session = repo.attach(session_id)
session.objects.put("data/file2.csv", b"more content")
session.commit("finishing work")

Objects

with repo.session() as session:
    # Upload
    session.objects.put("data/file.csv", b"content")

    # Download (streaming)
    with session.objects.get("data/file.csv") as f:
        data = f.read()

    # Read a specific byte range (e.g., first 1 KB)
    with session.objects.get("data/file.parquet", byte_range=(0, 1023)) as f:
        header = f.read()

    # Stream large objects without caching
    with session.objects.get("data/large.bin", cache=False) as f:
        for chunk in f.iter_bytes(chunk_size=8192):
            process(chunk)

    # List (auto-paginating, with directory grouping)
    for entry in session.objects.list(prefix="data/", delimiter="/"):
        print(entry.path, entry.type)  # type is "object" or "prefix"

    # Check metadata
    meta = session.objects.head("data/file.csv")
    print(meta.etag, meta.content_type, meta.content_length)

    # Delete
    session.objects.delete("data/file.csv")

    session.commit("object operations")

Uncommitted Changes

session = repo.session()
session.objects.put("data/new.csv", b"content")

for entry in session.uncommitted():
    print(entry.path)

Error Handling

All SDK exceptions inherit from TildeError:

TildeError                           # base for all SDK errors
+-- ConfigurationError               # missing API key, bad endpoint
+-- TransportError                   # network failures, DNS, timeouts
+-- SerializationError               # invalid JSON in response
+-- APIError                         # base for HTTP API errors
    +-- BadRequestError              # 400
    +-- AuthenticationError          # 401
    +-- ForbiddenError               # 403
    +-- NotFoundError                # 404
    +-- ConflictError                # 409
    +-- GoneError                    # 410
    +-- PreconditionFailedError      # 412
    +-- LockedError                  # 423
    +-- ServerError                  # 5xx

APIError includes status_code, message, code, request_id, method, url, and response_text for debugging.

from tilde import NotFoundError, TildeError

try:
    with repo.session() as session:
        with session.objects.get("nonexistent") as f:
            f.read()
except NotFoundError as e:
    print(f"Not found: {e.message} (request_id={e.request_id})")
except TildeError as e:
    print(f"SDK error: {e}")

Large Object Handling

By default, objects.get() caches the full object in memory on .read(). For large objects, disable caching and stream:

with repo.session() as session:
    with session.objects.get("large-file.bin", cache=False) as f:
        for chunk in f.iter_bytes(chunk_size=1024 * 1024):
            output.write(chunk)

Byte Range Requests

Use byte_range to read only a portion of an object without downloading the full content. This is useful for reading file headers, tailing logs, or formats like Parquet that support random access.

# Read the first 4 bytes (e.g., a magic number)
with repo.objects.get("data/file.parquet", byte_range=(0, 3)) as f:
    magic = f.read()

# Read from offset 1024 to end of file
with repo.objects.get("data/file.parquet", byte_range=(1024, None)) as f:
    tail = f.read()

The reader exposes the content_range property with the server's Content-Range header value (e.g., "bytes 0-3/49152"):

with repo.objects.get("data/file.parquet", byte_range=(0, 1023)) as f:
    data = f.read()
    print(f.content_range)   # "bytes 0-1023/49152"
    print(f.content_length)  # 1024

Documentation

Full documentation is available at https://docs.tilde.run/python-sdk/.

Development

# Install dev dependencies
uv sync --all-extras

# Run tests
uv run pytest

# Lint and format
uv run ruff check src/ tests/
uv run ruff format src/ tests/

# Type check
uv run mypy src/tilde/

# Build
uv build

License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tilde_sdk-0.7.5.tar.gz (166.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tilde_sdk-0.7.5-py3-none-any.whl (61.4 kB view details)

Uploaded Python 3

File details

Details for the file tilde_sdk-0.7.5.tar.gz.

File metadata

  • Download URL: tilde_sdk-0.7.5.tar.gz
  • Upload date:
  • Size: 166.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for tilde_sdk-0.7.5.tar.gz
Algorithm Hash digest
SHA256 bd47de876f30e20992b9fe0cd2d6b7d2e4473d50fff1b9ae245d4d11e0c42cbb
MD5 543b6ef165a983ea26c571326752cfe1
BLAKE2b-256 c5aafedb665f20d9a9ba85d54a8c88deba04794b43d706a7d5ba04e58cfcba50

See more details on using hashes here.

File details

Details for the file tilde_sdk-0.7.5-py3-none-any.whl.

File metadata

  • Download URL: tilde_sdk-0.7.5-py3-none-any.whl
  • Upload date:
  • Size: 61.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for tilde_sdk-0.7.5-py3-none-any.whl
Algorithm Hash digest
SHA256 6304c8cfe7fce6e8c3a1caf76f53b9efa95e25f532aa896e0f3b818eb335ff98
MD5 fc6ea141b90442469881ac674ced7ec9
BLAKE2b-256 25ec9cf48c5943b074ee7c2fe5b5811070ef114c1473c08f4aa3e403c6c35dc0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page