Skip to main content

Python SDK for the Sail sandbox platform

Project description

sail-sdk

Python SDK for Sail sailboxes.

Install

pip install sail-sdk
uv add sail-sdk

Configure

The SDK reads configuration from environment variables:

export SAIL_API_KEY=sk_...

By default the SDK talks to production.

For dev or staging, set SAIL_MODE:

export SAIL_MODE=staging  # or dev

Create A Sailbox

import sail

app = sail.App.find(name="example-app", mint_if_missing=True)

sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="sandbox-1",
    cpu=1,
    memory_mib=1024,
)

print(sb.sailbox_id)
print(sb.status)
print(sb.worker_address)

Sailbox.create returns after the VM is running. Supported create arguments are:

  • app: a sail.App from App.find
  • image: a sail.Image value or a built custom image
  • name: sailbox name
  • cpu: vCPU count, default 1
  • memory_mib: memory in MiB, default 1024
  • disk_gib: writable disk size in GiB, default 1
  • ingress_ports: optional list of guest ports to expose

AMD64 image values still exist in the SDK for compatibility, but new sailboxes currently must use arm64 images. The scheduler rejects AMD64 sailbox create and image build requests.

Daemon Sailboxes

Use Sailbox.create_daemon when the sailbox should start and manage one long-running process. Daemon sailboxes support lifecycle operations, listeners, and outbound requests, but public exec() is disabled after the daemon is registered.

import sail

app = sail.App.find(name="daemon-demo", mint_if_missing=True)

sb = sail.Sailbox.create_daemon(
    app=app,
    image=sail.Image.debian_arm64,
    name="daemon-1",
    command="python3 -m http.server 3000",
    can_checkpoint_cmd="true",
    checkpoint_poll_frequency_s=5,
    min_checkpoint_cooldown_s=30,
    ingress_ports=[3000],
)

print(sb.sailbox_id)
print(sb.daemon_pgid)

Exec

result = sb.exec("echo hi", timeout=5).wait()

print(result.stdout)
print(result.stderr)
print(result.returncode)

timeout is the command runtime budget in seconds. Omit it to let the command run without an SDK-provided runtime limit.

Background exec starts a detached process and waits only for the launcher shell:

sb.exec("python3 -m http.server 3000", background=True).wait()

Only one exec request may run at a time for a sailbox. If another exec is already active, the SDK raises sail.SailboxExecAlreadyRunningError.

Networking

Expose guest ports when creating the sailbox:

sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="sandbox-net",
    ingress_ports=[3000],
)

sb.exec("python3 -m http.server 3000", background=True).wait()

listener = sb.listener(3000)
print(listener.url)
print(listener.route_status)
listener.wait(timeout=60)

Use listeners() to list every exposed port:

for listener in sb.listeners():
    print(listener.port, listener.url, listener.route_status)

Use request() to ask the worker proxy to make an outbound HTTP request on behalf of the sailbox:

req = sb.request(
    "POST",
    "https://example.com/api",
    json={"hello": "world"},
    idempotency_key="example-1",
)

completed = req.wait()
print(completed.status)
if completed.response:
    print(completed.response.status_code)
    print(completed.response.text)

idempotency_key is required for request() and ensures only a single request is sent from the client side. data and json are mutually exclusive.

You can refresh, wait for, or cancel a tracked request:

req.refresh()
req.wait(timeout=30)
req.cancel()

Lifecycle

sb.checkpoint()  # durably checkpoint while keeping the sailbox running
sb.stop()        # checkpoint and stop
sb.start()       # resume a stopped sailbox
sb.terminate()   # permanently terminate

After stop(), exec() raises SailboxExecutionError until start() succeeds.

Custom Images

Start from the arm64 Debian base image, add build steps, then call build():

image = (
    sail.Image.debian_arm64.apt_install("git", "curl")
    .pip_install("requests")
    .run_commands("python3 -m pip show requests >/tmp/requests.txt")
    .env({"APP_ENV": "demo"})
)

built_image = image.build(timeout=1800)

sb = sail.Sailbox.create(
    app=app,
    image=built_image,
    name="custom-image-demo",
)

Supported image build helpers:

  • apt_install(*packages)
  • pip_install(*packages)
  • run_commands(*commands)
  • env(dict[str, str])
  • build(timeout=1800)

Images

Current image properties:

arm64_image = sail.Image.debian_arm64
arm_image = sail.Image.debian_arm
amd64_image = sail.Image.debian_amd64
amd_image = sail.Image.debian_amd

Common SDK exceptions:

  • sail.SailError
  • sail.SailboxError
  • sail.SailboxCreationError
  • sail.SailboxExecutionError
  • sail.SailboxExecAlreadyRunningError
  • sail.SailboxExecRequestNotFoundError
  • sail.SailboxTerminatedError
  • sail.SailboxWorkerLostError
  • sail.ImageBuildError

Examples

  • examples/sailbox_smoke.py: start an arm64 Debian sailbox and run exec commands.
  • examples/sailbox_custom_image.py: build an arm64 custom image with apt_install, pip_install, run_commands, and env, then launch a sailbox from it.

Publishing

Build a distributable package locally from the repo root:

just python-sdk-build

Publish from a developer machine with a PyPI token:

export UV_PUBLISH_TOKEN=pypi-...
just python-sdk-publish

The repository also includes a GitHub Actions release workflow at .github/workflows/python-sdk-publish.yml. It publishes when you push a tag like python-sdk-v0.1.0, after verifying that the tag version matches sail.__version__.

Recommended setup:

  1. Create the sail-sdk project on PyPI.
  2. Configure PyPI Trusted Publishing for this GitHub repository and the python-sdk-publish.yml workflow.
  3. Bump sdk/python/src/sail/__about__.py.
  4. Push a matching tag: git tag python-sdk-v0.1.0 && git push origin python-sdk-v0.1.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

sail_sdk-0.1.3.tar.gz (32.7 kB view details)

Uploaded Source

Built Distribution

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

sail_sdk-0.1.3-py3-none-any.whl (31.0 kB view details)

Uploaded Python 3

File details

Details for the file sail_sdk-0.1.3.tar.gz.

File metadata

  • Download URL: sail_sdk-0.1.3.tar.gz
  • Upload date:
  • Size: 32.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","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":true}

File hashes

Hashes for sail_sdk-0.1.3.tar.gz
Algorithm Hash digest
SHA256 fe98ca3f279224f4ff9644aa416c84e683f852045bbb55173b9cb0e727bad411
MD5 0c33fd0251ec87f2631f0d8e45a62b60
BLAKE2b-256 1f956b2788a23d653b3e143aec6264fbf17d850356c4b2d27c628a96a1c23dee

See more details on using hashes here.

File details

Details for the file sail_sdk-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: sail_sdk-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 31.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","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":true}

File hashes

Hashes for sail_sdk-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 1e6a4a04659dbc7b96520bceba0281fc1fe15f209e108ee70bcf1ac7919e69b9
MD5 f668185d9f05242989001b152b08bc2a
BLAKE2b-256 9f59241c141f4d54ee0ad16945353324b7f4509893d509ba9b2b4fb0ebd1efeb

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