grasp-sdk 0.1.0__py3-none-any.whl
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/__init__.py +262 -0
- grasp_sdk/models/__init__.py +78 -0
- grasp_sdk/sandbox/chrome-stable.mjs +381 -0
- grasp_sdk/sandbox/chromium.mjs +378 -0
- grasp_sdk/sandbox/jsconfig.json +22 -0
- grasp_sdk/services/__init__.py +8 -0
- grasp_sdk/services/browser.py +414 -0
- grasp_sdk/services/sandbox.py +583 -0
- grasp_sdk/utils/__init__.py +31 -0
- grasp_sdk/utils/auth.py +227 -0
- grasp_sdk/utils/config.py +150 -0
- grasp_sdk/utils/logger.py +233 -0
- grasp_sdk-0.1.0.dist-info/METADATA +201 -0
- grasp_sdk-0.1.0.dist-info/RECORD +17 -0
- grasp_sdk-0.1.0.dist-info/WHEEL +5 -0
- grasp_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- grasp_sdk-0.1.0.dist-info/top_level.txt +1 -0
grasp_sdk/__init__.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Grasp E2B Python SDK
|
|
2
|
+
|
|
3
|
+
A Python SDK for E2B platform providing secure command execution
|
|
4
|
+
and browser automation in isolated cloud environments.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import signal
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Dict, Optional, Any, Literal
|
|
11
|
+
|
|
12
|
+
# Import utilities
|
|
13
|
+
from .utils.logger import init_logger, get_logger
|
|
14
|
+
from .utils.config import get_config
|
|
15
|
+
from .services.browser import BrowserService
|
|
16
|
+
from .services.sandbox import SandboxService
|
|
17
|
+
|
|
18
|
+
# Import models and types
|
|
19
|
+
from .models import (
|
|
20
|
+
ISandboxConfig,
|
|
21
|
+
IBrowserConfig,
|
|
22
|
+
ICommandOptions,
|
|
23
|
+
IScriptOptions,
|
|
24
|
+
SandboxStatus,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = "0.1.0"
|
|
28
|
+
__author__ = "Grasp Team"
|
|
29
|
+
__email__ = "team@grasp.dev"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class GraspServer:
|
|
33
|
+
"""Main Grasp E2B class for browser automation."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, sandbox_config: Optional[Dict[str, Any]] = None):
|
|
36
|
+
"""Initialize GraspServer with configuration.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
sandbox_config: Optional sandbox configuration overrides
|
|
40
|
+
"""
|
|
41
|
+
if sandbox_config is None:
|
|
42
|
+
sandbox_config = {}
|
|
43
|
+
|
|
44
|
+
config = get_config()
|
|
45
|
+
config['sandbox'].update(sandbox_config)
|
|
46
|
+
self.config = config
|
|
47
|
+
|
|
48
|
+
# Initialize logger first
|
|
49
|
+
init_logger(config['logger'])
|
|
50
|
+
self.logger = get_logger().child('GraspE2B')
|
|
51
|
+
|
|
52
|
+
self.browser_service: Optional[BrowserService] = None
|
|
53
|
+
|
|
54
|
+
self.logger.info(
|
|
55
|
+
f'GraspE2B initialized (templateId: {config["sandbox"]["templateId"]})'
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def sandbox(self) -> Optional[SandboxService]:
|
|
60
|
+
"""Get the underlying sandbox service.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
SandboxService instance or None
|
|
64
|
+
"""
|
|
65
|
+
return self.browser_service.get_sandbox() if self.browser_service else None
|
|
66
|
+
|
|
67
|
+
def get_status(self) -> Optional[SandboxStatus]:
|
|
68
|
+
"""Get current sandbox status.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Sandbox status or None
|
|
72
|
+
"""
|
|
73
|
+
return self.sandbox.get_status() if self.sandbox else None
|
|
74
|
+
|
|
75
|
+
def get_sandbox_id(self) -> Optional[str]:
|
|
76
|
+
"""Get sandbox ID.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Sandbox ID or None
|
|
80
|
+
"""
|
|
81
|
+
return self.sandbox.get_sandbox_id() if self.sandbox else None
|
|
82
|
+
|
|
83
|
+
async def create_browser_task(
|
|
84
|
+
self,
|
|
85
|
+
browser_type: Literal['chrome-stable', 'chromium'] = 'chromium',
|
|
86
|
+
config: Optional[Dict[str, Any]] = None
|
|
87
|
+
) -> Dict[str, Any]:
|
|
88
|
+
"""Create and launch a browser task.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
browser_type: Type of browser to launch
|
|
92
|
+
config: Browser configuration overrides
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dictionary containing browser connection info
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
RuntimeError: If browser service is already initialized
|
|
99
|
+
"""
|
|
100
|
+
if self.browser_service:
|
|
101
|
+
raise RuntimeError('Browser service can only be initialized once')
|
|
102
|
+
|
|
103
|
+
if config is None:
|
|
104
|
+
config = {}
|
|
105
|
+
|
|
106
|
+
# Create base browser config
|
|
107
|
+
browser_config: IBrowserConfig = {
|
|
108
|
+
'cdpPort': 9222,
|
|
109
|
+
'headless': True,
|
|
110
|
+
'launchTimeout': 30000,
|
|
111
|
+
'args': [
|
|
112
|
+
'--disable-web-security',
|
|
113
|
+
'--disable-features=VizDisplayCompositor',
|
|
114
|
+
],
|
|
115
|
+
'envs': {},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Apply user config overrides with type safety
|
|
119
|
+
if 'cdpPort' in config:
|
|
120
|
+
browser_config['cdpPort'] = config['cdpPort']
|
|
121
|
+
if 'headless' in config:
|
|
122
|
+
browser_config['headless'] = config['headless']
|
|
123
|
+
if 'launchTimeout' in config:
|
|
124
|
+
browser_config['launchTimeout'] = config['launchTimeout']
|
|
125
|
+
if 'args' in config:
|
|
126
|
+
browser_config['args'] = config['args']
|
|
127
|
+
if 'envs' in config:
|
|
128
|
+
browser_config['envs'] = config['envs']
|
|
129
|
+
|
|
130
|
+
self.browser_service = BrowserService(
|
|
131
|
+
self.config['sandbox'],
|
|
132
|
+
browser_config
|
|
133
|
+
)
|
|
134
|
+
await self.browser_service.initialize()
|
|
135
|
+
|
|
136
|
+
self.logger.info('🌐 Launching Chromium browser with CDP...')
|
|
137
|
+
cdp_connection = await self.browser_service.launch_browser(browser_type)
|
|
138
|
+
|
|
139
|
+
self.logger.info('✅ Browser launched successfully!')
|
|
140
|
+
self.logger.debug(
|
|
141
|
+
f'CDP Connection Info (wsUrl: {cdp_connection.ws_url}, httpUrl: {cdp_connection.http_url})'
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
'id': self.browser_service.id,
|
|
146
|
+
'ws_url': cdp_connection.ws_url,
|
|
147
|
+
'http_url': cdp_connection.http_url
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async def cleanup(self) -> None:
|
|
151
|
+
"""Cleanup resources.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Promise that resolves when cleanup is complete
|
|
155
|
+
"""
|
|
156
|
+
self.logger.info('Starting cleanup process')
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
# Cleanup browser service
|
|
160
|
+
if self.browser_service:
|
|
161
|
+
await self.browser_service.cleanup()
|
|
162
|
+
|
|
163
|
+
self.logger.info('Cleanup completed successfully')
|
|
164
|
+
except Exception as error:
|
|
165
|
+
self.logger.error(f'Cleanup failed: {error}')
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Global server registry
|
|
170
|
+
_servers: Dict[str, GraspServer] = {}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def launch_browser(
|
|
174
|
+
options: Optional[Dict[str, Any]] = None
|
|
175
|
+
) -> Dict[str, Any]:
|
|
176
|
+
"""Launch a browser instance.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
options: Launch options including type, headless, adblock settings
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
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
|
+
|
|
192
|
+
# Create server instance
|
|
193
|
+
server = GraspServer(options)
|
|
194
|
+
|
|
195
|
+
# Create browser task
|
|
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)
|
|
202
|
+
|
|
203
|
+
# Register server
|
|
204
|
+
if connection['id']:
|
|
205
|
+
_servers[connection['id']] = server
|
|
206
|
+
|
|
207
|
+
return connection
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def _graceful_shutdown(signal_name: str) -> None:
|
|
211
|
+
"""Handle graceful shutdown.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
signal_name: Name of the signal received
|
|
215
|
+
"""
|
|
216
|
+
print(f'Received {signal_name}, starting cleanup...')
|
|
217
|
+
|
|
218
|
+
# Cleanup all GraspServer instances
|
|
219
|
+
for server_id in list(_servers.keys()):
|
|
220
|
+
await _servers[server_id].cleanup()
|
|
221
|
+
del _servers[server_id]
|
|
222
|
+
|
|
223
|
+
print('All servers cleaned up, exiting...')
|
|
224
|
+
sys.exit(0)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _setup_signal_handlers() -> None:
|
|
228
|
+
"""Setup signal handlers for graceful shutdown."""
|
|
229
|
+
def signal_handler(signum, frame):
|
|
230
|
+
signal_name = signal.Signals(signum).name
|
|
231
|
+
asyncio.create_task(_graceful_shutdown(signal_name))
|
|
232
|
+
|
|
233
|
+
# Register signal handlers
|
|
234
|
+
signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
|
|
235
|
+
signal.signal(signal.SIGTERM, signal_handler) # Termination signal
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Setup signal handlers on import
|
|
239
|
+
_setup_signal_handlers()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Export all public APIs
|
|
243
|
+
__all__ = [
|
|
244
|
+
'GraspServer',
|
|
245
|
+
'launch_browser',
|
|
246
|
+
'ISandboxConfig',
|
|
247
|
+
'IBrowserConfig',
|
|
248
|
+
'ICommandOptions',
|
|
249
|
+
'IScriptOptions',
|
|
250
|
+
'SandboxStatus',
|
|
251
|
+
'get_config',
|
|
252
|
+
'init_logger',
|
|
253
|
+
'get_logger',
|
|
254
|
+
'BrowserService',
|
|
255
|
+
'SandboxService',
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# Default export equivalent
|
|
260
|
+
default = {
|
|
261
|
+
'launch_browser': launch_browser,
|
|
262
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Type definitions for Grasp SDK Python implementation.
|
|
2
|
+
|
|
3
|
+
This module contains TypedDict classes and enums that correspond to
|
|
4
|
+
the TypeScript interfaces in the Node.js version.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TypedDict, Optional, Dict, Any, List, Union
|
|
8
|
+
from typing_extensions import NotRequired
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SandboxStatus(Enum):
|
|
13
|
+
"""Sandbox status enumeration."""
|
|
14
|
+
CREATING = "creating"
|
|
15
|
+
RUNNING = "running"
|
|
16
|
+
STOPPED = "stopped"
|
|
17
|
+
ERROR = "error"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ISandboxConfig(TypedDict):
|
|
21
|
+
"""Sandbox configuration interface."""
|
|
22
|
+
key: str # Required: Grasp API key
|
|
23
|
+
templateId: str # Required: Sandbox template ID
|
|
24
|
+
timeout: int # Required: Default timeout in milliseconds
|
|
25
|
+
workspace: NotRequired[str] # Optional: Grasp workspace ID
|
|
26
|
+
debug: NotRequired[bool] # Optional: Enable debug mode for detailed logging
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class IBrowserConfig(TypedDict):
|
|
30
|
+
"""Browser service configuration interface."""
|
|
31
|
+
cdpPort: int # Required: Port for CDP server (default: 9222)
|
|
32
|
+
args: List[str] # Required: Chromium launch arguments
|
|
33
|
+
headless: bool # Required: Headless mode (default: true)
|
|
34
|
+
launchTimeout: int # Required: Timeout for browser launch (default: 30000ms)
|
|
35
|
+
envs: Dict[str, str] # Required: The environment variables
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ICommandOptions(TypedDict):
|
|
39
|
+
"""Command execution options interface."""
|
|
40
|
+
inBackground: NotRequired[bool] # Whether to run command in background
|
|
41
|
+
timeout: NotRequired[int] # Timeout in milliseconds
|
|
42
|
+
cwd: NotRequired[str] # Working directory
|
|
43
|
+
nohup: NotRequired[bool] # Whether to use nohup for command execution
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class IScriptOptions(TypedDict):
|
|
47
|
+
"""Script execution options interface."""
|
|
48
|
+
type: str # Required: Script type: 'cjs' for CommonJS, 'esm' for ES Modules
|
|
49
|
+
cwd: NotRequired[str] # Working directory
|
|
50
|
+
timeoutMs: NotRequired[int] # Timeout in milliseconds
|
|
51
|
+
background: NotRequired[bool] # Run in background
|
|
52
|
+
nohup: NotRequired[bool] # Use nohup for background execution
|
|
53
|
+
envs: NotRequired[Dict[str, str]] # The environment variables
|
|
54
|
+
preCommand: NotRequired[str] # Pre command
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ILoggerConfig(TypedDict):
|
|
58
|
+
"""Logger configuration interface."""
|
|
59
|
+
level: str # Required: Log level ('debug' | 'info' | 'warn' | 'error')
|
|
60
|
+
console: bool # Required: Enable console output
|
|
61
|
+
file: NotRequired[str] # Optional: Log file path
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class IAppConfig(TypedDict):
|
|
65
|
+
"""Application configuration interface."""
|
|
66
|
+
sandbox: ISandboxConfig # Required: E2B configuration
|
|
67
|
+
logger: ILoggerConfig # Required: Logger configuration
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
'SandboxStatus',
|
|
72
|
+
'ISandboxConfig',
|
|
73
|
+
'IBrowserConfig',
|
|
74
|
+
'ICommandOptions',
|
|
75
|
+
'IScriptOptions',
|
|
76
|
+
'ILoggerConfig',
|
|
77
|
+
'IAppConfig',
|
|
78
|
+
]
|