Skip to main content

Sandbox SDK

A Python SDK for interacting with Flash Sandbox over HTTP. It talks to the coordinator (cluster binary) — point host/port (or address) at your coordinator, not at a bare node (the SDK uses the coordinator's /sandboxes API). Opt-in scheduler mode routes sandbox operations through the external scheduler.

The SDK offers two layers of abstraction:

  • Low-level clients (HTTPClient, AsyncHTTPClient) – manage sandboxes via client methods and return Sandbox / AsyncSandbox objects from start_sandbox, so you can call methods directly on the result.
  • High-level wrappers (Sandbox, AsyncSandbox) – bind a single sandbox instance so you never need to pass the ID around.

Installation

pip install flash-sandbox

The package pulls in httpx2 automatically (a single library that provides both the sync and async HTTP transports).

HTTP transport

The SDK requires Python 3.10 or later. Both clients use httpx2; callers may supply their own sync or async session for TLS, authentication, and pooling. Caller-supplied sessions remain caller-owned and are not closed by the SDK.

Requests carry an explicit RequestPolicy: operation lane, replayability, admission deadline, and observation context. Reads and fenced creates can retry transient failures. An unfenced mutation is retried only when connection establishment proves no request was dispatched. A lost response or interrupted stream never causes an exec to run again. Streaming requires a terminal exit frame with an integer exit code. Empty, truncated, or malformed streams raise SandboxHTTPError with code execution_uncertain; server error frames preserve their typed error envelope and raise instead of appearing as successful EOF.

All HTTP and transport failures expose SandboxHTTPError.code, .origin, and .outcome alongside .status_code and .detail. SandboxNotFoundError means worker-confirmed sandbox absence; a coordinator's missing route is a distinct route_not_found error. Unknown outcomes remain uncertain. Transport timeouts also retain the SandboxTimeoutError type.

Use client.configure_transport(request_hooks=..., response_hooks=...) for observation hooks. Hooks follow the sync or async httpx2 callback contract and apply to every request pool. Advanced calls use client.request(method, path, policy=RequestPolicy(...)), which returns an httpx2.Response in both clients. Configure hooks before starting concurrent requests.

Scheduler routing

Both clients keep coordinator-direct routing by default. To use the scheduler, set with_scheduler=True and an explicit scheduler_address:

client = HTTPClient(
    address="http://127.0.0.1:8080",
    with_scheduler=True,
    scheduler_address="http://127.0.0.1:8090",
)

All sandbox operations use the scheduler endpoint, including creation, execution, snapshot, and restore. AsyncHTTPClient accepts the same routing options. The scheduler applies its configured admission, priority, and residency policies; the SDK retains its existing sandbox methods. There is no automatic coordinator fallback if the scheduler is unavailable.

Run the scheduler's live proxy as described in the trainer contract. Use RLVRClient at the same scheduler address to declare rollout groups and pass execution_metadata(group, rollout_id) to sandbox execution when needed.

Quick Start

HTTPClient.start_sandbox() returns a Sandbox object. You can call methods directly on it, or access the raw ID via .id:

from flash_sandbox import HTTPClient

client = HTTPClient(host="localhost", port=8080)

# start_sandbox returns a Sandbox object.
# No `type` → the cluster picks any available backend (Docker, Kubernetes, …).
# Pass type="docker" (etc.) to force a specific backend.
sandbox = client.start_sandbox(
    image="alpine:latest",
    command=["tail", "-f", "/dev/null"],
    memory_mb=128,
    cpu_cores=0.5,
)
print(f"Sandbox ID: {sandbox.id}")

# Call methods directly on the sandbox — no need to pass an ID
print(f"Status: {sandbox.get_status()}")

result = sandbox.exec_command(["echo", "Hello!"])
print(f"Output: {result.stdout.strip()}")
print(f"Exit: {result.exit_code}, server time: {result.duration_ms:.1f}ms")

# Optional `metadata` is forwarded uninterpreted to the exec trace /
# event log so execs can be classified downstream (see "Classifying
# execs" below). The backend ignores it; it doesn't change execution.
result = sandbox.exec_command(
    ["echo", "verified"],
    metadata={"query_type": "verification", "task_id": 7},
)

py_out = sandbox.run_python("print('Hello Python in Sandbox')")
print(f"Python Output: {py_out.strip()}")

details = sandbox.get_info()
print(f"Scheduled on: {details.node_id} ({details.backend})")

plat_info = sandbox.get_platform_info()
print(f"Platform: {plat_info['system']} {plat_info.get('release', '')}")

metrics = sandbox.get_metrics()
print(f"Memory: {metrics.memory_usage_bytes} / {metrics.memory_limit_bytes}")
print(f"CPU: {metrics.cpu_percent}%")

snap = sandbox.snapshot()
print(f"Snapshot: {snap.snapshot_path}")
sandbox.resume()

sandbox.write_file("/tmp/hello.txt", b"Hello from SDK!")
content = sandbox.read_file("/tmp/hello.txt")
print(f"File Content: {content.decode()}")

sandbox.stop()            # cleanup=True by default: removes the container
# sandbox.stop(cleanup=False)  # stop without removing resources
client.close()

Pre-warm a future batch with a claimable ticket:

ticket = client.demand(
    "trainer",
    "batch-43",
    image="python:3.11-slim",
    backend="libkrun",
    expect=next_batch_at,  # RFC3339 string or timezone-aware datetime
    count=8,
    cpu=2,
    memory=1024,
)

sandbox = client.claim(ticket)
cleanup = client.cancel(ticket)  # cleans up anything still unclaimed

claim(ticket) waits for one prepared sandbox, returns a normal Sandbox, and can be repeated while ticket.remaining_claims is nonzero. Retrying with the same live ticket safely replays an ambiguous claim. Use the lower-level claim_warm_demand(..., claim_id=...) API when the claim ID must survive a client process restart. The async client exposes the same methods with await. Cancelling a ticket does not stop sandboxes that were already claimed.

Use as a context manager for automatic cleanup:

from flash_sandbox import HTTPClient, Sandbox

client = HTTPClient(host="localhost", port=8080)

with Sandbox.create(
    client,
    type="docker",
    image="alpine:latest",
    command=["tail", "-f", "/dev/null"],
) as sandbox:
    result = sandbox.exec_command(["echo", "Hello!"])
    print(result.stdout.strip())

# sandbox.stop() is called automatically on exit
client.close()

You can also wrap an existing sandbox ID:

from flash_sandbox import HTTPClient, Sandbox

client = HTTPClient(host="localhost")
sandbox = Sandbox("existing-sandbox-id", client)
print(sandbox.get_status())

Async usage

AsyncHTTPClient.start_sandbox() returns an AsyncSandbox object. Call await on its methods:

import asyncio
from flash_sandbox import AsyncHTTPClient

async def main():
    client = AsyncHTTPClient(host="localhost", port=8080)

    # start_sandbox returns an AsyncSandbox object
    sandbox = await client.start_sandbox(
        type="docker",
        image="alpine:latest",
        command=["tail", "-f", "/dev/null"],
    )
    print(f"Sandbox ID: {sandbox.id}")

    result = await sandbox.exec_command(["echo", "Hello from Async!"])
    print(f"Output: {result.stdout.strip()}")

    await sandbox.write_file("/tmp/async.txt", b"Async hello!")
    content = await sandbox.read_file("/tmp/async.txt")
    print(f"Async File Content: {content.decode()}")

    await sandbox.stop()
    await client.close()

asyncio.run(main())

Class-based pattern

When the client lives as an instance attribute and sandbox start/stop happen across different methods:

from flash_sandbox import AsyncHTTPClient

class MyAgent:
    def __init__(self, orchestrator_host: str = "localhost"):
        self.sandbox_client = AsyncHTTPClient(host=orchestrator_host, port=8080)

    async def forward(self, prompt: str, metadata: dict):
        # start_sandbox returns an AsyncSandbox object
        sandbox = await self.sandbox_client.start_sandbox(
            type="docker",
            image=metadata["image_name"],
            command=["tail", "-f", "/dev/null"],
        )

        result = await sandbox.exec_command(["echo", "Hello!"])
        print(f"Output: {result.stdout.strip()}")

        platform_info = await sandbox.get_platform_info()
        print(f"Platform: {platform_info['system']} {platform_info.get('release', '')}")

        await sandbox.stop()

    async def close(self):
        await self.sandbox_client.close()

Context manager pattern

Use async with for automatic cleanup:

import asyncio
from flash_sandbox import AsyncHTTPClient, AsyncSandbox

async def main():
    async with AsyncHTTPClient(host="localhost", port=8080) as client:
        async with await AsyncSandbox.create(
            client,
            type="docker",
            image="alpine:latest",
            command=["tail", "-f", "/dev/null"],
        ) as sandbox:
            status = await sandbox.get_status()
            print(f"Status: {status}")

            result = await sandbox.exec_command(["echo", "Hello!"])
            print(f"Output: {result.stdout.strip()}")

        # sandbox.stop() is called automatically

asyncio.run(main())

API Reference

Sandbox / AsyncSandbox (high-level)

The Sandbox class wraps a sandbox ID and a client, exposing all operations as simple method calls. AsyncSandbox is the async equivalent — all I/O methods are coroutines.

Both work with HTTPClient and AsyncHTTPClient.

Construction

# HTTP clients return Sandbox / AsyncSandbox directly from start_sandbox
sandbox = client.start_sandbox(type="docker", image="alpine:latest", ...)
sandbox = await async_client.start_sandbox(type="docker", image="alpine:latest", ...)

# Or wrap an existing sandbox ID (works with any client)
sandbox = Sandbox(sandbox_id, client)
sandbox = AsyncSandbox(sandbox_id, async_client)

# Or use the create() factory (reuses the object returned by start_sandbox)
sandbox = Sandbox.create(client, type="docker", image="alpine:latest", ...)
sandbox = await AsyncSandbox.create(async_client, type="docker", image="alpine:latest", ...)

Methods

Method Description
get_status() Return the status string ("running", "stopped", etc.).
get_info() / describe() Return SandboxDetails with status, node/backend placement, image, resources, TTL, and creation metadata when the coordinator provides it.
exec_command(command, *, persistent=False, metadata=None, stream=False) Execute one-shot by default. Pass persistent=True for a persistent shell; combining persistence and streaming is rejected. Strings are shell scripts and sequences are literal argv. Returns ExecResult with stdout/stderr/exit and timing metadata.
run_python(code) Execute Python code inside the sandbox. Returns stdout.
run_python_result(code) Execute Python code and return PythonResult with result text plus stdout/stderr/exit metadata when available.
get_platform_info() Get platform information. Returns a dict with keys like system, node, release, etc.
get_metrics() Return point-in-time resource-usage metrics.
read_file(path) Read a file from the sandbox as bytes.
read_file_result(path) Read a file and return ReadFileResult with path, content, size, and optional stat metadata.
write_file(path, content) Write bytes to a path in the sandbox.
write_file_result(path, content) Write bytes and return WriteFileResult with bytes-written acknowledgement.
agent_health_detail() Probe the in-sandbox agent and return status, detail, and latency.
snapshot() Create a snapshot. Returns SnapshotResult with paths, owning snapshot node ID, and size_bytes when available.
resume() Resume a paused / snapshotted sandbox.
resume_result() Resume and return a lifecycle acknowledgement.
stop(cleanup=True) Stop the sandbox. When cleanup=True (default), backing resources are removed. Pass cleanup=False to stop without removing resources.
stop_result(cleanup=True) Stop and return a lifecycle acknowledgement.

Properties

Property Description
id The raw sandbox ID string.
details Cached SandboxDetails from creation or the last get_info() call, when available.

Context manager

Sandbox supports with and AsyncSandbox supports async with. The sandbox is automatically stopped when the block exits:

with Sandbox.create(client, type="docker", image="alpine:latest") as sandbox:
    sandbox.exec_command(["echo", "hi"])
# sandbox.stop(cleanup=True) is called automatically on exit

async with await AsyncSandbox.create(client, type="docker", image="alpine:latest") as sandbox:
    await sandbox.exec_command(["echo", "hi"])
# await sandbox.stop(cleanup=True) is called automatically on exit

Low-level client methods

HTTPClient and AsyncHTTPClient expose the same set of methods. AsyncHTTPClient methods must be awaited.

SandboxID — flexible sandbox identifiers

Every method that accepts a sandbox_id parameter uses the SandboxID type, which is Union[str, Sandbox, AsyncSandbox]. This means you can pass either a plain string ID or a Sandbox / AsyncSandbox object — they are fully interchangeable:

sandbox = client.start_sandbox(type="docker", image="alpine:latest")

# All three forms are equivalent:
client.get_status(sandbox)            # pass the Sandbox object
client.get_status(sandbox.id)         # pass the .id string
client.get_status("sb-abc123")        # pass any string ID

This makes it easy to mix high-level wrapper usage with low-level client calls during migration or when you need both styles in the same codebase.

Method Description
start_sandbox(image, command, memory_mb, cpu_cores, type="", ...) Start a new sandbox. Returns a Sandbox/AsyncSandbox. type is optional — omit it (or pass "auto") to let the cluster pick any available backend; set it ("docker", "kubernetes", …) to force one.
stop_sandbox(sandbox_id, cleanup=True) Stop a sandbox. When cleanup=True (default), backing resources are removed. Pass cleanup=False to keep resources for inspection. Accepts SandboxID.
exec_command(sandbox_id, command, *, persistent=False, metadata=None, stream=False) Execute one-shot by default. Pass persistent=True for a persistent shell; combining persistence and streaming is rejected. Strings are shell scripts and sequences are literal argv. Accepts SandboxID.
get_status(sandbox_id) Return the status string ("running", "stopped", etc.). Accepts SandboxID.
get_metrics(sandbox_id) Return point-in-time resource-usage metrics (memory, CPU, network, block I/O). Accepts SandboxID.
snapshot_sandbox(sandbox_id) Create a snapshot. Returns a SnapshotResult. Accepts SandboxID.
fork_snapshot(snapshot, count=N, config=None) Restore one snapshot into up to 100 sandboxes concurrently. Returns one SnapshotForkResult per child, including partial failures.
resume_sandbox(sandbox_id) Resume a paused / snapshotted sandbox. Accepts SandboxID.
run_python(sandbox_id, code) Execute arbitrary Python code inside the sandbox. Returns the stdout output. Accepts SandboxID.
get_platform_info(sandbox_id) Get platform information from the sandbox. Returns a dict with platform data. Accepts SandboxID.
read_file(sandbox_id, path) Read a file from the sandbox as bytes. Accepts SandboxID.
write_file(sandbox_id, path, content) Write bytes to a path in the sandbox. Accepts SandboxID.
close() Release the underlying connection / session.

Constructor options

HTTPClient

HTTPClient(
    host="localhost",       # Coordinator hostname — or a full URL (see below)
    port=8080,              # Coordinator HTTP port (default 8080)
    address=None,           # Full URL, overrides host/port (e.g. "http://proxy:9090/v1/service/sandbox")
    timeout=30.0,           # Request timeout in seconds (None = no timeout)
    session=None,           # Optional httpx2.Client for custom TLS / auth / retries
    pool_size=128,          # Max connections (and keepalive) per client
    api_key=None,           # Sent as "Authorization: Bearer <key>"; defaults to $FLASH_API_KEY
)

AsyncHTTPClient

AsyncHTTPClient(
    host="localhost",       # Coordinator hostname — or a full URL (see below)
    port=8080,              # Coordinator HTTP port (default 8080)
    address=None,           # Full URL, overrides host/port
    timeout=30.0,           # Request timeout in seconds
    session=None,           # Optional httpx2.AsyncClient
    pool_size=128,          # Max connections (and keepalive) per client
    api_key=None,           # Sent as "Authorization: Bearer <key>"; defaults to $FLASH_API_KEY
)

Connecting with just a URL

The first positional argument accepts a full URL, so a hosted coordinator needs nothing else:

client = HTTPClient("https://sandbox.example.com")
client = HTTPClient("localhost:8080")
client = HTTPClient("http://proxy:9090/v1/service/sandbox")

A value is treated as a URL when it carries a scheme, a path, a bracketed IPv6 authority, or an explicit port; a bare hostname (HTTPClient("localhost")) still pairs with port. When the first argument is a URL, port is ignored — the URL is the single source of truth. The explicit address= keyword keeps working and wins over host.

Authentication

Against a coordinator started with --api-key, pass the key as api_key (or set $FLASH_API_KEY); it is sent as Authorization: Bearer <key> on every request:

client = HTTPClient("https://sandbox.example.com", api_key="s3cret")

The orchestrator also accepts the key as an X-API-Key header, which is what the admin tooling under meta/tools/ uses.

HTTPClient-only extras

The HTTP transport includes a few additional features:

  • Firecracker fields on start_sandbox: kernel_image, initrd_path, snapshot_path, mem_file_path.
  • Custom timeouts per-client via the timeout parameter.
  • Session injection – pass your own httpx2.Client / httpx2.AsyncClient for connection pooling, mutual TLS, retry policies, or authentication headers.
  • Typed exceptions: SandboxHTTPError preserves .code, .origin, and .outcome; SandboxNotFoundError is reserved for worker-confirmed absence.
  • Auto-expiry via ttl_seconds on start_sandbox — see below.

Auto-expiry (ttl_seconds)

Pass ttl_seconds=N to start_sandbox to cap the sandbox's wall-clock lifetime. The node-side reaper deletes the sandbox once the deadline passes, even if the caller exits without calling stop_sandbox. Useful as a safety net for batch jobs and benchmarks that can leak sandboxes on crash.

# Sandbox is auto-deleted ~10 minutes after creation, even if this
# process is killed before reaching stop().
sandbox = client.start_sandbox(
    type="kubernetes",
    image="alpine:latest",
    ttl_seconds=600,
)

Currently enforced by the kubernetes backend only; other backends accept the field and silently ignore it. The deadline is stamped on the pod as the flash-sandbox/ttl-expires-at annotation, so it survives orchestrator restarts.

Classifying execs (metadata)

exec_command (sync and async, on both HTTPClient/AsyncHTTPClient and Sandbox/AsyncSandbox) accepts an optional metadata mapping. It is opaque to the backend — backends ignore it and never change the command based on it — and is forwarded verbatim onto the exec trace (.jsonl, on the start and finish events), the structured event log (container_execs, under the client key), and the raw request body captured by FSB_DEBUG. This lets you classify execs downstream (e.g. interactive vs. verification, by task id) without encoding semantics into the command:

# verification query
sb.exec_command(["pytest", "-x"],
                metadata={"query_type": "verification", "task_id": 7})

# interactive query
sb.exec_command(["git", "status"],
                metadata={"query_type": "interactive", "task_id": 7})

# async is identical
await sb.exec_command(["pytest"],
                      metadata={"query_type": "verification", "task_id": 7})

# streaming variant honors metadata too
for frame in sb.exec_command(["pytest", "-v"], stream=True,
                             metadata={"query_type": "verification"}):
    ...

Values must be JSON-serializable. Omit metadata (the default) to keep the wire body minimal. To attribute CPU to a query type by bracketing an exec with get_metrics() samples, see docs/guides/exec-cpu-attribution.md.

Receiving metadata in Go

On the server side, metadata is a plain map[string]any that flows through four layers. None of them interpret it — the field is read from the request and forwarded onto the downstream captures.

Layer Type / location Notes
HTTP request api.ExecRequest.Metadata (api/handlers.go) Decoded from the JSON body; json:"metadata,omitempty".
Backend call pebble.ExecOpts.Metadata (internal/pebble/sandbox.go) Passed into Manager.Exec(...) / Sandbox.Exec(...); backends MUST NOT interpret it.
Coordinator request DB (FSB_DEBUG) api_calls.metadata (api/request_log_sqlite.go) The top-level metadata object is compacted into a queryable column (no BLOB parse needed).
Structured event log metadata["client"] in Manager.exec (internal/pebble/manager.go) The container_execs event recorder writes the bag under client.

To add a new exec-like endpoint that also receives metadata, decode the same field into a pebble.ExecOpts and pass it to Manager.Exec:

var req struct {
    Command  []string       `json:"command"`
    Metadata map[string]any `json:"metadata,omitempty"`
}
// ... decode req ...
res, err := m.Exec(ctx, id, req.Command, "my_op",
    pebble.ExecOpts{Metadata: req.Metadata})

For execs dispatched outside Manager.Exec (the streaming path), the coordinator records the same bag: Manager.RecordExecTerminal(id, metadata, valid) applies RLVR terminal-outcome accounting, and the per-exec metadata lands in the coordinator's api_calls.metadata column when FSB_DEBUG=1 (the former node-side TraceExecStart JSONL trace has been removed).

Response types (HTTP client)

Class Fields
ExecResult stdout: str, stderr: str, exit_code: int
MetricsResult memory_usage_bytes, memory_limit_bytes, memory_percent, cpu_percent, pids_current, net_rx_bytes, net_tx_bytes, block_read_bytes, block_write_bytes
SnapshotResult snapshot_path: str, mem_file_path: str, node_id: str
SnapshotForkResult sandbox, error, status_code; call .ok() per child

All response dataclasses are frozen (immutable).

Context manager

All clients support the context-manager protocol (with for sync, async with for async):

from flash_sandbox import HTTPClient

with HTTPClient(host="localhost") as client:
    sandbox = client.start_sandbox(type="docker", image="alpine:latest")
    sandbox.exec_command(["echo", "hello"])
    sandbox.stop()
# Connection is automatically closed here.

Proxy / reverse-proxy support

Both clients support routing through a reverse proxy that uses path-based routing. Pass the full address including the path prefix:

http_client = HTTPClient(address="http://proxy.example.com:8092/v1/service/sandbox")

The Sandbox / AsyncSandbox wrappers inherit proxy support from whichever client you pass in.

Running tests

pip install -e ".[dev]"
pytest tests/

CLI (fsb)

Installing flash-sandbox also installs the fsb command.

pip install flash-sandbox

Point it at a coordinator with -H/--address or the FLASH_SANDBOX_ADDR env var (default localhost:8080). Like the SDK, fsb uses the coordinator's /sandboxes API, not a bare node's /native/*:

export FLASH_SANDBOX_ADDR=coordinator.example:8080

fsb ps                       # list running sandboxes
fsb inspect sb-a1b2c3        # status + platform info
fsb nodes                    # cluster nodes
fsb templates                # registered templates
fsb status                   # cluster summary

fsb create --template python # create from a template (or --image IMG)
fsb stop sb-a1b2c3
fsb snapshot sb-a1b2c3
fsb resume sb-a1b2c3

fsb exec sb-a1b2c3 -- ls -la /workspace   # exits with the remote exit code
fsb metrics sb-a1b2c3
fsb cp ./local.txt sb-a1b2c3:/tmp/x       # upload
fsb cp sb-a1b2c3:/tmp/x ./out.txt         # download

Add --json (before the subcommand) for machine-readable output:

fsb --json ps | jq '.[].id'

fsb speaks the coordinator's HTTP API only. For host-level containerd administration on a node (images/snapshots/content cleanup), use the containerd CLI (ctr) or the Docker CLI directly.

Metadata

Release files for flash-sandbox 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for flash-sandbox 0.4.0
File Size Uploaded
flash_sandbox-0.4.0.tar.gz 136.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flash-sandbox 0.4.0
File Interpreter ABI Platform
flash_sandbox-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 225.2 kB

Release files / flash_sandbox-0.4.0.tar.gz

Download URL flash_sandbox-0.4.0.tar.gz
Size 136.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3a565379112b12a23446c02acc0a66ec0746fedbd5d592851a7ee8b2f33c411a
BLAKE2b-256 checksum
How to use checksums
f8ec4b745348e387c0ca7f76d253030204b3dd1e9a958854d6ecde61835ae186
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / flash_sandbox-0.4.0-py3-none-any.whl

Download URL flash_sandbox-0.4.0-py3-none-any.whl
Size 88.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dbb5d58a5eb1f55f2ca53497ba10efa6d3cdcd0fecb90ddfe93e8ca4df1e0f80
BLAKE2b-256 checksum
How to use checksums
37c802e73a4e20a5779918cb3d247d0d96988e3473ef220160590d2dbe124abd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.12

2 release files

0.2.11

2 release files

0.2.10

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.1.13

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page