grasp-sdk 0.1.9__py3-none-any.whl → 0.2.0b1__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.

@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Terminal service for managing terminal command operations.
4
+
5
+ This module provides Python implementation of the terminal service,
6
+ equivalent to the TypeScript version in src/services/terminal.service.ts
7
+ """
8
+
9
+ import asyncio
10
+ import io
11
+ from typing import Optional, Any, Dict, Callable
12
+ from ..models import ICommandOptions
13
+ from ..utils.logger import get_logger, Logger
14
+ from .sandbox import SandboxService, CommandEventEmitter
15
+ from .browser import CDPConnection
16
+
17
+
18
+ class StreamableCommandResult:
19
+ """
20
+ Streamable command result with event-based output streaming and json result.
21
+ """
22
+
23
+ def __init__(self, emitter: CommandEventEmitter, task):
24
+ self.emitter = emitter
25
+ self._task = task
26
+ # For backward compatibility, provide stdout/stderr as StringIO
27
+ self.stdout = io.StringIO()
28
+ self.stderr = io.StringIO()
29
+
30
+ # Set up internal event handlers to populate StringIO streams
31
+ def on_stdout(data: str) -> None:
32
+ self.stdout.write(data)
33
+ self.stdout.flush()
34
+
35
+ def on_stderr(data: str) -> None:
36
+ self.stderr.write(data)
37
+ self.stderr.flush()
38
+
39
+ # Register internal handlers
40
+ self.emitter.on('stdout', on_stdout)
41
+ self.emitter.on('stderr', on_stderr)
42
+
43
+ def on(self, event: str, callback: Callable) -> None:
44
+ """Register event listener for stdout, stderr, or exit events.
45
+
46
+ Args:
47
+ event: Event name ('stdout', 'stderr', 'exit')
48
+ callback: Callback function to handle the event
49
+ """
50
+ self.emitter.on(event, callback)
51
+
52
+ def off(self, event: str, callback: Callable) -> None:
53
+ """Remove event listener.
54
+
55
+ Args:
56
+ event: Event name ('stdout', 'stderr', 'exit')
57
+ callback: Callback function to remove
58
+ """
59
+ self.emitter.off(event, callback)
60
+
61
+ async def end(self) -> None:
62
+ """Wait until the command finishes."""
63
+ try:
64
+ await self._task
65
+ except Exception as e:
66
+ # Log error but don't raise to avoid breaking cleanup
67
+ pass
68
+
69
+ async def kill(self) -> None:
70
+ """Kill the running command and cleanup resources."""
71
+ try:
72
+ await self.emitter.kill()
73
+ # Close the streams
74
+ self.stdout.close()
75
+ self.stderr.close()
76
+ except Exception as e:
77
+ # Log error but don't raise to avoid breaking cleanup
78
+ pass
79
+
80
+ async def json(self) -> Any:
81
+ """Get the final command result as JSON."""
82
+ try:
83
+ return await self._task
84
+ except Exception as e:
85
+ # 如果任务中发生异常,返回异常信息作为结果
86
+ return {
87
+ 'error': str(e),
88
+ 'exit_code': getattr(e, 'exit_code', -1),
89
+ 'stdout': '',
90
+ 'stderr': str(e)
91
+ }
92
+
93
+
94
+ class Commands:
95
+ """
96
+ Command execution operations for terminal service.
97
+ """
98
+
99
+ def __init__(self, sandbox: SandboxService, connection: CDPConnection):
100
+ self.sandbox = sandbox
101
+ self.connection = connection
102
+ self.logger = self._get_default_logger()
103
+
104
+ def _get_default_logger(self) -> Logger:
105
+ """Gets or creates a default logger instance.
106
+
107
+ Returns:
108
+ Logger instance
109
+ """
110
+ try:
111
+ return get_logger().child('TerminalService')
112
+ except Exception:
113
+ # If logger is not initialized, create a default one
114
+ from ..utils.logger import Logger
115
+ default_logger = Logger({
116
+ 'level': 'debug' if self.sandbox.is_debug else 'info',
117
+ 'console': True
118
+ })
119
+ return default_logger.child('TerminalService')
120
+
121
+ async def run_command(
122
+ self, command: str, options: Optional[ICommandOptions] = None
123
+ ) -> StreamableCommandResult:
124
+ """Run command in sandbox with streaming output.
125
+
126
+ Args:
127
+ command: Command to execute
128
+ options: Command execution options
129
+
130
+ Returns:
131
+ StreamableCommandResult with event-based streaming
132
+ """
133
+ ws = None
134
+ try:
135
+ if options is None:
136
+ options = {}
137
+
138
+ # Force background execution for streaming
139
+ bg_options: ICommandOptions = {**options, 'inBackground': True, 'nohup': False}
140
+
141
+ # Set browser endpoint in environment variables
142
+ if 'envs' not in bg_options:
143
+ bg_options['envs'] = {}
144
+ bg_options['envs']['BROWSER_ENDPOINT'] = self.connection.ws_url
145
+
146
+ emitter: Optional[CommandEventEmitter] = await self.sandbox.run_command(
147
+ command, bg_options
148
+ )
149
+
150
+ if emitter is None:
151
+ raise RuntimeError(f"Failed to start command: {command}")
152
+
153
+ # Create task for final result
154
+ async def get_result():
155
+ result = await emitter.wait()
156
+ return result
157
+
158
+ task = asyncio.create_task(get_result())
159
+
160
+ return StreamableCommandResult(emitter, task)
161
+ except Exception as error:
162
+ self.logger.error('Run command failed', {'error': str(error)})
163
+ raise error
164
+
165
+
166
+ class TerminalService(Commands):
167
+ """
168
+ Terminal service for managing terminal command operations.
169
+ """
170
+
171
+ def __init__(self, sandbox: SandboxService, connection: CDPConnection):
172
+ """Initialize terminal service.
173
+
174
+ Args:
175
+ sandbox: The sandbox service instance
176
+ connection: The CDP connection for browser integration
177
+ """
178
+ super().__init__(sandbox, connection)
179
+
180
+ def close(self) -> None:
181
+ """Close terminal service and cleanup resources."""
182
+ self.logger.info('Terminal service closed')
@@ -4,7 +4,7 @@ This package contains utility functions and classes for configuration,
4
4
  logging, authentication, and other common functionality."""
