Skip to content

convox/convox-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Python SDK for the Convox API.

Installation

pip install convox

Quick Start

from convox import ConvoxClient

client = ConvoxClient(
    host="console.convox.com",
    api_key="your-deploy-key",
    rack="org/rack-name",          # required for console connections
)

# List apps
for app in client.apps.list():
    print(f"{app.name}: {app.status}")

# Get app details
app = client.apps.get("myapp")
print(f"Release: {app.release}, Locked: {app.locked}")

Authentication

For console-managed racks the api_key is a deploy key generated in the Convox console (Organization > Deploy Keys). Deploy keys are the recommended auth method for programmatic and SDK usage. The examples below all use deploy keys.

Three options, in order of precedence:

1. Explicit credentials (recommended)

client = ConvoxClient(
    host="console.convox.com",
    api_key="your-deploy-key",
    rack="org/rack-name",        # required for console-managed racks
)

2. Environment variables

Set CONVOX_HOST, CONVOX_PASSWORD (deploy key), and CONVOX_RACK:

client = ConvoxClient.from_env()

3. CLI config file

Reads the auth file written by convox login (~/.config/convox/auth for v3, ~/.convox/auth for v2). This is a convenience for local development when you have already authenticated via the CLI -- it reads whatever credential is stored for the given host, whether that is a CLI session token or a deploy key you placed there manually.

# Console-managed rack — specify both host and rack
client = ConvoxClient.from_cli_config(
    host="console.convox.com",       # auth file lookup key
    rack="org/rack-name",            # rack to target
)

# Direct rack connection (host and rack are the same)
client = ConvoxClient.from_cli_config(rack="rack.example.com")

API Reference

Apps

# List, get, create, delete
apps = client.apps.list()
app = client.apps.get("myapp")
app = client.apps.create("myapp")
client.apps.delete("myapp")

# Update (lock/unlock, set parameters)
client.apps.update("myapp", lock=True)
client.apps.update("myapp", parameters={"BuildCpu": "512"})

# Cancel in-progress operation
client.apps.cancel("myapp")

Builds

builds = client.builds.list("myapp")
build = client.builds.get("myapp", "BABCDEF")
build = client.builds.create("myapp", "https://example.com/source.tgz",
                              description="v2.0 release")

Releases

releases = client.releases.list("myapp")
release = client.releases.get("myapp", "RABCDEF")

# Promote a release to production
client.releases.promote("myapp", "RABCDEF")

# Rollback to a previous release
client.releases.rollback("myapp", "RPREVIOUS")

Environment Variables

# Get current env
env = client.environment.get("myapp")
print(env)  # {"DATABASE_URL": "postgres://...", "SECRET_KEY": "..."}

# Set env vars (creates a new release)
release = client.environment.set("myapp", {"KEY": "value", "OTHER": "val"})

# Unset env vars (creates a new release)
release = client.environment.unset("myapp", "KEY")

Services

services = client.services.list("myapp")
client.services.update("myapp", "web", count=3, memory=1024)
client.services.restart("myapp", "web")

Processes

processes = client.processes.list("myapp")
process = client.processes.get("myapp", "proc-abc123")
client.processes.stop("myapp", "proc-abc123")

Resources

# App-scoped resources
resources = client.resources.list("myapp")
resource = client.resources.get("myapp", "database")

# System-scoped resources
all_resources = client.resources.system_list()
client.resources.system_create("postgres", name="mydb")
client.resources.system_link("mydb", "myapp")

System

system = client.system.get()
print(f"{system.name} v{system.version} ({system.provider})")

capacity = client.system.capacity()

Low-Level API

Escape hatch for endpoints not covered by resource methods:

# Equivalent to `convox api get /apps`
data = client.api.get("/apps")

# POST with form data
data = client.api.post("/apps", data={"name": "newapp"})

Error Handling

All API errors raise typed exceptions:

from convox import (
    ConvoxAPIError,        # Base for all API errors
    ConvoxAuthError,       # 401 - bad credentials
    ConvoxForbiddenError,  # 401 - insufficient permissions
    ConvoxNotFoundError,   # 404 - resource not found
    ConvoxValidationError, # 400 - invalid input
    ConvoxConflictError,   # 409 - already exists / locked
    ConvoxServerError,     # 500 - server error
    ConvoxConnectionError, # Network failure
    ConvoxTimeoutError,    # Request timeout
)

try:
    app = client.apps.get("myapp")
except ConvoxNotFoundError:
    print("App does not exist")
except ConvoxConflictError:
    print("App is locked")
except ConvoxAPIError as e:
    print(f"API error {e.status_code}: {e.message}")
    print(f"Request ID: {e.request_id}")  # for support tickets

Retry Configuration

The SDK retries on 429 (rate limit) and 502/503/504 (gateway errors) with exponential backoff:

from convox import ConvoxClient, RetryConfig

client = ConvoxClient(
    host="rack.example.com",
    api_key="key",
    retry=RetryConfig(
        max_retries=5,           # default: 3
        backoff_base=2.0,        # default: 1.0
        backoff_max=60.0,        # default: 30.0
        retry_on_500=True,       # default: False (500s may be permanent)
    ),
)

Disable retries:

client = ConvoxClient(host="...", api_key="...", retry=RetryConfig(max_retries=0))

Timeouts

The default timeout is 30 seconds. Most API operations complete well within this limit, since build creation (builds.create) returns immediately after dispatching the build to a Kubernetes pod.

The one exception is builds.import_build(), which is synchronous — the server reads the entire gzip archive and pushes every service image to the container registry before responding. This method defaults to a 1800-second (30-minute) per-request timeout, independent of the client-wide default:

# Uses the 30-minute default automatically
build = client.builds.import_build("myapp", tarball_bytes)

# Override with a custom timeout if needed
build = client.builds.import_build("myapp", tarball_bytes, timeout=3600)

You can also pass a per-request timeout to the low-level API:

data = client.api.post("/apps/myapp/builds/import", content=tarball, timeout=600)

Streaming

The stream() method returns the raw httpx.Response without checking for error status codes. This is intentional: streaming endpoints (logs, build output) may send partial data before an error occurs.

response = client.stream("GET", "/apps/myapp/logs", params={"follow": "true"})
try:
    if response.status_code >= 400:
        print(f"Error: {response.status_code}")
    else:
        for line in response.iter_lines():
            print(line)
finally:
    response.close()

CLI Comparison

CLI Command SDK Equivalent
convox apps client.apps.list()
convox apps info -a myapp client.apps.get("myapp")
convox apps create myapp client.apps.create("myapp")
convox apps delete myapp client.apps.delete("myapp")
convox builds -a myapp client.builds.list("myapp")
convox releases -a myapp client.releases.list("myapp")
convox releases promote RABCDEF -a myapp client.releases.promote("myapp", "RABCDEF")
convox env -a myapp client.environment.get("myapp")
convox env set KEY=val -a myapp client.environment.set("myapp", {"KEY": "val"})
convox env unset KEY -a myapp client.environment.unset("myapp", "KEY")
convox api get /apps client.api.get("/apps")

Contributing

# Install dev dependencies
pip install -e ".[dev]"

# Run unit tests (mocked, no rack needed)
pytest tests/

# Run integration tests (requires a live rack)
CONVOX_HOST=console.convox.com \
CONVOX_PASSWORD=your-deploy-key \
CONVOX_RACK=org/rack-name \
pytest integration_tests/ -v

# Lint and format
ruff check .
ruff format .

# Type check
mypy src/

License

Apache 2.0

About

Python SDK for the Convox API

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages