orkestr 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- orkestr-0.1.0/LICENSE +21 -0
- orkestr-0.1.0/PKG-INFO +238 -0
- orkestr-0.1.0/README.md +211 -0
- orkestr-0.1.0/orkestr/__init__.py +68 -0
- orkestr-0.1.0/orkestr/_client.py +239 -0
- orkestr-0.1.0/orkestr/_streaming.py +147 -0
- orkestr-0.1.0/orkestr/errors.py +175 -0
- orkestr-0.1.0/orkestr/sandbox.py +580 -0
- orkestr-0.1.0/orkestr.egg-info/PKG-INFO +238 -0
- orkestr-0.1.0/orkestr.egg-info/SOURCES.txt +16 -0
- orkestr-0.1.0/orkestr.egg-info/dependency_links.txt +1 -0
- orkestr-0.1.0/orkestr.egg-info/requires.txt +6 -0
- orkestr-0.1.0/orkestr.egg-info/top_level.txt +1 -0
- orkestr-0.1.0/pyproject.toml +55 -0
- orkestr-0.1.0/setup.cfg +4 -0
- orkestr-0.1.0/tests/test_client.py +182 -0
- orkestr-0.1.0/tests/test_sandbox.py +521 -0
- orkestr-0.1.0/tests/test_streaming.py +157 -0
orkestr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Orkestr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
orkestr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orkestr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: EU-hosted sandbox VMs for AI agents - official Python SDK
|
|
5
|
+
Author-email: Orkestr <hello@orkestr.eu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://orkestr.eu/sandboxes
|
|
8
|
+
Project-URL: Documentation, https://orkestr.eu/docs
|
|
9
|
+
Keywords: orkestr,sandbox,ai,agents,code-execution,eu
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# orkestr Python SDK
|
|
29
|
+
|
|
30
|
+
EU-hosted sandbox VMs for AI agents. Type-safe Python client for the
|
|
31
|
+
`api.orkestr.eu/v1/sandboxes` REST API.
|
|
32
|
+
|
|
33
|
+
> **Status: private beta pre-release (`0.1.0a1`).** The client surface is
|
|
34
|
+
> stable and tested, but the `api.orkestr.eu/v1/sandboxes` backend is not
|
|
35
|
+
> live yet - every endpoint returns `501 Not Implemented` until the public
|
|
36
|
+
> beta. This release exists so you can pin the package and review the API.
|
|
37
|
+
> See `docs/sandbox-mvp-plan.md` in the orkestr repo for the timeline.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install --pre orkestr
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python 3.10+. The `--pre` flag is needed while the SDK is a
|
|
46
|
+
pre-release; it is dropped once `0.1.0` ships with the public beta.
|
|
47
|
+
|
|
48
|
+
## Authenticate
|
|
49
|
+
|
|
50
|
+
Mint an API token in the orkestr console with the `sandboxes:write` scope.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export ORKESTR_API_KEY="ork_..."
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The SDK picks the key up from `ORKESTR_API_KEY` or you pass it explicitly.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
One-shot execution inside a fresh sandbox. The `with` block auto-terminates
|
|
61
|
+
the sandbox on exit, preventing runaway costs if the caller crashes.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from orkestr import Sandbox
|
|
65
|
+
|
|
66
|
+
with Sandbox.create(template="python-3.12") as sbx:
|
|
67
|
+
sbx.files.write("/workspace/main.py", "print(sum(range(1_000_000)))")
|
|
68
|
+
result = sbx.exec("python /workspace/main.py")
|
|
69
|
+
print(result.stdout) # 499999500000
|
|
70
|
+
print(result.duration_ms) # ~120
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
### Create a sandbox
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
sbx = Sandbox.create(
|
|
79
|
+
template="python-3.12", # one of the templates listed below
|
|
80
|
+
size="small", # "small" | "medium" | "large" (plan-capped)
|
|
81
|
+
network="off", # "off" | "restricted" | "open"
|
|
82
|
+
timeout_seconds=600, # auto-terminate after this many seconds
|
|
83
|
+
env={"OPENAI_API_KEY": "sk-..."},
|
|
84
|
+
metadata={"agent_run": "r_123"},
|
|
85
|
+
region="fsn1", # "fsn1" (DE) | "hel1" (FI) | None for auto
|
|
86
|
+
api_key=None, # falls back to ORKESTR_API_KEY
|
|
87
|
+
)
|
|
88
|
+
print(sbx.id) # "sbx_01HXYZ..."
|
|
89
|
+
print(sbx.status) # "running"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Templates
|
|
93
|
+
|
|
94
|
+
| Template | Description |
|
|
95
|
+
|---------------------|------------------------------------------|
|
|
96
|
+
| `python-3.12` | CPython 3.12 with pip and common libs |
|
|
97
|
+
| `python-3.12-bare` | CPython 3.12 only, faster start |
|
|
98
|
+
| `node-22` | Node 22 with npm |
|
|
99
|
+
| `ubuntu-24.04` | Minimal Ubuntu shell environment |
|
|
100
|
+
|
|
101
|
+
### Sizes
|
|
102
|
+
|
|
103
|
+
`size` picks from a fixed menu. Allowed sizes are plan-capped.
|
|
104
|
+
|
|
105
|
+
| Size | vCPU | RAM | Plans |
|
|
106
|
+
|----------|------|--------|------------------------|
|
|
107
|
+
| `small` | 0.5 | 512 MB | free, pro, team |
|
|
108
|
+
| `medium` | 2 | 4 GB | pro, team |
|
|
109
|
+
| `large` | 4 | 8 GB | team |
|
|
110
|
+
|
|
111
|
+
### Check your plan limits
|
|
112
|
+
|
|
113
|
+
`Sandbox.limits()` reports the sizes and caps available to your API
|
|
114
|
+
key's plan — pick a `size` up front instead of discovering the limit
|
|
115
|
+
from a `PlanLimitError`. Handy when the same code runs under keys on
|
|
116
|
+
different plans (e.g. a reseller provisioning per customer).
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
limits = Sandbox.limits()
|
|
120
|
+
limits.plan # "free"
|
|
121
|
+
limits.allowed_sizes # ["small"]
|
|
122
|
+
"medium" in limits.allowed_sizes # False
|
|
123
|
+
limits.max_concurrent # 1
|
|
124
|
+
limits.usage_gb_hours_used # 2.5 (memory consumed this month)
|
|
125
|
+
limits.usage_gb_hours_included # 10.0 (memory budget for the month)
|
|
126
|
+
limits.usage_cpu_hours_used # 0.42 (CPU consumed this month)
|
|
127
|
+
limits.usage_cpu_hours_included # 2.0 (CPU budget for the month)
|
|
128
|
+
limits.usage_resets_at # datetime - 1st of next month UTC
|
|
129
|
+
for s in limits.sizes: # full menu, each with .allowed
|
|
130
|
+
print(s.size, s.cpu, s.memory_mb, s.allowed)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
When `usage_gb_hours_used >= usage_gb_hours_included`, `Sandbox.create()`
|
|
134
|
+
raises `PlanLimitError` with `code="usage_exhausted"` until the counter
|
|
135
|
+
resets. The metering rolls up `mem_mb_seconds` from the host's
|
|
136
|
+
per-second compute samples, so an idle sandbox still accrues toward the
|
|
137
|
+
budget until it is terminated.
|
|
138
|
+
|
|
139
|
+
### Run a command
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
result = sbx.exec("python /workspace/main.py", timeout_seconds=60)
|
|
143
|
+
result.stdout # str
|
|
144
|
+
result.stderr # str
|
|
145
|
+
result.exit_code # int
|
|
146
|
+
result.duration_ms # int
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Stream a long command
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
for chunk in sbx.exec_stream("python long_task.py"):
|
|
153
|
+
if chunk.stream == "stdout":
|
|
154
|
+
print(chunk.data, end="", flush=True)
|
|
155
|
+
else:
|
|
156
|
+
print(chunk.data, end="", flush=True, file=sys.stderr)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Files
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
sbx.files.write("/workspace/data.json", '{"x": 1}')
|
|
163
|
+
sbx.files.write_bytes("/workspace/blob.bin", b"\x00\x01\x02")
|
|
164
|
+
|
|
165
|
+
content = sbx.files.read("/workspace/out.txt") # returns str
|
|
166
|
+
raw = sbx.files.read_bytes("/workspace/blob.bin") # returns bytes
|
|
167
|
+
|
|
168
|
+
for entry in sbx.files.list("/workspace"):
|
|
169
|
+
print(entry.name, entry.is_dir, entry.size)
|
|
170
|
+
|
|
171
|
+
sbx.files.delete("/workspace/out.txt")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Pause + resume
|
|
175
|
+
|
|
176
|
+
Pausing snapshots the sandbox memory + disk and stops the compute meter.
|
|
177
|
+
Resume restores it on the same or a different host. `pause()` returns
|
|
178
|
+
the sandbox id; persist it across processes and pass to `Sandbox.resume`.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
sbx = Sandbox.create(template="node-22", network="restricted")
|
|
182
|
+
sandbox_id = sbx.pause()
|
|
183
|
+
# ... minutes or hours later, from any worker:
|
|
184
|
+
sbx = Sandbox.resume(sandbox_id)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Snapshot retention is plan-capped (free: 1, pro: 3, team: 10). Calling
|
|
188
|
+
`pause()` over the cap raises `SnapshotCapReached`.
|
|
189
|
+
|
|
190
|
+
### Terminate
|
|
191
|
+
|
|
192
|
+
Context-manager exit calls `terminate()`. Outside `with`:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
sbx.terminate()
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
After `terminate()` the sandbox row stays in your account history but
|
|
199
|
+
the VM and any in-memory state are gone.
|
|
200
|
+
|
|
201
|
+
### List your sandboxes
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
for sbx in Sandbox.list(status="running"):
|
|
205
|
+
print(sbx.id, sbx.template, sbx.created_at)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Errors
|
|
209
|
+
|
|
210
|
+
All SDK errors inherit from `orkestr.OrkestrError`.
|
|
211
|
+
|
|
212
|
+
| Exception | When |
|
|
213
|
+
|--------------------------|---------------------------------------------------|
|
|
214
|
+
| `AuthError` | Missing / invalid / expired API key, or scope mismatch |
|
|
215
|
+
| `RateLimitError` | Plan rate limit hit |
|
|
216
|
+
| `PlanLimitError` | Sandbox limit, concurrent limit, snapshot cap |
|
|
217
|
+
| `SandboxNotFound` | `sandbox_id` doesn't exist or isn't yours |
|
|
218
|
+
| `SandboxNotReady` | Operation called on a paused / terminated sandbox |
|
|
219
|
+
| `ExecTimeout` | `exec()` exceeded `timeout_seconds` |
|
|
220
|
+
| `NetworkPolicyError` | Command tried to reach a host the policy blocks |
|
|
221
|
+
| `OrkestrError` | Any other API-level error |
|
|
222
|
+
|
|
223
|
+
## Async support
|
|
224
|
+
|
|
225
|
+
The MVP SDK is sync only. Async (`AsyncSandbox`) lands in v0.2.0 if a
|
|
226
|
+
design partner blocks on it; the wire format is identical.
|
|
227
|
+
|
|
228
|
+
## Versioning
|
|
229
|
+
|
|
230
|
+
Follows [Semantic Versioning](https://semver.org). The SDK targets the
|
|
231
|
+
`/v1/sandboxes` API; bumps to v1 of the API are non-breaking for SDK
|
|
232
|
+
callers. v2 of the API will require an SDK major.
|
|
233
|
+
|
|
234
|
+
## Links
|
|
235
|
+
|
|
236
|
+
- [Sandbox waitlist](https://orkestr.eu/sandboxes)
|
|
237
|
+
- [Public API docs](https://orkestr.eu/docs)
|
|
238
|
+
- Questions and bug reports during the private beta: <hello@orkestr.eu>
|
orkestr-0.1.0/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# orkestr Python SDK
|
|
2
|
+
|
|
3
|
+
EU-hosted sandbox VMs for AI agents. Type-safe Python client for the
|
|
4
|
+
`api.orkestr.eu/v1/sandboxes` REST API.
|
|
5
|
+
|
|
6
|
+
> **Status: private beta pre-release (`0.1.0a1`).** The client surface is
|
|
7
|
+
> stable and tested, but the `api.orkestr.eu/v1/sandboxes` backend is not
|
|
8
|
+
> live yet - every endpoint returns `501 Not Implemented` until the public
|
|
9
|
+
> beta. This release exists so you can pin the package and review the API.
|
|
10
|
+
> See `docs/sandbox-mvp-plan.md` in the orkestr repo for the timeline.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install --pre orkestr
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires Python 3.10+. The `--pre` flag is needed while the SDK is a
|
|
19
|
+
pre-release; it is dropped once `0.1.0` ships with the public beta.
|
|
20
|
+
|
|
21
|
+
## Authenticate
|
|
22
|
+
|
|
23
|
+
Mint an API token in the orkestr console with the `sandboxes:write` scope.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
export ORKESTR_API_KEY="ork_..."
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The SDK picks the key up from `ORKESTR_API_KEY` or you pass it explicitly.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
One-shot execution inside a fresh sandbox. The `with` block auto-terminates
|
|
34
|
+
the sandbox on exit, preventing runaway costs if the caller crashes.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from orkestr import Sandbox
|
|
38
|
+
|
|
39
|
+
with Sandbox.create(template="python-3.12") as sbx:
|
|
40
|
+
sbx.files.write("/workspace/main.py", "print(sum(range(1_000_000)))")
|
|
41
|
+
result = sbx.exec("python /workspace/main.py")
|
|
42
|
+
print(result.stdout) # 499999500000
|
|
43
|
+
print(result.duration_ms) # ~120
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
### Create a sandbox
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
sbx = Sandbox.create(
|
|
52
|
+
template="python-3.12", # one of the templates listed below
|
|
53
|
+
size="small", # "small" | "medium" | "large" (plan-capped)
|
|
54
|
+
network="off", # "off" | "restricted" | "open"
|
|
55
|
+
timeout_seconds=600, # auto-terminate after this many seconds
|
|
56
|
+
env={"OPENAI_API_KEY": "sk-..."},
|
|
57
|
+
metadata={"agent_run": "r_123"},
|
|
58
|
+
region="fsn1", # "fsn1" (DE) | "hel1" (FI) | None for auto
|
|
59
|
+
api_key=None, # falls back to ORKESTR_API_KEY
|
|
60
|
+
)
|
|
61
|
+
print(sbx.id) # "sbx_01HXYZ..."
|
|
62
|
+
print(sbx.status) # "running"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Templates
|
|
66
|
+
|
|
67
|
+
| Template | Description |
|
|
68
|
+
|---------------------|------------------------------------------|
|
|
69
|
+
| `python-3.12` | CPython 3.12 with pip and common libs |
|
|
70
|
+
| `python-3.12-bare` | CPython 3.12 only, faster start |
|
|
71
|
+
| `node-22` | Node 22 with npm |
|
|
72
|
+
| `ubuntu-24.04` | Minimal Ubuntu shell environment |
|
|
73
|
+
|
|
74
|
+
### Sizes
|
|
75
|
+
|
|
76
|
+
`size` picks from a fixed menu. Allowed sizes are plan-capped.
|
|
77
|
+
|
|
78
|
+
| Size | vCPU | RAM | Plans |
|
|
79
|
+
|----------|------|--------|------------------------|
|
|
80
|
+
| `small` | 0.5 | 512 MB | free, pro, team |
|
|
81
|
+
| `medium` | 2 | 4 GB | pro, team |
|
|
82
|
+
| `large` | 4 | 8 GB | team |
|
|
83
|
+
|
|
84
|
+
### Check your plan limits
|
|
85
|
+
|
|
86
|
+
`Sandbox.limits()` reports the sizes and caps available to your API
|
|
87
|
+
key's plan — pick a `size` up front instead of discovering the limit
|
|
88
|
+
from a `PlanLimitError`. Handy when the same code runs under keys on
|
|
89
|
+
different plans (e.g. a reseller provisioning per customer).
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
limits = Sandbox.limits()
|
|
93
|
+
limits.plan # "free"
|
|
94
|
+
limits.allowed_sizes # ["small"]
|
|
95
|
+
"medium" in limits.allowed_sizes # False
|
|
96
|
+
limits.max_concurrent # 1
|
|
97
|
+
limits.usage_gb_hours_used # 2.5 (memory consumed this month)
|
|
98
|
+
limits.usage_gb_hours_included # 10.0 (memory budget for the month)
|
|
99
|
+
limits.usage_cpu_hours_used # 0.42 (CPU consumed this month)
|
|
100
|
+
limits.usage_cpu_hours_included # 2.0 (CPU budget for the month)
|
|
101
|
+
limits.usage_resets_at # datetime - 1st of next month UTC
|
|
102
|
+
for s in limits.sizes: # full menu, each with .allowed
|
|
103
|
+
print(s.size, s.cpu, s.memory_mb, s.allowed)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
When `usage_gb_hours_used >= usage_gb_hours_included`, `Sandbox.create()`
|
|
107
|
+
raises `PlanLimitError` with `code="usage_exhausted"` until the counter
|
|
108
|
+
resets. The metering rolls up `mem_mb_seconds` from the host's
|
|
109
|
+
per-second compute samples, so an idle sandbox still accrues toward the
|
|
110
|
+
budget until it is terminated.
|
|
111
|
+
|
|
112
|
+
### Run a command
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
result = sbx.exec("python /workspace/main.py", timeout_seconds=60)
|
|
116
|
+
result.stdout # str
|
|
117
|
+
result.stderr # str
|
|
118
|
+
result.exit_code # int
|
|
119
|
+
result.duration_ms # int
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Stream a long command
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
for chunk in sbx.exec_stream("python long_task.py"):
|
|
126
|
+
if chunk.stream == "stdout":
|
|
127
|
+
print(chunk.data, end="", flush=True)
|
|
128
|
+
else:
|
|
129
|
+
print(chunk.data, end="", flush=True, file=sys.stderr)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Files
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
sbx.files.write("/workspace/data.json", '{"x": 1}')
|
|
136
|
+
sbx.files.write_bytes("/workspace/blob.bin", b"\x00\x01\x02")
|
|
137
|
+
|
|
138
|
+
content = sbx.files.read("/workspace/out.txt") # returns str
|
|
139
|
+
raw = sbx.files.read_bytes("/workspace/blob.bin") # returns bytes
|
|
140
|
+
|
|
141
|
+
for entry in sbx.files.list("/workspace"):
|
|
142
|
+
print(entry.name, entry.is_dir, entry.size)
|
|
143
|
+
|
|
144
|
+
sbx.files.delete("/workspace/out.txt")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Pause + resume
|
|
148
|
+
|
|
149
|
+
Pausing snapshots the sandbox memory + disk and stops the compute meter.
|
|
150
|
+
Resume restores it on the same or a different host. `pause()` returns
|
|
151
|
+
the sandbox id; persist it across processes and pass to `Sandbox.resume`.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
sbx = Sandbox.create(template="node-22", network="restricted")
|
|
155
|
+
sandbox_id = sbx.pause()
|
|
156
|
+
# ... minutes or hours later, from any worker:
|
|
157
|
+
sbx = Sandbox.resume(sandbox_id)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Snapshot retention is plan-capped (free: 1, pro: 3, team: 10). Calling
|
|
161
|
+
`pause()` over the cap raises `SnapshotCapReached`.
|
|
162
|
+
|
|
163
|
+
### Terminate
|
|
164
|
+
|
|
165
|
+
Context-manager exit calls `terminate()`. Outside `with`:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
sbx.terminate()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
After `terminate()` the sandbox row stays in your account history but
|
|
172
|
+
the VM and any in-memory state are gone.
|
|
173
|
+
|
|
174
|
+
### List your sandboxes
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
for sbx in Sandbox.list(status="running"):
|
|
178
|
+
print(sbx.id, sbx.template, sbx.created_at)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Errors
|
|
182
|
+
|
|
183
|
+
All SDK errors inherit from `orkestr.OrkestrError`.
|
|
184
|
+
|
|
185
|
+
| Exception | When |
|
|
186
|
+
|--------------------------|---------------------------------------------------|
|
|
187
|
+
| `AuthError` | Missing / invalid / expired API key, or scope mismatch |
|
|
188
|
+
| `RateLimitError` | Plan rate limit hit |
|
|
189
|
+
| `PlanLimitError` | Sandbox limit, concurrent limit, snapshot cap |
|
|
190
|
+
| `SandboxNotFound` | `sandbox_id` doesn't exist or isn't yours |
|
|
191
|
+
| `SandboxNotReady` | Operation called on a paused / terminated sandbox |
|
|
192
|
+
| `ExecTimeout` | `exec()` exceeded `timeout_seconds` |
|
|
193
|
+
| `NetworkPolicyError` | Command tried to reach a host the policy blocks |
|
|
194
|
+
| `OrkestrError` | Any other API-level error |
|
|
195
|
+
|
|
196
|
+
## Async support
|
|
197
|
+
|
|
198
|
+
The MVP SDK is sync only. Async (`AsyncSandbox`) lands in v0.2.0 if a
|
|
199
|
+
design partner blocks on it; the wire format is identical.
|
|
200
|
+
|
|
201
|
+
## Versioning
|
|
202
|
+
|
|
203
|
+
Follows [Semantic Versioning](https://semver.org). The SDK targets the
|
|
204
|
+
`/v1/sandboxes` API; bumps to v1 of the API are non-breaking for SDK
|
|
205
|
+
callers. v2 of the API will require an SDK major.
|
|
206
|
+
|
|
207
|
+
## Links
|
|
208
|
+
|
|
209
|
+
- [Sandbox waitlist](https://orkestr.eu/sandboxes)
|
|
210
|
+
- [Public API docs](https://orkestr.eu/docs)
|
|
211
|
+
- Questions and bug reports during the private beta: <hello@orkestr.eu>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""orkestr Python SDK.
|
|
2
|
+
|
|
3
|
+
EU-hosted sandbox VMs for AI agents. See README.md or
|
|
4
|
+
https://orkestr.eu/sandboxes for the full API.
|
|
5
|
+
|
|
6
|
+
from orkestr import Sandbox
|
|
7
|
+
|
|
8
|
+
with Sandbox.create(template="python-3.12") as sbx:
|
|
9
|
+
sbx.files.write("/workspace/main.py", "print(2+2)")
|
|
10
|
+
result = sbx.exec("python /workspace/main.py")
|
|
11
|
+
print(result.stdout)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from ._client import OrkestrClient, get_default_client, set_default_client
|
|
17
|
+
from .errors import (
|
|
18
|
+
APIError,
|
|
19
|
+
AuthError,
|
|
20
|
+
ConnectionError,
|
|
21
|
+
ExecKilled,
|
|
22
|
+
ExecTimeout,
|
|
23
|
+
NetworkPolicyError,
|
|
24
|
+
OrkestrError,
|
|
25
|
+
PlanLimitError,
|
|
26
|
+
RateLimitError,
|
|
27
|
+
SandboxNotFound,
|
|
28
|
+
SandboxNotReady,
|
|
29
|
+
SnapshotCapReached,
|
|
30
|
+
)
|
|
31
|
+
from .sandbox import (
|
|
32
|
+
ExecChunk,
|
|
33
|
+
ExecResult,
|
|
34
|
+
FileEntry,
|
|
35
|
+
Sandbox,
|
|
36
|
+
SandboxLimits,
|
|
37
|
+
SandboxSizeInfo,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__version__ = "0.1.0a2"
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Core
|
|
44
|
+
"Sandbox",
|
|
45
|
+
"ExecResult",
|
|
46
|
+
"ExecChunk",
|
|
47
|
+
"FileEntry",
|
|
48
|
+
"SandboxLimits",
|
|
49
|
+
"SandboxSizeInfo",
|
|
50
|
+
# Client (advanced)
|
|
51
|
+
"OrkestrClient",
|
|
52
|
+
"get_default_client",
|
|
53
|
+
"set_default_client",
|
|
54
|
+
# Errors
|
|
55
|
+
"OrkestrError",
|
|
56
|
+
"AuthError",
|
|
57
|
+
"RateLimitError",
|
|
58
|
+
"PlanLimitError",
|
|
59
|
+
"SnapshotCapReached",
|
|
60
|
+
"SandboxNotFound",
|
|
61
|
+
"SandboxNotReady",
|
|
62
|
+
"ExecTimeout",
|
|
63
|
+
"ExecKilled",
|
|
64
|
+
"NetworkPolicyError",
|
|
65
|
+
"APIError",
|
|
66
|
+
"ConnectionError",
|
|
67
|
+
"__version__",
|
|
68
|
+
]
|