Python SDK
The promigence package on PyPI, version 0.1.0b7, for Python 3.9 or newer. It has two parts: a Sandbox class for working in one sandbox, and the Promigence client for everything else the API does. Each has an async twin.
pip install promigence
Language: bashIt brings one dependency with it, httpx. Using TypeScript instead? The TypeScript SDK has the same two parts.
Sign in
The SDK finds your API key the way the CLI does, and the first match wins: the api_key= argument, then the PROMIGENCE_API_KEY environment variable, then the key promigence login saved on this machine.
export PROMIGENCE_API_KEY=... # or sign in once with: promigence login
Language: bashEvery run has a spend cap in US dollars: spend_cap=, else PROMIGENCE_SPEND_CAP, else your organization's default.
Start a sandbox, run a command
Sandbox.create() starts a sandbox from the built-in promigence/base snapshot. Pass a snapshot ID or alias, or a template's name, to start from another.
from promigence import Sandbox
with Sandbox.create(spend_cap=1) as sb: # starts from promigence/base
result = sb.commands.run("python3 -c 'print(6 * 7)'")
print(result.stdout) # 42
print(result.exit_code) # 0
# leaving the block ends the sandbox
Language: pythonA command that exits non-zero raises CommandExitError; pass throw_on_exit=False to get its result instead. Each command takes a timeout_ms, and every duration in the SDK is in milliseconds.
To start from a container image, name it with a tag or a digest and pass allow_build=True. The first use builds a snapshot of the image, billed per second at its size's rate while it builds. Later sandboxes reuse it.
sb = Sandbox.create(image="python:3.12", allow_build=True, spend_cap=1)
Language: pythonFiles
sb.files.write("/work/notes.txt", "hello\n")
print(sb.files.read("/work/notes.txt")) # hello
print([e.name for e in sb.files.list("/work")])
sb.files.remove("/work/notes.txt")
Language: pythonfiles.read returns text. For binary files, pass format="bytes".
Pause and resume
sb.pause() # not billed by the second while paused
sb.resume() # back where it was
sb.set_auto_pause(300) # or let it pause itself after 5 idle minutes
Language: pythonA paused sandbox keeps its files and is not billed by the second. Commands left running in the background end at the pause.
Fork
Sandbox.fork(snapshot, count=N) starts N identical sandboxes from one snapshot and returns when all of them are ready. To fork a sandbox as it is now, save it as a snapshot first.
from promigence import Promigence, Sandbox
with Promigence() as client:
saved = client.sandboxes.snapshot(sb.sandbox_id, alias="my-env")
copies = Sandbox.fork(saved["snapshot_id"], count=5, spend_cap=2)
for copy in copies:
print(copy.commands.run("ls /work").stdout)
copy.kill()
Language: pythonEnd a sandbox
sb.kill() ends a sandbox and stops its bill, and returns False if it had already ended. Leaving a with block calls it for you. set_timeout(timeout_ms) changes how long a sandbox may run.
Errors
Everything the SDK raises on purpose is a PromigenceError. It carries a stable code, a message, a fix and a docs link to the code's row on the errors page, plus the HTTP status and request_id when the API answered, and the CLI's exit_code.
Its subclasses name the cases you are likely to catch: AuthError, NotFoundError, RateLimitError (with retry_after_ms), SpendCapError, PromigenceTimeoutError and InvalidArgumentError. EnvFailedError means the environment failed, which is ours and never counted against your code. CommandExitError is a command that failed, and CommandTimeoutError one that ran past its timeout_ms.
from promigence import CommandExitError, PromigenceError, Sandbox
try:
with Sandbox.create(spend_cap=1) as sb:
sb.commands.run("cd /work && pytest -q")
except CommandExitError as e:
print("the tests failed with exit code", e.exit_code)
except PromigenceError as e:
print(e.code, e.fix, e.docs)
Language: pythonAsync
AsyncSandbox and AsyncPromigence have the methods of Sandbox and Promigence. Await them.
import asyncio
from promigence import AsyncSandbox
async def main():
async with await AsyncSandbox.create(spend_cap=1) as sb:
result = await sb.commands.run("echo hi")
print(result.stdout)
asyncio.run(main())
Language: pythonThe SDK calls the same routes as the CLI. The API reference lists every one, with its parameters and answers.