python-sandbox-sdk 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,228 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-sandbox-sdk
3
+ Version: 1.0.0
4
+ Summary: Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers
5
+ Home-page: https://github.com/zjwan461/python-sandbox
6
+ Author-email: ITSU Team <826935261@qq.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/zjwan461/python-sandbox
9
+ Project-URL: Repository, https://github.com/zjwan461/python-sandbox
10
+ Project-URL: Documentation, https://github.com/zjwan461/python-sandbox#readme
11
+ Project-URL: Issues, https://github.com/zjwan461/python-sandbox/issues
12
+ Project-URL: Changelog, https://github.com/zjwan461/python-sandbox/blob/main/CHANGELOG.md
13
+ Project-URL: Usage Examples, https://github.com/zjwan461/python-sandbox/tree/main/sdk/python-sandbox-sdk/usage
14
+ Keywords: sandbox,docker,python,security,shell
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: requests>=2.31.0
27
+ Dynamic: home-page
28
+ Dynamic: requires-python
29
+
30
+ # python-sandbox-sdk
31
+
32
+ [![Python](https://img.shields.io/badge/python-≥3.8-blue)](https://www.python.org)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
34
+ [![Docker](https://img.shields.io/badge/docker-required-blue)](https://www.docker.com)
35
+
36
+ Python Sandbox 官方 Python SDK — 在隔离的 Docker 容器中执行 Python 代码、Shell 命令、管理 pip 包、读写文件。
37
+
38
+ 对应后端:[`python-sandbox`](https://github.com/zjwan461/python-sandbox)(Spring Boot 3 + Java 17)。
39
+
40
+ ## ✨ 特性
41
+
42
+ - 🚀 **简洁 API**:10 个方法覆盖会话、代码执行、Shell、pip、文件读写、文件上传下载、健康检查
43
+ - 🔌 **上下文管理器**:自动管理 HTTP 连接与会话生命周期
44
+ - 🛡️ **异常体系**:独立的 `ApiRequestError` / `SandboxError` 类型
45
+ - 🧵 **线程安全**:`requests.Session` 复用,支持 `ThreadPoolExecutor` 并发
46
+ - 🐍 **Python ≥ 3.8**:仅依赖 `requests>=2.31.0`
47
+
48
+ ## 📦 安装
49
+
50
+ ```bash
51
+ # 方式 A:从 PyPI 安装(推荐)
52
+ pip install python-sandbox-sdk
53
+
54
+ # 方式 B:从源码安装
55
+ git clone https://github.com/zjwan461/python-sandbox.git
56
+ cd python-sandbox/sdk/python-sandbox-sdk
57
+ pip install -e .
58
+ ```
59
+
60
+ ## 🚀 快速开始
61
+
62
+ ### 0. 启动后端服务
63
+
64
+ 参考 [`python-sandbox/README.md`](https://github.com/zjwan461/python-sandbox/blob/main/python-sandbox/README.md) 启动:
65
+
66
+ ```bash
67
+ cd python-sandbox
68
+ cp .env.example .env
69
+ docker-compose up -d --build
70
+ curl http://localhost:8080/health
71
+ ```
72
+
73
+ ### 1. 最简示例
74
+
75
+ ```python
76
+ from python_sandbox_sdk import SandboxClient
77
+
78
+ with SandboxClient("http://localhost:8080", "sandbox-secret-key") as client:
79
+ if not client.is_health():
80
+ raise SystemExit("Sandbox not ready")
81
+
82
+ session_id = client.create_session()
83
+ try:
84
+ result = client.exec_python(session_id, "print('Hello from sandbox!')")
85
+ print(result.stdout) # Hello from sandbox!
86
+ print(result.exit_code) # 0
87
+ print(result.success) # True
88
+ finally:
89
+ client.delete_session(session_id)
90
+ ```
91
+
92
+ ### 2. 安装并使用第三方包
93
+
94
+ ```python
95
+ client.pip_install(session_id, "requests==2.31.0")
96
+ result = client.exec_python(session_id, """
97
+ import requests
98
+ print(requests.get('https://httpbin.org/get', timeout=5).status_code)
99
+ """)
100
+ ```
101
+
102
+ ### 3. 文件读写
103
+
104
+ ```python
105
+ # 文本
106
+ client.write_file(session_id, "/tmp/config.json", '{"key": "value"}')
107
+ content = client.read_file(session_id, "/tmp/config.json")
108
+
109
+ # 二进制
110
+ client.upload_file(session_id, "/tmp/image.png", b"\\x89PNG...")
111
+ data = client.download_file(session_id, "/tmp/image.png")
112
+ ```
113
+
114
+ ## 📚 使用样例(覆盖全部 API 与典型场景)
115
+
116
+ 完整可运行样例见 [`usage/`](usage/) 目录:
117
+
118
+ | 编号 | 场景 | 覆盖 API |
119
+ |---|---|---|
120
+ | [`01_hello_world.py`](usage/01_hello_world.py) | 入门:连通性 + Hello World | `is_health`, `create_session`, `exec_python`, `delete_session` |
121
+ | [`02_session_management.py`](usage/02_session_management.py) | 会话管理:独立 / 复用 / 异常清理 | `create_session`, `delete_session` |
122
+ | [`03_python_execution.py`](usage/03_python_execution.py) | Python 执行:语法 / 异常 / 状态 / 长任务 | `exec_python` |
123
+ | [`04_shell_execution.py`](usage/04_shell_execution.py) | Shell 执行:基础 + 黑名单 + 安全路径 | `exec_shell` |
124
+ | [`05_pip_packages.py`](usage/05_pip_packages.py) | pip 管理:安装 / 卸载 / 列表 / 版本约束 | `pip_install`, `pip_uninstall`, `pip_list` |
125
+ | [`06_text_file_ops.py`](usage/06_text_file_ops.py) | 文本文件:JSON / CSV / 中文 / 日志 | `write_file`, `read_file` |
126
+ | [`07_binary_file_ops.py`](usage/07_binary_file_ops.py) | 二进制:图片缩略图 + numpy 序列化 | `upload_file`, `download_file` |
127
+ | [`08_data_analysis.py`](usage/08_data_analysis.py) | 数据分析实战:requests + pandas + matplotlib | 综合 |
128
+ | [`09_long_running.py`](usage/09_long_running.py) | 长任务:进度回报 + 异常安全 | `exec_python`, `exec_shell` |
129
+ | [`10_concurrent_sessions.py`](usage/10_concurrent_sessions.py) | 并发:`ThreadPoolExecutor` + 上限约束 | 多会话并行 |
130
+ | [`11_error_handling.py`](usage/11_error_handling.py) | 错误全景:鉴权 / 会话 / 黑名单 / 网络 | 全 API 错误路径 |
131
+
132
+ 运行样例:
133
+
134
+ ```bash
135
+ cd usage/
136
+ export SANDBOX_API_KEY="sandbox-secret-key"
137
+
138
+ python 01_hello_world.py
139
+ for f in 0[1-9]_*.py 1[0-1]_*.py; do python "$f"; done
140
+ ```
141
+
142
+ ## 🔧 API 速查
143
+
144
+ | 方法 | 返回 | 说明 |
145
+ |---|---|---|
146
+ | `is_health()` | `bool` | 健康检查(无需 API Key) |
147
+ | `create_session()` | `str` | 创建沙箱会话 |
148
+ | `delete_session(session_id)` | `None` | 删除会话并清理容器 |
149
+ | `exec_python(session_id, code)` | `CommandResult` | 执行 Python 代码 |
150
+ | `exec_shell(session_id, command)` | `CommandResult` | 执行 Shell 命令 |
151
+ | `pip_install(session_id, pkg)` | `CommandResult` | 安装 pip 包(支持版本约束) |
152
+ | `pip_uninstall(session_id, pkg)` | `CommandResult` | 卸载 pip 包 |
153
+ | `pip_list(session_id)` | `str` | 列出已安装包 |
154
+ | `write_file(session_id, path, content)` | `None` | 写文本文件 |
155
+ | `read_file(session_id, path)` | `str` | 读文本文件 |
156
+ | `upload_file(session_id, path, data)` | `None` | 上传二进制文件 |
157
+ | `download_file(session_id, path)` | `bytes` | 下载文件 |
158
+
159
+ `CommandResult` 字段:`exit_code`, `stdout`, `stderr`, `success`(property), `combined_output`(property)。
160
+
161
+ ## ⚙️ 环境变量
162
+
163
+ 为避免硬编码,样例通过环境变量注入连接信息:
164
+
165
+ | 变量 | 默认值 | 说明 |
166
+ |---|---|---|
167
+ | `SANDBOX_BASE_URL` | `http://localhost:8080` | Sandbox 服务地址 |
168
+ | `SANDBOX_API_KEY` | `sandbox-secret-key` | API 鉴权密钥(与后端 `SANDBOX_API_KEY` 保持一致) |
169
+
170
+ ## 🛡️ 异常处理
171
+
172
+ ```python
173
+ from python_sandbox_sdk import SandboxClient, ApiRequestError, SandboxError
174
+
175
+ try:
176
+ client.exec_python(session_id, "print(1)")
177
+ except ApiRequestError as e:
178
+ # 后端返回的 4xx / 5xx(鉴权失败、会话不存在、黑名单、容器上限等)
179
+ print(f"API Error: HTTP {e.status_code} - {e}")
180
+ except SandboxError as e:
181
+ # SDK 抛出的通用错误
182
+ print(f"SDK Error: {e}")
183
+ except OSError as e:
184
+ # 网络错误(连接失败、超时等)
185
+ print(f"Network Error: {e}")
186
+ ```
187
+
188
+ ## 🧵 并发使用
189
+
190
+ ```python
191
+ from concurrent.futures import ThreadPoolExecutor
192
+ # 每个任务使用独立的 client + session(线程安全)
193
+ with ThreadPoolExecutor(max_workers=5) as pool:
194
+ futures = [pool.submit(run_one_task, i) for i in range(5)]
195
+ for f in futures:
196
+ print(f.result())
197
+ ```
198
+
199
+ > ⚠️ 并发数受后端 `sandbox.max-containers`(默认 10)约束。
200
+
201
+ ## 📦 打包发布
202
+
203
+ ```bash
204
+ # 安装构建工具
205
+ pip install build twine
206
+
207
+ # 构建
208
+ cd sdk/python-sandbox-sdk
209
+ python -m build
210
+
211
+ # 上传到 TestPyPI
212
+ twine upload --repository testpypi dist/*
213
+
214
+ # 上传到 PyPI
215
+ twine upload dist/*
216
+ ```
217
+
218
+ 发布前请修改 `pyproject.toml` 中的 `version` 与 `urls` 字段。
219
+
220
+ ## 🔗 相关项目
221
+
222
+ - 后端服务:[`python-sandbox/`](https://github.com/zjwan461/python-sandbox)
223
+ - Java SDK:[`sdk/java-sandbox-sdk/`](https://github.com/zjwan461/python-sandbox/tree/main/sdk/java-sandbox-sdk)
224
+ - SDK 总览:[`sdk/README.md`](https://github.com/zjwan461/python-sandbox/tree/main/sdk)
225
+
226
+ ## 📄 许可证
227
+
228
+ MIT License
@@ -0,0 +1,199 @@
1
+ # python-sandbox-sdk
2
+
3
+ [![Python](https://img.shields.io/badge/python-≥3.8-blue)](https://www.python.org)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
5
+ [![Docker](https://img.shields.io/badge/docker-required-blue)](https://www.docker.com)
6
+
7
+ Python Sandbox 官方 Python SDK — 在隔离的 Docker 容器中执行 Python 代码、Shell 命令、管理 pip 包、读写文件。
8
+
9
+ 对应后端:[`python-sandbox`](https://github.com/zjwan461/python-sandbox)(Spring Boot 3 + Java 17)。
10
+
11
+ ## ✨ 特性
12
+
13
+ - 🚀 **简洁 API**:10 个方法覆盖会话、代码执行、Shell、pip、文件读写、文件上传下载、健康检查
14
+ - 🔌 **上下文管理器**:自动管理 HTTP 连接与会话生命周期
15
+ - 🛡️ **异常体系**:独立的 `ApiRequestError` / `SandboxError` 类型
16
+ - 🧵 **线程安全**:`requests.Session` 复用,支持 `ThreadPoolExecutor` 并发
17
+ - 🐍 **Python ≥ 3.8**:仅依赖 `requests>=2.31.0`
18
+
19
+ ## 📦 安装
20
+
21
+ ```bash
22
+ # 方式 A:从 PyPI 安装(推荐)
23
+ pip install python-sandbox-sdk
24
+
25
+ # 方式 B:从源码安装
26
+ git clone https://github.com/zjwan461/python-sandbox.git
27
+ cd python-sandbox/sdk/python-sandbox-sdk
28
+ pip install -e .
29
+ ```
30
+
31
+ ## 🚀 快速开始
32
+
33
+ ### 0. 启动后端服务
34
+
35
+ 参考 [`python-sandbox/README.md`](https://github.com/zjwan461/python-sandbox/blob/main/python-sandbox/README.md) 启动:
36
+
37
+ ```bash
38
+ cd python-sandbox
39
+ cp .env.example .env
40
+ docker-compose up -d --build
41
+ curl http://localhost:8080/health
42
+ ```
43
+
44
+ ### 1. 最简示例
45
+
46
+ ```python
47
+ from python_sandbox_sdk import SandboxClient
48
+
49
+ with SandboxClient("http://localhost:8080", "sandbox-secret-key") as client:
50
+ if not client.is_health():
51
+ raise SystemExit("Sandbox not ready")
52
+
53
+ session_id = client.create_session()
54
+ try:
55
+ result = client.exec_python(session_id, "print('Hello from sandbox!')")
56
+ print(result.stdout) # Hello from sandbox!
57
+ print(result.exit_code) # 0
58
+ print(result.success) # True
59
+ finally:
60
+ client.delete_session(session_id)
61
+ ```
62
+
63
+ ### 2. 安装并使用第三方包
64
+
65
+ ```python
66
+ client.pip_install(session_id, "requests==2.31.0")
67
+ result = client.exec_python(session_id, """
68
+ import requests
69
+ print(requests.get('https://httpbin.org/get', timeout=5).status_code)
70
+ """)
71
+ ```
72
+
73
+ ### 3. 文件读写
74
+
75
+ ```python
76
+ # 文本
77
+ client.write_file(session_id, "/tmp/config.json", '{"key": "value"}')
78
+ content = client.read_file(session_id, "/tmp/config.json")
79
+
80
+ # 二进制
81
+ client.upload_file(session_id, "/tmp/image.png", b"\\x89PNG...")
82
+ data = client.download_file(session_id, "/tmp/image.png")
83
+ ```
84
+
85
+ ## 📚 使用样例(覆盖全部 API 与典型场景)
86
+
87
+ 完整可运行样例见 [`usage/`](usage/) 目录:
88
+
89
+ | 编号 | 场景 | 覆盖 API |
90
+ |---|---|---|
91
+ | [`01_hello_world.py`](usage/01_hello_world.py) | 入门:连通性 + Hello World | `is_health`, `create_session`, `exec_python`, `delete_session` |
92
+ | [`02_session_management.py`](usage/02_session_management.py) | 会话管理:独立 / 复用 / 异常清理 | `create_session`, `delete_session` |
93
+ | [`03_python_execution.py`](usage/03_python_execution.py) | Python 执行:语法 / 异常 / 状态 / 长任务 | `exec_python` |
94
+ | [`04_shell_execution.py`](usage/04_shell_execution.py) | Shell 执行:基础 + 黑名单 + 安全路径 | `exec_shell` |
95
+ | [`05_pip_packages.py`](usage/05_pip_packages.py) | pip 管理:安装 / 卸载 / 列表 / 版本约束 | `pip_install`, `pip_uninstall`, `pip_list` |
96
+ | [`06_text_file_ops.py`](usage/06_text_file_ops.py) | 文本文件:JSON / CSV / 中文 / 日志 | `write_file`, `read_file` |
97
+ | [`07_binary_file_ops.py`](usage/07_binary_file_ops.py) | 二进制:图片缩略图 + numpy 序列化 | `upload_file`, `download_file` |
98
+ | [`08_data_analysis.py`](usage/08_data_analysis.py) | 数据分析实战:requests + pandas + matplotlib | 综合 |
99
+ | [`09_long_running.py`](usage/09_long_running.py) | 长任务:进度回报 + 异常安全 | `exec_python`, `exec_shell` |
100
+ | [`10_concurrent_sessions.py`](usage/10_concurrent_sessions.py) | 并发:`ThreadPoolExecutor` + 上限约束 | 多会话并行 |
101
+ | [`11_error_handling.py`](usage/11_error_handling.py) | 错误全景:鉴权 / 会话 / 黑名单 / 网络 | 全 API 错误路径 |
102
+
103
+ 运行样例:
104
+
105
+ ```bash
106
+ cd usage/
107
+ export SANDBOX_API_KEY="sandbox-secret-key"
108
+
109
+ python 01_hello_world.py
110
+ for f in 0[1-9]_*.py 1[0-1]_*.py; do python "$f"; done
111
+ ```
112
+
113
+ ## 🔧 API 速查
114
+
115
+ | 方法 | 返回 | 说明 |
116
+ |---|---|---|
117
+ | `is_health()` | `bool` | 健康检查(无需 API Key) |
118
+ | `create_session()` | `str` | 创建沙箱会话 |
119
+ | `delete_session(session_id)` | `None` | 删除会话并清理容器 |
120
+ | `exec_python(session_id, code)` | `CommandResult` | 执行 Python 代码 |
121
+ | `exec_shell(session_id, command)` | `CommandResult` | 执行 Shell 命令 |
122
+ | `pip_install(session_id, pkg)` | `CommandResult` | 安装 pip 包(支持版本约束) |
123
+ | `pip_uninstall(session_id, pkg)` | `CommandResult` | 卸载 pip 包 |
124
+ | `pip_list(session_id)` | `str` | 列出已安装包 |
125
+ | `write_file(session_id, path, content)` | `None` | 写文本文件 |
126
+ | `read_file(session_id, path)` | `str` | 读文本文件 |
127
+ | `upload_file(session_id, path, data)` | `None` | 上传二进制文件 |
128
+ | `download_file(session_id, path)` | `bytes` | 下载文件 |
129
+
130
+ `CommandResult` 字段:`exit_code`, `stdout`, `stderr`, `success`(property), `combined_output`(property)。
131
+
132
+ ## ⚙️ 环境变量
133
+
134
+ 为避免硬编码,样例通过环境变量注入连接信息:
135
+
136
+ | 变量 | 默认值 | 说明 |
137
+ |---|---|---|
138
+ | `SANDBOX_BASE_URL` | `http://localhost:8080` | Sandbox 服务地址 |
139
+ | `SANDBOX_API_KEY` | `sandbox-secret-key` | API 鉴权密钥(与后端 `SANDBOX_API_KEY` 保持一致) |
140
+
141
+ ## 🛡️ 异常处理
142
+
143
+ ```python
144
+ from python_sandbox_sdk import SandboxClient, ApiRequestError, SandboxError
145
+
146
+ try:
147
+ client.exec_python(session_id, "print(1)")
148
+ except ApiRequestError as e:
149
+ # 后端返回的 4xx / 5xx(鉴权失败、会话不存在、黑名单、容器上限等)
150
+ print(f"API Error: HTTP {e.status_code} - {e}")
151
+ except SandboxError as e:
152
+ # SDK 抛出的通用错误
153
+ print(f"SDK Error: {e}")
154
+ except OSError as e:
155
+ # 网络错误(连接失败、超时等)
156
+ print(f"Network Error: {e}")
157
+ ```
158
+
159
+ ## 🧵 并发使用
160
+
161
+ ```python
162
+ from concurrent.futures import ThreadPoolExecutor
163
+ # 每个任务使用独立的 client + session(线程安全)
164
+ with ThreadPoolExecutor(max_workers=5) as pool:
165
+ futures = [pool.submit(run_one_task, i) for i in range(5)]
166
+ for f in futures:
167
+ print(f.result())
168
+ ```
169
+
170
+ > ⚠️ 并发数受后端 `sandbox.max-containers`(默认 10)约束。
171
+
172
+ ## 📦 打包发布
173
+
174
+ ```bash
175
+ # 安装构建工具
176
+ pip install build twine
177
+
178
+ # 构建
179
+ cd sdk/python-sandbox-sdk
180
+ python -m build
181
+
182
+ # 上传到 TestPyPI
183
+ twine upload --repository testpypi dist/*
184
+
185
+ # 上传到 PyPI
186
+ twine upload dist/*
187
+ ```
188
+
189
+ 发布前请修改 `pyproject.toml` 中的 `version` 与 `urls` 字段。
190
+
191
+ ## 🔗 相关项目
192
+
193
+ - 后端服务:[`python-sandbox/`](https://github.com/zjwan461/python-sandbox)
194
+ - Java SDK:[`sdk/java-sandbox-sdk/`](https://github.com/zjwan461/python-sandbox/tree/main/sdk/java-sandbox-sdk)
195
+ - SDK 总览:[`sdk/README.md`](https://github.com/zjwan461/python-sandbox/tree/main/sdk)
196
+
197
+ ## 📄 许可证
198
+
199
+ MIT License
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python-sandbox-sdk"
7
+ version = "1.0.0"
8
+ description = "Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "ITSU Team", email = "826935261@qq.com"}
14
+ ]
15
+ keywords = ["sandbox", "docker", "python", "security", "shell"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+
28
+ dependencies = [
29
+ "requests>=2.31.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/zjwan461/python-sandbox"
34
+ Repository = "https://github.com/zjwan461/python-sandbox"
35
+ Documentation = "https://github.com/zjwan461/python-sandbox#readme"
36
+ Issues = "https://github.com/zjwan461/python-sandbox/issues"
37
+ Changelog = "https://github.com/zjwan461/python-sandbox/blob/main/CHANGELOG.md"
38
+ "Usage Examples" = "https://github.com/zjwan461/python-sandbox/tree/main/sdk/python-sandbox-sdk/usage"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["."]
42
+ include = ["python_sandbox_sdk*"]
@@ -0,0 +1,12 @@
1
+ from .client import SandboxClient, SandboxError, ApiKeyMissingError, ApiRequestError
2
+ from .dto import CommandResult, SessionResponse
3
+
4
+ __all__ = [
5
+ 'SandboxClient',
6
+ 'CommandResult',
7
+ 'SessionResponse',
8
+ 'SandboxError',
9
+ 'ApiKeyMissingError',
10
+ 'ApiRequestError',
11
+ ]
12
+ __version__ = '1.0.0'
@@ -0,0 +1,319 @@
1
+ """
2
+ Python Sandbox SDK
3
+ ==================
4
+
5
+ 提供简洁的 API 客户端以与 Python Sandbox 服务交互。
6
+
7
+ 使用示例:
8
+ >>> from python_sandbox_sdk import SandboxClient
9
+ >>> client = SandboxClient("http://localhost:8080", "your-api-key")
10
+ >>> session_id = client.create_session()
11
+ >>> result = client.exec_python(session_id, "print('Hello!')")
12
+ >>> print(result.stdout)
13
+ Hello!
14
+ """
15
+
16
+ import requests
17
+
18
+ from .dto import CommandResult
19
+
20
+
21
+ class SandboxError(Exception):
22
+ """SDK 异常基类"""
23
+ pass
24
+
25
+
26
+ class ApiKeyMissingError(SandboxError):
27
+ """API Key 缺失错误"""
28
+ pass
29
+
30
+
31
+ class ApiRequestError(SandboxError):
32
+ """API 请求错误"""
33
+ def __init__(self, status_code: int, message: str):
34
+ self.status_code = status_code
35
+ super().__init__(f"HTTP {status_code}: {message}")
36
+
37
+
38
+ class SandboxClient:
39
+ """
40
+ Python Sandbox 客户端
41
+
42
+ Attributes:
43
+ base_url: Sandbox API 基础 URL (如 http://localhost:8080)
44
+ api_key: API 认证密钥
45
+ """
46
+
47
+ def __init__(self, base_url: str, api_key: str):
48
+ """
49
+ 创建客户端实例
50
+
51
+ Args:
52
+ base_url: Sandbox API 基础 URL
53
+ api_key: API 认证密钥
54
+ """
55
+ self.base_url = base_url.rstrip("/")
56
+ self.api_key = api_key
57
+ self._session = requests.Session()
58
+ self._session.headers.update({
59
+ "X-Api-Key": api_key,
60
+ "Content-Type": "application/json"
61
+ })
62
+
63
+ # ==================== 会话管理 ====================
64
+
65
+ def create_session(self) -> str:
66
+ """
67
+ 创建新的沙箱会话
68
+
69
+ Returns:
70
+ 会话 ID
71
+ """
72
+ resp = self._session.post(f"{self.base_url}/api/sandbox/session")
73
+ self._raise_if_error(resp)
74
+ return resp.json()["sessionId"]
75
+
76
+ def delete_session(self, session_id: str) -> None:
77
+ """
78
+ 删除会话并清理容器
79
+
80
+ Args:
81
+ session_id: 会话 ID
82
+ """
83
+ url = f"{self.base_url}/api/sandbox/session/{session_id}"
84
+ resp = self._session.delete(url)
85
+ self._raise_if_error(resp)
86
+
87
+ # ==================== 代码执行 ====================
88
+
89
+ def exec_python(self, session_id: str, code: str) -> CommandResult:
90
+ """
91
+ 在沙箱中执行 Python 代码
92
+
93
+ Args:
94
+ session_id: 会话 ID
95
+ code: Python 源代码
96
+
97
+ Returns:
98
+ 命令执行结果
99
+ """
100
+ payload = {"sessionId": session_id, "code": code}
101
+ resp = self._session.post(
102
+ f"{self.base_url}/api/sandbox/exec/python",
103
+ json=payload
104
+ )
105
+ self._raise_if_error(resp)
106
+ data = resp.json()
107
+ return CommandResult(data["exitCode"], data.get("stdout", ""), data.get("stderr", ""))
108
+
109
+ def exec_shell(self, session_id: str, command: str) -> CommandResult:
110
+ """
111
+ 在沙箱中执行 Shell 命令
112
+
113
+ Args:
114
+ session_id: 会话 ID
115
+ command: Shell 命令
116
+
117
+ Returns:
118
+ 命令执行结果
119
+ """
120
+ payload = {"sessionId": session_id, "command": command}
121
+ resp = self._session.post(
122
+ f"{self.base_url}/api/sandbox/exec/shell",
123
+ json=payload
124
+ )
125
+ self._raise_if_error(resp)
126
+ data = resp.json()
127
+ return CommandResult(data["exitCode"], data.get("stdout", ""), data.get("stderr", ""))
128
+
129
+ # ==================== pip 包管理 ====================
130
+
131
+ def pip_install(self, session_id: str, package_name: str) -> CommandResult:
132
+ """
133
+ 安装 Python 包
134
+
135
+ Args:
136
+ session_id: 会话 ID
137
+ package_name: 包名(支持版本约束)
138
+
139
+ Returns:
140
+ 命令执行结果
141
+ """
142
+ payload = {"sessionId": session_id, "pkg": package_name}
143
+ resp = self._session.post(
144
+ f"{self.base_url}/api/sandbox/pip/install",
145
+ json=payload
146
+ )
147
+ self._raise_if_error(resp)
148
+ data = resp.json()
149
+ return CommandResult(data["exitCode"], data.get("stdout", ""), data.get("stderr", ""))
150
+
151
+ def pip_uninstall(self, session_id: str, package_name: str) -> CommandResult:
152
+ """
153
+ 卸载 Python 包
154
+
155
+ Args:
156
+ session_id: 会话 ID
157
+ package_name: 包名
158
+
159
+ Returns:
160
+ 命令执行结果
161
+ """
162
+ payload = {"sessionId": session_id, "pkg": package_name}
163
+ resp = self._session.post(
164
+ f"{self.base_url}/api/sandbox/pip/uninstall",
165
+ json=payload
166
+ )
167
+ self._raise_if_error(resp)
168
+ data = resp.json()
169
+ return CommandResult(data["exitCode"], data.get("stdout", ""), data.get("stderr", ""))
170
+
171
+ def pip_list(self, session_id: str) -> str:
172
+ """
173
+ 列出已安装的 Python 包
174
+
175
+ Args:
176
+ session_id: 会话 ID
177
+
178
+ Returns:
179
+ 安装包列表文本
180
+ """
181
+ resp = self._session.get(
182
+ f"{self.base_url}/api/sandbox/pip/list",
183
+ params={"sessionId": session_id}
184
+ )
185
+ self._raise_if_error(resp)
186
+ return resp.json()["packages"]
187
+
188
+ # ==================== 文件操作 ====================
189
+
190
+ def write_file(self, session_id: str, container_path: str, content: str) -> None:
191
+ """
192
+ 向沙箱写入文件内容
193
+
194
+ Args:
195
+ session_id: 会话 ID
196
+ container_path: 容器内目标路径
197
+ content: 文件内容
198
+ """
199
+ payload = {
200
+ "sessionId": session_id,
201
+ "path": container_path,
202
+ "content": content
203
+ }
204
+ resp = self._session.post(
205
+ f"{self.base_url}/api/sandbox/file/write",
206
+ json=payload
207
+ )
208
+ self._raise_if_error(resp)
209
+
210
+ def read_file(self, session_id: str, container_path: str) -> str:
211
+ """
212
+ 读取沙箱中的文件内容
213
+
214
+ Args:
215
+ session_id: 会话 ID
216
+ container_path: 容器内文件路径
217
+
218
+ Returns:
219
+ 文件内容字符串
220
+ """
221
+ resp = self._session.get(
222
+ f"{self.base_url}/api/sandbox/file/read",
223
+ params={"sessionId": session_id, "path": container_path}
224
+ )
225
+ self._raise_if_error(resp)
226
+ return resp.json()["content"]
227
+
228
+ def upload_file(self, session_id: str, container_path: str, data: bytes) -> None:
229
+ """
230
+ 上传二进制文件到沙箱
231
+
232
+ Args:
233
+ session_id: 会话 ID
234
+ container_path: 容器内目标路径
235
+ data: 文件字节数据
236
+ """
237
+ import tempfile
238
+ import os
239
+
240
+ # 写入临时文件
241
+ fd, tmp_path = tempfile.mkstemp(prefix="sandbox_upload_")
242
+ try:
243
+ # os.fdopen 接管了 fd 的所有权,with 块结束时会自动关闭 fd
244
+ with os.fdopen(fd, 'wb') as f:
245
+ f.write(data)
246
+
247
+ with open(tmp_path, 'rb') as f:
248
+ files = {'file': (os.path.basename(container_path), f)}
249
+ data_params = {'sessionId': session_id, 'path': container_path}
250
+
251
+ # 发送 multipart 请求(requests 会自动设置正确的 Content-Type)
252
+ url = f"{self.base_url}/api/sandbox/file/upload"
253
+ # 使用新的 session 避免继承主 session 的 Content-Type: application/json
254
+ with requests.Session() as upload_session:
255
+ upload_session.headers.update({"X-Api-Key": self.api_key})
256
+ resp = upload_session.post(url, files=files, data=data_params, timeout=120)
257
+ self._raise_if_error(resp)
258
+ finally:
259
+ if os.path.exists(tmp_path):
260
+ os.unlink(tmp_path)
261
+
262
+ def download_file(self, session_id: str, container_path: str) -> bytes:
263
+ """
264
+ 下载沙箱中的文件
265
+
266
+ Args:
267
+ session_id: 会话 ID
268
+ container_path: 容器内文件路径
269
+
270
+ Returns:
271
+ 文件字节数据
272
+ """
273
+ url = f"{self.base_url}/api/sandbox/file/download"
274
+ params = {"sessionId": session_id, "path": container_path}
275
+
276
+ # 使用新的 session 避免继承主 session 的 Content-Type: application/json
277
+ with requests.Session() as s:
278
+ s.headers.update({"X-Api-Key": self.api_key})
279
+ resp = s.get(url, params=params, timeout=60)
280
+ self._raise_if_error(resp)
281
+ return resp.content
282
+
283
+ # ==================== 健康检查 ====================
284
+
285
+ def is_health(self) -> bool:
286
+ """
287
+ 检查沙箱服务是否可用
288
+
289
+ Returns:
290
+ True 如果服务正常运行
291
+ """
292
+ try:
293
+ resp = requests.get(f"{self.base_url}/health", timeout=5)
294
+ return resp.status_code == 200
295
+ except Exception:
296
+ return False
297
+
298
+ # ==================== 内部方法 ====================
299
+
300
+ @staticmethod
301
+ def _raise_if_error(resp: requests.Response) -> None:
302
+ """如果响应状态码表示错误则抛出异常"""
303
+ if resp.status_code >= 400:
304
+ try:
305
+ error_data = resp.json()
306
+ message = error_data.get("message", resp.text)
307
+ except Exception:
308
+ message = resp.text
309
+ raise ApiRequestError(resp.status_code, message)
310
+
311
+ def close(self) -> None:
312
+ """关闭 HTTP 会话"""
313
+ self._session.close()
314
+
315
+ def __enter__(self):
316
+ return self
317
+
318
+ def __exit__(self, exc_type, exc_val, exc_tb):
319
+ self.close()
@@ -0,0 +1,32 @@
1
+ """
2
+ 数据传输对象定义
3
+ """
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass
9
+ class CommandResult:
10
+ """命令执行结果"""
11
+ exit_code: int
12
+ stdout: str = ""
13
+ stderr: str = ""
14
+
15
+ @property
16
+ def success(self) -> bool:
17
+ """是否执行成功"""
18
+ return self.exit_code == 0
19
+
20
+ @property
21
+ def combined_output(self) -> str:
22
+ """合并输出"""
23
+ if not self.stderr:
24
+ return self.stdout
25
+ return f"{self.stdout}\n{self.stderr}"
26
+
27
+
28
+ @dataclass
29
+ class SessionResponse:
30
+ """会话响应"""
31
+ session_id: str
32
+ message: Optional[str] = None
@@ -0,0 +1,228 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-sandbox-sdk
3
+ Version: 1.0.0
4
+ Summary: Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers
5
+ Home-page: https://github.com/zjwan461/python-sandbox
6
+ Author-email: ITSU Team <826935261@qq.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/zjwan461/python-sandbox
9
+ Project-URL: Repository, https://github.com/zjwan461/python-sandbox
10
+ Project-URL: Documentation, https://github.com/zjwan461/python-sandbox#readme
11
+ Project-URL: Issues, https://github.com/zjwan461/python-sandbox/issues
12
+ Project-URL: Changelog, https://github.com/zjwan461/python-sandbox/blob/main/CHANGELOG.md
13
+ Project-URL: Usage Examples, https://github.com/zjwan461/python-sandbox/tree/main/sdk/python-sandbox-sdk/usage
14
+ Keywords: sandbox,docker,python,security,shell
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: requests>=2.31.0
27
+ Dynamic: home-page
28
+ Dynamic: requires-python
29
+
30
+ # python-sandbox-sdk
31
+
32
+ [![Python](https://img.shields.io/badge/python-≥3.8-blue)](https://www.python.org)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
34
+ [![Docker](https://img.shields.io/badge/docker-required-blue)](https://www.docker.com)
35
+
36
+ Python Sandbox 官方 Python SDK — 在隔离的 Docker 容器中执行 Python 代码、Shell 命令、管理 pip 包、读写文件。
37
+
38
+ 对应后端:[`python-sandbox`](https://github.com/zjwan461/python-sandbox)(Spring Boot 3 + Java 17)。
39
+
40
+ ## ✨ 特性
41
+
42
+ - 🚀 **简洁 API**:10 个方法覆盖会话、代码执行、Shell、pip、文件读写、文件上传下载、健康检查
43
+ - 🔌 **上下文管理器**:自动管理 HTTP 连接与会话生命周期
44
+ - 🛡️ **异常体系**:独立的 `ApiRequestError` / `SandboxError` 类型
45
+ - 🧵 **线程安全**:`requests.Session` 复用,支持 `ThreadPoolExecutor` 并发
46
+ - 🐍 **Python ≥ 3.8**:仅依赖 `requests>=2.31.0`
47
+
48
+ ## 📦 安装
49
+
50
+ ```bash
51
+ # 方式 A:从 PyPI 安装(推荐)
52
+ pip install python-sandbox-sdk
53
+
54
+ # 方式 B:从源码安装
55
+ git clone https://github.com/zjwan461/python-sandbox.git
56
+ cd python-sandbox/sdk/python-sandbox-sdk
57
+ pip install -e .
58
+ ```
59
+
60
+ ## 🚀 快速开始
61
+
62
+ ### 0. 启动后端服务
63
+
64
+ 参考 [`python-sandbox/README.md`](https://github.com/zjwan461/python-sandbox/blob/main/python-sandbox/README.md) 启动:
65
+
66
+ ```bash
67
+ cd python-sandbox
68
+ cp .env.example .env
69
+ docker-compose up -d --build
70
+ curl http://localhost:8080/health
71
+ ```
72
+
73
+ ### 1. 最简示例
74
+
75
+ ```python
76
+ from python_sandbox_sdk import SandboxClient
77
+
78
+ with SandboxClient("http://localhost:8080", "sandbox-secret-key") as client:
79
+ if not client.is_health():
80
+ raise SystemExit("Sandbox not ready")
81
+
82
+ session_id = client.create_session()
83
+ try:
84
+ result = client.exec_python(session_id, "print('Hello from sandbox!')")
85
+ print(result.stdout) # Hello from sandbox!
86
+ print(result.exit_code) # 0
87
+ print(result.success) # True
88
+ finally:
89
+ client.delete_session(session_id)
90
+ ```
91
+
92
+ ### 2. 安装并使用第三方包
93
+
94
+ ```python
95
+ client.pip_install(session_id, "requests==2.31.0")
96
+ result = client.exec_python(session_id, """
97
+ import requests
98
+ print(requests.get('https://httpbin.org/get', timeout=5).status_code)
99
+ """)
100
+ ```
101
+
102
+ ### 3. 文件读写
103
+
104
+ ```python
105
+ # 文本
106
+ client.write_file(session_id, "/tmp/config.json", '{"key": "value"}')
107
+ content = client.read_file(session_id, "/tmp/config.json")
108
+
109
+ # 二进制
110
+ client.upload_file(session_id, "/tmp/image.png", b"\\x89PNG...")
111
+ data = client.download_file(session_id, "/tmp/image.png")
112
+ ```
113
+
114
+ ## 📚 使用样例(覆盖全部 API 与典型场景)
115
+
116
+ 完整可运行样例见 [`usage/`](usage/) 目录:
117
+
118
+ | 编号 | 场景 | 覆盖 API |
119
+ |---|---|---|
120
+ | [`01_hello_world.py`](usage/01_hello_world.py) | 入门:连通性 + Hello World | `is_health`, `create_session`, `exec_python`, `delete_session` |
121
+ | [`02_session_management.py`](usage/02_session_management.py) | 会话管理:独立 / 复用 / 异常清理 | `create_session`, `delete_session` |
122
+ | [`03_python_execution.py`](usage/03_python_execution.py) | Python 执行:语法 / 异常 / 状态 / 长任务 | `exec_python` |
123
+ | [`04_shell_execution.py`](usage/04_shell_execution.py) | Shell 执行:基础 + 黑名单 + 安全路径 | `exec_shell` |
124
+ | [`05_pip_packages.py`](usage/05_pip_packages.py) | pip 管理:安装 / 卸载 / 列表 / 版本约束 | `pip_install`, `pip_uninstall`, `pip_list` |
125
+ | [`06_text_file_ops.py`](usage/06_text_file_ops.py) | 文本文件:JSON / CSV / 中文 / 日志 | `write_file`, `read_file` |
126
+ | [`07_binary_file_ops.py`](usage/07_binary_file_ops.py) | 二进制:图片缩略图 + numpy 序列化 | `upload_file`, `download_file` |
127
+ | [`08_data_analysis.py`](usage/08_data_analysis.py) | 数据分析实战:requests + pandas + matplotlib | 综合 |
128
+ | [`09_long_running.py`](usage/09_long_running.py) | 长任务:进度回报 + 异常安全 | `exec_python`, `exec_shell` |
129
+ | [`10_concurrent_sessions.py`](usage/10_concurrent_sessions.py) | 并发:`ThreadPoolExecutor` + 上限约束 | 多会话并行 |
130
+ | [`11_error_handling.py`](usage/11_error_handling.py) | 错误全景:鉴权 / 会话 / 黑名单 / 网络 | 全 API 错误路径 |
131
+
132
+ 运行样例:
133
+
134
+ ```bash
135
+ cd usage/
136
+ export SANDBOX_API_KEY="sandbox-secret-key"
137
+
138
+ python 01_hello_world.py
139
+ for f in 0[1-9]_*.py 1[0-1]_*.py; do python "$f"; done
140
+ ```
141
+
142
+ ## 🔧 API 速查
143
+
144
+ | 方法 | 返回 | 说明 |
145
+ |---|---|---|
146
+ | `is_health()` | `bool` | 健康检查(无需 API Key) |
147
+ | `create_session()` | `str` | 创建沙箱会话 |
148
+ | `delete_session(session_id)` | `None` | 删除会话并清理容器 |
149
+ | `exec_python(session_id, code)` | `CommandResult` | 执行 Python 代码 |
150
+ | `exec_shell(session_id, command)` | `CommandResult` | 执行 Shell 命令 |
151
+ | `pip_install(session_id, pkg)` | `CommandResult` | 安装 pip 包(支持版本约束) |
152
+ | `pip_uninstall(session_id, pkg)` | `CommandResult` | 卸载 pip 包 |
153
+ | `pip_list(session_id)` | `str` | 列出已安装包 |
154
+ | `write_file(session_id, path, content)` | `None` | 写文本文件 |
155
+ | `read_file(session_id, path)` | `str` | 读文本文件 |
156
+ | `upload_file(session_id, path, data)` | `None` | 上传二进制文件 |
157
+ | `download_file(session_id, path)` | `bytes` | 下载文件 |
158
+
159
+ `CommandResult` 字段:`exit_code`, `stdout`, `stderr`, `success`(property), `combined_output`(property)。
160
+
161
+ ## ⚙️ 环境变量
162
+
163
+ 为避免硬编码,样例通过环境变量注入连接信息:
164
+
165
+ | 变量 | 默认值 | 说明 |
166
+ |---|---|---|
167
+ | `SANDBOX_BASE_URL` | `http://localhost:8080` | Sandbox 服务地址 |
168
+ | `SANDBOX_API_KEY` | `sandbox-secret-key` | API 鉴权密钥(与后端 `SANDBOX_API_KEY` 保持一致) |
169
+
170
+ ## 🛡️ 异常处理
171
+
172
+ ```python
173
+ from python_sandbox_sdk import SandboxClient, ApiRequestError, SandboxError
174
+
175
+ try:
176
+ client.exec_python(session_id, "print(1)")
177
+ except ApiRequestError as e:
178
+ # 后端返回的 4xx / 5xx(鉴权失败、会话不存在、黑名单、容器上限等)
179
+ print(f"API Error: HTTP {e.status_code} - {e}")
180
+ except SandboxError as e:
181
+ # SDK 抛出的通用错误
182
+ print(f"SDK Error: {e}")
183
+ except OSError as e:
184
+ # 网络错误(连接失败、超时等)
185
+ print(f"Network Error: {e}")
186
+ ```
187
+
188
+ ## 🧵 并发使用
189
+
190
+ ```python
191
+ from concurrent.futures import ThreadPoolExecutor
192
+ # 每个任务使用独立的 client + session(线程安全)
193
+ with ThreadPoolExecutor(max_workers=5) as pool:
194
+ futures = [pool.submit(run_one_task, i) for i in range(5)]
195
+ for f in futures:
196
+ print(f.result())
197
+ ```
198
+
199
+ > ⚠️ 并发数受后端 `sandbox.max-containers`(默认 10)约束。
200
+
201
+ ## 📦 打包发布
202
+
203
+ ```bash
204
+ # 安装构建工具
205
+ pip install build twine
206
+
207
+ # 构建
208
+ cd sdk/python-sandbox-sdk
209
+ python -m build
210
+
211
+ # 上传到 TestPyPI
212
+ twine upload --repository testpypi dist/*
213
+
214
+ # 上传到 PyPI
215
+ twine upload dist/*
216
+ ```
217
+
218
+ 发布前请修改 `pyproject.toml` 中的 `version` 与 `urls` 字段。
219
+
220
+ ## 🔗 相关项目
221
+
222
+ - 后端服务:[`python-sandbox/`](https://github.com/zjwan461/python-sandbox)
223
+ - Java SDK:[`sdk/java-sandbox-sdk/`](https://github.com/zjwan461/python-sandbox/tree/main/sdk/java-sandbox-sdk)
224
+ - SDK 总览:[`sdk/README.md`](https://github.com/zjwan461/python-sandbox/tree/main/sdk)
225
+
226
+ ## 📄 许可证
227
+
228
+ MIT License
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ ./python_sandbox_sdk/__init__.py
5
+ ./python_sandbox_sdk/client.py
6
+ ./python_sandbox_sdk/dto.py
7
+ python_sandbox_sdk/__init__.py
8
+ python_sandbox_sdk/client.py
9
+ python_sandbox_sdk/dto.py
10
+ python_sandbox_sdk.egg-info/PKG-INFO
11
+ python_sandbox_sdk.egg-info/SOURCES.txt
12
+ python_sandbox_sdk.egg-info/dependency_links.txt
13
+ python_sandbox_sdk.egg-info/requires.txt
14
+ python_sandbox_sdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.31.0
@@ -0,0 +1 @@
1
+ python_sandbox_sdk
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,58 @@
1
+ """Setuptools configuration for python-sandbox-sdk.
2
+
3
+ 本文件同时作为:
4
+ 1. 兼容 setup.py 的安装入口(`pip install .`)
5
+ 2. 长描述来源 — PyPI 包页面会展示 README.md 的渲染结果
6
+ """
7
+ import os
8
+ from pathlib import Path
9
+
10
+ from setuptools import setup, find_packages
11
+
12
+
13
+ def _read_readme() -> str:
14
+ """读取 README.md 作为 long_description。
15
+
16
+ README.md 位于 sdk/python-sandbox-sdk/README.md,
17
+ 通过相对路径回溯到仓库根再定位的方式不可靠,因此固定使用当前文件同级目录。
18
+ """
19
+ here = Path(__file__).resolve().parent
20
+ readme_path = here / "README.md"
21
+ if readme_path.exists():
22
+ return readme_path.read_text(encoding="utf-8")
23
+ # 兜底:仅返回一行标题,避免发布到 PyPI 时缺失描述
24
+ return "python-sandbox-sdk: Python SDK for Python Sandbox API"
25
+
26
+
27
+ setup(
28
+ name="python-sandbox-sdk",
29
+ version="1.0.0",
30
+ description="Python SDK for Python Sandbox API - Run Python code, shell commands, and manage packages in Docker containers",
31
+ long_description=_read_readme(),
32
+ long_description_content_type="text/markdown",
33
+ author_email="826935261@qq.com",
34
+ url="https://github.com/zjwan461/python-sandbox",
35
+ project_urls={
36
+ "Repository": "https://github.com/zjwan461/python-sandbox",
37
+ "Documentation": "https://github.com/zjwan461/python-sandbox#readme",
38
+ "Issues": "https://github.com/zjwan461/python-sandbox/issues",
39
+ "Changelog": "https://github.com/zjwan461/python-sandbox/blob/main/CHANGELOG.md",
40
+ "Usage Examples": "https://github.com/zjwan461/python-sandbox/tree/main/sdk/python-sandbox-sdk/usage",
41
+ },
42
+ packages=find_packages(where="."),
43
+ package_dir={"": "."},
44
+ python_requires=">=3.8",
45
+ classifiers=[
46
+ "Development Status :: 4 - Beta",
47
+ "Intended Audience :: Developers",
48
+ "Programming Language :: Python :: 3",
49
+ "Programming Language :: Python :: 3.8",
50
+ "Programming Language :: Python :: 3.9",
51
+ "Programming Language :: Python :: 3.10",
52
+ "Programming Language :: Python :: 3.11",
53
+ "Programming Language :: Python :: 3.12",
54
+ "Topic :: Software Development :: Libraries :: Python Modules",
55
+ "Topic :: System :: Sandboxing",
56
+ ],
57
+ keywords="sandbox docker python security shell",
58
+ )