Skip to content

Latest commit

 

History

History
1192 lines (907 loc) · 38.1 KB

README.md

File metadata and controls

1192 lines (907 loc) · 38.1 KB

Python SDK API reference

Complete API reference for the BoxLite Python SDK.

Python: 3.10+ Platforms: macOS (Apple Silicon), Linux (x86_64, ARM64)

Table of contents


Runtime management

boxlite.Boxlite

The main runtime for creating and managing boxes.

from boxlite import Boxlite, Options, BoxOptions, ImageRegistry

Class methods

Method Signature Description
default() () -> Boxlite Create runtime with default settings (~/.boxlite)
__init__() (options: Options) -> Boxlite Create runtime with custom options

Instance methods

Method Signature Description
create() async (options: BoxOptions, name: str = None) -> Box Create a new box
get() async (box_id: str) -> Optional[Box] Reattach to an existing box by ID
get_info() async (box_id: str) -> Optional[BoxInfo] Get current box metadata by ID or name
list_info() async () -> List[BoxInfo] List all boxes
metrics() async () -> RuntimeMetrics Get runtime-wide metrics
images property -> ImageHandle The images this runtime can boot from; see boxlite.ImageHandle
registries property -> RegistryHandle The server's registry logins; raises UnsupportedError on a local runtime. See boxlite.RegistryHandle

Example

# Default runtime
runtime = Boxlite.default()

# Custom runtime
runtime = Boxlite(Options(home_dir="/custom/path"))

# Registry host config with auth
runtime = Boxlite(Options(
    image_registries=[
        ImageRegistry(
            host="registry.example.com",
            username="user",
            password="password",
        )
    ]
))

# Create a box
box = await runtime.create(BoxOptions(image="alpine:latest"))

# List all boxes
for info in await runtime.list_info():
    print(f"{info.id}: {info.state.status}")

boxlite.ImageHandle

The images a runtime can boot from: the local cache on an embedded runtime, the server catalog on a REST runtime. Registry credentials are accessed through runtime.images.registries.

Operation Embedded runtime REST runtime
pull Pulls into the cache Unsupported; creating a box pulls
list One row per cached reference Catalog alias and digest references
get Cached versions under the name Catalog versions, aliases and history
remove Forgets the name; layers stay Removes the entry; refused while a box can boot from it
usage Unsupported Image count, allowance and known bytes
create_alias() Unsupported Creates a new alias for a ready digest; existing names conflict
delete_alias() Unsupported Removes only the named alias; absent names return NotFound

get and remove take an image name, without a tag or digest, such as quay.io/acme/app. Alias names use OCI tag syntax; creation takes the sha256: digest of a ready version recorded for that image.

A registry tag belongs to the upstream registry. A BoxLite alias belongs to the runtime catalog and selects a recorded digest independently. A successful pull seeds an alias from the registry tag; it never overwrites an existing binding. Creating an alias never pushes a registry tag or pulls image bytes.

Alias creation refuses every existing name, even for the same digest, with AlreadyExists. Delete the alias explicitly before creating it again. Deletion removes only the binding: versions and existing boxes keep their digests. Creation history remains available; deletion is recorded by the server audit log.

Image details and versions expose aliases. Their history contains ImageAliasEvent records with alias, optional from_digest, to_digest, optional actor and recorded_at. A local cache mirrors its cached registry tags in aliases, leaves history empty and refuses alias writes.

detail = await runtime.images.get("quay.io/acme/app")
digest = detail.versions[0].digest
await runtime.images.create_alias(detail.name, "verified", digest)
await runtime.images.delete_alias(detail.name, "verified")
logins = await runtime.images.registries.list()

boxlite.RegistryHandle

The logins a REST server presents when it pulls a private image for a box. runtime.images.registries raises UnsupportedError on a local runtime, which pulls with the logins in Options(image_registries=...) instead.

