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.

@@ -25,20 +25,20 @@ class CDPConnection:
25
25
  ws_url: str,
26
26
  http_url: str,
27
27
  port: int,
28
- pid: Optional[int] = None
28
+ id: str,
29
29
  ):
30
30
  self.ws_url = ws_url
31
31
  self.http_url = http_url
32
32
  self.port = port
33
- self.pid = pid
33
+ self.id = id
34
34
 
35
35
  def to_dict(self) -> Dict[str, Any]:
36
36
  """Convert to dictionary representation."""
37
37
  return {
38
+ 'id': self.id,
38
39
  'wsUrl': self.ws_url,
39
40
  'httpUrl': self.http_url,
40
- 'port': self.port,
41
- 'pid': self.pid
41
+ 'port': self.port
42
42
  }
43
43
 
44
44
 
@@ -77,6 +77,7 @@ class BrowserService:
77
77
  self.cdp_connection: Optional[CDPConnection] = None
78
78
  self.browser_process: Optional[CommandEventEmitter] = None
79
79
  self._health_check_task: Optional[asyncio.Task] = None
80
+ self.config['envs']['APIKEY'] = sandbox_config['key']
80
81
 
81
82
  def _get_default_logger(self):
82
83
  """Gets or creates a default logger instance."""
@@ -99,24 +100,54 @@ class BrowserService:
99
100
  """
100
101
  self.logger.info('Initializing Browser service')
101
102
  envs = {
102
- 'CDP_PORT': '9222',
103
103
  'BROWSER_ARGS': json.dumps(self.config['args']),
104
104
  'LAUNCH_TIMEOUT': str(self.config['launchTimeout']),
105
105
  'SANDBOX_TIMEOUT': str(self.sandbox_service.timeout),
106
106
  'HEADLESS': str(self.config['headless']).lower(),
107
107
  'NODE_ENV': 'production',
108
108
  # 'SANDBOX_ID': self.sandbox_service.id,
109
+ 'PLAYWRIGHT_BROWSERS_PATH': '0',
109
110
  'WORKSPACE': self.sandbox_service.workspace,
110
- 'BS_SOURCE_TOKEN': 'Qth8JGboEKVersqr1PSsUFMW',
111
- 'BS_INGESTING_HOST': 's1363065.eu-nbg-2.betterstackdata.com',
112
- 'SENTRY_DSN': 'https://21fa729ceb72d7f0adef06b4f786c067@o4509574910509056.ingest.us.sentry.io/4509574913720320',
111
+ 'BROWSER_TYPE': browser_type,
113
112
  **self.config['envs']
114
113
  }
115
- await self.sandbox_service.create_sandbox(f'grasp-run-{browser_type}', envs)
116
- if(self.sandbox_service.sandbox is not None):
117
- await self.sandbox_service.sandbox.files.write('/home/user/.sandbox_id', self.id)
114
+ await self.sandbox_service.create_sandbox(f'grasp-run-{browser_type}-v2', envs)
115
+ # if(self.sandbox_service.sandbox is not None):
116
+ # await self.sandbox_service.sandbox.files.write('/home/user/.sandbox_id', self.id)
118
117
  self.logger.info('Grasp sandbox initialized successfully')
119
118
 
119
+ async def connect(self, sandbox_id: str) -> CDPConnection:
120
+ """Connect to an existing sandbox.
121
+
122
+ Args:
123
+ sandbox_id: ID of the sandbox to connect to
124
+
125
+ Returns:
126
+ CDP connection information
127
+ """
128
+ self.logger.info('Initializing Browser service')
129
+ await self.sandbox_service.connect_sandbox(sandbox_id)
130
+ self.logger.info('Browser service initialized successfully')
131
+
132
+ # Read CDP connection info from file
133
+ if not self.sandbox_service.sandbox:
134
+ raise RuntimeError('Sandbox is not available')
135
+
136
+ cdp_content = await self.sandbox_service.sandbox.files.read(
137
+ '/home/user/.grasp-cdp.json'
138
+ )
139
+ cdp_data = json.loads(cdp_content)
140
+
141
+ # Create CDPConnection object
142
+ self.cdp_connection = CDPConnection(
143
+ ws_url=cdp_data['wsUrl'],
144
+ http_url=cdp_data['httpUrl'],
145
+ port=cdp_data['port'],
146
+ id=cdp_data['id']
147
+ )
148
+
149
+ return self.cdp_connection
150
+
120
151
  async def launch_browser(
121
152
  self,
122
153
  ) -> CDPConnection:
@@ -174,15 +205,22 @@ class BrowserService:
174
205
  # Create CDP connection info
175
206
  self.cdp_connection = result
176
207
 
208
+ # Write CDP connection info to file
209
+ if not self.sandbox_service.sandbox:
210
+ raise RuntimeError('Sandbox is not available')
211
+
212
+ await self.sandbox_service.sandbox.files.write(
213
+ '/home/user/.grasp-cdp.json',
214
+ json.dumps(self.cdp_connection.to_dict())
215
+ )
216
+
177
217
  self.logger.info(
178
218
  f'Chromium browser launched successfully (cdpPort: 9222, wsUrl: {self.cdp_connection.ws_url})'
179
219
  )
180
220
 
181
- # Start health check if not in debug mode
182
- if not self.sandbox_service.is_debug:
183
- self._health_check_task = asyncio.create_task(
184
- self._start_health_check()
185
- )
221
+ self._health_check_task = asyncio.create_task(
222
+ self._start_health_check()
223
+ )
186
224
 
187
225
  return self.cdp_connection
188
226
 
@@ -201,11 +239,11 @@ class BrowserService:
201
239
  def on_stderr(data: str) -> None:
202
240
  self.logger.info(f'Browser stderr: {data}')
203
241
 
204
- def on_exit(exit_code: int) -> None:
205
- self.logger.info(f'Browser process exited (exitCode: {exit_code})')
206
- self.cdp_connection = None
207
- self.browser_process = None
208
- asyncio.create_task(self.sandbox_service.destroy())
242
+ # def on_exit(exit_code: int) -> None:
243
+ # self.logger.info(f'Browser process exited (exitCode: {exit_code})')
244
+ # self.cdp_connection = None
245
+ # self.browser_process = None
246
+ # asyncio.create_task(self.sandbox_service.destroy())
209
247
 
210
248
  def on_error(error: Exception) -> None:
211
249
  self.logger.error(f'Browser process error: {error}')
@@ -214,7 +252,7 @@ class BrowserService:
214
252
  if hasattr(self.browser_process, 'on'):
215
253
  self.browser_process.on('stdout', on_stdout)
216
254
  self.browser_process.on('stderr', on_stderr)
217
- self.browser_process.on('exit', on_exit)
255
+ # self.browser_process.on('exit', on_exit)
218
256
  self.browser_process.on('error', on_error)
219
257
 
220
258
  async def _wait_for_cdp_ready(self) -> CDPConnection:
@@ -252,7 +290,7 @@ class BrowserService:
252
290
  ws_url = metadata['webSocketDebuggerUrl'].replace(
253
291
  'ws://', 'wss://'
254
292
  ).replace(
255
- f'localhost:9222', host
293
+ '127.0.0.1:9222', host
256
294
  )
257
295
 
258
296
  http_url = f'https://{host}'
@@ -260,7 +298,8 @@ class BrowserService:
260
298
  connection = CDPConnection(
261
299
  ws_url=ws_url,
262
300
  http_url=http_url,
263
- port=9222
301
+ port=9222,
302
+ id=str(self.sandbox_service.id)
264
303
  )
265
304
 
266
305
  self.logger.info(f'CDP server is ready (metadata: {metadata})')
@@ -338,8 +377,9 @@ class BrowserService:
338
377
  self._health_check_task = None
339
378
 
340
379
  # Kill the browser process
341
- if hasattr(self.browser_process, 'kill'):
342
- await self.browser_process.kill()
380
+ if self.browser_process is not None:
381
+ if hasattr(self.browser_process, 'kill'):
382
+ await self.browser_process.kill()
343
383
 
344
384
  self.browser_process = None
345
385
  self.cdp_connection = None
@@ -347,8 +387,8 @@ class BrowserService:
347
387
  self.logger.info('Chromium browser stopped successfully')
348
388
 
349
389
  except Exception as error:
350
- self.logger.error(f'Error stopping browser: {error}')
351
- raise
390
+ self.logger.debug(f'Error stopping browser: {error}')
391
+ # raise
352
392
 
353
393
  async def cleanup(self) -> None:
354
394
  """Cleanup all resources including Grasp sandbox.
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Filesystem service for managing file operations in sandbox.
4
+
5
+ This module provides Python implementation of the filesystem service,
6
+ equivalent to the TypeScript version in src/services/filesystem.service.ts
7
+ """
8
+
9
+ import asyncio
10
+ from typing import Optional, Dict, Union
11
+ from ..utils.logger import get_logger, Logger
12
+ from .sandbox import SandboxService
13
+ from .browser import CDPConnection
14
+
15
+
16
+ class FileSystemService:
17
+ """
18
+ Filesystem service for managing file operations in sandbox.
19
+ """
20
+
21
+ def __init__(self, sandbox: SandboxService, connection: CDPConnection):
22
+ """Initialize filesystem service.
23
+
24
+ Args:
25
+ sandbox: The sandbox service instance
26
+ connection: The CDP connection for browser integration
27
+ """
28
+ self.sandbox = sandbox
29
+ self.connection = connection
30
+ self.logger = self._get_default_logger()
31
+
32
+ def _get_default_logger(self) -> Logger:
33
+ """Gets or creates a default logger instance.
34
+
35
+ Returns:
36
+ Logger instance
37
+ """
38
+ try:
39
+ return get_logger().child('FileSystemService')
40
+ except Exception:
41
+ # If logger is not initialized, create a default one
42
+ from ..utils.logger import Logger
43
+ default_logger = Logger({
44
+ 'level': 'debug' if self.sandbox.is_debug else 'info',
45
+ 'console': True
46
+ })
47
+ return default_logger.child('FileSystemService')
48
+
49
+ async def upload_file(self, local_path: str, remote_path: str) -> None:
50
+ """Upload file from local to sandbox.
51
+
52
+ Args:
53
+ local_path: Local file path
54
+ remote_path: Remote file path in sandbox
55
+ """
56
+ upload_result = await self.sandbox.upload_file_to_sandbox(local_path, remote_path)
57
+ return upload_result
58
+
59
+ async def download_file(self, remote_path: str, local_path: str) -> None:
60
+ """Download file from sandbox to local.
61
+
62
+ Args:
63
+ remote_path: Remote file path in sandbox
64
+ local_path: Local file path
65
+ """
66
+ download_result = await self.sandbox.copy_file_from_sandbox(remote_path, local_path)
67
+ return download_result
68
+
69
+ async def write_file(self, remote_path: str, content: Union[str, bytes]) -> None:
70
+ """Write content to file in sandbox.
71
+
72
+ Args:
73
+ remote_path: Remote file path in sandbox
74
+ content: File content to write (string or bytes)
75
+ """
76
+ write_result = await self.sandbox.write_file_to_sandbox(remote_path, content)
77
+ return write_result
78
+
79
+ async def read_file(
80
+ self,
81
+ remote_path: str,
82
+ options: Optional[Dict[str, str]] = None
83
+ ) -> Union[str, bytes]:
84
+ """Read content from file in sandbox.
85
+
86
+ Args:
87
+ remote_path: Remote file path in sandbox
88
+ options: Dictionary with 'encoding' key ('utf8', 'base64', or 'binary')
89
+
90
+ Returns:
91
+ File content as string or bytes depending on encoding
92
+ """
93
+ read_result = await self.sandbox.read_file_from_sandbox(remote_path, options)
94
+ return read_result
95
+
96
+ async def sync_downloads_directory(
97
+ self,
98
+ local_path: str,
99
+ remote_path: Optional[str] = None
100
+ ) -> str:
101
+ """Synchronize downloads directory from sandbox to local filesystem.
102
+
103
+ This method is experimental and may change or be removed in future versions.
104
+ Use with caution in production environments.
105
+
106
+ Args:
107
+ local_path: Local directory path to sync to
108
+ remote_path: Remote directory path to sync from (defaults to local_path)
109
+
110
+ Returns:
111
+ Local sync directory path
112
+
113
+ Raises:
114
+ RuntimeError: If sandbox is not running or sync fails
115
+ """
116
+ if remote_path is None:
117
+ remote_path = local_path
118
+
119
+ sync_result = await self.sandbox.sync_downloads_directory(
120
+ dist=local_path,
121
+ src=remote_path
122
+ )
123
+ return sync_result