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.
@@ -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
+ ]