Method Signature Description
list() async () -> List[RegistryCredential] Every login the organization holds, oldest first
create() async (*, registry_host: str, username: str, password: str, repository_prefix: str | None = None) -> RegistryCredential Add a login; AlreadyExistsError while one is held for the same registry and prefix
remove() async (id: str) -> None Remove a login; InvalidStateError, naming the boxes, while a box still pulls through it; NotFoundError for an unknown id

create() takes keyword arguments only. repository_prefix is whole path segments ending in /, such as "acme/"; leave it out for the whole registry. The password goes up once: RegistryCredential has id, registry_host, repository_prefix, username, created_by and created_at, and no password. remove() raises InvalidArgumentError for an id that is not a UUID, before any request.

import os
from boxlite import Boxlite, BoxliteRestOptions, BoxOptions

runtime = Boxlite.rest(BoxliteRestOptions.from_env())
login = await runtime.images.registries.create(
    registry_host="ghcr.io",
    repository_prefix="acme/",
    username="acme-bot",
    password=os.environ["GHCR_TOKEN"],
)
box = await runtime.create(BoxOptions(image="ghcr.io/acme/private-app:1"))
for login in await runtime.images.registries.list():
    print(login.registry_host, login.repository_prefix, login.username)

boxlite.Options

Runtime configuration options.

Field Type Default Description
home_dir str ~/.boxlite Base directory for runtime data
image_registries List[ImageRegistry] [] Registry transport, TLS, search, and auth configuration

boxlite.ImageRegistry

ImageRegistry(
    host="registry.local:5000",
    transport="http",
    search=True,
)

ImageRegistry(
    host="registry.example.com",
    skip_verify=True,
    username="user",
    password="password",
)

ImageRegistry(
    host="ghcr.io",
    bearer_token="token",
)
Field Type Default Description
host str Required Registry host, optionally with port. Do not include http:// or https://
transport str "https" "https" or "http"
skip_verify bool False Disable TLS certificate and hostname verification for HTTPS registries
search bool False Include this host when resolving unqualified image references
username / password str | None None Basic auth credentials. Provide both together
bearer_token str | None None Bearer token auth

boxlite.BoxOptions

Configuration options for creating a box.