5
5
 
6
6
  # Import all utility modules
7
- from .config import get_config, get_sandbox_config, get_browser_config, DEFAULT_CONFIG
7
+ from .config import get_config, get_sandbox_config, DEFAULT_CONFIG
8
8
  from .logger import Logger, init_logger, get_logger, debug, info, warn, error
9
9
  from .auth import verify, login, AuthError, KeyVerificationError, AuthManager
10
10
 
@@ -12,7 +12,6 @@ __all__ = [
12
12
  # Configuration
13
13
  'get_config',
14
14
  'get_sandbox_config',
15
- 'get_browser_config',
16
15
  'DEFAULT_CONFIG',
17
16
  # Logging
18
17
  'Logger',
grasp_sdk/utils/auth.py CHANGED
@@ -16,8 +16,6 @@ except ImportError:
16
16
  if TYPE_CHECKING:
17
17
  from aiohttp import ClientSession
18
18
 
19
- from ..models import ISandboxConfig
20
-
21
19
 
22
20
  class AuthError(Exception):
23
21
  """Exception raised for authentication errors."""
@@ -69,11 +67,11 @@ async def login(token: str) -> Dict[str, Any]:
69
67
  raise AuthError(f"Unexpected error during authentication: {str(e)}")
70
68
 
71
69
 
72
- async def verify(config: ISandboxConfig) -> Dict[str, Any]:
73
- """Verifies the sandbox configuration and authenticates.
70
+ async def verify(config: Dict[str, str]) -> Dict[str, Any]:
71
+ """Verifies the configuration and authenticates.
74
72
 
75
73
  Args:
76
- config: Sandbox configuration containing the API key
74
+ config: Configuration containing the API key (format: {'key': 'your-key'})
77
75
 
78
76
  Returns:
79
77
  Dict[str, Any]: Authentication response
@@ -82,7 +80,7 @@ async def verify(config: ISandboxConfig) -> Dict[str, Any]:
82
80
  KeyVerificationError: If the key is missing or invalid
83
81
  AuthError: If authentication fails
84
82
  """
85
- if not config['key']:
83
+ if not config.get('key'):
86
84
  raise KeyVerificationError('Grasp key is required')
87
85
 
88
86
  try:
@@ -93,11 +91,11 @@ async def verify(config: ISandboxConfig) -> Dict[str, Any]:
93
91
  raise KeyVerificationError(f"Key verification failed: {str(e)}")
94
92
 
95
93
 
96
- def verify_sync(config: ISandboxConfig) -> Dict[str, Any]:
94
+ def verify_sync(config: Dict[str, str]) -> Dict[str, Any]:
97
95
  """Synchronous wrapper for the verify function.
98
96
 
99
97
  Args:
100
- config: Sandbox configuration containing the API key
98
+ config: Configuration containing the API key (format: {'key': 'your-key'})
101
99
 
102
100
  Returns:
103
101
  Dict[str, Any]: Authentication response
grasp_sdk/utils/config.py CHANGED
@@ -25,9 +25,10 @@ def get_config() -> Dict[str, Any]:
25
25
  'key': os.getenv('GRASP_KEY', ''),
26
26
  'timeout': int(os.getenv('GRASP_SERVICE_TIMEOUT', '900000')),
27
27
  'debug': os.getenv('GRASP_DEBUG', 'false').lower() == 'true',
28
+ 'keepAliveMs': int(os.getenv('GRASP_KEEP_ALIVE_MS', '10000')),
28
29
  },
