ucloud-sandbox 0.1.0b3__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.
- ucloud_sandbox-0.1.0b3/LICENSE +12 -0
- ucloud_sandbox-0.1.0b3/PKG-INFO +77 -0
- ucloud_sandbox-0.1.0b3/README.md +44 -0
- ucloud_sandbox-0.1.0b3/e2b_connect/__init__.py +1 -0
- ucloud_sandbox-0.1.0b3/e2b_connect/client.py +493 -0
- ucloud_sandbox-0.1.0b3/pyproject.toml +46 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/__init__.py +164 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/__init__.py +164 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/__init__.py +8 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/__init__.py +1 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/__init__.py +1 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/get_sandboxes.py +176 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/post_sandboxes.py +172 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +165 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/__init__.py +1 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/delete_templates_template_id.py +157 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/get_templates.py +172 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/get_templates_template_id.py +195 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +217 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/patch_templates_template_id.py +183 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/post_templates.py +172 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/post_templates_template_id.py +181 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/post_v2_templates.py +172 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/post_v3_templates.py +172 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/client.py +286 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/errors.py +16 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/__init__.py +123 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/aws_registry.py +85 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/aws_registry_type.py +8 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/build_log_entry.py +89 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/build_status_reason.py +95 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/connect_sandbox.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/created_access_token.py +100 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/created_team_api_key.py +166 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/disk_metrics.py +91 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/error.py +67 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/gcp_registry.py +69 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/gcp_registry_type.py +8 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/general_registry.py +77 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/general_registry_type.py +8 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/identifier_masking_details.py +83 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/listed_sandbox.py +154 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/log_level.py +11 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/max_team_metric.py +78 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/mcp_type_0.py +44 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/new_access_token.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/new_sandbox.py +172 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/new_team_api_key.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/node.py +155 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/node_detail.py +165 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/node_metrics.py +122 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/node_status.py +11 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/node_status_change.py +79 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/resumed_sandbox.py +68 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox.py +145 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_detail.py +183 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_log.py +70 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_log_entry.py +93 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_log_entry_fields.py +44 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_logs.py +91 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_metric.py +118 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_network_config.py +92 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandbox_state.py +9 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/sandboxes_with_metrics.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/team.py +83 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/team_api_key.py +158 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/team_metric.py +86 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/team_user.py +68 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template.py +217 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build.py +139 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build_file_upload.py +70 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build_info.py +126 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build_request.py +115 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build_request_v2.py +88 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build_request_v3.py +88 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build_start_v2.py +184 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_build_status.py +11 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_legacy.py +207 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_request_response_v3.py +83 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_step.py +91 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_update_request.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/template_with_builds.py +148 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/models/update_team_api_key.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/py.typed +1 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client/types.py +54 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client_async/__init__.py +50 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/client_sync/__init__.py +52 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/api/metadata.py +17 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/connection_config.py +217 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/api.py +59 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/filesystem/filesystem_connect.py +193 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/filesystem/filesystem_pb2.py +76 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/filesystem/filesystem_pb2.pyi +233 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/process/process_connect.py +155 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/process/process_pb2.py +92 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/process/process_pb2.pyi +304 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/rpc.py +61 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/envd/versions.py +6 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/exceptions.py +95 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/commands/command_handle.py +69 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/commands/main.py +39 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/filesystem/filesystem.py +94 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/filesystem/watch_handle.py +60 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/main.py +194 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/network.py +8 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/sandbox_api.py +181 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/signature.py +45 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox/utils.py +34 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/commands/command.py +336 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/commands/command_handle.py +196 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/commands/pty.py +193 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/filesystem/filesystem.py +531 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/filesystem/watch_handle.py +62 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/main.py +686 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/paginator.py +69 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/sandbox_api.py +322 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_async/utils.py +7 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/commands/command.py +328 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/commands/command_handle.py +150 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/commands/pty.py +186 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/filesystem/filesystem.py +518 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/filesystem/watch_handle.py +69 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/main.py +680 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/paginator.py +69 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/sandbox_sync/sandbox_api.py +305 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template/consts.py +30 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template/dockerfile_parser.py +275 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template/logger.py +232 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template/main.py +1323 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template/readycmd.py +138 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template/types.py +105 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template/utils.py +320 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template_async/build_api.py +202 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template_async/main.py +366 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template_sync/build_api.py +199 -0
- ucloud_sandbox-0.1.0b3/ucloud_sandbox/template_sync/main.py +371 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 UCloud Technology Co., Ltd.
|
|
4
|
+
|
|
5
|
+
This project is based on E2B (https://github.com/e2b-dev/e2b), originally developed by FoundryLabs, Inc.
|
|
6
|
+
Original E2B License: MIT License, Copyright (c) 2025 FOUNDRYLABS, INC.
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ucloud_sandbox
|
|
3
|
+
Version: 0.1.0b3
|
|
4
|
+
Summary: UCloud Sandbox SDK - Cloud sandbox environments for AI agents
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: UCloud
|
|
8
|
+
Author-email: support@ucloud.cn
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Dist: attrs (>=23.2.0)
|
|
19
|
+
Requires-Dist: dockerfile-parse (>=2.0.1,<3.0.0)
|
|
20
|
+
Requires-Dist: httpcore (>=1.0.5,<2.0.0)
|
|
21
|
+
Requires-Dist: httpx (>=0.27.0,<1.0.0)
|
|
22
|
+
Requires-Dist: packaging (>=24.1)
|
|
23
|
+
Requires-Dist: protobuf (>=4.21.0)
|
|
24
|
+
Requires-Dist: python-dateutil (>=2.8.2)
|
|
25
|
+
Requires-Dist: rich (>=14.0.0)
|
|
26
|
+
Requires-Dist: typing-extensions (>=4.1.0)
|
|
27
|
+
Requires-Dist: wcmatch (>=10.1,<11.0)
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/ucloud/ucloud-sandbox-sdk/issues
|
|
29
|
+
Project-URL: Homepage, https://ucloud.cn/
|
|
30
|
+
Project-URL: Repository, https://github.com/ucloud/ucloud-sandbox-sdk
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# UCloud Sandbox Python SDK
|
|
34
|
+
|
|
35
|
+
UCloud Sandbox Python SDK 提供云端沙箱环境,用于安全运行 AI 生成的代码。
|
|
36
|
+
|
|
37
|
+
## 安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install ucloud_sandbox
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 快速开始
|
|
44
|
+
|
|
45
|
+
### 1. 获取 API Key
|
|
46
|
+
|
|
47
|
+
1. 访问 [UCloud Sandbox](https://sandbox.ucloudai.com) 注册账号
|
|
48
|
+
2. 在控制台获取 API Key
|
|
49
|
+
3. 设置环境变量:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export UCLOUD_SANDBOX_API_KEY=your_api_key
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. 运行代码
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from ucloud_sandbox import Sandbox
|
|
59
|
+
|
|
60
|
+
with Sandbox.create() as sandbox:
|
|
61
|
+
sandbox.run_code("x = 1")
|
|
62
|
+
execution = sandbox.run_code("x += 1; x")
|
|
63
|
+
print(execution.text) # 输出: 2
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 文档
|
|
67
|
+
TODO
|
|
68
|
+
访问 [Sandbox 文档](https://docs.sandbox.ucloudai.com) 获取更多信息。
|
|
69
|
+
|
|
70
|
+
## 致谢
|
|
71
|
+
|
|
72
|
+
本项目基于 [E2B](https://github.com/e2b-dev/e2b) 开源项目开发,感谢 E2B 团队的贡献。
|
|
73
|
+
|
|
74
|
+
## 许可证
|
|
75
|
+
|
|
76
|
+
MIT License - 详见 [LICENSE](./LICENSE) 文件
|
|
77
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# UCloud Sandbox Python SDK
|
|
2
|
+
|
|
3
|
+
UCloud Sandbox Python SDK 提供云端沙箱环境,用于安全运行 AI 生成的代码。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install ucloud_sandbox
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
### 1. 获取 API Key
|
|
14
|
+
|
|
15
|
+
1. 访问 [UCloud Sandbox](https://sandbox.ucloudai.com) 注册账号
|
|
16
|
+
2. 在控制台获取 API Key
|
|
17
|
+
3. 设置环境变量:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export UCLOUD_SANDBOX_API_KEY=your_api_key
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. 运行代码
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from ucloud_sandbox import Sandbox
|
|
27
|
+
|
|
28
|
+
with Sandbox.create() as sandbox:
|
|
29
|
+
sandbox.run_code("x = 1")
|
|
30
|
+
execution = sandbox.run_code("x += 1; x")
|
|
31
|
+
print(execution.text) # 输出: 2
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 文档
|
|
35
|
+
TODO
|
|
36
|
+
访问 [Sandbox 文档](https://docs.sandbox.ucloudai.com) 获取更多信息。
|
|
37
|
+
|
|
38
|
+
## 致谢
|
|
39
|
+
|
|
40
|
+
本项目基于 [E2B](https://github.com/e2b-dev/e2b) 开源项目开发,感谢 E2B 团队的贡献。
|
|
41
|
+
|
|
42
|
+
## 许可证
|
|
43
|
+
|
|
44
|
+
MIT License - 详见 [LICENSE](./LICENSE) 文件
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .client import Client, GzipCompressor, ConnectException, Code # noqa: F401
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import gzip
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
import struct
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
from httpcore import (
|
|
8
|
+
ConnectionPool,
|
|
9
|
+
AsyncConnectionPool,
|
|
10
|
+
RemoteProtocolError,
|
|
11
|
+
Response,
|
|
12
|
+
)
|
|
13
|
+
from enum import Flag, Enum
|
|
14
|
+
from typing import Callable, Optional, Dict, Any, Generator, Tuple
|
|
15
|
+
from google.protobuf import json_format
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EnvelopeFlags(Flag):
|
|
19
|
+
compressed = 0b00000001
|
|
20
|
+
end_stream = 0b00000010
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Code(Enum):
|
|
24
|
+
canceled = "canceled"
|
|
25
|
+
unknown = "unknown"
|
|
26
|
+
invalid_argument = "invalid_argument"
|
|
27
|
+
deadline_exceeded = "deadline_exceeded"
|
|
28
|
+
not_found = "not_found"
|
|
29
|
+
already_exists = "already_exists"
|
|
30
|
+
permission_denied = "permission_denied"
|
|
31
|
+
resource_exhausted = "resource_exhausted"
|
|
32
|
+
failed_precondition = "failed_precondition"
|
|
33
|
+
aborted = "aborted"
|
|
34
|
+
out_of_range = "out_of_range"
|
|
35
|
+
unimplemented = "unimplemented"
|
|
36
|
+
internal = "internal"
|
|
37
|
+
unavailable = "unavailable"
|
|
38
|
+
data_loss = "data_loss"
|
|
39
|
+
unauthenticated = "unauthenticated"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def make_error_from_http_code(http_code: int):
|
|
43
|
+
error_code_map = {
|
|
44
|
+
400: Code.invalid_argument,
|
|
45
|
+
401: Code.unauthenticated,
|
|
46
|
+
403: Code.permission_denied,
|
|
47
|
+
404: Code.not_found,
|
|
48
|
+
409: Code.already_exists,
|
|
49
|
+
413: Code.resource_exhausted,
|
|
50
|
+
429: Code.resource_exhausted,
|
|
51
|
+
499: Code.canceled,
|
|
52
|
+
500: Code.internal,
|
|
53
|
+
501: Code.unimplemented,
|
|
54
|
+
502: Code.unavailable,
|
|
55
|
+
503: Code.unavailable,
|
|
56
|
+
504: Code.deadline_exceeded,
|
|
57
|
+
505: Code.unimplemented,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return error_code_map.get(http_code, Code.unknown)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ConnectException(Exception):
|
|
64
|
+
def __init__(self, status: Code, message: str):
|
|
65
|
+
self.status = status
|
|
66
|
+
self.message = message
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
envelope_header_length = 5
|
|
70
|
+
envelope_header_pack = ">BI"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def encode_envelope(*, flags: EnvelopeFlags, data):
|
|
74
|
+
return encode_envelope_header(flags=flags.value, data=data) + data
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def encode_envelope_header(*, flags, data):
|
|
78
|
+
return struct.pack(envelope_header_pack, flags, len(data))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def decode_envelope_header(header):
|
|
82
|
+
flags, data_len = struct.unpack(envelope_header_pack, header)
|
|
83
|
+
return EnvelopeFlags(flags), data_len
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def error_for_response(http_resp: Response):
|
|
87
|
+
try:
|
|
88
|
+
error = json.loads(http_resp.content)
|
|
89
|
+
return make_error(error)
|
|
90
|
+
except (json.decoder.JSONDecodeError, KeyError):
|
|
91
|
+
error = {"code": http_resp.status, "message": http_resp.content.decode("utf-8")}
|
|
92
|
+
return make_error(error)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def make_error(error):
|
|
96
|
+
status = None
|
|
97
|
+
try:
|
|
98
|
+
code_value = error.get("code")
|
|
99
|
+
# return error code from http status code
|
|
100
|
+
if isinstance(code_value, int):
|
|
101
|
+
status = make_error_from_http_code(code_value)
|
|
102
|
+
else:
|
|
103
|
+
status = Code(code_value)
|
|
104
|
+
except (KeyError, ValueError):
|
|
105
|
+
status = Code.unknown
|
|
106
|
+
|
|
107
|
+
return ConnectException(status, error.get("message", ""))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _sync_retry(func, exc, retries):
|
|
111
|
+
def retry(*args, **kwargs):
|
|
112
|
+
for _ in range(retries):
|
|
113
|
+
try:
|
|
114
|
+
return func(*args, **kwargs)
|
|
115
|
+
except exc:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
return func(*args, **kwargs)
|
|
119
|
+
|
|
120
|
+
return retry
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _async_retry(func, exc, retries):
|
|
124
|
+
async def retry(*args, **kwargs):
|
|
125
|
+
for _ in range(retries):
|
|
126
|
+
try:
|
|
127
|
+
return await func(*args, **kwargs)
|
|
128
|
+
except exc:
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
return await func(*args, **kwargs)
|
|
132
|
+
|
|
133
|
+
return retry
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _retry(exc: typing.Type[Exception], retries: int):
|
|
137
|
+
def decorator(func):
|
|
138
|
+
if inspect.iscoroutinefunction(func):
|
|
139
|
+
return _async_retry(func, exc, retries)
|
|
140
|
+
|
|
141
|
+
return _sync_retry(func, exc, retries)
|
|
142
|
+
|
|
143
|
+
return decorator
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class GzipCompressor:
|
|
147
|
+
name = "gzip"
|
|
148
|
+
decompress = gzip.decompress
|
|
149
|
+
compress = gzip.compress
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class JSONCodec:
|
|
153
|
+
content_type = "json"
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def encode(msg):
|
|
157
|
+
return json_format.MessageToJson(msg).encode("utf8")
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def decode(data, *, msg_type):
|
|
161
|
+
msg = msg_type()
|
|
162
|
+
json_format.Parse(data.decode("utf8"), msg, ignore_unknown_fields=True)
|
|
163
|
+
return msg
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ProtobufCodec:
|
|
167
|
+
content_type = "proto"
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def encode(msg):
|
|
171
|
+
return msg.SerializeToString()
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def decode(data, *, msg_type):
|
|
175
|
+
msg = msg_type()
|
|
176
|
+
msg.ParseFromString(data)
|
|
177
|
+
return msg
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class Client:
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
*,
|
|
184
|
+
pool: Optional[ConnectionPool] = None,
|
|
185
|
+
async_pool: Optional[AsyncConnectionPool] = None,
|
|
186
|
+
url: str,
|
|
187
|
+
response_type,
|
|
188
|
+
compressor=None,
|
|
189
|
+
json: Optional[bool] = False,
|
|
190
|
+
headers: Optional[Dict[str, str]] = None,
|
|
191
|
+
):
|
|
192
|
+
if headers is None:
|
|
193
|
+
headers = {}
|
|
194
|
+
|
|
195
|
+
self.pool = pool
|
|
196
|
+
self.async_pool = async_pool
|
|
197
|
+
self.url = url
|
|
198
|
+
self._codec = JSONCodec if json else ProtobufCodec
|
|
199
|
+
self._response_type = response_type
|
|
200
|
+
self._compressor = compressor
|
|
201
|
+
self._headers = headers
|
|
202
|
+
self._connection_retries = 3
|
|
203
|
+
|
|
204
|
+
def _prepare_unary_request(
|
|
205
|
+
self,
|
|
206
|
+
req,
|
|
207
|
+
request_timeout=None,
|
|
208
|
+
headers: Optional[dict] = None,
|
|
209
|
+
**opts,
|
|
210
|
+
) -> dict:
|
|
211
|
+
data = self._codec.encode(req)
|
|
212
|
+
|
|
213
|
+
if self._compressor is not None:
|
|
214
|
+
data = self._compressor.compress(data)
|
|
215
|
+
|
|
216
|
+
if headers is None:
|
|
217
|
+
headers = {}
|
|
218
|
+
|
|
219
|
+
extensions = (
|
|
220
|
+
None
|
|
221
|
+
if request_timeout is None
|
|
222
|
+
else {
|
|
223
|
+
"timeout": {
|
|
224
|
+
"connect": request_timeout,
|
|
225
|
+
"pool": request_timeout,
|
|
226
|
+
"read": request_timeout,
|
|
227
|
+
"write": request_timeout,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"method": "POST",
|
|
234
|
+
"url": self.url,
|
|
235
|
+
"content": data,
|
|
236
|
+
"extensions": extensions,
|
|
237
|
+
"headers": {
|
|
238
|
+
**self._headers,
|
|
239
|
+
**headers,
|
|
240
|
+
**opts.get("headers", {}),
|
|
241
|
+
"connect-protocol-version": "1",
|
|
242
|
+
"content-encoding": (
|
|
243
|
+
"identity" if self._compressor is None else self._compressor.name
|
|
244
|
+
),
|
|
245
|
+
"content-type": f"application/{self._codec.content_type}",
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
def _process_unary_response(
|
|
250
|
+
self,
|
|
251
|
+
http_resp: Response,
|
|
252
|
+
):
|
|
253
|
+
if http_resp.status != 200:
|
|
254
|
+
raise error_for_response(http_resp)
|
|
255
|
+
|
|
256
|
+
content = http_resp.content
|
|
257
|
+
|
|
258
|
+
if self._compressor is not None:
|
|
259
|
+
content = self._compressor.decompress(content)
|
|
260
|
+
|
|
261
|
+
return self._codec.decode(
|
|
262
|
+
content,
|
|
263
|
+
msg_type=self._response_type,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@_retry(RemoteProtocolError, 3)
|
|
267
|
+
async def acall_unary(
|
|
268
|
+
self,
|
|
269
|
+
req,
|
|
270
|
+
request_timeout=None,
|
|
271
|
+
headers: Optional[dict] = None,
|
|
272
|
+
**opts,
|
|
273
|
+
):
|
|
274
|
+
if self.async_pool is None:
|
|
275
|
+
raise ValueError("async_pool is required")
|
|
276
|
+
|
|
277
|
+
req_data = self._prepare_unary_request(
|
|
278
|
+
req,
|
|
279
|
+
request_timeout,
|
|
280
|
+
headers,
|
|
281
|
+
**opts,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
res = await self.async_pool.request(**req_data)
|
|
285
|
+
return self._process_unary_response(res)
|
|
286
|
+
|
|
287
|
+
@_retry(RemoteProtocolError, 3)
|
|
288
|
+
def call_unary(
|
|
289
|
+
self,
|
|
290
|
+
req,
|
|
291
|
+
request_timeout=None,
|
|
292
|
+
headers: Optional[dict] = None,
|
|
293
|
+
**opts,
|
|
294
|
+
):
|
|
295
|
+
if self.pool is None:
|
|
296
|
+
raise ValueError("pool is required")
|
|
297
|
+
|
|
298
|
+
req_data = self._prepare_unary_request(
|
|
299
|
+
req,
|
|
300
|
+
request_timeout,
|
|
301
|
+
headers,
|
|
302
|
+
**opts,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
res = self.pool.request(**req_data)
|
|
306
|
+
return self._process_unary_response(res)
|
|
307
|
+
|
|
308
|
+
def _create_stream_timeout(self, timeout: Optional[int]):
|
|
309
|
+
if timeout:
|
|
310
|
+
return {"connect-timeout-ms": str(timeout * 1000)}
|
|
311
|
+
return {}
|
|
312
|
+
|
|
313
|
+
def _prepare_server_stream_request(
|
|
314
|
+
self,
|
|
315
|
+
req,
|
|
316
|
+
request_timeout=None,
|
|
317
|
+
timeout=None,
|
|
318
|
+
headers: Optional[dict] = None,
|
|
319
|
+
**opts,
|
|
320
|
+
) -> dict:
|
|
321
|
+
headers = headers or {}
|
|
322
|
+
data = self._codec.encode(req)
|
|
323
|
+
flags = EnvelopeFlags(0)
|
|
324
|
+
|
|
325
|
+
extensions = (
|
|
326
|
+
None
|
|
327
|
+
if request_timeout is None
|
|
328
|
+
else {"timeout": {"connect": request_timeout, "pool": request_timeout}}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if self._compressor is not None:
|
|
332
|
+
data = self._compressor.compress(data)
|
|
333
|
+
flags |= EnvelopeFlags.compressed
|
|
334
|
+
|
|
335
|
+
stream_timeout = self._create_stream_timeout(timeout)
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"method": "POST",
|
|
339
|
+
"url": self.url,
|
|
340
|
+
"content": encode_envelope(
|
|
341
|
+
flags=flags,
|
|
342
|
+
data=data,
|
|
343
|
+
),
|
|
344
|
+
"extensions": extensions,
|
|
345
|
+
"headers": {
|
|
346
|
+
**self._headers,
|
|
347
|
+
**headers,
|
|
348
|
+
**opts.get("headers", {}),
|
|
349
|
+
**stream_timeout,
|
|
350
|
+
"connect-protocol-version": "1",
|
|
351
|
+
"connect-content-encoding": (
|
|
352
|
+
"identity" if self._compressor is None else self._compressor.name
|
|
353
|
+
),
|
|
354
|
+
"content-type": f"application/connect+{self._codec.content_type}",
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@_retry(RemoteProtocolError, 3)
|
|
359
|
+
async def acall_server_stream(
|
|
360
|
+
self,
|
|
361
|
+
req,
|
|
362
|
+
request_timeout=None,
|
|
363
|
+
timeout=None,
|
|
364
|
+
headers: Optional[dict] = None,
|
|
365
|
+
**opts,
|
|
366
|
+
):
|
|
367
|
+
if self.async_pool is None:
|
|
368
|
+
raise ValueError("async_pool is required")
|
|
369
|
+
|
|
370
|
+
req_data = self._prepare_server_stream_request(
|
|
371
|
+
req,
|
|
372
|
+
request_timeout,
|
|
373
|
+
timeout,
|
|
374
|
+
headers,
|
|
375
|
+
**opts,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
parser = ServerStreamParser(
|
|
379
|
+
decode=self._codec.decode,
|
|
380
|
+
response_type=self._response_type,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
async with self.async_pool.stream(**req_data) as http_resp:
|
|
384
|
+
if http_resp.status != 200:
|
|
385
|
+
await http_resp.aread()
|
|
386
|
+
raise error_for_response(http_resp)
|
|
387
|
+
|
|
388
|
+
async for chunk in http_resp.aiter_stream():
|
|
389
|
+
for parsed in parser.parse(chunk):
|
|
390
|
+
yield parsed
|
|
391
|
+
|
|
392
|
+
@_retry(RemoteProtocolError, 3)
|
|
393
|
+
def call_server_stream(
|
|
394
|
+
self,
|
|
395
|
+
req,
|
|
396
|
+
request_timeout=None,
|
|
397
|
+
timeout=None,
|
|
398
|
+
headers: Optional[dict] = None,
|
|
399
|
+
**opts,
|
|
400
|
+
):
|
|
401
|
+
if self.pool is None:
|
|
402
|
+
raise ValueError("pool is required")
|
|
403
|
+
|
|
404
|
+
req_data = self._prepare_server_stream_request(
|
|
405
|
+
req,
|
|
406
|
+
request_timeout,
|
|
407
|
+
timeout,
|
|
408
|
+
headers,
|
|
409
|
+
**opts,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
parser = ServerStreamParser(
|
|
413
|
+
decode=self._codec.decode,
|
|
414
|
+
response_type=self._response_type,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
with self.pool.stream(**req_data) as http_resp:
|
|
418
|
+
if http_resp.status != 200:
|
|
419
|
+
http_resp.read()
|
|
420
|
+
raise error_for_response(http_resp)
|
|
421
|
+
|
|
422
|
+
for chunk in http_resp.iter_stream():
|
|
423
|
+
for parsed in parser.parse(chunk):
|
|
424
|
+
yield parsed
|
|
425
|
+
|
|
426
|
+
def call_client_stream(self, req, **opts):
|
|
427
|
+
raise NotImplementedError("client stream not supported")
|
|
428
|
+
|
|
429
|
+
def acall_client_stream(self, req, **opts):
|
|
430
|
+
raise NotImplementedError("client stream not supported")
|
|
431
|
+
|
|
432
|
+
def call_bidi_stream(self, req, **opts):
|
|
433
|
+
raise NotImplementedError("bidi stream not supported")
|
|
434
|
+
|
|
435
|
+
def acall_bidi_stream(self, req, **opts):
|
|
436
|
+
raise NotImplementedError("bidi stream not supported")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
DataLen = int
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class ServerStreamParser:
|
|
443
|
+
def __init__(
|
|
444
|
+
self,
|
|
445
|
+
decode: Callable,
|
|
446
|
+
response_type: Any,
|
|
447
|
+
):
|
|
448
|
+
self.decode = decode
|
|
449
|
+
self.response_type = response_type
|
|
450
|
+
|
|
451
|
+
self.buffer: bytes = b""
|
|
452
|
+
self._header: Optional[tuple[EnvelopeFlags, DataLen]] = None
|
|
453
|
+
|
|
454
|
+
def shift_buffer(self, size: int):
|
|
455
|
+
buffer = self.buffer[:size]
|
|
456
|
+
self.buffer = self.buffer[size:]
|
|
457
|
+
return buffer
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def header(self) -> Tuple[EnvelopeFlags, DataLen]:
|
|
461
|
+
if self._header:
|
|
462
|
+
return self._header
|
|
463
|
+
|
|
464
|
+
header_data = self.shift_buffer(envelope_header_length)
|
|
465
|
+
self._header = decode_envelope_header(header_data)
|
|
466
|
+
|
|
467
|
+
return self._header
|
|
468
|
+
|
|
469
|
+
@header.deleter
|
|
470
|
+
def header(self):
|
|
471
|
+
self._header = None
|
|
472
|
+
|
|
473
|
+
def parse(self, chunk: bytes) -> Generator[Any, None, None]:
|
|
474
|
+
self.buffer += chunk
|
|
475
|
+
|
|
476
|
+
while len(self.buffer) >= envelope_header_length:
|
|
477
|
+
flags, data_len = self.header
|
|
478
|
+
|
|
479
|
+
if data_len > len(self.buffer):
|
|
480
|
+
break
|
|
481
|
+
|
|
482
|
+
data = self.shift_buffer(data_len)
|
|
483
|
+
|
|
484
|
+
if EnvelopeFlags.end_stream in flags:
|
|
485
|
+
data = json.loads(data)
|
|
486
|
+
|
|
487
|
+
if "error" in data:
|
|
488
|
+
raise make_error(data["error"])
|
|
489
|
+
|
|
490
|
+
return
|
|
491
|
+
|
|
492
|
+
yield self.decode(data, msg_type=self.response_type)
|
|
493
|
+
del self.header
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "ucloud_sandbox"
|
|
3
|
+
version = "0.1.0-beta3"
|
|
4
|
+
description = "UCloud Sandbox SDK - Cloud sandbox environments for AI agents"
|
|
5
|
+
authors = ["UCloud <support@ucloud.cn>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://ucloud.cn/"
|
|
9
|
+
repository = "https://github.com/ucloud/ucloud-sandbox-sdk"
|
|
10
|
+
packages = [{ include = "ucloud_sandbox" }, { include = "e2b_connect" }]
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = "^3.9"
|
|
14
|
+
python-dateutil = ">=2.8.2"
|
|
15
|
+
wcmatch = "^10.1"
|
|
16
|
+
protobuf = ">=4.21.0"
|
|
17
|
+
httpcore = "^1.0.5"
|
|
18
|
+
httpx = ">=0.27.0, <1.0.0"
|
|
19
|
+
attrs = ">=23.2.0"
|
|
20
|
+
packaging = ">=24.1"
|
|
21
|
+
typing-extensions = ">=4.1.0"
|
|
22
|
+
dockerfile-parse = "^2.0.1"
|
|
23
|
+
rich = ">=14.0.0"
|
|
24
|
+
|
|
25
|
+
[tool.poetry.group.dev.dependencies]
|
|
26
|
+
pytest = "^7.4.0"
|
|
27
|
+
pytest-xdist = "^3.3.1"
|
|
28
|
+
python-dotenv = "^1.0.0"
|
|
29
|
+
pytest-dotenv = "^0.5.2"
|
|
30
|
+
pytest-asyncio = "^0.23.7"
|
|
31
|
+
pydoc-markdown = "^4.8.2"
|
|
32
|
+
datamodel-code-generator = "^0.34.0"
|
|
33
|
+
ruff = "^0.11.12"
|
|
34
|
+
pytest-timeout = "^2.4.0"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["poetry-core"]
|
|
38
|
+
build-backend = "poetry.core.masonry.api"
|
|
39
|
+
|
|
40
|
+
[tool.poetry.urls]
|
|
41
|
+
"Bug Tracker" = "https://github.com/ucloud/ucloud-sandbox-sdk/issues"
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
exclude = [
|
|
45
|
+
"ucloud_sandbox/envd/filesystem/filesystem_pb2.py"
|
|
46
|
+
]
|