together-sandbox 1.11.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 (166) hide show
  1. together_sandbox-1.11.0/.gitignore +43 -0
  2. together_sandbox-1.11.0/CLAUDE.md +176 -0
  3. together_sandbox-1.11.0/PKG-INFO +227 -0
  4. together_sandbox-1.11.0/README.md +208 -0
  5. together_sandbox-1.11.0/generate.sh +25 -0
  6. together_sandbox-1.11.0/pyproject.toml +35 -0
  7. together_sandbox-1.11.0/tests/__init__.py +0 -0
  8. together_sandbox-1.11.0/tests/e2e/__init__.py +1 -0
  9. together_sandbox-1.11.0/tests/e2e/conftest.py +10 -0
  10. together_sandbox-1.11.0/tests/e2e/helpers.py +126 -0
  11. together_sandbox-1.11.0/tests/e2e/test_sandbox_execs.py +204 -0
  12. together_sandbox-1.11.0/tests/e2e/test_sandbox_filesystem.py +167 -0
  13. together_sandbox-1.11.0/tests/e2e/test_sandbox_lifecycle.py +51 -0
  14. together_sandbox-1.11.0/tests/e2e/test_snapshots.py +98 -0
  15. together_sandbox-1.11.0/tests/test_call_api.py +535 -0
  16. together_sandbox-1.11.0/tests/test_sse_streaming.py +89 -0
  17. together_sandbox-1.11.0/tests/test_utils.py +47 -0
  18. together_sandbox-1.11.0/tests/test_with_retry.py +262 -0
  19. together_sandbox-1.11.0/together_sandbox/__init__.py +60 -0
  20. together_sandbox-1.11.0/together_sandbox/_configuration.py +14 -0
  21. together_sandbox-1.11.0/together_sandbox/_lifecycle.py +144 -0
  22. together_sandbox-1.11.0/together_sandbox/_remote_image_builder.py +218 -0
  23. together_sandbox-1.11.0/together_sandbox/_sandbox.py +720 -0
  24. together_sandbox-1.11.0/together_sandbox/_sandboxes.py +179 -0
  25. together_sandbox-1.11.0/together_sandbox/_snapshots.py +509 -0
  26. together_sandbox-1.11.0/together_sandbox/_streaming.py +110 -0
  27. together_sandbox-1.11.0/together_sandbox/_together_sandbox.py +82 -0
  28. together_sandbox-1.11.0/together_sandbox/_utils.py +431 -0
  29. together_sandbox-1.11.0/together_sandbox/api/__init__.py +8 -0
  30. together_sandbox-1.11.0/together_sandbox/api/api/__init__.py +1 -0
  31. together_sandbox-1.11.0/together_sandbox/api/api/default/__init__.py +1 -0
  32. together_sandbox-1.11.0/together_sandbox/api/api/default/alias_snapshot.py +196 -0
  33. together_sandbox-1.11.0/together_sandbox/api/api/default/authorize.py +174 -0
  34. together_sandbox-1.11.0/together_sandbox/api/api/default/batch_get_sandboxes.py +183 -0
  35. together_sandbox-1.11.0/together_sandbox/api/api/default/batch_get_snapshots.py +187 -0
  36. together_sandbox-1.11.0/together_sandbox/api/api/default/create_sandbox.py +181 -0
  37. together_sandbox-1.11.0/together_sandbox/api/api/default/create_snapshot.py +181 -0
  38. together_sandbox-1.11.0/together_sandbox/api/api/default/delete_sandbox.py +174 -0
  39. together_sandbox-1.11.0/together_sandbox/api/api/default/delete_sandbox_version.py +175 -0
  40. together_sandbox-1.11.0/together_sandbox/api/api/default/delete_sandbox_version_by_number.py +188 -0
  41. together_sandbox-1.11.0/together_sandbox/api/api/default/delete_snapshot.py +175 -0
  42. together_sandbox-1.11.0/together_sandbox/api/api/default/delete_snapshot_alias.py +175 -0
  43. together_sandbox-1.11.0/together_sandbox/api/api/default/delete_snapshot_alias_by_alias.py +174 -0
  44. together_sandbox-1.11.0/together_sandbox/api/api/default/delete_snapshot_by_alias.py +174 -0
  45. together_sandbox-1.11.0/together_sandbox/api/api/default/get_current_sandbox_version.py +176 -0
  46. together_sandbox-1.11.0/together_sandbox/api/api/default/get_sandbox.py +176 -0
  47. together_sandbox-1.11.0/together_sandbox/api/api/default/get_sandbox_version.py +177 -0
  48. together_sandbox-1.11.0/together_sandbox/api/api/default/get_sandbox_version_by_number.py +190 -0
  49. together_sandbox-1.11.0/together_sandbox/api/api/default/get_snapshot.py +177 -0
  50. together_sandbox-1.11.0/together_sandbox/api/api/default/get_snapshot_alias.py +177 -0
  51. together_sandbox-1.11.0/together_sandbox/api/api/default/get_snapshot_alias_by_alias.py +176 -0
  52. together_sandbox-1.11.0/together_sandbox/api/api/default/get_snapshot_by_alias.py +176 -0
  53. together_sandbox-1.11.0/together_sandbox/api/api/default/issue_container_registry_credential.py +151 -0
  54. together_sandbox-1.11.0/together_sandbox/api/api/default/list_sandboxes.py +181 -0
  55. together_sandbox-1.11.0/together_sandbox/api/api/default/list_snapshots.py +181 -0
  56. together_sandbox-1.11.0/together_sandbox/api/api/default/start_sandbox.py +198 -0
  57. together_sandbox-1.11.0/together_sandbox/api/api/default/stop_sandbox.py +198 -0
  58. together_sandbox-1.11.0/together_sandbox/api/api/default/wait_for_sandbox.py +176 -0
  59. together_sandbox-1.11.0/together_sandbox/api/client.py +282 -0
  60. together_sandbox-1.11.0/together_sandbox/api/errors.py +16 -0
  61. together_sandbox-1.11.0/together_sandbox/api/models/__init__.py +67 -0
  62. together_sandbox-1.11.0/together_sandbox/api/models/alias_snapshot_body.py +61 -0
  63. together_sandbox-1.11.0/together_sandbox/api/models/authorize_body.py +62 -0
  64. together_sandbox-1.11.0/together_sandbox/api/models/container_registry_credential.py +104 -0
  65. together_sandbox-1.11.0/together_sandbox/api/models/create_sandbox_body.py +123 -0
  66. together_sandbox-1.11.0/together_sandbox/api/models/create_snapshot_body.py +81 -0
  67. together_sandbox-1.11.0/together_sandbox/api/models/create_snapshot_body_architecture.py +9 -0
  68. together_sandbox-1.11.0/together_sandbox/api/models/error.py +91 -0
  69. together_sandbox-1.11.0/together_sandbox/api/models/error_errors_item.py +101 -0
  70. together_sandbox-1.11.0/together_sandbox/api/models/error_errors_item_details.py +47 -0
  71. together_sandbox-1.11.0/together_sandbox/api/models/sandbox.py +656 -0
  72. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_recovery_status_type_1.py +11 -0
  73. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_recovery_status_type_2_type_1.py +11 -0
  74. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_recovery_status_type_3_type_1.py +11 -0
  75. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_requested_stop_type_type_1.py +9 -0
  76. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_requested_stop_type_type_2_type_1.py +9 -0
  77. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_requested_stop_type_type_3_type_1.py +9 -0
  78. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_start_type_type_1.py +9 -0
  79. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_start_type_type_2_type_1.py +9 -0
  80. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_start_type_type_3_type_1.py +9 -0
  81. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_status.py +12 -0
  82. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_stop_reason_type_1.py +15 -0
  83. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_stop_reason_type_2_type_1.py +15 -0
  84. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_stop_reason_type_3_type_1.py +15 -0
  85. together_sandbox-1.11.0/together_sandbox/api/models/sandbox_version.py +96 -0
  86. together_sandbox-1.11.0/together_sandbox/api/models/snapshot.py +145 -0
  87. together_sandbox-1.11.0/together_sandbox/api/models/snapshot_alias.py +96 -0
  88. together_sandbox-1.11.0/together_sandbox/api/models/start_sandbox_body.py +61 -0
  89. together_sandbox-1.11.0/together_sandbox/api/models/stop_sandbox_body.py +69 -0
  90. together_sandbox-1.11.0/together_sandbox/api/models/stop_sandbox_body_stop_type.py +9 -0
  91. together_sandbox-1.11.0/together_sandbox/api/types.py +54 -0
  92. together_sandbox-1.11.0/together_sandbox/docker.py +172 -0
  93. together_sandbox-1.11.0/together_sandbox/errors.py +53 -0
  94. together_sandbox-1.11.0/together_sandbox/sandbox/__init__.py +8 -0
  95. together_sandbox-1.11.0/together_sandbox/sandbox/api/__init__.py +1 -0
  96. together_sandbox-1.11.0/together_sandbox/sandbox/api/directories/__init__.py +1 -0
  97. together_sandbox-1.11.0/together_sandbox/sandbox/api/directories/create_directory.py +177 -0
  98. together_sandbox-1.11.0/together_sandbox/sandbox/api/directories/delete_directory.py +182 -0
  99. together_sandbox-1.11.0/together_sandbox/sandbox/api/directories/list_directory.py +177 -0
  100. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/__init__.py +1 -0
  101. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/connect_to_exec_web_socket.py +212 -0
  102. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/create_exec.py +182 -0
  103. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/delete_exec.py +177 -0
  104. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/exec_exec_stdin.py +203 -0
  105. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/get_exec.py +177 -0
  106. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/get_exec_output.py +203 -0
  107. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/list_execs.py +140 -0
  108. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/start_exec.py +222 -0
  109. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/stream_exec_output.py +198 -0
  110. together_sandbox-1.11.0/together_sandbox/sandbox/api/execs/stream_execs_list.py +140 -0
  111. together_sandbox-1.11.0/together_sandbox/sandbox/api/files/__init__.py +1 -0
  112. together_sandbox-1.11.0/together_sandbox/sandbox/api/files/create_file.py +197 -0
  113. together_sandbox-1.11.0/together_sandbox/sandbox/api/files/delete_file.py +182 -0
  114. together_sandbox-1.11.0/together_sandbox/sandbox/api/files/get_file_stat.py +177 -0
  115. together_sandbox-1.11.0/together_sandbox/sandbox/api/files/perform_file_action.py +198 -0
  116. together_sandbox-1.11.0/together_sandbox/sandbox/api/files/read_file.py +177 -0
  117. together_sandbox-1.11.0/together_sandbox/sandbox/api/ports/__init__.py +1 -0
  118. together_sandbox-1.11.0/together_sandbox/sandbox/api/ports/list_ports.py +145 -0
  119. together_sandbox-1.11.0/together_sandbox/sandbox/api/ports/stream_ports_list.py +149 -0
  120. together_sandbox-1.11.0/together_sandbox/sandbox/api/streams/__init__.py +1 -0
  121. together_sandbox-1.11.0/together_sandbox/sandbox/api/streams/create_watcher.py +217 -0
  122. together_sandbox-1.11.0/together_sandbox/sandbox/api/tasks/__init__.py +1 -0
  123. together_sandbox-1.11.0/together_sandbox/sandbox/api/tasks/execute_task_action.py +210 -0
  124. together_sandbox-1.11.0/together_sandbox/sandbox/api/tasks/get_task.py +177 -0
  125. together_sandbox-1.11.0/together_sandbox/sandbox/api/tasks/list_setup_tasks.py +144 -0
  126. together_sandbox-1.11.0/together_sandbox/sandbox/api/tasks/list_tasks.py +140 -0
  127. together_sandbox-1.11.0/together_sandbox/sandbox/client.py +282 -0
  128. together_sandbox-1.11.0/together_sandbox/sandbox/errors.py +16 -0
  129. together_sandbox-1.11.0/together_sandbox/sandbox/models/__init__.py +77 -0
  130. together_sandbox-1.11.0/together_sandbox/sandbox/models/create_exec_request.py +134 -0
  131. together_sandbox-1.11.0/together_sandbox/sandbox/models/create_exec_request_env.py +47 -0
  132. together_sandbox-1.11.0/together_sandbox/sandbox/models/directory_list_response.py +83 -0
  133. together_sandbox-1.11.0/together_sandbox/sandbox/models/error.py +69 -0
  134. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_delete_response.py +61 -0
  135. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_item.py +121 -0
  136. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_list_response.py +75 -0
  137. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_start.py +50 -0
  138. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_stdin.py +71 -0
  139. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_stdin_type.py +9 -0
  140. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_stdout.py +107 -0
  141. together_sandbox-1.11.0/together_sandbox/sandbox/models/exec_stdout_type.py +9 -0
  142. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_action_request.py +81 -0
  143. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_action_request_action.py +9 -0
  144. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_action_response.py +77 -0
  145. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_create_request.py +61 -0
  146. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_info.py +93 -0
  147. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_operation_response.py +69 -0
  148. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_read_response.py +79 -0
  149. together_sandbox-1.11.0/together_sandbox/sandbox/models/file_read_response_encoding.py +9 -0
  150. together_sandbox-1.11.0/together_sandbox/sandbox/models/get_task_response.py +67 -0
  151. together_sandbox-1.11.0/together_sandbox/sandbox/models/port_info.py +69 -0
  152. together_sandbox-1.11.0/together_sandbox/sandbox/models/ports_list_response.py +75 -0
  153. together_sandbox-1.11.0/together_sandbox/sandbox/models/setup_task_item.py +103 -0
  154. together_sandbox-1.11.0/together_sandbox/sandbox/models/setup_task_list_response.py +75 -0
  155. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_action_response.py +120 -0
  156. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_action_type.py +10 -0
  157. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_base.py +88 -0
  158. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_config.py +101 -0
  159. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_item.py +109 -0
  160. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_list_response.py +75 -0
  161. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_preview.py +61 -0
  162. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_restart.py +85 -0
  163. together_sandbox-1.11.0/together_sandbox/sandbox/models/task_status.py +13 -0
  164. together_sandbox-1.11.0/together_sandbox/sandbox/models/watcher_event.py +81 -0
  165. together_sandbox-1.11.0/together_sandbox/sandbox/models/watcher_event_type.py +12 -0
  166. together_sandbox-1.11.0/together_sandbox/sandbox/types.py +54 -0