29
30
  'logger': {
30
- 'level': os.getenv('GRASP_LOG_LEVEL', 'info'),
31
+ 'level': os.getenv('GRASP_LOG_LEVEL', None),
31
32
  'console': True,
32
33
  'file': os.getenv('GRASP_LOG_FILE'),
33
34
  },
@@ -54,40 +55,9 @@ def get_sandbox_config() -> ISandboxConfig:
54
55
 
55
56
  return sandbox_config
56
57
 
57
-
58
- def get_browser_config() -> IBrowserConfig:
59
- """Gets browser-specific configuration.
60
-
61
- Returns:
62
- IBrowserConfig: Browser configuration object.
63
- """
64
- return IBrowserConfig(
65
- args=[
66
- '--no-sandbox',
67
- '--disable-setuid-sandbox',
68
- '--disable-dev-shm-usage',
69
- '--disable-gpu',
70
- '--remote-debugging-port=9222',
71
- '--remote-debugging-address=0.0.0.0',
72
- ],
73
- headless=os.getenv('GRASP_HEADLESS', 'true').lower() == 'true',
74
- launchTimeout=int(os.getenv('GRASP_LAUNCH_TIMEOUT', '30000')),
75
- envs={
76
- 'PLAYWRIGHT_BROWSERS_PATH': '0',
77
- 'DISPLAY': ':99',
78
- },
79
- )
80
-
81
-
82
58
  # Default configuration constants
83
59
  DEFAULT_CONFIG = {
84
- 'PLAYWRIGHT_BROWSERS_PATH': '0',
85
60
  'WORKING_DIRECTORY': '/home/user',
86
- 'SCREENSHOT_PATH': '/home/user',
87
- 'DEFAULT_VIEWPORT': {
88
- 'width': 1280,
89
- 'height': 720,
90
- },
91
61
  }
92
62
 
93
63
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grasp_sdk
3
- Version: 0.1.9
3
+ Version: 0.2.0b1
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
@@ -27,6 +27,7 @@ Description-Content-Type: text/markdown
27
27
  Requires-Dist: aiohttp>=3.9.0
28
28
  Requires-Dist: python-dotenv>=1.0.0
29
29
  Requires-Dist: typing-extensions>=4.8.0
30
+ Requires-Dist: websockets>=12.0
30
31
  Requires-Dist: e2b>=1.5.0
31
32
  Requires-Dist: e2b-code-interpreter>=1.5.0
32
33
  Provides-Extra: dev
