grasp-sdk 0.1.0__tar.gz → 0.1.2__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.
Potentially problematic release.
This version of grasp-sdk might be problematic. Click here for more details.
- {grasp_sdk-0.1.0/grasp_sdk.egg-info → grasp_sdk-0.1.2}/PKG-INFO +139 -33
- grasp_sdk-0.1.2/README.md +260 -0
- grasp_sdk-0.1.2/example_usage.py +101 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/__init__.py +42 -20
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/sandbox/chrome-stable.mjs +4 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/sandbox/chromium.mjs +5 -1
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/services/browser.py +9 -18
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/services/sandbox.py +10 -7
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2/grasp_sdk.egg-info}/PKG-INFO +139 -33
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk.egg-info/SOURCES.txt +0 -2
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/pyproject.toml +1 -1
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/setup.py +1 -1
- grasp_sdk-0.1.0/README.md +0 -154
- grasp_sdk-0.1.0/example_simple.py +0 -138
- grasp_sdk-0.1.0/example_usage.py +0 -100
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/MANIFEST.in +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/build_and_publish.py +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/models/__init__.py +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/sandbox/jsconfig.json +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/services/__init__.py +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/utils/__init__.py +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/utils/auth.py +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/utils/config.py +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk/utils/logger.py +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk.egg-info/dependency_links.txt +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk.egg-info/entry_points.txt +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk.egg-info/not-zip-safe +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk.egg-info/requires.txt +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/grasp_sdk.egg-info/top_level.txt +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/py.typed +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/requirements.txt +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/setup.cfg +0 -0
- {grasp_sdk-0.1.0 → grasp_sdk-0.1.2}/test_install.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: grasp_sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python SDK for Grasp E2B - Browser automation and sandbox management
|
|
5
5
|
Home-page: https://github.com/grasp-team/grasp-e2b
|
|
6
6
|
Author: Grasp Team
|
|
@@ -73,35 +73,90 @@ pip install -e ".[dev]"
|
|
|
73
73
|
|
|
74
74
|
## 🔧 Quick Start
|
|
75
75
|
|
|
76
|
+
### Using GraspServer Context Manager (Recommended)
|
|
77
|
+
|
|
76
78
|
```python
|
|
77
79
|
import asyncio
|
|
80
|
+
import os
|
|
81
|
+
from pathlib import Path
|
|
82
|
+
from playwright.async_api import async_playwright
|
|
83
|
+
from dotenv import load_dotenv
|
|
84
|
+
|
|
78
85
|
from grasp_sdk import GraspServer
|
|
86
|
+
from grasp_sdk.models import IBrowserConfig, ISandboxConfig
|
|
87
|
+
|
|
88
|
+
# 加载环境变量
|
|
89
|
+
load_dotenv("../.env.grasp")
|
|
79
90
|
|
|
80
91
|
async def main():
|
|
81
|
-
|
|
82
|
-
server = GraspServer({
|
|
83
|
-
"key": api_key,
|
|
84
|
-
"timeout": 30000,
|
|
85
|
-
})
|
|
92
|
+
"""主函数:演示 Grasp SDK 的基本用法"""
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
# 检查是否有 API key
|
|
95
|
+
api_key = os.getenv('GRASP_KEY')
|
|
96
|
+
if not api_key:
|
|
97
|
+
print("⚠️ 警告:未设置 GRASP_KEY 环境变量")
|
|
98
|
+
print("请设置 GRASP_KEY 环境变量或在 .env 文件中配置")
|
|
99
|
+
print("示例:export GRASP_KEY=your_api_key_here")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
print("🚀 正在启动浏览器...")
|
|
103
|
+
|
|
104
|
+
async with GraspServer({
|
|
105
|
+
# 'key': api_key,
|
|
106
|
+
# 'type': 'chrome-stable',
|
|
107
|
+
# 'headless': False,
|
|
108
|
+
# 'adblock': True,
|
|
109
|
+
# 'debug': True,
|
|
110
|
+
'timeout': 3600000, # 容器最长运行1小时(最大值可以为一天 86400000)
|
|
111
|
+
}) as connection:
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
print(f"连接信息: {connection}")
|
|
115
|
+
print(f"WebSocket URL: {connection['ws_url']}")
|
|
116
|
+
print(f"HTTP URL: {connection['http_url']}")
|
|
117
|
+
|
|
118
|
+
# 使用 Playwright 连接到 CDP
|
|
119
|
+
async with async_playwright() as p:
|
|
120
|
+
browser = await p.chromium.connect_over_cdp(
|
|
121
|
+
connection['ws_url'],
|
|
122
|
+
timeout=150000
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# 创建第一个页面并访问网站
|
|
126
|
+
page1 = await browser.new_page()
|
|
127
|
+
await page1.goto('https://getgrasp.ai/', wait_until='domcontentloaded')
|
|
128
|
+
await page1.screenshot(path='grasp-ai.png')
|
|
129
|
+
await page1.close()
|
|
130
|
+
|
|
131
|
+
# 获取或创建上下文
|
|
132
|
+
contexts = browser.contexts
|
|
133
|
+
context = contexts[0] if contexts else await browser.new_context()
|
|
134
|
+
|
|
135
|
+
# 创建第二个页面
|
|
136
|
+
page2 = await context.new_page()
|
|
137
|
+
|
|
138
|
+
# 将 HTML 字符串渲染到页面中
|
|
139
|
+
await page2.set_content('<h1>Hello Grasp</h1>', wait_until='networkidle')
|
|
140
|
+
|
|
141
|
+
# 截图演示
|
|
142
|
+
await page2.screenshot(path='hello-world.png', full_page=True)
|
|
143
|
+
|
|
144
|
+
# 清理资源
|
|
145
|
+
await page2.close()
|
|
146
|
+
await context.close()
|
|
147
|
+
await browser.close()
|
|
148
|
+
|
|
149
|
+
print('✅ 任务完成。')
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f"❌ 执行过程中出现错误: {str(e)}")
|
|
153
|
+
raise
|
|
99
154
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
155
|
+
finally:
|
|
156
|
+
# 注意:使用 GraspServer 上下文管理器时,资源会自动清理
|
|
157
|
+
print("程序结束,资源将自动清理")
|
|
103
158
|
|
|
104
|
-
if __name__ ==
|
|
159
|
+
if __name__ == '__main__':
|
|
105
160
|
asyncio.run(main())
|
|
106
161
|
```
|
|
107
162
|
|
|
@@ -145,13 +200,32 @@ GRASP_TEMPLATE=python
|
|
|
145
200
|
|
|
146
201
|
## 🧪 Development
|
|
147
202
|
|
|
203
|
+
### Quick Setup
|
|
204
|
+
|
|
148
205
|
```bash
|
|
149
|
-
# Install development dependencies
|
|
206
|
+
# Install development dependencies automatically
|
|
207
|
+
python install_dev_deps.py
|
|
208
|
+
|
|
209
|
+
# Or manually install
|
|
150
210
|
pip install -e ".[dev]"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Running Tests
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Simple tests (no pytest required)
|
|
217
|
+
python test_connect_simple.py
|
|
218
|
+
|
|
219
|
+
# Full test suite (requires pytest)
|
|
220
|
+
pytest tests/
|
|
221
|
+
|
|
222
|
+
# Run specific test file
|
|
223
|
+
pytest tests/test_connect.py -v
|
|
224
|
+
```
|
|
151
225
|
|
|
152
|
-
|
|
153
|
-
pytest
|
|
226
|
+
### Code Quality
|
|
154
227
|
|
|
228
|
+
```bash
|
|
155
229
|
# Format code
|
|
156
230
|
black .
|
|
157
231
|
isort .
|
|
@@ -163,22 +237,54 @@ mypy .
|
|
|
163
237
|
flake8 .
|
|
164
238
|
```
|
|
165
239
|
|
|
240
|
+
### Examples
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Run Connect usage examples
|
|
244
|
+
python example_connect.py
|
|
245
|
+
```
|
|
246
|
+
|
|
166
247
|
## 📚 API Reference
|
|
167
248
|
|
|
249
|
+
### GraspServer (Recommended)
|
|
250
|
+
|
|
251
|
+
Async context manager for automatic sandbox resource management and browser automation.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
class GraspServer:
|
|
255
|
+
def __init__(self, options: Optional[Dict[str, Any]] = None)
|
|
256
|
+
async def __aenter__(self) -> Dict[str, Any] # Returns connection info
|
|
257
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Usage:**
|
|
261
|
+
```python
|
|
262
|
+
async with GraspServer(options) as connection:
|
|
263
|
+
# connection contains: {'id', 'ws_url', 'http_url'}
|
|
264
|
+
# Automatic cleanup on exit
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Options:**
|
|
268
|
+
- `key`: Your Grasp API key (loaded from environment if not provided)
|
|
269
|
+
- `type`: Browser type ('chromium' or 'chrome-stable')
|
|
270
|
+
- `headless`: Run in headless mode (default: True)
|
|
271
|
+
- `adblock`: Enable adblock (default: False)
|
|
272
|
+
- `debug`: Enable debug mode (default: False)
|
|
273
|
+
- `timeout`: Container maximum runtime in milliseconds (default: 30000, max: 86400000)
|
|
274
|
+
|
|
168
275
|
### GraspServer
|
|
169
276
|
|
|
170
277
|
Main class for interacting with E2B sandboxes and browser automation.
|
|
171
278
|
|
|
172
279
|
```python
|
|
173
280
|
class GraspServer:
|
|
174
|
-
def __init__(self,
|
|
175
|
-
async def
|
|
176
|
-
async def
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def
|
|
181
|
-
def get_sandbox_id(self) -> str
|
|
281
|
+
def __init__(self, sandbox_config: Optional[Dict[str, Any]] = None)
|
|
282
|
+
async def create_browser_task(self, browser_type: str = 'chromium', config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]
|
|
283
|
+
async def cleanup(self) -> None
|
|
284
|
+
def get_status(self) -> Optional[SandboxStatus]
|
|
285
|
+
def get_sandbox_id(self) -> Optional[str]
|
|
286
|
+
@property
|
|
287
|
+
def sandbox(self) -> Optional[SandboxService]
|
|
182
288
|
```
|
|
183
289
|
|
|
184
290
|
## 🤝 Compatibility
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Grasp SDK - Python Implementation
|
|
2
|
+
|
|
3
|
+
🐍 Python implementation of Grasp SDK for E2B platform providing secure command execution and browser automation in isolated cloud environments.
|
|
4
|
+
|
|
5
|
+
## 🚀 Features
|
|
6
|
+
|
|
7
|
+
- **Secure Execution**: Run commands and scripts in isolated E2B sandboxes
|
|
8
|
+
- **Browser Automation**: Control Chromium browsers with Playwright integration
|
|
9
|
+
- **Async/Await Support**: Full async/await support for modern Python development
|
|
10
|
+
- **Type Safety**: Complete type hints with Pydantic models
|
|
11
|
+
- **WebSocket Communication**: Real-time communication with sandbox environments
|
|
12
|
+
- **Multi-language Support**: Compatible with Node.js/TypeScript version
|
|
13
|
+
|
|
14
|
+
## 📦 Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Install from PyPI (when published)
|
|
18
|
+
pip install grasp-sdk
|
|
19
|
+
|
|
20
|
+
# Install from source
|
|
21
|
+
pip install -e .
|
|
22
|
+
|
|
23
|
+
# Install with development dependencies
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 🔧 Quick Start
|
|
28
|
+
|
|
29
|
+
### Using GraspServer Context Manager (Recommended)
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import asyncio
|
|
33
|
+
import os
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from playwright.async_api import async_playwright
|
|
36
|
+
from dotenv import load_dotenv
|
|
37
|
+
|
|
38
|
+
from grasp_sdk import GraspServer
|
|
39
|
+
from grasp_sdk.models import IBrowserConfig, ISandboxConfig
|
|
40
|
+
|
|
41
|
+
# 加载环境变量
|
|
42
|
+
load_dotenv("../.env.grasp")
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
"""主函数:演示 Grasp SDK 的基本用法"""
|
|
46
|
+
|
|
47
|
+
# 检查是否有 API key
|
|
48
|
+
api_key = os.getenv('GRASP_KEY')
|
|
49
|
+
if not api_key:
|
|
50
|
+
print("⚠️ 警告:未设置 GRASP_KEY 环境变量")
|
|
51
|
+
print("请设置 GRASP_KEY 环境变量或在 .env 文件中配置")
|
|
52
|
+
print("示例:export GRASP_KEY=your_api_key_here")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
print("🚀 正在启动浏览器...")
|
|
56
|
+
|
|
57
|
+
async with GraspServer({
|
|
58
|
+
# 'key': api_key,
|
|
59
|
+
# 'type': 'chrome-stable',
|
|
60
|
+
# 'headless': False,
|
|
61
|
+
# 'adblock': True,
|
|
62
|
+
# 'debug': True,
|
|
63
|
+
'timeout': 3600000, # 容器最长运行1小时(最大值可以为一天 86400000)
|
|
64
|
+
}) as connection:
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
print(f"连接信息: {connection}")
|
|
68
|
+
print(f"WebSocket URL: {connection['ws_url']}")
|
|
69
|
+
print(f"HTTP URL: {connection['http_url']}")
|
|
70
|
+
|
|
71
|
+
# 使用 Playwright 连接到 CDP
|
|
72
|
+
async with async_playwright() as p:
|
|
73
|
+
browser = await p.chromium.connect_over_cdp(
|
|
74
|
+
connection['ws_url'],
|
|
75
|
+
timeout=150000
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# 创建第一个页面并访问网站
|
|
79
|
+
page1 = await browser.new_page()
|
|
80
|
+
await page1.goto('https://getgrasp.ai/', wait_until='domcontentloaded')
|
|
81
|
+
await page1.screenshot(path='grasp-ai.png')
|
|
82
|
+
await page1.close()
|
|
83
|
+
|
|
84
|
+
# 获取或创建上下文
|
|
85
|
+
contexts = browser.contexts
|
|
86
|
+
context = contexts[0] if contexts else await browser.new_context()
|
|
87
|
+
|
|
88
|
+
# 创建第二个页面
|
|
89
|
+
page2 = await context.new_page()
|
|
90
|
+
|
|
91
|
+
# 将 HTML 字符串渲染到页面中
|
|
92
|
+
await page2.set_content('<h1>Hello Grasp</h1>', wait_until='networkidle')
|
|
93
|
+
|
|
94
|
+
# 截图演示
|
|
95
|
+
await page2.screenshot(path='hello-world.png', full_page=True)
|
|
96
|
+
|
|
97
|
+
# 清理资源
|
|
98
|
+
await page2.close()
|
|
99
|
+
await context.close()
|
|
100
|
+
await browser.close()
|
|
101
|
+
|
|
102
|
+
print('✅ 任务完成。')
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f"❌ 执行过程中出现错误: {str(e)}")
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
finally:
|
|
109
|
+
# 注意:使用 GraspServer 上下文管理器时,资源会自动清理
|
|
110
|
+
print("程序结束,资源将自动清理")
|
|
111
|
+
|
|
112
|
+
if __name__ == '__main__':
|
|
113
|
+
asyncio.run(main())
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 🏗️ Architecture
|
|
117
|
+
|
|
118
|
+
The Python implementation mirrors the Node.js/TypeScript version:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
py-src/
|
|
122
|
+
├── __init__.py # Main package exports
|
|
123
|
+
├── grasp_server.py # Main GraspServer class
|
|
124
|
+
├── services/ # Core services
|
|
125
|
+
│ ├── __init__.py
|
|
126
|
+
│ ├── sandbox_service.py # E2B sandbox management
|
|
127
|
+
│ └── browser_service.py # Browser automation
|
|
128
|
+
├── types/ # Type definitions
|
|
129
|
+
│ └── __init__.py
|
|
130
|
+
├── utils/ # Utilities
|
|
131
|
+
│ ├── __init__.py
|
|
132
|
+
│ ├── config.py # Configuration management
|
|
133
|
+
│ ├── logger.py # Logging utilities
|
|
134
|
+
│ └── auth.py # Authentication
|
|
135
|
+
├── cli/ # Command line interface
|
|
136
|
+
│ ├── __init__.py
|
|
137
|
+
│ └── main.py
|
|
138
|
+
└── tests/ # Test suite
|
|
139
|
+
└── ...
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 🔑 Environment Variables
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Required
|
|
146
|
+
E2B_API_KEY=your_e2b_api_key_here
|
|
147
|
+
|
|
148
|
+
# Optional
|
|
149
|
+
GRASP_LOG_LEVEL=info
|
|
150
|
+
GRASP_TIMEOUT=30000
|
|
151
|
+
GRASP_TEMPLATE=python
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 🧪 Development
|
|
155
|
+
|
|
156
|
+
### Quick Setup
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Install development dependencies automatically
|
|
160
|
+
python install_dev_deps.py
|
|
161
|
+
|
|
162
|
+
# Or manually install
|
|
163
|
+
pip install -e ".[dev]"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Running Tests
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Simple tests (no pytest required)
|
|
170
|
+
python test_connect_simple.py
|
|
171
|
+
|
|
172
|
+
# Full test suite (requires pytest)
|
|
173
|
+
pytest tests/
|
|
174
|
+
|
|
175
|
+
# Run specific test file
|
|
176
|
+
pytest tests/test_connect.py -v
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Code Quality
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Format code
|
|
183
|
+
black .
|
|
184
|
+
isort .
|
|
185
|
+
|
|
186
|
+
# Type checking
|
|
187
|
+
mypy .
|
|
188
|
+
|
|
189
|
+
# Linting
|
|
190
|
+
flake8 .
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Examples
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Run Connect usage examples
|
|
197
|
+
python example_connect.py
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 📚 API Reference
|
|
201
|
+
|
|
202
|
+
### GraspServer (Recommended)
|
|
203
|
+
|
|
204
|
+
Async context manager for automatic sandbox resource management and browser automation.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
class GraspServer:
|
|
208
|
+
def __init__(self, options: Optional[Dict[str, Any]] = None)
|
|
209
|
+
async def __aenter__(self) -> Dict[str, Any] # Returns connection info
|
|
210
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Usage:**
|
|
214
|
+
```python
|
|
215
|
+
async with GraspServer(options) as connection:
|
|
216
|
+
# connection contains: {'id', 'ws_url', 'http_url'}
|
|
217
|
+
# Automatic cleanup on exit
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Options:**
|
|
221
|
+
- `key`: Your Grasp API key (loaded from environment if not provided)
|
|
222
|
+
- `type`: Browser type ('chromium' or 'chrome-stable')
|
|
223
|
+
- `headless`: Run in headless mode (default: True)
|
|
224
|
+
- `adblock`: Enable adblock (default: False)
|
|
225
|
+
- `debug`: Enable debug mode (default: False)
|
|
226
|
+
- `timeout`: Container maximum runtime in milliseconds (default: 30000, max: 86400000)
|
|
227
|
+
|
|
228
|
+
### GraspServer
|
|
229
|
+
|
|
230
|
+
Main class for interacting with E2B sandboxes and browser automation.
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
class GraspServer:
|
|
234
|
+
def __init__(self, sandbox_config: Optional[Dict[str, Any]] = None)
|
|
235
|
+
async def create_browser_task(self, browser_type: str = 'chromium', config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]
|
|
236
|
+
async def cleanup(self) -> None
|
|
237
|
+
def get_status(self) -> Optional[SandboxStatus]
|
|
238
|
+
def get_sandbox_id(self) -> Optional[str]
|
|
239
|
+
@property
|
|
240
|
+
def sandbox(self) -> Optional[SandboxService]
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## 🤝 Compatibility
|
|
244
|
+
|
|
245
|
+
This Python implementation provides the same API surface as the Node.js/TypeScript version, ensuring:
|
|
246
|
+
|
|
247
|
+
- **Feature Parity**: All features available in both implementations
|
|
248
|
+
- **API Consistency**: Same method names and behavior
|
|
249
|
+
- **Type Safety**: Equivalent type definitions using TypedDict and Pydantic
|
|
250
|
+
- **Error Handling**: Consistent error types and messages
|
|
251
|
+
|
|
252
|
+
## 📄 License
|
|
253
|
+
|
|
254
|
+
MIT License - see the [LICENSE](../LICENSE) file for details.
|
|
255
|
+
|
|
256
|
+
## 🔗 Related
|
|
257
|
+
|
|
258
|
+
- [Node.js/TypeScript Implementation](../src/)
|
|
259
|
+
- [E2B Platform](https://e2b.dev/)
|
|
260
|
+
- [Playwright Python](https://playwright.dev/python/)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Grasp SDK Python 使用示例
|
|
4
|
+
|
|
5
|
+
这个示例展示了如何使用 grasp_sdk 启动浏览器、连接 CDP、执行基本操作并截图。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from playwright.async_api import async_playwright
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
|
|
14
|
+
from grasp_sdk import GraspServer
|
|
15
|
+
from grasp_sdk.models import IBrowserConfig, ISandboxConfig
|
|
16
|
+
|
|
17
|
+
# 加载环境变量
|
|
18
|
+
load_dotenv("../.env.grasp")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def main():
|
|
22
|
+
"""主函数:演示 Grasp SDK 的基本用法"""
|
|
23
|
+
|
|
24
|
+
# 检查是否有 API key
|
|
25
|
+
api_key = os.getenv('GRASP_KEY')
|
|
26
|
+
if not api_key:
|
|
27
|
+
print("⚠️ 警告:未设置 GRASP_KEY 环境变量")
|
|
28
|
+
print("请设置 GRASP_KEY 环境变量或在 .env 文件中配置")
|
|
29
|
+
print("示例:export GRASP_KEY=your_api_key_here")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
print("🚀 正在启动浏览器...")
|
|
33
|
+
|
|
34
|
+
async with GraspServer({
|
|
35
|
+
# 'key': api_key,
|
|
36
|
+
# 'type': 'chrome-stable',
|
|
37
|
+
# 'headless': False,
|
|
38
|
+
# 'adblock': True,
|
|
39
|
+
# 'debug': True,
|
|
40
|
+
'timeout': 3600000, # 容器最长运行1小时(最大值可以为一天 86400000)
|
|
41
|
+
}) as connection:
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
print(f"连接信息: {connection}")
|
|
45
|
+
print(f"WebSocket URL: {connection['ws_url']}")
|
|
46
|
+
print(f"HTTP URL: {connection['http_url']}")
|
|
47
|
+
|
|
48
|
+
# 使用 Playwright 连接到 CDP
|
|
49
|
+
async with async_playwright() as p:
|
|
50
|
+
browser = await p.chromium.connect_over_cdp(
|
|
51
|
+
connection['ws_url'],
|
|
52
|
+
timeout=150000
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# 等待一段时间(可选)
|
|
56
|
+
# await asyncio.sleep(10)
|
|
57
|
+
|
|
58
|
+
# 创建第一个页面并访问网站
|
|
59
|
+
page1 = await browser.new_page()
|
|
60
|
+
await page1.goto('https://getgrasp.ai/', wait_until='domcontentloaded')
|
|
61
|
+
await page1.screenshot(path='grasp-ai.png')
|
|
62
|
+
await page1.close()
|
|
63
|
+
|
|
64
|
+
# 获取或创建上下文
|
|
65
|
+
contexts = browser.contexts
|
|
66
|
+
context = contexts[0] if contexts else await browser.new_context()
|
|
67
|
+
|
|
68
|
+
# 创建第二个页面
|
|
69
|
+
page2 = await context.new_page()
|
|
70
|
+
|
|
71
|
+
# 将 HTML 字符串渲染到页面中
|
|
72
|
+
await page2.set_content('<h1>Hello Grasp</h1>', wait_until='networkidle')
|
|
73
|
+
|
|
74
|
+
# 等待特定元素可见(可选)
|
|
75
|
+
# await page2.wait_for_selector('#my-element')
|
|
76
|
+
|
|
77
|
+
# 截图演示
|
|
78
|
+
await page2.screenshot(path='hello-world.png', full_page=True)
|
|
79
|
+
|
|
80
|
+
# 清理资源
|
|
81
|
+
await page2.close()
|
|
82
|
+
await context.close()
|
|
83
|
+
await browser.close()
|
|
84
|
+
|
|
85
|
+
# print('⏳ 等待10秒...')
|
|
86
|
+
# await asyncio.sleep(10)
|
|
87
|
+
print('✅ 任务完成。')
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"❌ 执行过程中出现错误: {str(e)}")
|
|
91
|
+
raise
|
|
92
|
+
|
|
93
|
+
finally:
|
|
94
|
+
# 注意:使用 launch_browser 函数时,资源会在程序退出时自动清理
|
|
95
|
+
# 如果需要手动清理,可以使用 GraspServer 类的实例方法
|
|
96
|
+
print("程序结束,资源将自动清理")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == '__main__':
|
|
100
|
+
# 运行主函数
|
|
101
|
+
asyncio.run(main())
|
|
@@ -24,7 +24,7 @@ from .models import (
|
|
|
24
24
|
SandboxStatus,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
__version__ = "0.1.
|
|
27
|
+
__version__ = "0.1.1"
|
|
28
28
|
__author__ = "Grasp Team"
|
|
29
29
|
__email__ = "team@grasp.dev"
|
|
30
30
|
|
|
@@ -40,6 +40,19 @@ class GraspServer:
|
|
|
40
40
|
"""
|
|
41
41
|
if sandbox_config is None:
|
|
42
42
|
sandbox_config = {}
|
|
43
|
+
|
|
44
|
+
# Extract browser-specific options
|
|
45
|
+
browser_type = sandbox_config.pop('type', 'chromium')
|
|
46
|
+
headless = sandbox_config.pop('headless', True)
|
|
47
|
+
adblock = sandbox_config.pop('adblock', False)
|
|
48
|
+
|
|
49
|
+
self.__browser_type = browser_type
|
|
50
|
+
|
|
51
|
+
# Create browser task
|
|
52
|
+
self.__browser_config = {
|
|
53
|
+
'headless': headless,
|
|
54
|
+
'envs': {'ADBLOCK': 'true' if adblock else 'false'}
|
|
55
|
+
}
|
|
43
56
|
|
|
44
57
|
config = get_config()
|
|
45
58
|
config['sandbox'].update(sandbox_config)
|
|
@@ -55,6 +68,22 @@ class GraspServer:
|
|
|
55
68
|
f'GraspE2B initialized (templateId: {config["sandbox"]["templateId"]})'
|
|
56
69
|
)
|
|
57
70
|
|
|
71
|
+
async def __aenter__(self):
|
|
72
|
+
connection = await self.create_browser_task()
|
|
73
|
+
|
|
74
|
+
# Register server
|
|
75
|
+
# if connection['id']:
|
|
76
|
+
# _servers[connection['id']] = self
|
|
77
|
+
|
|
78
|
+
return connection
|
|
79
|
+
|
|
80
|
+
async def __aexit__(self, exc_type, exc, tb):
|
|
81
|
+
if self.browser_service and self.browser_service.id:
|
|
82
|
+
service_id = self.browser_service.id
|
|
83
|
+
self.logger.info(f'Closing browser service {service_id}')
|
|
84
|
+
await _servers[service_id].cleanup()
|
|
85
|
+
del _servers[service_id]
|
|
86
|
+
|
|
58
87
|
@property
|
|
59
88
|
def sandbox(self) -> Optional[SandboxService]:
|
|
60
89
|
"""Get the underlying sandbox service.
|
|
@@ -82,8 +111,6 @@ class GraspServer:
|
|
|
82
111
|
|
|
83
112
|
async def create_browser_task(
|
|
84
113
|
self,
|
|
85
|
-
browser_type: Literal['chrome-stable', 'chromium'] = 'chromium',
|
|
86
|
-
config: Optional[Dict[str, Any]] = None
|
|
87
114
|
) -> Dict[str, Any]:
|
|
88
115
|
"""Create and launch a browser task.
|
|
89
116
|
|
|
@@ -100,6 +127,9 @@ class GraspServer:
|
|
|
100
127
|
if self.browser_service:
|
|
101
128
|
raise RuntimeError('Browser service can only be initialized once')
|
|
102
129
|
|
|
130
|
+
config = self.__browser_config
|
|
131
|
+
browser_type = self.__browser_type
|
|
132
|
+
|
|
103
133
|
if config is None:
|
|
104
134
|
config = {}
|
|
105
135
|
|
|
@@ -132,6 +162,12 @@ class GraspServer:
|
|
|
132
162
|
browser_config
|
|
133
163
|
)
|
|
134
164
|
await self.browser_service.initialize()
|
|
165
|
+
|
|
166
|
+
# Register server
|
|
167
|
+
_servers[str(self.browser_service.id)] = self
|
|
168
|
+
self.logger.info("🚀 Browser service initialized", {
|
|
169
|
+
'id': self.browser_service.id,
|
|
170
|
+
})
|
|
135
171
|
|
|
136
172
|
self.logger.info('🌐 Launching Chromium browser with CDP...')
|
|
137
173
|
cdp_connection = await self.browser_service.launch_browser(browser_type)
|
|
@@ -180,25 +216,11 @@ async def launch_browser(
|
|
|
180
216
|
|
|
181
217
|
Returns:
|
|
182
218
|
Dictionary containing connection information
|
|
183
|
-
"""
|
|
184
|
-
if options is None:
|
|
185
|
-
options = {}
|
|
186
|
-
|
|
187
|
-
# Extract browser-specific options
|
|
188
|
-
browser_type = options.pop('type', 'chromium')
|
|
189
|
-
headless = options.pop('headless', True)
|
|
190
|
-
adblock = options.pop('adblock', False)
|
|
191
|
-
|
|
219
|
+
"""
|
|
192
220
|
# Create server instance
|
|
193
221
|
server = GraspServer(options)
|
|
194
222
|
|
|
195
|
-
|
|
196
|
-
browser_config = {
|
|
197
|
-
'headless': headless,
|
|
198
|
-
'envs': {'ADBLOCK': 'true' if adblock else 'false'}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
connection = await server.create_browser_task(browser_type, browser_config)
|
|
223
|
+
connection = await server.create_browser_task()
|
|
202
224
|
|
|
203
225
|
# Register server
|
|
204
226
|
if connection['id']:
|
|
@@ -258,5 +280,5 @@ __all__ = [
|
|
|
258
280
|
|
|
259
281
|
# Default export equivalent
|
|
260
282
|
default = {
|
|
261
|
-
'
|
|
283
|
+
'GraspServer': GraspServer,
|
|
262
284
|
}
|