dify-sandbox 1.0.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.
- dify_sandbox-1.0.0/PKG-INFO +352 -0
- dify_sandbox-1.0.0/README.md +315 -0
- dify_sandbox-1.0.0/dify_sandbox/__init__.py +22 -0
- dify_sandbox-1.0.0/dify_sandbox/client.py +410 -0
- dify_sandbox-1.0.0/dify_sandbox/models.py +106 -0
- dify_sandbox-1.0.0/dify_sandbox.egg-info/PKG-INFO +352 -0
- dify_sandbox-1.0.0/dify_sandbox.egg-info/SOURCES.txt +10 -0
- dify_sandbox-1.0.0/dify_sandbox.egg-info/dependency_links.txt +1 -0
- dify_sandbox-1.0.0/dify_sandbox.egg-info/requires.txt +7 -0
- dify_sandbox-1.0.0/dify_sandbox.egg-info/top_level.txt +1 -0
- dify_sandbox-1.0.0/setup.cfg +4 -0
- dify_sandbox-1.0.0/setup.py +40 -0
|
@@ -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,315 @@
|
|
|
1
|
+
# Dify Sandbox Python SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for interacting with the Dify Sandbox API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e sdk/
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install dependencies directly:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install requests
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from dify_sandbox import DifySandboxClient
|
|
21
|
+
|
|
22
|
+
# Initialize client
|
|
23
|
+
client = DifySandboxClient(
|
|
24
|
+
base_url="http://localhost:8194",
|
|
25
|
+
api_key="your-api-key"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Run Python code
|
|
29
|
+
result = client.run_python("print('Hello from sandbox!')")
|
|
30
|
+
print(result.stdout) # Output: Hello from sandbox!
|
|
31
|
+
|
|
32
|
+
# Run Node.js code
|
|
33
|
+
result = client.run_nodejs("console.log('Hello from Node.js!')")
|
|
34
|
+
print(result.stdout) # Output: Hello from Node.js!
|
|
35
|
+
|
|
36
|
+
# Upload a Python script and run it from the sandbox upload directory
|
|
37
|
+
with open("hello.py", "rb") as f:
|
|
38
|
+
uploaded = client.upload_file(f, filename="hello.py")
|
|
39
|
+
|
|
40
|
+
result = client.run_command(
|
|
41
|
+
command="python3",
|
|
42
|
+
args=[uploaded.filename],
|
|
43
|
+
timeout=30,
|
|
44
|
+
)
|
|
45
|
+
print(result.stdout)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **Code Execution**: Run Python and Node.js code in a secure sandbox
|
|
51
|
+
- **Command Execution**: Launch sandbox-approved binaries (e.g. `python3`,
|
|
52
|
+
`node`) against previously uploaded files via the deny-list enforced
|
|
53
|
+
`POST /v1/sandbox/run/command` endpoint
|
|
54
|
+
- **File Operations**: Upload and download files to/from the sandbox
|
|
55
|
+
- **Dependency Management**: View and manage sandbox dependencies
|
|
56
|
+
- **Health Check**: Monitor sandbox server status
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
### Client Initialization
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
client = DifySandboxClient(
|
|
64
|
+
base_url="http://localhost:8194", # Sandbox server URL
|
|
65
|
+
api_key="your-api-key", # API key for authentication
|
|
66
|
+
timeout=30 # Request timeout in seconds
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Code Execution
|
|
71
|
+
|
|
72
|
+
#### Run Python Code
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
result = client.run_python(
|
|
76
|
+
code="print('Hello, World!')",
|
|
77
|
+
preload="", # Optional: code to run before main code
|
|
78
|
+
enable_network=False # Optional: enable network access
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
print(result.stdout) # Standard output
|
|
82
|
+
print(result.stderr) # Standard error
|
|
83
|
+
print(result.exit_code) # Exit code (0 = success)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Run Node.js Code
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
result = client.run_nodejs(
|
|
90
|
+
code="console.log('Hello, World!')",
|
|
91
|
+
preload="",
|
|
92
|
+
enable_network=False
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
print(result.stdout)
|
|
96
|
+
print(result.stderr)
|
|
97
|
+
print(result.exit_code)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Command Execution
|
|
101
|
+
|
|
102
|
+
Run a sandbox-approved binary against a file that was previously uploaded
|
|
103
|
+
to the sandbox via `upload_file`. The endpoint enforces a deny-list of
|
|
104
|
+
dangerous commands (shells, `rm`, `sudo`, package managers, …) and rejects
|
|
105
|
+
any argument containing shell metacharacters so the request can never
|
|
106
|
+
accidentally spawn a shell.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# 1. Upload the script you want to execute
|
|
110
|
+
with open("hello.py", "rb") as f:
|
|
111
|
+
uploaded = client.upload_file(f, filename="hello.py")
|
|
112
|
+
|
|
113
|
+
# 2. Invoke python3 with the uploaded file as an argument
|
|
114
|
+
result = client.run_command(
|
|
115
|
+
command="python3", # Command basename (resolved via PATH)
|
|
116
|
+
args=[uploaded.filename], # Arguments; cannot contain shell metachars
|
|
117
|
+
work_dir="", # "" or "." → sandbox upload_dir; or a relative subdir
|
|
118
|
+
timeout=30, # 0 → use the sandbox worker timeout
|
|
119
|
+
enable_network=False, # Must respect global enable_network setting
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
print(result.stdout)
|
|
123
|
+
print(result.stderr)
|
|
124
|
+
print(result.exit_code)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### `work_dir` rules
|
|
128
|
+
|
|
129
|
+
| Input | Result |
|
|
130
|
+
|----------------------|-------------------------------------------------------|
|
|
131
|
+
| `""` or `"."` | Resolved to the sandbox `upload_dir` itself |
|
|
132
|
+
| `"scripts"` | Resolved to `<upload_dir>/scripts` |
|
|
133
|
+
| `<upload_dir>` (absolute) | Resolved to `upload_dir` itself |
|
|
134
|
+
| `/etc`, `../etc`, … | Rejected with `work_dir is invalid: ...` |
|
|
135
|
+
|
|
136
|
+
#### Deny-list
|
|
137
|
+
|
|
138
|
+
The deny-list is the union of a built-in default (shells, `rm`, `sudo`,
|
|
139
|
+
`apt`, `pip3`, `npm`, …) and the operator-configured
|
|
140
|
+
`blocked_commands` list. User configuration can only add entries — never
|
|
141
|
+
remove them. Commands that match the deny-list, or arguments containing
|
|
142
|
+
shell metacharacters (`|`, `&`, `;`, `<`, `>`, `` ` ``, `$`, `*`, `?`,
|
|
143
|
+
`{`, `}`, `~`, `!`, `#`, quotes, …) are rejected with a 400 before any
|
|
144
|
+
process is spawned.
|
|
145
|
+
|
|
146
|
+
### File Operations
|
|
147
|
+
|
|
148
|
+
#### Upload File
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Upload from file path
|
|
152
|
+
result = client.upload_file("/path/to/file.txt")
|
|
153
|
+
print(result.filename) # Uploaded filename in sandbox
|
|
154
|
+
print(result.size) # File size in bytes
|
|
155
|
+
|
|
156
|
+
# Upload from file object
|
|
157
|
+
with open("local_file.txt", "rb") as f:
|
|
158
|
+
result = client.upload_file(f, filename="custom_name.txt")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Download File
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# Download to memory
|
|
165
|
+
content = client.download_file("sandbox_file.txt")
|
|
166
|
+
|
|
167
|
+
# Download to local file
|
|
168
|
+
client.download_file("sandbox_file.txt", save_path="local_copy.txt")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Dependency Management
|
|
172
|
+
|
|
173
|
+
#### Get Dependencies
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
deps = client.get_dependencies(language="python3")
|
|
177
|
+
print(deps.dependencies) # List of installed packages
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Update Dependencies
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
response = client.update_dependencies(language="python3")
|
|
184
|
+
print(response.message)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Refresh Dependencies
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
response = client.refresh_dependencies(language="python3")
|
|
191
|
+
print(response.message)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Health Check
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
if client.health_check():
|
|
198
|
+
print("Sandbox is healthy")
|
|
199
|
+
else:
|
|
200
|
+
print("Sandbox is not responding")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Data Models
|
|
204
|
+
|
|
205
|
+
### RunCodeResponse
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
@dataclass
|
|
209
|
+
class RunCodeResponse:
|
|
210
|
+
stdout: str # Standard output from code execution
|
|
211
|
+
stderr: str # Standard error from code execution
|
|
212
|
+
exit_code: int # Exit code (0 = success)
|
|
213
|
+
error: str # Sandbox-side error message (empty on success)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### RunCommandResponse
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
@dataclass
|
|
220
|
+
class RunCommandResponse:
|
|
221
|
+
stdout: str # Standard output from the executed command
|
|
222
|
+
stderr: str # Standard error from the executed command
|
|
223
|
+
exit_code: int # Exit code (0 = success)
|
|
224
|
+
error: str # Sandbox-side error message (empty on success)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### UploadFileResponse
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
@dataclass
|
|
231
|
+
class UploadFileResponse:
|
|
232
|
+
filename: str # Filename in sandbox
|
|
233
|
+
size: int # File size in bytes
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### DependencyInfo
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
@dataclass
|
|
240
|
+
class DependencyInfo:
|
|
241
|
+
language: str # Language (python3/nodejs)
|
|
242
|
+
dependencies: list # List of dependencies
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### DifySandboxResponse
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
@dataclass
|
|
249
|
+
class DifySandboxResponse:
|
|
250
|
+
code: int # Response code (0 = success)
|
|
251
|
+
message: str # Response message
|
|
252
|
+
data: Any # Response data
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## End-to-end Example: Upload a Script, Then Run It
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from dify_sandbox import DifySandboxClient
|
|
259
|
+
|
|
260
|
+
client = DifySandboxClient(base_url="http://localhost:8194", api_key="dify-sandbox")
|
|
261
|
+
|
|
262
|
+
# Local script we want to run inside the sandbox
|
|
263
|
+
script = b"""
|
|
264
|
+
import sys
|
|
265
|
+
print("hello from the sandbox!")
|
|
266
|
+
print("args:", sys.argv[1:])
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
# 1. Upload the script — the server stores it in upload_dir and returns the
|
|
270
|
+
# filename it used (a UUID is appended to avoid collisions).
|
|
271
|
+
with open("hello.py", "wb") as f:
|
|
272
|
+
f.write(script)
|
|
273
|
+
|
|
274
|
+
uploaded = client.upload_file("hello.py")
|
|
275
|
+
print("uploaded as:", uploaded.filename)
|
|
276
|
+
|
|
277
|
+
# 2. Run python3 with the uploaded file as its argument.
|
|
278
|
+
result = client.run_command(
|
|
279
|
+
command="python3",
|
|
280
|
+
args=[uploaded.filename],
|
|
281
|
+
timeout=10,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
assert result.exit_code == 0, result.stderr
|
|
285
|
+
print(result.stdout)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Examples
|
|
289
|
+
|
|
290
|
+
See the `examples/` directory for complete usage examples:
|
|
291
|
+
|
|
292
|
+
- `basic_usage.py` - Basic code execution examples
|
|
293
|
+
- `file_operations.py` - File upload and download examples
|
|
294
|
+
- `dependency_management.py` - Dependency management examples
|
|
295
|
+
- `command_execution.py` - Upload a script and run it through the deny-list-enforced `run_command` endpoint
|
|
296
|
+
|
|
297
|
+
## Error Handling
|
|
298
|
+
|
|
299
|
+
The SDK raises exceptions for API errors:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
try:
|
|
303
|
+
result = client.run_python("invalid code")
|
|
304
|
+
except Exception as e:
|
|
305
|
+
print(f"Error: {e}")
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`run_command` raises the same kind of exception when the deny-list rejects
|
|
309
|
+
the command, when an argument contains shell metacharacters, or when the
|
|
310
|
+
work directory is invalid — the exception message is the human-readable
|
|
311
|
+
reason returned by the sandbox.
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
MIT License
|
|
@@ -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
|
+
]
|