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 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>
@@ -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
+ ]