Install
pip install contree-sdk
Quick Start
Pull an image, run a command, read the output. Three lines of real code:
from contree_sdk import ContreeSync
contree = ContreeSync()
image = contree.images.pull("python:3.11-slim")
result = image.run(shell='python -c "print(2 ** 128)"').wait()
print(result.stdout)
The async counterpart looks the same, minus .wait():
from contree_sdk import Contree
contree = Contree()
image = await contree.images.pull("python:3.11-slim")
result = await image.run(shell='python -c "print(2 ** 128)"')
Both clients read CONTREE_TOKEN and CONTREE_BASE_URL from the environment by default, or accept them
explicitly via ContreeConfig.
Images and Branching
Every non-disposable run produces a new immutable image. That image can be used as the starting point for the next command, creating a chain of states you can branch from at any point.
base = contree.images.pull("ubuntu:noble")
# Install dependencies once
env = base.run(
shell="apt-get update && apt-get install -y build-essential",
disposable=False,
).wait()
# Branch from the same prepared state
branch_a = env.run(shell="gcc -O2 solution_a.c -o a.out", files={"solution_a.c": "a.c"}).wait()
branch_b = env.run(shell="gcc -O2 solution_b.c -o b.out", files={"solution_b.c": "b.c"}).wait()
Each branch starts from an identical filesystem. No cleanup, no side effects between runs.
Sessions
Sessions track state automatically. Each command picks up where the previous one left off:
session = image.session()
session.run(shell="pip install numpy pandas", disposable=False).wait()
result = session.run(shell="python -c 'import numpy; print(numpy.__version__)'").wait()
print(result.stdout)
No need to pass UUIDs between calls. The session holds the current image reference internally.
File Handling
Upload files into a run, or download artifacts out of one:
# Upload files directly in a run
result = image.run(
shell="python /app/main.py",
files={"/app/main.py": "./main.py", "/app/data.csv": "./data.csv"},
).wait()
# Pre-upload for reuse across multiple runs
uploaded = contree.files.upload("./model.bin")
r1 = image.run(shell="./evaluate --model /model.bin", files={"/model.bin": uploaded}).wait()
r2 = image.run(shell="./benchmark --model /model.bin", files={"/model.bin": uploaded}).wait()
# Download artifacts
await result.download("/output/report.html", "./report.html")
content = await result.read("/output/metrics.json")
Subprocess Interface
For code that already works with subprocess.Popen, the SDK provides a drop-in replacement:
proc = image.popen(
["python", "test_suite.py"],
cwd="/workspace",
env={"PYTHONDONTWRITEBYTECODE": "1"},
)
stdout, stderr = proc.communicate(timeout=120)
print(proc.returncode)
Same patterns, same attribute names, sandboxed execution.
Flexible I/O
Control how stdout and stderr are captured:
# As string (default)
result = await image.run(shell="ls /", stdout=str)
# As bytes
result = await image.run(shell="cat /bin/ls", stdout=bytes)
# Into a buffer
from io import StringIO
buf = StringIO()
result = await image.run(shell="env", stdout=buf)
Error Handling
The SDK provides specific exception types for each failure mode:
ContreeImageStateError— invalid state transition.FailedOperationError— command or import failed.OperationTimedOutError— execution exceeded the timeout.NotFoundError— image or resource not found.
All inherit from ContreeError, so a single except clause can catch everything.
Takeaway
The ConTree SDK turns ConTree's HTTP API into native Python objects. Pull images, execute commands, branch states, move files, and handle errors with the same patterns you already use. Install it, set a token, and start building.
Get Started
- Install:
pip install contree-sdk - Read the docs: docs.contree.dev
- Join the free early access program at contree.dev