@@ -0,0 +1,43 @@
1
+ # Generated code
2
+ together-sandbox-typescript/src/api-clients
3
+ together-sandbox-cli/src/api-clients
4
+
5
+ # Node
6
+ node_modules/
7
+ npm-debug.log*
8
+ test-template
9
+
10
+ # TypeScript SDK build output
11
+ together-sandbox-typescript/dist/
12
+
13
+ # CLI build output (esbuild bundle + compiled binaries)
14
+ together-sandbox-cli/dist/
15
+
16
+ # Python
17
+ __pycache__/
18
+ *.py[cod]
19
+ *.egg-info/
20
+ .eggs/
21
+ dist/
22
+ build/
23
+ .venv/
24
+ venv/
25
+ *.pth
26
+
27
+ # static docs
28
+ api-docs/*.json
29
+
30
+ # Python generation temp dirs
31
+ together-sandbox-python/.tmp-api/
32
+ together-sandbox-python/.tmp-sandbox/
33
+
34
+ # Test scripts
35
+ together-sandbox-typescript/test.ts
36
+ together-sandbox-python/test.py
37
+
38
+ # OS
39
+ .DS_Store
40
+
41
+ # Local env files
42
+ .env
43
+ .env.local
@@ -0,0 +1,176 @@
1
+ # Python SDK — Developer Guide
2
+
3
+ Package: `together-sandbox` (PyPI)
4
+
5
+ ## File layout
6
+
7
+ ```
8
+ together_sandbox/
9
+ ├── __init__.py # Public re-exports — keep in sync with facade.py exports
10
+ ├── facade.py # ← hand-written facade (the only file to edit)
11
+ ├── _streaming.py # SSE streaming helpers — hand-written
12
+ ├── api/ # Generated — management API client (openapi-python-client)
13
+ └── sandbox/ # Generated — in-VM sandbox API client (openapi-python-client)
14
+ ```
15
+
16
+ **Only `facade.py`, `_streaming.py`, and `__init__.py` are hand-edited.**
17
+ Everything inside `api/` and `sandbox/` is regenerated by `bash generate.sh`
18
+ (run from the repo root) and must not be modified directly.
19
+
20
+ ## Facade patterns
21
+
22
+ ### Always `from __future__ import annotations`
23
+
24
+ Every facade Python file must start with `from __future__ import annotations`.
25
+ This enables PEP 563 deferred evaluation so modern union syntax works on all
26
+ supported Python versions (≥3.10).
27
+
28
+ ### Modern type annotation style
29
+
30
+ Use `X | None` instead of `Optional[X]`, and `list[X]` instead of `List[X]`.
31
+ Do **not** import `Optional` or `List` from `typing`.
32
+
33
+ ```python
34
+ # ✅ correct
35
+ def watch(self, path: str, recursive: bool | None = None) -> AsyncIterator[dict[str, Any]]: ...
36
+
37
+ # ❌ wrong — legacy typing imports
38
+ from typing import Optional, List
39
+ def watch(self, path: str, recursive: Optional[bool] = None) -> AsyncIterator[List[dict]]: ...
40
+ ```
41
+
42
+ ### All method names must be snake_case
43
+
44
+ Python method names follow PEP 8. Do not use camelCase even if the equivalent
45
+ TypeScript method is camelCase.
46
+
47
+ ```python
48
+ # ✅ correct
49
+ async def list_setup(self) -> list[...]: ...
50
+
51
+ # ❌ wrong — camelCase in Python
52
+ async def listSetup(self) -> list[...]: ...
53
+ ```
54
+
55
+ ### Always return unwrapped data
56
+
57
+ Facade methods return the actual payload value, not the raw response object:
58
+
59
+ ```python
60
+ # ✅ correct
61
+ result = await read_file_api(path, client=self._client)
62
+ return result.content
63
+
64
+ # ❌ wrong — returns the full response object
65
+ return result
66
+ ```
67
+
68
+ ### Use typed models for API request bodies
69
+
70
+ Never pass raw `dict` literals as request bodies. Use the typed models
71
+ generated in the `api/` and `sandbox/` packages.
72
+
73
+ ```python
74
+ # ✅ correct (when a typed body model exists)
75
+ await stop_sandbox_api(sandbox_id, client=self._api_client,
76
+ body=StopSandboxBody(stop_type="hibernate"))
77
+
78
+ # ❌ wrong — raw dict
79
+ await stop_sandbox_api(sandbox_id, client=self._api_client,
80
+ body={"stop_type": "hibernate"})
81
+ ```
82
+
83
+ ### File uploads use `File(payload=io.BytesIO(bytes))` only
84
+
85
+ String content must be encoded to bytes before wrapping in a `File` object.
86
+ Never pass raw strings to `create_file_api`.
87
+
88
+ ```python
89
+ if isinstance(content, str):
90
+ content_bytes = content.encode("utf-8")
91
+ else:
92
+ content_bytes = content
93
+ file_obj = File(payload=io.BytesIO(content_bytes))
94
+ ```
95
+
96
+ Import `io` at the **top of the module**, not inside the method body.
97
+
98
+ ### Do not use `assert` for runtime type checking
99
+
100
+ `assert` statements are stripped in optimised Python (`python -O`). Raise
101
+ explicit exceptions instead.
102
+
103
+ ```python
104
+ # ✅ correct
105
+ result = await get_exec_api(id_, client=self._client)
106
+ if result is None:
107
+ raise RuntimeError(f"getExec returned None for id {id_!r}")
108
+ return result
109
+
110
+ # ❌ wrong
111
+ result = await get_exec_api(id_, client=self._client)
112
+ assert result is not None
113
+ return result
114
+ ```
115
+
116
+ ### Async context manager support
117
+
118
+ Both `TogetherSandbox` and `Sandbox` must support `async with`. The context
119
+ manager closes the HTTP client connection but does **not** shut down the VM —
120
+ callers must call `.shutdown()` explicitly.
121
+
122
+ ### Always unwrap API responses with `_unwrap_or_raise`
123
+
124
+ Every call to a generated API endpoint function must have its return value
125
+ checked through `_unwrap_or_raise` — never check `None`/`Error` inline.
126
+ Import the helper from `._utils`, not from `._sandbox`.
127
+
128
+ Even void-returning endpoints (delete, stop, alias) must capture and unwrap
129
+ their result — the generated clients can return an `Error` model on failure.
130
+
131
+ ```python
132
+ # ✅ correct
133
+ _unwrap_or_raise(
134
+ await delete_snapshot_api(UUID(id), client=self._api_client),
135
+ op="deleteSnapshot",
136
+ context=f"for id {id!r}"
137
+ )
138
+
139
+ # ❌ wrong — inline check, misses Error model, inconsistent
140
+ result = await delete_snapshot_api(UUID(id), client=self._api_client)
141
+ if result is None:
142
+ raise RuntimeError("deleteSnapshot returned None")
143
+ ```
144
+
145
+ ### Section dividers
146
+
147
+ Use the established ASCII box-drawing style:
148
+
149
+ ```python
150
+ # ─── Section Name ─────────────────────────────────────────────────────────────
151
+ ```
152
+
153
+ ## Build and test
154
+
155
+ ```bash
156
+ # From repo root — regenerate generated clients first
157
+ bash generate.sh
158
+
159
+ # Install package with dev extras
160
+ cd together-sandbox-python
161
+ pip install ".[dev]"
162
+
163
+ # Run tests
164
+ pytest tests/ -v
165
+ ```
166
+
167
+ ## Testing conventions
168
+
169
+ - Tests live in `together-sandbox-python/tests/`.
170
+ - Use `pytest` with `asyncio_mode = "auto"` (set in `pyproject.toml`).
171
+ - Use `unittest.mock.AsyncMock` + `patch` for API call mocking.
172
+ - Use class-based test organisation (`class TestFoo:`).
173
+ - The `_make_sandbox_model(**overrides)` helper pattern (see `test_facade.py`)
174
+ should be used to build minimal valid fixture `SandboxModel` mocks.
175
+ - Mirror the TypeScript SDK's test coverage: verify namespace delegation,
176
+ renamed/removed methods, public API surface, and factory classmethods.
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: together-sandbox
3
+ Version: 1.11.0
4
+ Summary: Together Sandbox Python SDK
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: attrs>=23.0.0
8
+ Requires-Dist: cattrs>=23.0.0
9
+ Requires-Dist: httpx-sse>=0.4.0
10
+ Requires-Dist: httpx>=0.20.0
11
+ Requires-Dist: pathspec>=0.11.0
12
+ Requires-Dist: python-dateutil>=2.8.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: openapi-python-client>=0.28.0; extra == 'dev'
15
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
16
+ Requires-Dist: pytest-timeout>=2.0.0; extra == 'dev'
17
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # together-sandbox (Python)
21
+
22
+ Developer guide for the `together-sandbox` Python SDK.
23
+
24
+ [SDK Docs](../docs/python-sdk.md)
25
+
26
+ > **Note:** The generated OpenAPI clients (`together_sandbox/api/` and
27
+ > `together_sandbox/sandbox/`) are committed to the repository temporarily until
28
+ > the package is published to PyPI. Once published they will be removed from
29
+ > version control and regenerated as part of the install/build step instead.
30
+
31
+ ## Generating the OpenAPI clients
32
+
33
+ The SDK wraps two auto-generated OpenAPI clients. Regenerate them whenever an
34
+ OpenAPI spec changes (both specs live in the repo root):
35
+
36
+ ```bash
37
+ # From the repo root
38
+ bash generate.sh
39
+ ```
40
+
41
+ This runs `together-sandbox-python/generate.sh` which invokes
42
+ `openapi-python-client` against `api-openapi.json` and `sandbox-openapi.json`
43
+ and writes the output to:
44
+
45
+ - `together_sandbox/api/` — management API client
46
+ - `together_sandbox/sandbox/` — in-VM sandbox API client
47
+
48
+ **Never edit those directories by hand** — they are overwritten on every run.
49
+
50
+ ## Tests
51
+
52
+ ## Prerequisites
53
+
54
+ 1. Together AI API key
55
+ 2. Python 3.10 or higher
56
+ 3. Install dev dependencies:
57
+
58
+ ```bash
59
+ cd together-sandbox-python
60
+ pip install .[dev]
61
+ ```
62
+
63
+ 4. Set up a virtual environment (optional but recommended):
64
+
65
+ ```bash
66
+ python -m venv .venv
67
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
68
+ ```
69
+
70
+ 5. Create a snapshot to run the tests with
71
+
72
+ ```bash
73
+ node ./together-sandbox-cli/dist/together-sandbox.mjs snapshots create --context ./test-template
74
+ ```
75
+
76
+ ## Environment Variables
77
+
78
+ Set the following environment variables before running e2e tests:
79
+
80
+ ```bash
81
+ # Required
82
+ export TOGETHER_API_KEY="your-api-key-here"
83
+
84
+ # Optional
85
+ export TOGETHER_SNAPSHOT_ID="snapshot-id" # If you want to test with a specific snapshot
86
+ export TOGETHER_BASE_URL="https://api.codesandbox.io" # Override default base URL
87
+ ```
88
+
89
+ ## Running Tests
90
+
91
+ **Note:** All `pytest` commands must be run from the `together-sandbox-python/` directory.
92
+
93
+ ### Unit tests
94
+
95
+ ```bash
96
+ pytest tests/ -v
97
+ ```
98
+
99
+ ### Run all e2e tests
100
+
101
+ ```bash
102
+ pytest tests/e2e/ -v
103
+ ```
104
+
105
+ ### Run specific e2e test file
106
+
107
+ ```bash
108
+ pytest tests/e2e/test_sandbox_filesystem.py -v
109
+ ```
110
+
111
+ ### Run specific test class or function
112
+
113
+ ```bash
114
+ # Run a specific test class
115
+ pytest tests/e2e/test_sandbox_filesystem.py::TestSandboxFilesystem -v
116
+
117
+ # Run a specific test function
118
+ pytest tests/e2e/test_sandbox_filesystem.py::TestSandboxFilesystem::test_write_and_read_text_file -v
119
+ ```
120
+
121
+ ### Run with output from print statements
122
+
123
+ ```bash
124
+ pytest tests/e2e/ -v -s
125
+ ```
126
+
127
+ ## Test Structure
128
+
129
+ ### Fixtures (e2e/helpers.py)
130
+
131
+ The `helpers.py` module provides pytest fixtures that handle sandbox lifecycle:
132
+
133
+ - `sdk`: Provides a configured `TogetherSandbox` instance
134
+ - `sandbox`: Creates a sandbox instance and automatically cleans it up after tests
135
+ - `retry_until`: Helper function for polling operations
136
+
137
+ Example usage in tests:
138
+
139
+ ```python
140
+ @pytest.mark.asyncio
141
+ async def test_something(sandbox: Sandbox):
142
+ # sandbox is automatically created and will be cleaned up
143
+ await sandbox.files.create_file("/test.txt", "content")
144
+ result = await sandbox.files.read_file("/test.txt")
145
+ assert result.content == "content"
146
+ ```
147
+
148
+ ## Test Patterns
149
+
150
+ ### Using the sandbox fixture
151
+
152
+ Most tests should use the `sandbox` fixture which handles creation and cleanup:
153
+
154
+ ```python
155
+ async def test_my_feature(sandbox: Sandbox):
156
+ # Your test code here
157
+ # Sandbox will be automatically cleaned up
158
+ pass
159
+ ```
160
+
161
+ ### Manual sandbox management
162
+
163
+ For lifecycle tests that need explicit control:
164
+
165
+ ```python
166
+ async def test_lifecycle():
167
+ sdk = TogetherSandbox(api_key=get_api_key())
168
+ try:
169
+ sandbox = await sdk.sandboxes.start(template_id)
170
+ # Test code
171
+ finally:
172
+ await sdk.sandboxes.shutdown(sandbox.id)
173
+ await sdk.close()
174
+ ```
175
+
176
+ ### Retry polling pattern
177
+
178
+ For operations that may take time:
179
+
180
+ ```python
181
+ from .helpers import retry_until
182
+
183
+ result = await retry_until(
184
+ lambda: sandbox.files.read_file("/path"),
185
+ lambda r: r.content == "expected",
186
+ timeout=5.0,
187
+ interval=0.1
188
+ )
189
+ ```
190
+
191
+ ## Notes
192
+
193
+ - E2E tests connect to real sandbox instances and may incur API costs
194
+ - Tests automatically clean up sandboxes, but interrupted tests may leave orphaned sandboxes
195
+ - Some exec tests are placeholders and may need implementation based on the actual API structure
196
+ - Binary file tests verify the application/octet-stream content type handling
197
+ - Timeout for sandbox cleanup is set to 10 seconds to prevent hanging
198
+
199
+ ## Troubleshooting
200
+
201
+ ### Tests skip with "TOGETHER_API_KEY environment variable not set"
202
+
203
+ Make sure you've exported the `TOGETHER_API_KEY` environment variable:
204
+
205
+ ```bash
206
+ export TOGETHER_API_KEY="your-api-key"
207
+ ```
208
+
209
+ ### Tests fail with timeout errors
210
+
211
+ The sandbox may be taking longer to start or respond. You can:
212
+
213
+ 1. Check your network connection
214
+ 2. Verify the API service status
215
+ 3. Increase timeout values in the tests if needed
216
+
217
+ ### Tests fail with authentication errors
218
+
219
+ Verify your API key is valid and has the necessary permissions for:
220
+
221
+ - Creating sandboxes
222
+ - Reading/writing files
223
+ - Managing sandbox lifecycle
224
+
225
+ ### Orphaned sandboxes
226
+
227
+ If tests are interrupted, sandboxes may not be cleaned up. You can manually delete them using the API or wait for automatic cleanup policies.
@@ -0,0 +1,208 @@
1
+ # together-sandbox (Python)
2
+
3
+ Developer guide for the `together-sandbox` Python SDK.
4
+
5
+ [SDK Docs](../docs/python-sdk.md)
6
+
7
+ > **Note:** The generated OpenAPI clients (`together_sandbox/api/` and
8
+ > `together_sandbox/sandbox/`) are committed to the repository temporarily until
9
+ > the package is published to PyPI. Once published they will be removed from
10
+ > version control and regenerated as part of the install/build step instead.
11
+
12
+ ## Generating the OpenAPI clients
13
+
14
+ The SDK wraps two auto-generated OpenAPI clients. Regenerate them whenever an
15
+ OpenAPI spec changes (both specs live in the repo root):
16
+
17
+ ```bash
18
+ # From the repo root
19
+ bash generate.sh
20
+ ```
21
+
22
+ This runs `together-sandbox-python/generate.sh` which invokes
23
+ `openapi-python-client` against `api-openapi.json` and `sandbox-openapi.json`
24
+ and writes the output to:
25
+
26
+ - `together_sandbox/api/` — management API client
27
+ - `together_sandbox/sandbox/` — in-VM sandbox API client
28
+
29
+ **Never edit those directories by hand** — they are overwritten on every run.
30
+
31
+ ## Tests
32
+
33
+ ## Prerequisites
34
+
35
+ 1. Together AI API key
36
+ 2. Python 3.10 or higher
37
+ 3. Install dev dependencies:
38
+
39
+ ```bash
40
+ cd together-sandbox-python
41
+ pip install .[dev]
42
+ ```
43
+
44
+ 4. Set up a virtual environment (optional but recommended):
45
+
46
+ ```bash
47
+ python -m venv .venv
48
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
49
+ ```
50
+
51
+ 5. Create a snapshot to run the tests with
52
+
53
+ ```bash
54
+ node ./together-sandbox-cli/dist/together-sandbox.mjs snapshots create --context ./test-template
55
+ ```
56
+
57
+ ## Environment Variables
58
+
59
+ Set the following environment variables before running e2e tests:
60
+
61
+ ```bash
62
+ # Required
63
+ export TOGETHER_API_KEY="your-api-key-here"
64
+
65
+ # Optional
66
+ export TOGETHER_SNAPSHOT_ID="snapshot-id" # If you want to test with a specific snapshot
67
+ export TOGETHER_BASE_URL="https://api.codesandbox.io" # Override default base URL
68
+ ```
69
+
70
+ ## Running Tests
71
+
72
+ **Note:** All `pytest` commands must be run from the `together-sandbox-python/` directory.
73
+
74
+ ### Unit tests
75
+
76
+ ```bash
77
+ pytest tests/ -v
78
+ ```
79
+
80
+ ### Run all e2e tests
81
+
82
+ ```bash
83
+ pytest tests/e2e/ -v
84
+ ```
85
+
86
+ ### Run specific e2e test file
87
+
88
+ ```bash
89
+ pytest tests/e2e/test_sandbox_filesystem.py -v
90
+ ```
91
+
92
+ ### Run specific test class or function
93
+
94
+ ```bash
95
+ # Run a specific test class
96
+ pytest tests/e2e/test_sandbox_filesystem.py::TestSandboxFilesystem -v
97
+
98
+ # Run a specific test function
99
+ pytest tests/e2e/test_sandbox_filesystem.py::TestSandboxFilesystem::test_write_and_read_text_file -v
100
+ ```
101
+
102
+ ### Run with output from print statements
103
+
104
+ ```bash
105
+ pytest tests/e2e/ -v -s
106
+ ```
107
+
108
+ ## Test Structure
109
+
110
+ ### Fixtures (e2e/helpers.py)
111
+
112
+ The `helpers.py` module provides pytest fixtures that handle sandbox lifecycle:
113
+
114
+ - `sdk`: Provides a configured `TogetherSandbox` instance
115
+ - `sandbox`: Creates a sandbox instance and automatically cleans it up after tests
116
+ - `retry_until`: Helper function for polling operations
117
+
118
+ Example usage in tests:
119
+
120
+ ```python
121
+ @pytest.mark.asyncio
122
+ async def test_something(sandbox: Sandbox):
123
+ # sandbox is automatically created and will be cleaned up
124
+ await sandbox.files.create_file("/test.txt", "content")
125
+ result = await sandbox.files.read_file("/test.txt")
126
+ assert result.content == "content"
127
+ ```
128
+
129
+ ## Test Patterns
130
+
131
+ ### Using the sandbox fixture
132
+
133
+ Most tests should use the `sandbox` fixture which handles creation and cleanup:
134
+
135
+ ```python
136
+ async def test_my_feature(sandbox: Sandbox):
137
+ # Your test code here
138
+ # Sandbox will be automatically cleaned up
139
+ pass
140
+ ```
141
+
142
+ ### Manual sandbox management
143
+
144
+ For lifecycle tests that need explicit control:
145
+
146
+ ```python
147
+ async def test_lifecycle():
148
+ sdk = TogetherSandbox(api_key=get_api_key())
149
+ try:
150
+ sandbox = await sdk.sandboxes.start(template_id)
151
+ # Test code
152
+ finally:
153
+ await sdk.sandboxes.shutdown(sandbox.id)
154
+ await sdk.close()
155
+ ```
156
+
157
+ ### Retry polling pattern
158
+
159
+ For operations that may take time:
160
+
161
+ ```python
162
+ from .helpers import retry_until
163
+
164
+ result = await retry_until(
165
+ lambda: sandbox.files.read_file("/path"),
166
+ lambda r: r.content == "expected",
167
+ timeout=5.0,
168
+ interval=0.1
169
+ )
170
+ ```
171
+
172
+ ## Notes
173
+
174
+ - E2E tests connect to real sandbox instances and may incur API costs
175
+ - Tests automatically clean up sandboxes, but interrupted tests may leave orphaned sandboxes
176
+ - Some exec tests are placeholders and may need implementation based on the actual API structure
177
+ - Binary file tests verify the application/octet-stream content type handling
178
+ - Timeout for sandbox cleanup is set to 10 seconds to prevent hanging
179
+
180
+ ## Troubleshooting
181
+
182
+ ### Tests skip with "TOGETHER_API_KEY environment variable not set"
183
+
184
+ Make sure you've exported the `TOGETHER_API_KEY` environment variable:
185
+
186
+ ```bash
187
+ export TOGETHER_API_KEY="your-api-key"
188
+ ```
189
+
190
+ ### Tests fail with timeout errors
191
+
192
+ The sandbox may be taking longer to start or respond. You can:
193
+
194
+ 1. Check your network connection
195
+ 2. Verify the API service status
196
+ 3. Increase timeout values in the tests if needed
197
+
198
+ ### Tests fail with authentication errors
199
+
200
+ Verify your API key is valid and has the necessary permissions for:
201
+
202
+ - Creating sandboxes
203
+ - Reading/writing files
204
+ - Managing sandbox lifecycle
205
+
206
+ ### Orphaned sandboxes
207
+
208
+ If tests are interrupted, sandboxes may not be cleaned up. You can manually delete them using the API or wait for automatic cleanup policies.
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # Regenerate both OpenAPI clients from the specs in the parent directory.
3
+ # Run from the repo root: bash together-sandbox-python/generate.sh
4
+ set -e
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ REPO_ROOT="$(dirname "$SCRIPT_DIR")"
8
+
9
+ mkdir -p "$SCRIPT_DIR/together_sandbox"
10
+
11
+ echo "Generating API client from api-openapi.json..."
12
+ python3 -m openapi_python_client generate \
13
+ --path "$REPO_ROOT/api-openapi.json" \
14
+ --meta none \
15
+ --output-path "$SCRIPT_DIR/together_sandbox/api" \
16
+ --overwrite
17
+
18
+ echo "Generating Sandbox client from sandbox-openapi.json..."
19
+ python3 -m openapi_python_client generate \
20
+ --path "$REPO_ROOT/sandbox-openapi.json" \
21
+ --meta none \
22
+ --output-path "$SCRIPT_DIR/together_sandbox/sandbox" \
23
+ --overwrite
24
+
25
+ echo "Done. Both clients available via: from together_sandbox import ApiClient, SandboxClient"
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "together-sandbox"
7
+ version = "1.11.0"
8
+ description = "Together Sandbox Python SDK"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "httpx>=0.20.0",
13
+ "httpx-sse>=0.4.0",
14
+ "cattrs>=23.0.0",
15
+ "attrs>=23.0.0",
16
+ "python-dateutil>=2.8.0",
17
+ "pathspec>=0.11.0",
18
+ ]
19
+ license = { text = "MIT" }
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "openapi-python-client>=0.28.0",
24
+ "pytest>=7.0.0",
25
+ "pytest-asyncio>=0.21.0",
26
+ "pytest-timeout>=2.0.0",
27
+ ]
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["together_sandbox"]
31
+
32
+ [tool.pytest.ini_options]
33
+ asyncio_mode = "auto"
34
+ markers = ["e2e: end-to-end tests that require TOGETHER_API_KEY"]
35
+ norecursedirs = ["e2e"]