sandboxer-sdk 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.
Files changed (37) hide show
  1. sandboxer_sdk-0.1.0/.gitignore +8 -0
  2. sandboxer_sdk-0.1.0/CHANGELOG.md +14 -0
  3. sandboxer_sdk-0.1.0/LICENSE +21 -0
  4. sandboxer_sdk-0.1.0/PKG-INFO +351 -0
  5. sandboxer_sdk-0.1.0/README.md +316 -0
  6. sandboxer_sdk-0.1.0/pyproject.toml +80 -0
  7. sandboxer_sdk-0.1.0/scripts/generate.py +739 -0
  8. sandboxer_sdk-0.1.0/scripts/unasync.py +122 -0
  9. sandboxer_sdk-0.1.0/src/sandboxer/__init__.py +128 -0
  10. sandboxer_sdk-0.1.0/src/sandboxer/_config.py +45 -0
  11. sandboxer_sdk-0.1.0/src/sandboxer/_generated_api.py +6100 -0
  12. sandboxer_sdk-0.1.0/src/sandboxer/_helpers_async.py +481 -0
  13. sandboxer_sdk-0.1.0/src/sandboxer/_helpers_sync.py +481 -0
  14. sandboxer_sdk-0.1.0/src/sandboxer/_results.py +104 -0
  15. sandboxer_sdk-0.1.0/src/sandboxer/_retry.py +97 -0
  16. sandboxer_sdk-0.1.0/src/sandboxer/_sandbox_async.py +906 -0
  17. sandboxer_sdk-0.1.0/src/sandboxer/_sandbox_sync.py +908 -0
  18. sandboxer_sdk-0.1.0/src/sandboxer/_streaming.py +108 -0
  19. sandboxer_sdk-0.1.0/src/sandboxer/_transport_async.py +232 -0
  20. sandboxer_sdk-0.1.0/src/sandboxer/_transport_sync.py +234 -0
  21. sandboxer_sdk-0.1.0/src/sandboxer/_types.py +47 -0
  22. sandboxer_sdk-0.1.0/src/sandboxer/_utils.py +128 -0
  23. sandboxer_sdk-0.1.0/src/sandboxer/_version.py +1 -0
  24. sandboxer_sdk-0.1.0/src/sandboxer/aio.py +61 -0
  25. sandboxer_sdk-0.1.0/src/sandboxer/errors.py +256 -0
  26. sandboxer_sdk-0.1.0/src/sandboxer/models.py +3666 -0
  27. sandboxer_sdk-0.1.0/src/sandboxer/py.typed +0 -0
  28. sandboxer_sdk-0.1.0/src/sandboxer/webhooks.py +198 -0
  29. sandboxer_sdk-0.1.0/tests/conftest.py +147 -0
  30. sandboxer_sdk-0.1.0/tests/test_api.py +242 -0
  31. sandboxer_sdk-0.1.0/tests/test_async.py +164 -0
  32. sandboxer_sdk-0.1.0/tests/test_docs_snippets.py +44 -0
  33. sandboxer_sdk-0.1.0/tests/test_errors.py +120 -0
  34. sandboxer_sdk-0.1.0/tests/test_retries.py +178 -0
  35. sandboxer_sdk-0.1.0/tests/test_sandbox.py +740 -0
  36. sandboxer_sdk-0.1.0/tests/test_streaming.py +39 -0
  37. sandboxer_sdk-0.1.0/tests/test_webhooks.py +97 -0
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .pytest_cache/
8
+ __pycache__/
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to `sandboxer-sdk` (imported as `sandboxer`). The Python and TypeScript SDKs are released together under one version.
4
+
5
+ ## 0.1.0
6
+
7
+ First public release.
8
+
9
+ - `Sandboxer` / `Sandbox` (sync) and `AsyncSandboxer` / `AsyncSandbox` (asyncio): create (waiting until ready),
10
+ exec, streaming exec, background commands, files, hosting, lifecycle (stop, resume, fork, snapshot), agents and events.
11
+ - `SandboxerApi` / `AsyncSandboxerApi`: one typed method for each of the 93 operations of the public API v1.
12
+ - Typed pydantic models, envelope-aware errors, automatic retries with jittered backoff and automatic
13
+ `Idempotency-Key` on create and fork.
14
+ - Webhook signature verification.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sandboxer
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.
@@ -0,0 +1,351 @@
1
+ Metadata-Version: 2.5
2
+ Name: sandboxer-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Sandboxer: cloud sandboxes for AI agents and humans.
5
+ Project-URL: Homepage, https://sandboxer.com
6
+ Project-URL: Documentation, https://sandboxer.com/docs/sdk-python
7
+ Author-email: Sandboxer <support@sandboxer.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,ai,cloud,microvm,sandbox,sandboxer,sdk
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Requires-Dist: pydantic<3,>=2.7
26
+ Provides-Extra: dev
27
+ Requires-Dist: datamodel-code-generator==0.83.0; extra == 'dev'
28
+ Requires-Dist: mypy>=1.10; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
30
+ Requires-Dist: pytest>=8; extra == 'dev'
31
+ Requires-Dist: pyyaml>=6; extra == 'dev'
32
+ Requires-Dist: respx>=0.21; extra == 'dev'
33
+ Requires-Dist: ruff==0.17.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # sandboxer-sdk — Python SDK
37
+
38
+ Python client for [Sandboxer](https://sandboxer.com): persistent cloud sandboxes (Ubuntu microVMs with root,
39
+ Docker, SSH, HTTPS port hosting, stop/resume, forks, snapshots, a streamed desktop and integrated coding agents).
40
+
41
+ - **High-level API** — `Sandboxer` / `Sandbox` (sync) and `AsyncSandboxer` / `AsyncSandbox` (asyncio).
42
+ - **Low-level API** — `SandboxerApi` / `AsyncSandboxerApi`: one typed method for every operation of the public
43
+ API (`/api/v1`), wire-compatible with boat's API v1 so boat-sdk code ports by renaming imports.
44
+ - Typed pydantic models, envelope-aware errors, automatic retries with jittered backoff, automatic
45
+ `Idempotency-Key` on create/fork, NDJSON command streaming, event streaming (polling and SSE) and webhook
46
+ signature verification.
47
+
48
+ Requires Python 3.10+.
49
+
50
+ ```bash
51
+ pip install sandboxer-sdk # imported as `import sandboxer`
52
+ ```
53
+
54
+ ## Configure
55
+
56
+ | Option | Default |
57
+ | --- | --- |
58
+ | `api_key` | `SANDBOXER_API_KEY`, then `SANDBOXER_TOKEN` (the per-sandbox key injected inside sandboxes) |
59
+ | `base_url` | `SANDBOXER_API_URL`, else `https://sandboxer.com/api/v1` |
60
+ | `org` | `SANDBOXER_ORG`; sent as `X-Sandboxer-Org` (billing wallet) |
61
+ | `timeout` | `60` seconds (`None` disables it) |
62
+ | `max_retries` | `3` |
63
+ | `http_client` | your own `httpx.Client` / `httpx.AsyncClient` (not closed by the SDK) |
64
+ | `default_headers` | extra headers for every request |
65
+
66
+ ```python
67
+ from sandboxer import Sandboxer
68
+
69
+ client = Sandboxer() # reads SANDBOXER_API_KEY
70
+ client = Sandboxer(api_key="sbx_live_...", org="team_acme", timeout=30)
71
+ ```
72
+
73
+ Keep API keys, secrets, SSH keys and desktop/hosted URLs (they can carry access tokens) out of your logs.
74
+
75
+ ## Quickstart
76
+
77
+ ```python
78
+ from sandboxer import Sandboxer
79
+
80
+ with Sandboxer() as client:
81
+ sandbox = client.create(type="small", ttl_seconds=1800) # waits until it is ready
82
+
83
+ result = sandbox.exec("python3 --version")
84
+ print(result.exit_code, result.stdout)
85
+
86
+ sandbox.files.write("app/main.py", "print('hi')\n")
87
+ print(sandbox.files.read("app/main.py"))
88
+
89
+ sandbox.start("python3 -m http.server 8000", cwd="app") # detached
90
+ print(sandbox.host(8000, public=True).url) # https://<slug>-8000.box.sandboxer.com
91
+
92
+ sandbox.stop() # archive (resume later)
93
+ ```
94
+
95
+ `Sandboxer.create(...)`, `Sandboxer.get(...)` and `Sandboxer.list(...)` also work on the class itself, using a
96
+ default client configured from the environment:
97
+
98
+ ```python
99
+ sandbox = Sandboxer.create(ttl_seconds=600)
100
+ ```
101
+
102
+ ### Create options
103
+
104
+ `client.create(type=, ttl_seconds=, env=, no_env=, from_=, setup_script=, environment=, snapshots=, monitoring=,
105
+ fail_fast=, org=, name=, idempotency_key=, wait=True, wait_for_setup=False, timeout=300, on_exit=None)`
106
+
107
+ - `ttl_seconds=None` disables auto-stop (omitting it uses the server default of one hour).
108
+ - `from_="web-stack"` starts from a named snapshot (or a `bsh_...` share code).
109
+ - `no_env=True` withholds every account secret — use it for sandboxes you hand to your own users.
110
+ - `wait_for_setup=True` also waits for `setup_script` and raises if it failed.
111
+ - Waiting raises `SandboxStateError` if the sandbox ends in `error`/`cancelled`, `WaitTimeoutError` on timeout.
112
+
113
+ ## Commands
114
+
115
+ ```python
116
+ r = sandbox.exec(["ls", "-la", "my dir"], cwd="project", env={"DEBUG": "1"}, timeout_seconds=120)
117
+ r.stdout, r.stderr, r.exit_code, r.success, r.timed_out
118
+
119
+ sandbox.exec("make test", check=True) # raises CommandError on a non-zero exit
120
+ ```
121
+
122
+ Commands run through the shell; a list is quoted for you. The API has no `env` field for commands, so `env` is
123
+ exported in front of the command line.
124
+
125
+ ### Streaming output
126
+
127
+ ```python
128
+ stream = sandbox.exec_stream("npm install")
129
+ for frame in stream: # started, stdout/stderr chunks, then exit or error
130
+ if frame.type in ("stdout", "stderr"):
131
+ print(frame.data, end="")
132
+ result = stream.wait() # CommandResult with the collected output
133
+ ```
134
+
135
+ ### Detached commands
136
+
137
+ ```python
138
+ server = sandbox.start("node server.js", cwd="app")
139
+ server.process_id, server.pid, server.command_id
140
+
141
+ server.status() # running?, exit code, log tails
142
+ for frame in server.attach(): # live output; reconnects transparently
143
+ print(frame.type, frame.data)
144
+ server.kill() # SIGTERM, then SIGKILL after 5 s
145
+ server.wait() # follow to the end -> CommandResult
146
+ server.logs() # full stored stdout from the command history
147
+
148
+ sandbox.command_status(42); sandbox.attach(42); sandbox.kill(42) # by process id
149
+ ```
150
+
151
+ ## Files
152
+
153
+ ```python
154
+ sandbox.files.write("notes.txt", "hello\n") # text
155
+ sandbox.files.write("logo.png", png_bytes) # bytes (sent base64)
156
+ sandbox.files.read("notes.txt") # -> str (up to 5 MiB)
157
+ sandbox.files.read_bytes("logo.png") # -> bytes (up to 5 MiB)
158
+ sandbox.files.download("build/app.tar.gz") # -> bytes (up to 50 MiB)
159
+ ```
160
+
161
+ Paths are absolute or relative to `/home/user`, and must resolve under `/home/user` or `/tmp`.
162
+
163
+ ## Hosting, SSH and desktop
164
+
165
+ ```python
166
+ sandbox.host(3000) # private URL with a _token query parameter
167
+ sandbox.host(3000, public=True) # or private=False
168
+ sandbox.host(5173, localhost=True) # service bound to 127.0.0.1 only
169
+ sandbox.ssh_key(Path.home().joinpath(".ssh/id_ed25519.pub").read_text()) # then ssh user@<machine_ip>
170
+ sandbox.desktop(public_access=True).desktop_url # waits while VNC is provisioning
171
+ ```
172
+
173
+ ## Lifecycle
174
+
175
+ ```python
176
+ sandbox.stop() # waits until archived (wait=False to return at once)
177
+ sandbox.resume(type="large") # waits until ready again
178
+ child = sandbox.fork() # new Sandbox from the current state; the source is untouched
179
+ sandbox.snapshot("web-stack") # named snapshot, waits until ready -> deploy with create(from_="web-stack")
180
+ sandbox.extend(7200) # auto-stop in 2 h from now (None disables auto-stop)
181
+ sandbox.update(name="demo", subdomain="acme-demo")
182
+ sandbox.usage(); sandbox.processes(); sandbox.history(); sandbox.snapshots(); sandbox.latest_snapshot()
183
+ sandbox.delete() # PERMANENT: deletes the sandbox and its snapshots
184
+
185
+ for sb in client.list(state=["ready", "idle"]):
186
+ print(sb.id, sb.state)
187
+ ```
188
+
189
+ ### Context managers
190
+
191
+ ```python
192
+ with client.create(on_exit="delete") as sandbox: # or on_exit="stop"; default None leaves it running
193
+ sandbox.exec("pytest", check=True)
194
+ ```
195
+
196
+ `on_exit` can also be set later: `sandbox.on_exit = "stop"`.
197
+
198
+ ## Agents: prompts and events
199
+
200
+ ```python
201
+ run = sandbox.prompt("Run the tests and fix what fails.", provider="codex", model="gpt-5.4")
202
+ for event in run.events(): # this run's events, ending when it is done
203
+ if event.type == "response" and event.data.get("content"):
204
+ print(event.data["content"])
205
+ run.wait() # PromptRun: finished / failed / interrupted
206
+ print(run.text()) # final response text
207
+ run.steer("Skip the integration tests.")
208
+ run.interrupt()
209
+
210
+ for event in sandbox.events(type="prompt,response"): # every conversation, follows new events
211
+ ...
212
+ sandbox.conversations(); sandbox.steer("..."); sandbox.interrupt(conversation="...")
213
+ ```
214
+
215
+ `sandbox.events(mode=...)`: `"poll"` long-polls `GET /events?sort=asc` with the page cursor, `"sse"` uses
216
+ `text/event-stream` (reconnecting with `Last-Event-ID`), `"auto"` (default) tries SSE and falls back to polling.
217
+ Repeated pages are de-duplicated, while an event whose payload changed (a streaming partial becoming final) is
218
+ yielded again.
219
+
220
+ ## Async
221
+
222
+ Everything above exists with `async`/`await` on `AsyncSandboxer` / `AsyncSandbox`:
223
+
224
+ ```python
225
+ import asyncio
226
+ from sandboxer import AsyncSandboxer
227
+
228
+ async def main() -> None:
229
+ async with AsyncSandboxer() as client:
230
+ async with await client.create(on_exit="delete") as sandbox:
231
+ print((await sandbox.exec("uname -a")).stdout)
232
+ async for frame in sandbox.exec_stream("ls -R /etc"):
233
+ ...
234
+
235
+ asyncio.run(main())
236
+ ```
237
+
238
+ Async versions of the helper functions are in `sandboxer.aio`.
239
+
240
+ ## Low-level client
241
+
242
+ `client.api` is a `SandboxerApi` with one method per API operation (snake_case operation ids):
243
+
244
+ ```python
245
+ from sandboxer import models
246
+
247
+ api = client.api
248
+ api.me(); api.limits(); api.sandboxes(state="ready,idle")
249
+ created = api.create(ttl_seconds=None, no_env=True) # keyword body fields...
250
+ api.update(created.sandbox.id, models.UpdateSandboxRequest(name="x")) # ...or a body model / dict
251
+ api.events(sandbox_id, sort="asc", limit=50, type="prompt,response")
252
+ for frame in api.command_stream(sandbox_id, command="make"): ...
253
+ api.request("GET", "/provider-models") # escape hatch
254
+ ```
255
+
256
+ - Keyword arguments left out are not sent; `None` is sent as JSON `null` (`ttl_seconds=None` disables auto-stop).
257
+ - `request_options={"timeout": 5, "max_retries": 0, "headers": {...}, "org": "team_x", "idempotency_key": "..."}`
258
+ works on every method.
259
+ - Deletes fill `X-Sandboxer-Confirm-Delete` with the target id for you (override with `confirm_delete=`).
260
+ - Responses are pydantic models with snake_case attributes (`sandbox.memory_gb`); unknown fields are kept
261
+ (`model_extra`) and unexpected shapes are logged instead of raising.
262
+
263
+ ## Errors, retries and idempotency
264
+
265
+ Every failure raises `SandboxerError` (subclasses: `BadRequestError`, `AuthenticationError`,
266
+ `PaymentRequiredError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `RateLimitError`,
267
+ `InternalServerError`, `APIConnectionError`, `APITimeoutError`, plus `WaitTimeoutError`, `SandboxStateError`,
268
+ `CommandError`, `WebhookVerificationError`).
269
+
270
+ ```python
271
+ from sandboxer import SandboxerError
272
+
273
+ try:
274
+ sandbox.exec("true")
275
+ except SandboxerError as e:
276
+ print(e.status, e.code, e.message, e.details, e.request_id, e.retryable)
277
+ ```
278
+
279
+ Automatic retries (default 3, jittered exponential backoff, `Retry-After` honored):
280
+
281
+ | Request | Retried on |
282
+ | --- | --- |
283
+ | `GET` | network errors, timeouts, 5xx, 429 with `Retry-After`, `sandbox_starting`, retryable `sandbox_direct_failed` |
284
+ | `POST /commands` | only `sandbox_starting`, `sandbox_direct_failed` + `sandbox_restoring` (the command provably did not run) and 429 with `Retry-After` — never network errors or 5xx, since the command may already be running |
285
+ | create / fork | network errors, timeouts, 5xx, 429 with `Retry-After`, `409 idempotency_in_progress` — safe because each call carries an `Idempotency-Key` (a UUID, reused across retries; pass `idempotency_key=` to choose it) |
286
+ | other writes | 429 with `Retry-After`, `sandbox_starting`, retryable `sandbox_direct_failed` |
287
+
288
+ ## Webhooks
289
+
290
+ Deliveries carry `Sandboxer-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>`. Verify the raw
291
+ bytes before parsing:
292
+
293
+ ```python
294
+ from sandboxer import construct_event, verify_webhook, WebhookVerificationError
295
+
296
+ # FastAPI
297
+ @app.post("/hooks/sandbox")
298
+ async def hook(request: Request):
299
+ raw = await request.body()
300
+ try:
301
+ event = construct_event(raw, request.headers, os.environ["SANDBOXER_WEBHOOK_SECRET"])
302
+ except WebhookVerificationError:
303
+ raise HTTPException(400)
304
+ if event.type == "sandbox.ready":
305
+ ...
306
+ return {"ok": True}
307
+
308
+ # Flask
309
+ @app.post("/hooks/sandbox")
310
+ def hook():
311
+ if not verify_webhook(request.get_data(), request.headers.get("Sandboxer-Signature"), SECRET):
312
+ abort(400)
313
+ ...
314
+ ```
315
+
316
+ - `tolerance_seconds` (default 300) rejects stale timestamps; pass a list of secrets while rotating.
317
+ - `verify_webhook_headers(raw, headers, secret)` / `construct_event(raw, headers, secret)` also accept the
318
+ `X-Sandboxer-Signature` / `X-Sandboxer-Delivery` / `X-Sandboxer-Timestamp` header set.
319
+ - Deliveries are at-least-once and may arrive out of order: de-duplicate on `event.id`.
320
+
321
+ ## Porting from boat-sdk
322
+
323
+ | boat-sdk | sandboxer |
324
+ | --- | --- |
325
+ | `from boat_sdk.api.boat_api import BoatApi` | `from sandboxer import SandboxerApi` (or `Sandboxer().api`) |
326
+ | `Configuration(host=..., access_token=...)` | `Sandboxer(base_url=..., api_key=...)` |
327
+ | `BOAT_API_KEY`, `X-Boat-Org` | `SANDBOXER_API_KEY`, `X-Sandboxer-Org` (`org=`) |
328
+ | `boat_sdk.models.CreateSandboxRequest(var_from=...)` | `sandboxer.models.CreateSandboxRequest(from_=...)` |
329
+ | `sandbox.create(CreateSandboxRequest(ttl_seconds=1800))` | `api.create(CreateSandboxRequest(ttl_seconds=1800))` or `api.create(ttl_seconds=1800)` |
330
+ | `sandbox.get(id)`, `update`, `stop`, `resume`, `fork`, `delete_sandbox`, `prompt`, `events`, `read_file`, `write_file`, `command`, `artifact`, `desktop`, `ssh_key`, snapshot methods ... | same names on `SandboxerApi` |
331
+ | `ApiException` (`.status`, `.body`) | `SandboxerError` (`.status`, `.code`, `.message`, `.details`, `.request_id`) |
332
+ | `wait_until_ready`, `wait_until_idle`, `wait_for_desktop`, `wait_for_prompt`, `wait_for_prompt_done`, `stream_events`, `stream_prompt`, `stop_and_remove`, `read_text`, `write_text`, `exec_command` | same names in `sandboxer` (async versions in `sandboxer.aio`) |
333
+
334
+ ## Development
335
+
336
+ ```bash
337
+ python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
338
+ .venv/bin/python scripts/generate.py # regenerate models + low-level client from ../../api/openapi.yaml
339
+ .venv/bin/python scripts/unasync.py # regenerate the sync modules from the async sources
340
+ .venv/bin/pytest -q && .venv/bin/ruff check src tests && .venv/bin/mypy --strict src
341
+ ```
342
+
343
+ `models.py`, `_generated_api.py` and the `*_sync.py` modules are generated: edit the generator or the async sources.
344
+
345
+ ## Support
346
+
347
+ Questions and bug reports: [support@sandboxer.com](mailto:support@sandboxer.com). Full docs: [sandboxer.com/docs/sdk-python](https://sandboxer.com/docs/sdk-python).
348
+
349
+ ## License
350
+
351
+ MIT