@@ -37,8 +38,6 @@ Requires-Dist: flake8>=6.0.0; extra == "dev"
37
38
  Requires-Dist: mypy>=1.0.0; extra == "dev"
38
39
  Provides-Extra: browser
39
40
  Requires-Dist: playwright>=1.40.0; extra == "browser"
40
- Provides-Extra: websocket
41
- Requires-Dist: websockets>=12.0; extra == "websocket"
42
41
  Provides-Extra: validation
43
42
  Requires-Dist: pydantic>=2.5.0; extra == "validation"
44
43
  Dynamic: author
@@ -0,0 +1,33 @@
1
+ examples/example_async_context.py,sha256=Ml-WDaKWTZQKeFMoYEPwEFm1W1k7_8Uqyqa6bRTLrNg,4022
2
+ examples/example_binary_file_support.py,sha256=katDD0u8Wd0D3-8QbsNIK7-dnMwWQSPNdl3kqHOQgq0,4432
3
+ examples/example_grasp_usage.py,sha256=mFC04yD_ONaIO_z6jlOvKbgI01WW_F1m17sseiiKTyI,2722
4
+ examples/example_readfile_usage.py,sha256=2KM0lJoMdlroIogaZr_dIqsh_BOrc3GmY1AD2N-2nK8,4505
5
+ examples/grasp_terminal.py,sha256=LRgW1e2jTPkYDytBaM3c1JolEGDIuQMeDZqOW9WhR8k,3442
6
+ examples/grasp_usage.py,sha256=kOq2eU51TX6C8oWXC19-JLWshCssBoc_8Y9h2Yv901Y,3806
7
+ examples/test_async_context.py,sha256=0iDq7ofGM8gUcH5m6MUrgLiRbaEka3mgKO-s9DWZdpM,2379
8
+ examples/test_grasp_classes.py,sha256=MOf3I6V1jZSltHVM1LmZOTkYXLmj3l1CVsUWYooJVVE,3059
9
+ examples/test_python_script.py,sha256=dqJSmvmrq2fdNXynJtMjqMXorrrM08ho6pFibIAZMpI,5265
10
+ examples/test_removed_methods.py,sha256=xGF_AMjoGOB3b70hBosZCnEHj_jEfnvc1yC_fJaI31o,2793
11
+ examples/test_terminal_updates.py,sha256=LihPUOVBOeM5Ee2A4ka4IwZH2vf9ZCiCesvpHWwD2Lk,6694
12
+ grasp_sdk/__init__.py,sha256=1whV0tf-vAQnc0CdU1sza1OWllka0E0tP3giKODhhMo,5161
13
+ grasp_sdk/grasp/__init__.py,sha256=op-QqDUSLjuVhoztmmFfw1ubDDKwAPSAYCTsPFwW6vY,442
14
+ grasp_sdk/grasp/browser.py,sha256=2DWAsDjKpg1MKycQFIpMPHlxVew-Aatdh98Syj-lEkE,5734
15
+ grasp_sdk/grasp/index.py,sha256=PMEFUDvPlW76pf2uBfht6h6rSLpqOrQYuSygx3vfbF8,4352
16
+ grasp_sdk/grasp/server.py,sha256=arAYJZuJNozDuQTCX7HFgO9p9pA_TC-Sz5fz68jnKKY,8053
17
+ grasp_sdk/grasp/session.py,sha256=IxzpWyN8RIxZusgHkAPZGJP81OL2KO62r7gJehPGgng,5579
18
+ grasp_sdk/grasp/utils.py,sha256=j22tUI9yZacsdURObs-a5hxVM7sHsPBO4msRxdVb3MY,2691
19
+ grasp_sdk/models/__init__.py,sha256=M8ZpJARtNqZ5pczXxShUcfieJSCXSrfGARWcsefMi6A,3181
20
+ grasp_sdk/services/__init__.py,sha256=tfiAU211-iMObTflMX4y4TYPU6uXr8g5J0tcc7hLzYs,540
21
+ grasp_sdk/services/browser.py,sha256=oNRmg795NPivlYI5rq17cTHJVV7tVbyhN2pBa03Qv6s,15316
22
+ grasp_sdk/services/filesystem.py,sha256=mDLDqlLA70MnWqTUxnmpVr-6WZJZLZADa42IWjG7QaM,4184
23
+ grasp_sdk/services/sandbox.py,sha256=h6balEQg-CqblkdaqHQwltB-unXPTjAP-dCZGICBFsY,35766
24
+ grasp_sdk/services/terminal.py,sha256=Y7v3H1l_-fQp9C31lEf-57TP1ls9DwpiE7JfumVIDv8,6037
25
+ grasp_sdk/utils/__init__.py,sha256=0Z_1QTXQg4XQhCEgLdnNK0o3q927n15zAHc16QxtAtE,770
26
+ grasp_sdk/utils/auth.py,sha256=W51xyswim48wy2x3xGFFOfWXFAmG4jAwh5wxDgWjKP4,7261
27
+ grasp_sdk/utils/config.py,sha256=FAuAFNXPwtm6E463w6-QUBPDV9VgyfvZlmCncMI_oDQ,3183
28
+ grasp_sdk/utils/logger.py,sha256=k6WDmzL3cPTcQbiiTD4Q6fsDdUO_kyXQHz7nlmtv7G4,7228
29
+ grasp_sdk-0.2.0b1.dist-info/METADATA,sha256=uUyabbGeDiLb50cWVQNZ2pS2H889c-FQaFmKA1-AonU,10121
30
+ grasp_sdk-0.2.0b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ grasp_sdk-0.2.0b1.dist-info/entry_points.txt,sha256=roDjUu4JR6b1tUtRmq018mw167AbfC5zQiOtv3o6IMo,45
32
+ grasp_sdk-0.2.0b1.dist-info/top_level.txt,sha256=TCCFvqgXTCdDpREPkfO5su7ymn5KEA0WuzSSDjvA8Es,19
33
+ grasp_sdk-0.2.0b1.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- grasp_sdk/__init__.py,sha256=JEmSRZJblEfD09I_775_DMTDiEs4owE6cyVCLWr6CLc,8431
2
- grasp_sdk/models/__init__.py,sha256=mUfYvCX2S9Fmir9sTsQVV7LdLIPhq2UXQs5fSTRc0eM,2619
3
- grasp_sdk/services/__init__.py,sha256=HqRD-WRedLwOb2gZPXPFzI-3892VT9IknKSbuDyiss8,327
4
- grasp_sdk/services/browser.py,sha256=cvp-fxKDxwLFz0evJh2FckDrNdnS8vspkZ0NgZghEzY,14003
5
- grasp_sdk/services/sandbox.py,sha256=HdR5e30TW2_TPlsE6IBUIeUGnF8yssbsXGEzHrwdK58,21960
6
- grasp_sdk/utils/__init__.py,sha256=IQzRV-iZJXanSlaXBcgXBCcOXTVBCY6ZujxQpDTGW9w,816
7
- grasp_sdk/utils/auth.py,sha256=M_SX3uKTjjfi6fzk384IqPvUtqt98bif22HHMgio1D4,7258
8
- grasp_sdk/utils/config.py,sha256=NhHnUwmYOaaV_DZVep_BXsKoZPNT_-yRsiV04q83IXw,3967
9
- grasp_sdk/utils/logger.py,sha256=k6WDmzL3cPTcQbiiTD4Q6fsDdUO_kyXQHz7nlmtv7G4,7228
10
- grasp_sdk-0.1.9.dist-info/METADATA,sha256=p--L6e8fH_AqboS--urzcHvrW3_sEso49r5Rl0CcJuU,10167
11
- grasp_sdk-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- grasp_sdk-0.1.9.dist-info/entry_points.txt,sha256=roDjUu4JR6b1tUtRmq018mw167AbfC5zQiOtv3o6IMo,45
13
- grasp_sdk-0.1.9.dist-info/top_level.txt,sha256=C9GL798_aP9Hgjq7UUlaGHLDfUohO2PSGYuGDV0cMx8,10
14
- grasp_sdk-0.1.9.dist-info/RECORD,,