dify-sandbox 1.0.0__py3-none-any.whl

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,22 @@
1
+ """
2
+ Dify Sandbox Python SDK
3
+
4
+ A Python SDK for interacting with the Dify Sandbox API.
5
+ """
6
+
7
+ from .client import DifySandboxClient
8
+ from .models import (
9
+ DifySandboxResponse,
10
+ RunCodeResponse,
11
+ UploadFileResponse,
12
+ DependencyInfo,
13
+ )
14
+
15
+ __version__ = "1.0.0"
16
+ __all__ = [
17
+ "DifySandboxClient",
18
+ "DifySandboxResponse",
19
+ "RunCodeResponse",
20
+ "UploadFileResponse",
21
+ "DependencyInfo",
22
+ ]
dify_sandbox/client.py ADDED
@@ -0,0 +1,410 @@
1
+ """
2
+ Dify Sandbox SDK - Main Client
3
+
4
+ This module provides the main client for interacting with the Dify Sandbox API.
5
+ """
6
+
7
+ import os
8
+ from typing import Optional, Union, BinaryIO
9
+ import requests
10
+
11
+ from .models import (
12
+ DifySandboxResponse,
13
+ RunCodeResponse,
14
+ RunCommandResponse,
15
+ UploadFileResponse,
16
+ DependencyInfo,
17
+ )
18
+
19
+
20
+ class DifySandboxClient:
21
+ """
22
+ Client for interacting with Dify Sandbox API.
23
+
24
+ Example:
25
+ client = DifySandboxClient(
26
+ base_url="http://localhost:8194",
27
+ api_key="dify-sandbox"
28
+ )
29
+
30
+ # Run Python code
31
+ result = client.run_python("print('Hello, World!')")
32
+ print(result.stdout)
33
+
34
+ # Upload a file
35
+ with open("test.txt", "rb") as f:
36
+ upload_result = client.upload_file(f, "test.txt")
37
+
38
+ # Download a file
39
+ content = client.download_file(upload_result.data.filename)
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ base_url: str = "http://localhost:8194",
45
+ api_key: str = "dify-sandbox",
46
+ timeout: int = 30,
47
+ ):
48
+ """
49
+ Initialize the Dify Sandbox client.
50
+
51
+ Args:
52
+ base_url: Base URL of the Dify Sandbox server
53
+ api_key: API key for authentication (X-Api-Key header)
54
+ timeout: Request timeout in seconds
55
+ """
56
+ self.base_url = base_url.rstrip("/")
57
+ self.api_key = api_key
58
+ self.timeout = timeout
59
+ self.session = requests.Session()
60
+ self.session.headers.update({"X-Api-Key": self.api_key})
61
+
62
+ def _request(
63
+ self,
64
+ method: str,
65
+ path: str,
66
+ json: Optional[dict] = None,
67
+ files: Optional[dict] = None,
68
+ stream: bool = False,
69
+ ) -> requests.Response:
70
+ """
71
+ Make an HTTP request to the API.
72
+
73
+ Args:
74
+ method: HTTP method (GET, POST, etc.)
75
+ path: API path (will be appended to base_url)
76
+ json: JSON data to send
77
+ files: Files to upload
78
+ stream: Whether to stream the response
79
+
80
+ Returns:
81
+ requests.Response object
82
+
83
+ Raises:
84
+ requests.HTTPError: If the request fails
85
+ """
86
+ url = f"{self.base_url}{path}"
87
+ response = self.session.request(
88
+ method=method,
89
+ url=url,
90
+ json=json,
91
+ files=files,
92
+ timeout=self.timeout,
93
+ stream=stream,
94
+ )
95
+ response.raise_for_status()
96
+ return response
97
+
98
+ def health_check(self) -> bool:
99
+ """
100
+ Check if the sandbox server is healthy.
101
+
102
+ Returns:
103
+ True if server is healthy, False otherwise
104
+ """
105
+ try:
106
+ response = self._request("GET", "/health")
107
+ return response.status_code == 200
108
+ except requests.RequestException:
109
+ return False
110
+
111
+ def run_python(
112
+ self,
113
+ code: str,
114
+ preload: str = "",
115
+ enable_network: bool = False,
116
+ ) -> RunCodeResponse:
117
+ """
118
+ Run Python code in the sandbox.
119
+
120
+ Args:
121
+ code: Python code to execute
122
+ preload: Optional preload code to run before main code
123
+ enable_network: Whether to enable network access
124
+
125
+ Returns:
126
+ RunCodeResponse with execution results
127
+ """
128
+ return self._run_code("python3", code, preload, enable_network)
129
+
130
+ def run_nodejs(
131
+ self,
132
+ code: str,
133
+ preload: str = "",
134
+ enable_network: bool = False,
135
+ ) -> RunCodeResponse:
136
+ """
137
+ Run Node.js code in the sandbox.
138
+
139
+ Args:
140
+ code: Node.js code to execute
141
+ preload: Optional preload code to run before main code
142
+ enable_network: Whether to enable network access
143
+
144
+ Returns:
145
+ RunCodeResponse with execution results
146
+ """
147
+ return self._run_code("nodejs", code, preload, enable_network)
148
+
149
+ def _run_code(
150
+ self,
151
+ language: str,
152
+ code: str,
153
+ preload: str,
154
+ enable_network: bool,
155
+ ) -> RunCodeResponse:
156
+ """
157
+ Internal method to run code in the sandbox.
158
+
159
+ Args:
160
+ language: Programming language ("python3" or "nodejs")
161
+ code: Code to execute
162
+ preload: Preload code
163
+ enable_network: Enable network access
164
+
165
+ Returns:
166
+ RunCodeResponse with execution results
167
+ """
168
+ payload = {
169
+ "language": language,
170
+ "code": code,
171
+ "preload": preload,
172
+ "enable_network": enable_network,
173
+ }
174
+ response = self._request("POST", "/v1/sandbox/run", json=payload)
175
+ result = DifySandboxResponse.from_dict(response.json())
176
+
177
+ if not result.is_success:
178
+ raise Exception(f"API error: {result.message}")
179
+
180
+ return RunCodeResponse.from_dict(result.data)
181
+
182
+ def get_dependencies(self, language: str = "python3") -> DependencyInfo:
183
+ """
184
+ Get list of installed dependencies.
185
+
186
+ Args:
187
+ language: Programming language ("python3" or "nodejs")
188
+
189
+ Returns:
190
+ DependencyInfo with list of dependencies
191
+ """
192
+ response = self._request(
193
+ "GET", "/v1/sandbox/dependencies", json={"language": language}
194
+ )
195
+ result = DifySandboxResponse.from_dict(response.json())
196
+
197
+ if not result.is_success:
198
+ raise Exception(f"API error: {result.message}")
199
+
200
+ return DependencyInfo.from_dict(result.data)
201
+
202
+ def update_dependencies(self, language: str = "python3") -> DifySandboxResponse:
203
+ """
204
+ Update dependencies for the specified language.
205
+
206
+ Args:
207
+ language: Programming language ("python3" or "nodejs")
208
+
209
+ Returns:
210
+ DifySandboxResponse with update result
211
+ """
212
+ response = self._request(
213
+ "POST", "/v1/sandbox/dependencies/update", json={"language": language}
214
+ )
215
+ result = DifySandboxResponse.from_dict(response.json())
216
+
217
+ if not result.is_success:
218
+ raise Exception(f"API error: {result.message}")
219
+
220
+ return result
221
+
222
+ def refresh_dependencies(self, language: str = "python3") -> DifySandboxResponse:
223
+ """
224
+ Refresh dependencies for the specified language.
225
+
226
+ Args:
227
+ language: Programming language ("python3" or "nodejs")
228
+
229
+ Returns:
230
+ DifySandboxResponse with refresh result
231
+ """
232
+ response = self._request(
233
+ "GET", "/v1/sandbox/dependencies/refresh", json={"language": language}
234
+ )
235
+ result = DifySandboxResponse.from_dict(response.json())
236
+
237
+ if not result.is_success:
238
+ raise Exception(f"API error: {result.message}")
239
+
240
+ return result
241
+
242
+ def upload_file(
243
+ self,
244
+ file: Union[BinaryIO, str],
245
+ filename: Optional[str] = None,
246
+ ) -> UploadFileResponse:
247
+ """
248
+ Upload a file to the sandbox.
249
+
250
+ Args:
251
+ file: File object or file path to upload
252
+ filename: Optional filename (required if file is a file object)
253
+
254
+ Returns:
255
+ UploadFileResponse with uploaded file info
256
+
257
+ Example:
258
+ # Upload from file path
259
+ result = client.upload_file("/path/to/file.txt")
260
+
261
+ # Upload from file object
262
+ with open("file.txt", "rb") as f:
263
+ result = client.upload_file(f, "file.txt")
264
+ """
265
+ if isinstance(file, str):
266
+ # file is a path
267
+ if not os.path.exists(file):
268
+ raise FileNotFoundError(f"File not found: {file}")
269
+
270
+ if filename is None:
271
+ filename = os.path.basename(file)
272
+
273
+ with open(file, "rb") as f:
274
+ return self._upload_file_object(f, filename)
275
+ else:
276
+ # file is a file object
277
+ if filename is None:
278
+ raise ValueError("filename is required when file is a file object")
279
+ return self._upload_file_object(file, filename)
280
+
281
+ def _upload_file_object(
282
+ self,
283
+ file: BinaryIO,
284
+ filename: str,
285
+ ) -> UploadFileResponse:
286
+ """
287
+ Internal method to upload a file object.
288
+
289
+ Args:
290
+ file: File object to upload
291
+ filename: Name of the file
292
+
293
+ Returns:
294
+ UploadFileResponse with uploaded file info
295
+ """
296
+ files = {"file": (filename, file)}
297
+ response = self._request("POST", "/v1/sandbox/file/upload", files=files)
298
+ result = DifySandboxResponse.from_dict(response.json())
299
+
300
+ if not result.is_success:
301
+ raise Exception(f"API error: {result.message}")
302
+
303
+ return UploadFileResponse.from_dict(result.data)
304
+
305
+ def download_file(
306
+ self,
307
+ filename: str,
308
+ save_path: Optional[str] = None,
309
+ ) -> Union[bytes, str]:
310
+ """
311
+ Download a file from the sandbox.
312
+
313
+ Args:
314
+ filename: Name of the file to download
315
+ save_path: Optional path to save the file. If not provided, returns bytes.
316
+
317
+ Returns:
318
+ File content as bytes if save_path is None, otherwise the save_path
319
+
320
+ Example:
321
+ # Download to bytes
322
+ content = client.download_file("uploaded_file.txt")
323
+
324
+ # Download to file
325
+ client.download_file("uploaded_file.txt", "downloaded.txt")
326
+ """
327
+ payload = {"filename": filename}
328
+ response = self._request(
329
+ "POST", "/v1/sandbox/file/download", json=payload, stream=True
330
+ )
331
+
332
+ if save_path:
333
+ with open(save_path, "wb") as f:
334
+ for chunk in response.iter_content(chunk_size=8192):
335
+ f.write(chunk)
336
+ return save_path
337
+ else:
338
+ return response.content
339
+
340
+ def delete_file(self, filename: str) -> DifySandboxResponse:
341
+ """
342
+ Delete a file from the sandbox.
343
+
344
+ Args:
345
+ filename: Name of the file to delete
346
+
347
+ Returns:
348
+ DifySandboxResponse with deletion result
349
+
350
+ Example:
351
+ client.delete_file("uploaded_file.txt")
352
+ """
353
+ payload = {"filename": filename}
354
+ response = self._request("POST", "/v1/sandbox/file/delete", json=payload)
355
+ result = DifySandboxResponse.from_dict(response.json())
356
+
357
+ if not result.is_success:
358
+ raise Exception(f"API error: {result.message}")
359
+
360
+ return result
361
+
362
+ def run_command(
363
+ self,
364
+ command: str,
365
+ args: Optional[list] = None,
366
+ work_dir: str = "",
367
+ timeout: int = 0,
368
+ enable_network: bool = False,
369
+ ) -> RunCommandResponse:
370
+ """
371
+ Run a sandbox-approved binary against a previously uploaded file.
372
+
373
+ The command basename is matched against the deny-list exposed by the
374
+ sandbox (shells, rm, sudo, package managers, ...). Any
375
+ argument that looks like a shell metacharacter is rejected so the
376
+ request cannot accidentally invoke a shell.
377
+
378
+ Args:
379
+ command: Command basename to execute, e.g. "python3".
380
+ args: Arguments to pass to the command (each must be
381
+ shell-metachar free).
382
+ work_dir: Working directory, relative to the sandbox upload
383
+ directory. Empty means the upload directory itself.
384
+ timeout: Optional per-request timeout in seconds. 0 means
385
+ use the sandbox-wide worker timeout.
386
+ enable_network: Forwarded to the sandbox only when the sandbox
387
+ itself was started with network access enabled.
388
+
389
+ Returns:
390
+ RunCommandResponse with execution results.
391
+
392
+ Example:
393
+ client.upload_file("hello.py")
394
+ result = client.run_command("python3", ["hello.py"])
395
+ print(result.stdout)
396
+ """
397
+ payload = {
398
+ "command": command,
399
+ "args": args or [],
400
+ "work_dir": work_dir,
401
+ "timeout": timeout,
402
+ "enable_network": enable_network,
403
+ }
404
+ response = self._request("POST", "/v1/sandbox/run/command", json=payload)
405
+ result = DifySandboxResponse.from_dict(response.json())
406
+
407
+ if not result.is_success:
408
+ raise Exception(f"API error: {result.message}")
409
+
410
+ return RunCommandResponse.from_dict(result.data)
dify_sandbox/models.py ADDED
@@ -0,0 +1,106 @@
1
+ """
2
+ Dify Sandbox SDK - Data Models
3
+
4
+ This module contains data models for the Dify Sandbox SDK.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Optional, Any, Dict
9
+
10
+
11
+ @dataclass
12
+ class DifySandboxResponse:
13
+ """Base response from Dify Sandbox API"""
14
+ code: int
15
+ message: str
16
+ data: Optional[Any] = None
17
+
18
+ @classmethod
19
+ def from_dict(cls, data: Dict[str, Any]) -> "DifySandboxResponse":
20
+ return cls(
21
+ code=data.get("code", -1),
22
+ message=data.get("message", ""),
23
+ data=data.get("data")
24
+ )
25
+
26
+ @property
27
+ def is_success(self) -> bool:
28
+ return self.code == 0
29
+
30
+
31
+ @dataclass
32
+ class RunCodeResponse:
33
+ """Response from code execution endpoint"""
34
+ stdout: str
35
+ stderr: str
36
+ error: str
37
+ exit_code: int
38
+
39
+ @classmethod
40
+ def from_dict(cls, data: Dict[str, Any]) -> "RunCodeResponse":
41
+ return cls(
42
+ stdout=data.get("stdout", ""),
43
+ stderr=data.get("stderr", ""),
44
+ error=data.get("error", ""),
45
+ exit_code=data.get("exit_code", -1)
46
+ )
47
+
48
+ @property
49
+ def is_success(self) -> bool:
50
+ return self.exit_code == 0 and not self.error
51
+
52
+
53
+ @dataclass
54
+ class RunCommandResponse:
55
+ """Response from the command execution endpoint.
56
+
57
+ Mirrors ``RunCodeResponse`` because both endpoints share the same
58
+ stdout/stderr/exit_code envelope. ``RunCommandResponse`` exists as a
59
+ separate type so callers can tell which endpoint produced the data
60
+ without inspecting the request URL.
61
+ """
62
+ stdout: str
63
+ stderr: str
64
+ error: str
65
+ exit_code: int
66
+
67
+ @classmethod
68
+ def from_dict(cls, data: Dict[str, Any]) -> "RunCommandResponse":
69
+ return cls(
70
+ stdout=data.get("stdout", ""),
71
+ stderr=data.get("stderr", ""),
72
+ error=data.get("error", ""),
73
+ exit_code=data.get("exit_code", -1),
74
+ )
75
+
76
+ @property
77
+ def is_success(self) -> bool:
78
+ return self.exit_code == 0 and not self.error
79
+
80
+
81
+ @dataclass
82
+ class UploadFileResponse:
83
+ """Response from file upload endpoint"""
84
+ filename: str
85
+ size: int
86
+
87
+ @classmethod
88
+ def from_dict(cls, data: Dict[str, Any]) -> "UploadFileResponse":
89
+ return cls(
90
+ filename=data.get("filename", ""),
91
+ size=data.get("size", 0)
92
+ )
93
+
94
+
95
+ @dataclass
96
+ class DependencyInfo:
97
+ """Information about installed dependencies"""
98
+ language: str
99
+ dependencies: list
100
+
101
+ @classmethod
102
+ def from_dict(cls, data: Dict[str, Any]) -> "DependencyInfo":
103
+ return cls(
104
+ language=data.get("language", ""),
105
+ dependencies=data.get("dependencies", [])
106
+ )
@@ -0,0 +1,352 @@
1
+ Metadata-Version: 2.4
2
+ Name: dify-sandbox
3
+ Version: 1.0.0
4
+ Summary: Python SDK for Dify Sandbox API
5
+ Home-page: https://github.com/langgenius/dify-sandbox
6
+ Author: Dify
7
+ Author-email: support@dify.ai
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Requires-Python: >=3.7
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: requests>=2.25.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=6.0; extra == "dev"
23
+ Requires-Dist: pytest-cov>=2.0; extra == "dev"
24
+ Requires-Dist: black>=21.0; extra == "dev"
25
+ Requires-Dist: mypy>=0.900; extra == "dev"
26
+ Dynamic: author
27
+ Dynamic: author-email
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: license
33
+ Dynamic: provides-extra
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ # Dify Sandbox Python SDK
39
+
40
+ Python SDK for interacting with the Dify Sandbox API.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install -e sdk/
46
+ ```
47
+
48
+ Or install dependencies directly:
49
+
50
+ ```bash
51
+ pip install requests
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ ```python
57
+ from dify_sandbox import DifySandboxClient
58
+
59
+ # Initialize client
60
+ client = DifySandboxClient(
61
+ base_url="http://localhost:8194",
62
+ api_key="your-api-key"
63
+ )
64
+
65
+ # Run Python code
66
+ result = client.run_python("print('Hello from sandbox!')")
67
+ print(result.stdout) # Output: Hello from sandbox!
68
+
69
+ # Run Node.js code
70
+ result = client.run_nodejs("console.log('Hello from Node.js!')")
71
+ print(result.stdout) # Output: Hello from Node.js!
72
+
73
+ # Upload a Python script and run it from the sandbox upload directory
74
+ with open("hello.py", "rb") as f:
75
+ uploaded = client.upload_file(f, filename="hello.py")
76
+
77
+ result = client.run_command(
78
+ command="python3",
79
+ args=[uploaded.filename],
80
+ timeout=30,
81
+ )
82
+ print(result.stdout)
83
+ ```
84
+
85
+ ## Features
86
+
87
+ - **Code Execution**: Run Python and Node.js code in a secure sandbox
88
+ - **Command Execution**: Launch sandbox-approved binaries (e.g. `python3`,
89
+ `node`) against previously uploaded files via the deny-list enforced
90
+ `POST /v1/sandbox/run/command` endpoint
91
+ - **File Operations**: Upload and download files to/from the sandbox
92
+ - **Dependency Management**: View and manage sandbox dependencies
93
+ - **Health Check**: Monitor sandbox server status
94
+
95
+ ## API Reference
96
+
97
+ ### Client Initialization
98
+
99
+ ```python
100
+ client = DifySandboxClient(
101
+ base_url="http://localhost:8194", # Sandbox server URL
102
+ api_key="your-api-key", # API key for authentication
103
+ timeout=30 # Request timeout in seconds
104
+ )
105
+ ```
106
+
107
+ ### Code Execution
108
+
109
+ #### Run Python Code
110
+
111
+ ```python
112
+ result = client.run_python(
113
+ code="print('Hello, World!')",
114
+ preload="", # Optional: code to run before main code
115
+ enable_network=False # Optional: enable network access
116
+ )
117
+
118
+ print(result.stdout) # Standard output
119
+ print(result.stderr) # Standard error
120
+ print(result.exit_code) # Exit code (0 = success)
121
+ ```
122
+
123
+ #### Run Node.js Code
124
+
125
+ ```python
126
+ result = client.run_nodejs(
127
+ code="console.log('Hello, World!')",
128
+ preload="",
129
+ enable_network=False
130
+ )
131
+
132
+ print(result.stdout)
133
+ print(result.stderr)
134
+ print(result.exit_code)
135
+ ```
136
+
137
+ ### Command Execution
138
+
139
+ Run a sandbox-approved binary against a file that was previously uploaded
140
+ to the sandbox via `upload_file`. The endpoint enforces a deny-list of
141
+ dangerous commands (shells, `rm`, `sudo`, package managers, …) and rejects
142
+ any argument containing shell metacharacters so the request can never
143
+ accidentally spawn a shell.
144
+
145
+ ```python
146
+ # 1. Upload the script you want to execute
147
+ with open("hello.py", "rb") as f:
148
+ uploaded = client.upload_file(f, filename="hello.py")
149
+
150
+ # 2. Invoke python3 with the uploaded file as an argument
151
+ result = client.run_command(
152
+ command="python3", # Command basename (resolved via PATH)
153
+ args=[uploaded.filename], # Arguments; cannot contain shell metachars
154
+ work_dir="", # "" or "." → sandbox upload_dir; or a relative subdir
155
+ timeout=30, # 0 → use the sandbox worker timeout
156
+ enable_network=False, # Must respect global enable_network setting
157
+ )
158
+
159
+ print(result.stdout)
160
+ print(result.stderr)
161
+ print(result.exit_code)
162
+ ```
163
+
164
+ #### `work_dir` rules
165
+
166
+ | Input | Result |
167
+ |----------------------|-------------------------------------------------------|
168
+ | `""` or `"."` | Resolved to the sandbox `upload_dir` itself |
169
+ | `"scripts"` | Resolved to `<upload_dir>/scripts` |
170
+ | `<upload_dir>` (absolute) | Resolved to `upload_dir` itself |
171
+ | `/etc`, `../etc`, … | Rejected with `work_dir is invalid: ...` |
172
+
173
+ #### Deny-list
174
+
175
+ The deny-list is the union of a built-in default (shells, `rm`, `sudo`,
176
+ `apt`, `pip3`, `npm`, …) and the operator-configured
177
+ `blocked_commands` list. User configuration can only add entries — never
178
+ remove them. Commands that match the deny-list, or arguments containing
179
+ shell metacharacters (`|`, `&`, `;`, `<`, `>`, `` ` ``, `$`, `*`, `?`,
180
+ `{`, `}`, `~`, `!`, `#`, quotes, …) are rejected with a 400 before any
181
+ process is spawned.
182
+
183
+ ### File Operations
184
+
185
+ #### Upload File
186
+
187
+ ```python
188
+ # Upload from file path
189
+ result = client.upload_file("/path/to/file.txt")
190
+ print(result.filename) # Uploaded filename in sandbox
191
+ print(result.size) # File size in bytes
192
+
193
+ # Upload from file object
194
+ with open("local_file.txt", "rb") as f:
195
+ result = client.upload_file(f, filename="custom_name.txt")
196
+ ```
197
+
198
+ #### Download File
199
+
200
+ ```python
201
+ # Download to memory
202
+ content = client.download_file("sandbox_file.txt")
203
+
204
+ # Download to local file
205
+ client.download_file("sandbox_file.txt", save_path="local_copy.txt")
206
+ ```
207
+
208
+ ### Dependency Management
209
+
210
+ #### Get Dependencies
211
+
212
+ ```python
213
+ deps = client.get_dependencies(language="python3")
214
+ print(deps.dependencies) # List of installed packages
215
+ ```
216
+
217
+ #### Update Dependencies
218
+
219
+ ```python
220
+ response = client.update_dependencies(language="python3")
221
+ print(response.message)
222
+ ```
223
+
224
+ #### Refresh Dependencies
225
+
226
+ ```python
227
+ response = client.refresh_dependencies(language="python3")
228
+ print(response.message)
229
+ ```
230
+
231
+ ### Health Check
232
+
233
+ ```python
234
+ if client.health_check():
235
+ print("Sandbox is healthy")
236
+ else:
237
+ print("Sandbox is not responding")
238
+ ```
239
+
240
+ ## Data Models
241
+
242
+ ### RunCodeResponse
243
+
244
+ ```python
245
+ @dataclass
246
+ class RunCodeResponse:
247
+ stdout: str # Standard output from code execution
248
+ stderr: str # Standard error from code execution
249
+ exit_code: int # Exit code (0 = success)
250
+ error: str # Sandbox-side error message (empty on success)
251
+ ```
252
+
253
+ ### RunCommandResponse
254
+
255
+ ```python
256
+ @dataclass
257
+ class RunCommandResponse:
258
+ stdout: str # Standard output from the executed command
259
+ stderr: str # Standard error from the executed command
260
+ exit_code: int # Exit code (0 = success)
261
+ error: str # Sandbox-side error message (empty on success)
262
+ ```
263
+
264
+ ### UploadFileResponse
265
+
266
+ ```python
267
+ @dataclass
268
+ class UploadFileResponse:
269
+ filename: str # Filename in sandbox
270
+ size: int # File size in bytes
271
+ ```
272
+
273
+ ### DependencyInfo
274
+
275
+ ```python
276
+ @dataclass
277
+ class DependencyInfo:
278
+ language: str # Language (python3/nodejs)
279
+ dependencies: list # List of dependencies
280
+ ```
281
+
282
+ ### DifySandboxResponse
283
+
284
+ ```python
285
+ @dataclass
286
+ class DifySandboxResponse:
287
+ code: int # Response code (0 = success)
288
+ message: str # Response message
289
+ data: Any # Response data
290
+ ```
291
+
292
+ ## End-to-end Example: Upload a Script, Then Run It
293
+
294
+ ```python
295
+ from dify_sandbox import DifySandboxClient
296
+
297
+ client = DifySandboxClient(base_url="http://localhost:8194", api_key="dify-sandbox")
298
+
299
+ # Local script we want to run inside the sandbox
300
+ script = b"""
301
+ import sys
302
+ print("hello from the sandbox!")
303
+ print("args:", sys.argv[1:])
304
+ """
305
+
306
+ # 1. Upload the script — the server stores it in upload_dir and returns the
307
+ # filename it used (a UUID is appended to avoid collisions).
308
+ with open("hello.py", "wb") as f:
309
+ f.write(script)
310
+
311
+ uploaded = client.upload_file("hello.py")
312
+ print("uploaded as:", uploaded.filename)
313
+
314
+ # 2. Run python3 with the uploaded file as its argument.
315
+ result = client.run_command(
316
+ command="python3",
317
+ args=[uploaded.filename],
318
+ timeout=10,
319
+ )
320
+
321
+ assert result.exit_code == 0, result.stderr
322
+ print(result.stdout)
323
+ ```
324
+
325
+ ## Examples
326
+
327
+ See the `examples/` directory for complete usage examples:
328
+
329
+ - `basic_usage.py` - Basic code execution examples
330
+ - `file_operations.py` - File upload and download examples
331
+ - `dependency_management.py` - Dependency management examples
332
+ - `command_execution.py` - Upload a script and run it through the deny-list-enforced `run_command` endpoint
333
+
334
+ ## Error Handling
335
+
336
+ The SDK raises exceptions for API errors:
337
+
338
+ ```python
339
+ try:
340
+ result = client.run_python("invalid code")
341
+ except Exception as e:
342
+ print(f"Error: {e}")
343
+ ```
344
+
345
+ `run_command` raises the same kind of exception when the deny-list rejects
346
+ the command, when an argument contains shell metacharacters, or when the
347
+ work directory is invalid — the exception message is the human-readable
348
+ reason returned by the sandbox.
349
+
350
+ ## License
351
+
352
+ MIT License
@@ -0,0 +1,7 @@
1
+ dify_sandbox/__init__.py,sha256=cT-RoMHZVYVSy72gMq9bDzEqM2_qmgAum1vJnxy3-1U,402
2
+ dify_sandbox/client.py,sha256=UiLpaVCV7Lg02xE0xhPBYLXJXv1fQZ7y-RPqcknsfVY,12753
3
+ dify_sandbox/models.py,sha256=Mb-Oi-mkx6El8R_6DF7VD2OiPI8iSvgNA93HSmb7LqA,2658
4
+ dify_sandbox-1.0.0.dist-info/METADATA,sha256=XQNAIG51UZCKbZWc05PIyCnW5X-lji8AOcKiemgbqNM,9415
5
+ dify_sandbox-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ dify_sandbox-1.0.0.dist-info/top_level.txt,sha256=Gkc2twiAhYZSCB2n2dNa68hdwIuzuojtc3DTvMgKB-c,13
7
+ dify_sandbox-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ dify_sandbox