sandforge-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.
@@ -0,0 +1,391 @@
1
+ Metadata-Version: 2.4
2
+ Name: sandforge-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Sandforge hypervisor sandbox platform
5
+ Home-page: https://github.com/yanurag-dev/sandforge
6
+ Author: Anurag Yadav
7
+ Author-email: Anurag Yadav <yadavanurag1310@gmail.com>
8
+ License: Apache-2.0
9
+ Project-URL: Homepage, https://github.com/yanurag-dev/sandforge
10
+ Project-URL: Repository, https://github.com/yanurag-dev/sandforge
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: requests>=2.33.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=6.0; extra == "dev"
19
+ Requires-Dist: pytest-cov>=2.10; extra == "dev"
20
+ Requires-Dist: black>=21.0; extra == "dev"
21
+ Requires-Dist: mypy>=0.910; extra == "dev"
22
+ Requires-Dist: flake8>=3.9; extra == "dev"
23
+ Dynamic: author
24
+ Dynamic: home-page
25
+ Dynamic: requires-python
26
+
27
+ # Sandforge Python SDK
28
+
29
+ The Sandforge Python SDK provides a client library for interacting with the Sandforge hypervisor sandbox platform. It enables you to create, manage, and execute commands in isolated sandboxes programmatically.
30
+
31
+ ## Installation
32
+
33
+ Install the SDK from the repository:
34
+
35
+ ```bash
36
+ pip install -e .
37
+ ```
38
+
39
+ Or with development dependencies:
40
+
41
+ ```bash
42
+ pip install -e ".[dev]"
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ### Basic Usage
48
+
49
+ ```python
50
+ from sandforge import Client, SandboxSpec
51
+
52
+ # Create a client pointing to your Sandforge control plane
53
+ client = Client("http://localhost:8080")
54
+
55
+ # Create a sandbox with default configuration
56
+ sandbox = client.create_sandbox()
57
+
58
+ # Run a command
59
+ result = sandbox.commands.run(["echo", "Hello, Sandforge!"])
60
+ print(result.stdout) # "Hello, Sandforge!\n"
61
+
62
+ # Clean up
63
+ sandbox.kill()
64
+ ```
65
+
66
+ ### Custom Sandbox Configuration
67
+
68
+ ```python
69
+ from sandforge import Client, SandboxSpec, WorkspaceMount
70
+
71
+ spec = SandboxSpec(
72
+ cpu=4,
73
+ memory_mb=2048,
74
+ disk_gb=20,
75
+ timeout_sec=3600,
76
+ network_mode="fetch", # Allow package downloads
77
+ mounts=[
78
+ WorkspaceMount(
79
+ host_path="/path/to/project",
80
+ guest_path="/workspace",
81
+ read_only=False,
82
+ ),
83
+ ],
84
+ )
85
+
86
+ client = Client("http://localhost:8080")
87
+ sandbox = client.create_sandbox(spec)
88
+
89
+ # Work with the mounted directory
90
+ result = sandbox.commands.run(["ls", "-la", "/workspace"])
91
+ print(result.stdout)
92
+
93
+ sandbox.kill()
94
+ ```
95
+
96
+ ### Command Execution with Environment Variables
97
+
98
+ ```python
99
+ from sandforge import Client
100
+
101
+ client = Client("http://localhost:8080")
102
+ sandbox = client.create_sandbox()
103
+
104
+ # Run with custom environment variables
105
+ result = sandbox.commands.run(
106
+ command=["python", "-c", "import os; print(os.environ.get('MY_VAR'))"],
107
+ cwd="/",
108
+ env={"MY_VAR": "Hello World"},
109
+ timeout_sec=30,
110
+ )
111
+
112
+ print(result.stdout) # "Hello World\n"
113
+ print(result.exit_code) # 0
114
+
115
+ sandbox.kill()
116
+ ```
117
+
118
+ ### Error Handling
119
+
120
+ ```python
121
+ from sandforge import Client, NetworkError, SandboxNotFoundError
122
+
123
+ client = Client("http://localhost:8080")
124
+
125
+ try:
126
+ sandbox = client.create_sandbox()
127
+ result = sandbox.commands.run(["false"]) # Command that fails
128
+
129
+ if result.exit_code != 0:
130
+ print(f"Command failed with exit code {result.exit_code}")
131
+ print(f"stderr: {result.stderr}")
132
+
133
+ sandbox.kill()
134
+
135
+ except NetworkError as e:
136
+ print(f"Connection error: {e}")
137
+ except SandboxNotFoundError as e:
138
+ print(f"Sandbox not found: {e}")
139
+ ```
140
+
141
+ ### Sandbox Information
142
+
143
+ ```python
144
+ from sandforge import Client
145
+
146
+ client = Client("http://localhost:8080")
147
+ sandbox = client.create_sandbox()
148
+
149
+ # Get sandbox information
150
+ info = sandbox.info()
151
+ print(f"Sandbox ID: {info.id}")
152
+ print(f"State: {info.state}") # "ready", "executing", "destroyed", etc.
153
+ ```
154
+
155
+ ## API Reference
156
+
157
+ ### Client
158
+
159
+ The main entry point for interacting with Sandforge.
160
+
161
+ #### Constructor
162
+
163
+ ```python
164
+ Client(base_url: str, timeout: int = 60)
165
+ ```
166
+
167
+ - `base_url`: The control plane URL (e.g., "http://localhost:8080")
168
+ - `timeout`: Request timeout in seconds (default: 60)
169
+
170
+ #### Methods
171
+
172
+ ##### `create_sandbox(spec: Optional[SandboxSpec] = None) -> SandboxHandle`
173
+
174
+ Create a new sandbox.
175
+
176
+ - Returns: A `SandboxHandle` to the created sandbox
177
+ - Raises: `NetworkError` or `SandforgeException`
178
+
179
+ ##### `exec(sandbox_id: str, request: ExecRequest) -> ExecResult`
180
+
181
+ Execute a command in a sandbox.
182
+
183
+ - Returns: An `ExecResult` with exit code, stdout, and stderr
184
+ - Raises: `NetworkError` or `SandforgeException`
185
+
186
+ ##### `get_status(sandbox_id: str) -> str`
187
+
188
+ Get the current state of a sandbox.
189
+
190
+ - Returns: The sandbox state as a string
191
+ - Raises: `NetworkError`
192
+
193
+ ##### `get_info(sandbox_id: str) -> SandboxInfo`
194
+
195
+ Get detailed information about a sandbox.
196
+
197
+ - Returns: A `SandboxInfo` object
198
+ - Raises: `NetworkError`
199
+
200
+ ##### `destroy(sandbox_id: str) -> None`
201
+
202
+ Destroy a sandbox.
203
+
204
+ - Raises: `NetworkError` or `SandforgeException`
205
+
206
+ ### SandboxHandle
207
+
208
+ A handle to a created sandbox with convenience APIs.
209
+
210
+ #### Properties
211
+
212
+ - `id`: The sandbox ID (string)
213
+
214
+ #### Methods
215
+
216
+ ##### `kill() -> None`
217
+
218
+ Destroy the sandbox.
219
+
220
+ ```python
221
+ sandbox.kill()
222
+ ```
223
+
224
+ ##### `info() -> SandboxInfo`
225
+
226
+ Get sandbox information.
227
+
228
+ ```python
229
+ info = sandbox.info()
230
+ ```
231
+
232
+ #### Nested APIs
233
+
234
+ ##### `commands`
235
+
236
+ The `CommandsAPI` for executing commands.
237
+
238
+ ###### `run(command, cwd="/", env=None, timeout_sec=60) -> ExecResult`
239
+
240
+ Run a command in the sandbox.
241
+
242
+ - `command`: List of command and arguments
243
+ - `cwd`: Working directory (default: "/")
244
+ - `env`: Dictionary of environment variables (default: {})
245
+ - `timeout_sec`: Command timeout in seconds (default: 60)
246
+ - Returns: `ExecResult` with exit code, stdout, and stderr
247
+
248
+ ```python
249
+ result = sandbox.commands.run(
250
+ ["python", "script.py"],
251
+ cwd="/workspace",
252
+ env={"PYTHONUNBUFFERED": "1"},
253
+ timeout_sec=300,
254
+ )
255
+ ```
256
+
257
+ ##### `files`
258
+
259
+ The `FilesAPI` for reading files from the sandbox.
260
+
261
+ ###### `read(path: str) -> str`
262
+
263
+ Read a file from the sandbox.
264
+
265
+ **Note:** This method is currently not implemented and raises `NotImplementedError`. VSOCK copyout support is coming soon.
266
+
267
+ ```python
268
+ try:
269
+ content = sandbox.files.read("/etc/hostname")
270
+ except NotImplementedError:
271
+ print("files.read() not yet supported")
272
+ ```
273
+
274
+ ### Types
275
+
276
+ #### SandboxSpec
277
+
278
+ Specification for creating a sandbox.
279
+
280
+ ```python
281
+ SandboxSpec(
282
+ backend: str = "macos-vz", # "linux-kvm", "linux-firecracker", "macos-vz"
283
+ cpu: int = 2, # Number of vCPUs
284
+ memory_mb: int = 512, # Memory in MB
285
+ disk_gb: int = 10, # Disk size in GB
286
+ timeout_sec: int = 3600, # Sandbox lifetime in seconds
287
+ network_mode: str = "offline", # "offline", "fetch", "full"
288
+ task_isolation: str = "container", # "container", "process"
289
+ mounts: List[WorkspaceMount] = [], # Mounted directories
290
+ )
291
+ ```
292
+
293
+ #### WorkspaceMount
294
+
295
+ A directory mount from host to guest.
296
+
297
+ ```python
298
+ WorkspaceMount(
299
+ host_path: str, # Path on the host
300
+ guest_path: str, # Path in the sandbox
301
+ read_only: bool = False, # Whether the mount is read-only
302
+ )
303
+ ```
304
+
305
+ #### ExecRequest
306
+
307
+ A request to execute a command.
308
+
309
+ ```python
310
+ ExecRequest(
311
+ command: List[str], # Command and arguments
312
+ cwd: str = "/", # Working directory
313
+ env: Dict[str, str] = {}, # Environment variables
314
+ timeout_sec: int = 60, # Timeout in seconds
315
+ )
316
+ ```
317
+
318
+ #### ExecResult
319
+
320
+ The result of command execution.
321
+
322
+ ```python
323
+ ExecResult(
324
+ exit_code: int, # Command exit code
325
+ stdout: str, # Standard output
326
+ stderr: str, # Standard error
327
+ artifacts: List[str] = [], # Paths to generated artifacts
328
+ )
329
+ ```
330
+
331
+ #### SandboxInfo
332
+
333
+ Information about a sandbox.
334
+
335
+ ```python
336
+ SandboxInfo(
337
+ id: str, # Sandbox ID
338
+ state: str, # Current state (e.g., "ready", "executing", "destroyed")
339
+ )
340
+ ```
341
+
342
+ ### Exceptions
343
+
344
+ All exceptions inherit from `SandforgeException`.
345
+
346
+ - **SandforgeException**: Base exception for all Sandforge errors
347
+ - **NetworkError**: Network communication error with the control plane
348
+ - **SandboxNotFoundError**: Sandbox does not exist
349
+ - **ExecutionError**: Command execution failed
350
+ - **InvalidSpecError**: Invalid sandbox specification
351
+
352
+ ## Error Handling
353
+
354
+ The SDK provides specific exception types for different error scenarios:
355
+
356
+ ```python
357
+ from sandforge import Client, NetworkError, SandboxNotFoundError, SandforgeException
358
+
359
+ client = Client("http://localhost:8080")
360
+
361
+ try:
362
+ sandbox = client.create_sandbox()
363
+ result = sandbox.commands.run(["exit", "1"])
364
+ except NetworkError as e:
365
+ print(f"Network error: {e}")
366
+ except SandboxNotFoundError as e:
367
+ print(f"Sandbox not found: {e}")
368
+ except SandforgeException as e:
369
+ print(f"Sandforge error: {e}")
370
+ ```
371
+
372
+ ## Running Tests
373
+
374
+ ```bash
375
+ pip install -e ".[dev]"
376
+ pytest tests/
377
+ ```
378
+
379
+ ## Contributing
380
+
381
+ Contributions are welcome! Please ensure code passes linting and type checks:
382
+
383
+ ```bash
384
+ black sandforge/
385
+ flake8 sandforge/
386
+ mypy sandforge/
387
+ ```
388
+
389
+ ## License
390
+
391
+ Apache License 2.0. See LICENSE in the repository root for details.