Field Type Default Description
image str Required OCI image URI (e.g., "python:slim", "alpine:latest")
cpus int 1 Number of CPU cores (1–16, the guest kernel's limit)
memory_mib int 512 Memory limit in MiB (128-65536)
disk_size_gb int | None None Container disk size in GB, never smaller than the image (None = image size)
working_dir str "/root" Working directory inside container
env List[Tuple[str, str]] [] Environment variables as (key, value) pairs
volumes List[Tuple | Dict] [] Volume mounts; tuple = host bind, dict = managed_volume or host_path
network NetworkSpec | None None Structured network configuration. Omit for default enabled networking.
ports List[Tuple | Dict] [] Local TCP forwarding; omit host_port in a dict for automatic allocation
secrets List[Secret] [] Outbound HTTPS secret substitution rules
advanced AdvancedBoxOptions | None None Expert-only options, including capabilities.add and capabilities.drop
auto_delete int | None None 0 keeps the box after stop; above 0, a REST runtime deletes it that many seconds after stop and a local runtime at stop. None keeps the runtime's default (auto_remove locally)
auto_remove bool True Deprecated: use auto_delete. Auto cleanup when stopped
detach bool False Survive parent process exit

Capability policy is intentionally nested with the other expert-only options:

from boxlite import AdvancedBoxOptions, BoxOptions, ContainerCapabilities

options = BoxOptions(
    image="alpine:latest",
    advanced=AdvancedBoxOptions(
        capabilities=ContainerCapabilities(
            add=["NET_BIND_SERVICE"],
            drop=["NET_RAW"],
        )
    ),
)

NetworkSpec

from boxlite import NetworkSpec

network = NetworkSpec(
    mode="enabled",
    allow_net=["api.openai.com"],
)
Field Type Default Description
mode str Required "enabled" or "disabled"
allow_net List[str] [] Outbound allowlist used only when mode="enabled"

A host matched by a configured Secret is additionally reachable on port 443 without a rule of its own, so allow_net is not the only egress gate. The connection is dialed by name, and under a non-empty allow_net an answer in a private, loopback or CGNAT range is refused unless an IP or CIDR rule covers it.

mode="disabled" removes the guest network interface entirely. No secret makes a host reachable in that mode — there is no network to reach it over.

Volume mount format

A mount has exactly one origin. A tuple is always a host bind:

volumes=[
    ("/host/config", "/etc/app/config", True),  # Read-only
    ("/host/data", "/mnt/data", False),         # Read-write
]

A dict takes managed_volume — the volume's server-assigned id or its name, the server resolves either — or host_path, never both:

volumes=[
    {"managed_volume": "my-data", "guest_path": "/data"},
    {"managed_volume": "vol_01K2EXAMPLE", "guest_path": "/cache"},
    {"host_path": "/host/config", "guest_path": "/etc/app/config"},
]

Managed volumes require a REST runtime; host binds are local-runtime only. read_only is rejected on a managed mount - only host binds may be read-only.

Port forwarding format

ports=[
    (8080, 80, "tcp"),    # HTTP
    (5432, 5432, "tcp"),  # PostgreSQL
    {"guest_port": 3000},  # OS-selected host port
]

Port publication is local-only and TCP-only. For portable local/remote access, use box.network.tunnel(("127.0.0.1", port)); each returned tunnel is one-shot.

Secret format

from boxlite import Secret

secrets=[
    Secret(
        name="openai",
        value="sk-...",
        hosts=["api.openai.com"],
        # placeholder defaults to "<BOXLITE_SECRET:openai>"
    )
]

Each host a secret matches becomes reachable on port 443 under a non-empty allow_net without a rule of its own — a secret is an egress grant as well as a credential. It is a by-name dial like any hostname rule, so a host answering with a private, loopback or CGNAT address is still refused unless an IP or CIDR rule covers that range. Those hosts on port 80, on any other port, or over UDP are governed by allow_net alone.


Box handle

boxlite.Box

Handle to a running or stopped box.

Properties

Property Type Description
id str Unique box identifier (ULID format)
network NetworkHandle Box-scoped tunnel operations
ssh SshHandle SSH control (configure/status/disable)

Methods

Method Signature Description
exec() (cmd, args, env, tty) -> Execution Execute command (async)
attach() (execution_id=None, stdin=True) -> Execution Follow a running session (async)
stop() () -> None Stop the box gracefully (async)
info() async () -> BoxInfo Get box metadata
metrics() () -> BoxMetrics Get resource usage metrics (async)

attach() with no argument follows the box's main command — the container init, which is what run IMAGE COMMAND starts. Pass execution_id to reattach to an exec() session instead.

attach(stdin=False) attaches read-only — docker's --no-stdin. The returned execution exposes no stdin, and against a remote runtime the server refuses stdin, signal and resize sent over that socket. Use it to follow a box's output without a keystroke reaching the workload.

The guarantee is scoped to the attach stream, as it is in docker: holding a box handle still lets you stop() it, and Execution.kill() / .signal() / .resize_tty() go over their own routes rather than this socket.


boxlite.BoxInfo

Metadata about a box.

Field Type Description
id str Unique box identifier (ULID)
name str | None Optional user-assigned name
state BoxStateInfo Runtime state with status, running, and nullable pid and exit_code fields. exit_code is how the main command ended — its own code, or 128 + n when a signal ended it; stopping a box signals that command, so a stop is recorded here too; 0 is a real value, so test for None rather than falsiness
created_at str ISO 8601 creation timestamp
started_at str | None Time when the box most recently entered Running (RFC 3339); None if not recorded or unavailable over REST
last_activity_at str | None Time the box was last active (RFC 3339), the clock AutoStop measures idleness against; None for a local box, which records no activity
image str OCI image used
cpus int Allocated CPU cores
memory_mib int Allocated memory in MiB
network NetworkInfo | None Current network configuration and resolved local publications

NetworkInfo has outbound: OutboundNetworkInfo, inbound: InboundNetworkInfo, and published_ports: List[PublishedPort] | None; each direction has mode: str and allow_net: List[str]. The pre-split mode: str / allow_net: List[str] attributes remain readable on NetworkInfo as views onto outbound. Each PublishedPort has named guest_port, host_ip, host_port, and protocol attributes.

network is None means network information is unavailable. Within NetworkInfo, published_ports is None means the current handle does not know the lifecycle's publications, [] means there are no active publications, and a populated list contains concrete active local bindings. BoxOptions.ports remains the request API; its optional host port is not reused in resolved output.

box.info() has no synchronous variant. Local metadata reads report bindings captured when this handle started or reattached the box; REST metadata reads fetch the current server record. A newly loaded local running box has no live binding data yet, so box, get, or list info may report published_ports is None until an operation requiring live state reattaches it.


boxlite.BoxStateInfo

Detailed state information for a box.

Value Description
Created Box created but not yet started
Starting Box is initializing
Running Box is running and ready
Stopping Box is shutting down
Stopped Box is stopped
Failed Box encountered an error

SSH control

Both boxlite.Box and SyncBox expose box.ssh.

Operation Signature Description
Configure await box.ssh.configure(config) -> SshStatus Apply SSH configuration
Query await box.ssh.status() -> SshStatus Return SSH status
Disable await box.ssh.disable() -> SshStatus Disable SSH

If a box is not running and explicitly sets BoxOptions.cmd or entrypoint, call await box.start() before configuring SSH; otherwise configure() raises InvalidStateError. See the SSH lifecycle guide for implicit startup and stopped-box behavior.

from boxlite import SshAccount, SshConfig

config = SshConfig(
    listen_address="0.0.0.0:2222",
    host_private_key=open("host_key").read(),
    accounts=[SshAccount(login="alice", authorized_keys=[open("id_ed25519.pub").read()])],
)
status = await box.ssh.configure(config)

In the sync API the same calls drop await: box.ssh.configure(config).

SshConfig(listen_address, host_private_key, accounts=[]) accepts SshAccount(login, authorized_keys=[], ca=None) entries; certificate authentication uses SshCaConfig(public_key, principal). Credential-bearing objects redact keys in repr().

Configuration getters return copies: config.accounts, each account's authorized_keys, and its nested ca must be assigned back after editing. For example, config.accounts[0].authorized_keys.append(key) does not update config. This follows PyO3's field-copying rules.

accounts = config.accounts
account = accounts[0]
keys = account.authorized_keys
keys.append(open("new_user.pub").read())
account.authorized_keys = keys
ca = account.ca
if ca is not None:
    ca.principal = "updated-principal"
    account.ca = ca
config.accounts = accounts
status = await box.ssh.configure(config)

SshStatus exposes enabled: bool, generation: int, listen_address, host_public_key, and host_key_fingerprint. generation is a Python int that preserves the full unsigned 64-bit value.


Network tunnels

Both boxlite.Box and boxlite.SimpleBox expose box.network.

Operation Signature Description
Tunnel await box.network.tunnel(target, options=None) -> BoxTunnel Prepare a tunnel; target is a socket address tuple (ip, port) (loopback or the guest IP), options is TunnelOptions(is_public=False) for a private cloud tunnel, public by default
Token tunnel.token() -> str | None Full on creation, masked on later preparations; is_public() reports the mode
Revoke await box.network.revoke_tunnel(port) -> None Withdraw the port's tunnel; open connections stay open
Forward await tunnel.forward(listen) -> TunnelForwarder Bind one local listener and forward every client
Inspect tunnel.uri() -> str | None Read the prepared public URL; None for a local box
Connect await tunnel.connect() -> BoxConnection Consume the prepared tunnel into its byte stream
Read/write await connection.read(max_bytes), await connection.write(data) Exchange bytes with the service
Close await connection.close() Close the connection

Creation accepts (ip, port) and IPv6 (ip, port, 0, 0) tuples. IPs must be numeric; hostnames and nonzero IPv6 flowinfo/scope_id are rejected. Bare ports and SocketAddress objects are no longer creation arguments.

For the separate forward(listen) operation, use SocketAddress.tcp(host="127.0.0.1", port=0) or SocketAddress.unix(path). A forwarder exposes local_addr(), await wait(), and repeatable await close(); synchronous and SimpleBox wrappers provide the same operations.

Each BoxTunnel is one-shot: choose connect() or forward(). Forwarding opens authenticated connections against the existing declaration and cannot undo revocation. This differs from BoxOptions.ports, which creates a persistent, local-only host listener that accepts repeated connections from ordinary host applications.


Command execution

boxlite.Execution

Represents a running command execution.

Methods

Method Signature Description
stdout() () -> ExecStdout Get stdout stream (async iterator)
stderr() () -> ExecStderr Get stderr stream (async iterator)
stdin() () -> ExecStdin Get stdin writer
wait() () -> ExecResult Wait for completion (async)
kill() (signal: int = 9) -> None Send signal to process (async)
resize_tty() (rows: int, cols: int) -> None Resize PTY terminal for executions started with tty=True (async)

Example

# Execute with streaming output
execution = await box.exec("python", ["-c", "for i in range(5): print(i)"])

# Stream stdout
async for line in execution.stdout():
    print(f"Output: {line}")

# Wait for completion
result = await execution.wait()
print(f"Exit code: {result.exit_code}")

boxlite.ExecStdout / boxlite.ExecStderr

Async iterators for streaming output.

# Stream stdout line by line
stdout = execution.stdout()
async for line in stdout:
    print(line)

# Stream stderr
stderr = execution.stderr()
async for line in stderr:
    print(f"Error: {line}", file=sys.stderr)

Note: Each stream can only be iterated once. After iteration, the stream is consumed.


boxlite.ExecStdin

Writer for sending input to a running process.

Methods

Method Signature Description
send_input() (data: bytes) -> None Write bytes to stdin (async)

Example

# Interactive input
execution = await box.exec("cat")
stdin = execution.stdin()

# Send data
await stdin.send_input(b"Hello\n")
await stdin.send_input(b"World\n")

# Wait for completion
result = await execution.wait()

boxlite.ExecResult

Result of a completed execution.

Field Type Description
exit_code int Process exit code (0 = success)

Note: For higher-level APIs (SimpleBox.exec()), the result also includes stdout and stderr strings.


Box types

boxlite.SimpleBox

Context manager for basic command execution with automatic cleanup.

from boxlite import SimpleBox

Constructor

SimpleBox(
    image: str,
    memory_mib: int = None,
    cpus: int = None,
    runtime: Boxlite = None,
    name: str = None,
    auto_remove: bool = True,
    **kwargs
)
Parameter Type Default Description
image str Required Container image to use
memory_mib int System default Memory limit in MiB
cpus int System default Number of CPU cores
runtime Boxlite Global default Runtime instance
name str None Optional unique name
auto_remove bool True Remove box when stopped
**kwargs Additional options: env, volumes, ports, working_dir, network, secrets

Properties

Property Type Description
id str Box ID (raises if not started)

Methods

Method Signature Description
start() () -> Self Explicitly start the box (async)
exec() (cmd, *args, env=None, user=None, timeout=None, cwd=None) -> ExecResult Execute command and wait (async)
info() async () -> BoxInfo Get box metadata
shutdown() () -> None Shutdown and release resources

Example

async with SimpleBox(image="python:slim") as box:
    result = await box.exec("python", "-c", "print('Hello!')")
    print(result.stdout)   # "Hello!\n"
    print(result.exit_code)  # 0

    # Per-command options
    result = await box.exec("pwd", cwd="/tmp")         # working directory
    result = await box.exec("whoami", user="nobody")    # run as user
    result = await box.exec("sleep", "60", timeout=5)   # timeout in seconds

boxlite.CodeBox

Specialized box for Python code execution with package management.

from boxlite import CodeBox

Constructor

CodeBox(
    image: str = "python:slim",
    memory_mib: int = None,
    cpus: int = None,
    runtime: Boxlite = None,
    **kwargs
)

Methods

Method Signature Description
run() (code: str, timeout: int = None) -> str Execute Python code (async)
run_script() (script_path: str) -> str Execute Python script file (async)
install_package() (package: str) -> str Install package with pip (async)
install_packages() (*packages: str) -> str Install multiple packages (async)

Example

async with CodeBox() as cb:
    # Install packages
    await cb.install_package("requests")

    # Run code
    result = await cb.run("""
import requests
print(requests.get('https://api.github.com/zen').text)
""")
    print(result)

boxlite.BrowserBox

Box configured for browser automation with Chrome DevTools Protocol.

from boxlite import BrowserBox, BrowserBoxOptions

BrowserBoxOptions

Field Type Default Description
browser str "chromium" Browser type: "chromium", "firefox", "webkit"
memory int 2048 Memory in MiB
cpu int 2 Number of CPU cores

Browser CDP ports

Browser Port
chromium 9222
firefox 9223
webkit 9224

Methods

Method Signature Description
endpoint() () -> str Get CDP endpoint URL

Example

from boxlite import BrowserBox, BrowserBoxOptions

opts = BrowserBoxOptions(browser="chromium", memory=4096)
async with BrowserBox(opts) as browser:
    endpoint = browser.endpoint()  # "http://localhost:9222"

    # Connect with Puppeteer or Playwright
    # puppeteer.connect({ browserURL: endpoint })

boxlite.ComputerBox

Box with full desktop environment and GUI automation capabilities.

from boxlite import ComputerBox

Constructor

ComputerBox(
    cpu: int = 2,
    memory: int = 2048,
    gui_http_port: int = 3000,
    gui_https_port: int = 3001,
    runtime: Boxlite = None,
    **kwargs
)

Mouse methods

Method Signature Description
mouse_move() (x: int, y: int) -> None Move cursor to coordinates (async)
left_click() () -> None Left click at current position (async)
right_click() () -> None Right click at current position (async)
middle_click() () -> None Middle click at current position (async)
double_click() () -> None Double left click (async)
triple_click() () -> None Triple left click (async)
left_click_drag() (start_x, start_y, end_x, end_y) -> None Drag from start to end (async)
cursor_position() () -> Tuple[int, int] Get current cursor (x, y) (async)

Keyboard methods

Method Signature Description
type() (text: str) -> None Type text characters (async)
key() (text: str) -> None Press key or key combination (async)
Key syntax reference

The key() method uses xdotool key syntax:

Key Syntax
Enter Return
Tab Tab
Escape Escape
Backspace BackSpace
Delete Delete
Arrow keys Up, Down, Left, Right
Function keys F1, F2, ... F12
Modifiers ctrl, alt, shift, super
Combinations ctrl+c, ctrl+shift+s, alt+Tab

Examples:

await computer.key("Return")        # Press Enter
await computer.key("ctrl+c")        # Copy
await computer.key("ctrl+shift+s")  # Save As
await computer.key("alt+Tab")       # Switch window
await computer.key("ctrl+a Delete") # Select all and delete

Display methods

Method Signature Description
wait_until_ready() (timeout: int = 60) -> None Wait for desktop ready (async)
screenshot() () -> dict Capture screen (async)
scroll() (x, y, direction, amount=3) -> None Scroll at position (async)
get_screen_size() () -> Tuple[int, int] Get screen dimensions (async)
Screenshot return format
{
    "data": str,    # Base64-encoded PNG
    "width": int,   # 1024 (default)
    "height": int,  # 768 (default)
    "format": str   # "png"
}
Scroll directions
Direction Description
"up" Scroll up
"down" Scroll down
"left" Scroll left
"right" Scroll right

Example

async with ComputerBox() as desktop:
    await desktop.wait_until_ready()

    # Take screenshot
    screenshot = await desktop.screenshot()

    # Mouse interaction
    await desktop.mouse_move(100, 200)
    await desktop.left_click()

    # Type text
    await desktop.type("Hello, World!")
    await desktop.key("Return")

    # Get screen size
    width, height = await desktop.get_screen_size()

boxlite.InteractiveBox

Box for interactive terminal sessions with PTY support.

from boxlite import InteractiveBox

Constructor

InteractiveBox(
    image: str,
    shell: str = "/bin/sh",
    tty: bool = None,
    memory_mib: int = None,
    cpus: int = None,
    runtime: Boxlite = None,
    name: str = None,
    auto_remove: bool = True,
    **kwargs
)
Parameter Type Default Description
image str Required Container image
shell str "/bin/sh" Shell to run
tty bool | None None TTY mode (see below)
TTY mode
Value Behavior
None Auto-detect from sys.stdin.isatty()
True Force TTY mode with I/O forwarding
False No I/O forwarding (programmatic control)

Methods

Method Signature Description
wait() () -> None Wait for shell to exit (async)

Example

# Interactive shell session
async with InteractiveBox(image="alpine:latest") as box:
    # You're now in an interactive shell
    # Type commands, see output in real-time
    # Type "exit" to close
    await box.wait()

Sync API

Synchronous wrappers using greenlet fiber switching. Requires pip install boxlite[sync].

from boxlite import SyncBoxlite, SyncBox, SyncSimpleBox, SyncCodeBox

When to use

Use Case API
New async applications Async API (default)
Existing sync codebase Sync API
Jupyter notebooks Sync API
REPL/interactive use Sync API
Inside async functions Async API only

Comparison table

Async API Sync API Notes
Boxlite SyncBoxlite get_info() and list_info() are async-only
Box SyncBox Box.info() is async-only and is not exposed by SyncBox
Execution SyncExecution
ExecStdout SyncExecStdout Regular iterator
ExecStderr SyncExecStderr Regular iterator
SimpleBox SyncSimpleBox SimpleBox.info() is async-only and is not exposed by SyncSimpleBox
CodeBox SyncCodeBox Inherits the async-only metadata rule
RegistryHandle SyncRegistryHandle

Classes

SyncBoxlite

from boxlite import SyncBoxlite, BoxOptions

with SyncBoxlite.default() as runtime:
    box = runtime.create(BoxOptions(image="alpine:latest"))
    execution = box.exec("echo", ["Hello"])
    for line in execution.stdout():
        print(line)
    box.stop()

SyncBoxlite.rest(options) mirrors Boxlite.rest(...):

from boxlite import BoxliteRestOptions, SyncBoxlite

with SyncBoxlite.rest(BoxliteRestOptions.from_env()) as runtime:
    print(runtime.images.usage())
    print(runtime.images.registries.list())

SyncSimpleBox

from boxlite import SyncSimpleBox

with SyncSimpleBox(image="python:slim") as box:
    result = box.exec("python", "-c", "print('Hello!')")
    print(result.stdout)

SyncCodeBox

from boxlite import SyncCodeBox

with SyncCodeBox() as cb:
    result = cb.run("print('Hello, World!')")
    print(result)

Architecture

The sync API uses greenlet fiber switching:

  1. A dispatcher fiber runs the asyncio event loop
  2. User code runs in the main fiber
  3. Sync methods switch to dispatcher, await async operations, then switch back

Limitation: Cannot be used inside an async context (when an event loop is already running).


Error types

from boxlite import BoxliteError, ExecError, TimeoutError, ParseError

Exception hierarchy

BoxliteError (base)
├── ExecError       # Command execution failed
├── TimeoutError    # Operation timed out
├── ParseError      # Output parsing failed
└── NotFoundError, InvalidStateError, …   # Runtime failures, see below

Runtime failures

A failure the native runtime reports is raised as one class per kind of failure, each with a code. The names and codes are the ones BoxliteError::http() gives (src/shared/src/errors.rs), so a REST server answers the same failure with the same code. Every class is also a RuntimeError, which is what these failures were raised as before.

Class code Raised when
NotFoundError not_found The box, image, volume or snapshot does not exist
AlreadyExistsError already_exists The name is taken
InvalidStateError invalid_state The resource's state does not allow the operation
InvalidArgumentError invalid_argument A value was refused
UnsupportedError unsupported The operation is not available on this runtime
ResourceExhaustedError resource_exhausted A limit was reached
StoppedError stopped The box has stopped
SessionReapedError session_reaped The session was reaped
ImageError image_pull_failed An image could not be pulled or read
ExecutionError execution_failed A process could not be run
NetworkError network_unavailable The network was unavailable
UpstreamUnavailableError upstream_unavailable A component did not answer
EngineError engine_unavailable The virtualization engine failed
ConfigError config_error Configuration is invalid, or authentication failed
StorageError storage_error Storage could not be read or written
DatabaseError database_error The runtime's database failed
MetadataError metadata_error Stored metadata could not be read
InternalError internal An unexpected failure
from boxlite import NotFoundError

try:
    await runtime.remove("no-such-box")
except NotFoundError as e:
    print(e.code)  # not_found

BoxliteError

Base exception for all BoxLite errors.

try:
    async with SimpleBox(image="invalid:image") as box:
        pass
except BoxliteError as e:
    print(f"BoxLite error: {e}")

ExecError

Raised when a command execution fails (non-zero exit code).

Attribute Type Description
command str The command that failed
exit_code int Non-zero exit code
stderr str Standard error output
try:
    result = await box.exec("false")  # Exit code 1
except ExecError as e:
    print(f"Command: {e.command}")
    print(f"Exit code: {e.exit_code}")
    print(f"Stderr: {e.stderr}")

TimeoutError

Raised when an operation times out.

try:
    await computer.wait_until_ready(timeout=5)
except TimeoutError:
    print("Desktop did not become ready in time")

ParseError

Raised when output parsing fails.

try:
    x, y = await computer.cursor_position()
except ParseError:
    print("Failed to parse cursor position")

Metrics

boxlite.RuntimeMetrics

Aggregate metrics across all boxes.

Field Type Description
boxes_created int Total boxes created
boxes_destroyed int Total boxes destroyed
total_exec_calls int Total command executions
active_boxes int Currently running boxes
runtime = Boxlite.default()
metrics = await runtime.metrics()

print(f"Boxes created: {metrics.boxes_created}")
print(f"Active boxes: {metrics.active_boxes}")

boxlite.BoxMetrics

Per-box resource usage metrics.

Field Type Description
cpu_time_ms int Total CPU time in milliseconds
memory_usage_bytes int Current memory usage in bytes
network_bytes_sent int Total bytes sent
network_bytes_received int Total bytes received
metrics = await box.metrics()

print(f"CPU time: {metrics.cpu_time_ms}ms")
print(f"Memory: {metrics.memory_usage_bytes / (1024**2):.2f} MB")

Constants

Default values used by BoxLite.

Constant Value Description
DEFAULT_CPUS 1 Default CPU cores
DEFAULT_MEMORY_MIB 2048 Default memory in MiB
COMPUTERBOX_CPUS 2 ComputerBox default CPUs
COMPUTERBOX_MEMORY_MIB 2048 ComputerBox default memory
COMPUTERBOX_DISPLAY_WIDTH 1024 Screen width in pixels
COMPUTERBOX_DISPLAY_HEIGHT 768 Screen height in pixels
COMPUTERBOX_GUI_HTTP_PORT 3000 HTTP GUI port
COMPUTERBOX_GUI_HTTPS_PORT 3001 HTTPS GUI port
DESKTOP_READY_TIMEOUT 60 Desktop ready timeout (seconds)

See also