cmdop 0.1.21__py3-none-any.whl → 0.1.23__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.
Files changed (36) hide show
  1. cmdop/__init__.py +1 -1
  2. cmdop/_generated/rpc_messages/browser_pb2.py +135 -85
  3. cmdop/_generated/rpc_messages/browser_pb2.pyi +270 -2
  4. cmdop/_generated/rpc_messages_pb2.pyi +25 -0
  5. cmdop/_generated/service_pb2.py +2 -2
  6. cmdop/_generated/service_pb2_grpc.py +345 -0
  7. cmdop/client.py +2 -8
  8. cmdop/services/browser/__init__.py +44 -31
  9. cmdop/services/browser/capabilities/__init__.py +17 -0
  10. cmdop/services/browser/capabilities/_base.py +28 -0
  11. cmdop/services/browser/capabilities/_helpers.py +16 -0
  12. cmdop/services/browser/capabilities/dom.py +76 -0
  13. cmdop/services/browser/capabilities/fetch.py +45 -0
  14. cmdop/services/browser/capabilities/input.py +49 -0
  15. cmdop/services/browser/capabilities/network.py +245 -0
  16. cmdop/services/browser/capabilities/scroll.py +147 -0
  17. cmdop/services/browser/capabilities/timing.py +66 -0
  18. cmdop/services/browser/js/__init__.py +6 -4
  19. cmdop/services/browser/js/interaction.py +34 -0
  20. cmdop/services/browser/models.py +103 -0
  21. cmdop/services/browser/service/__init__.py +5 -0
  22. cmdop/services/browser/service/aio.py +30 -0
  23. cmdop/services/browser/{sync/service.py → service/sync.py} +206 -6
  24. cmdop/services/browser/session.py +194 -0
  25. {cmdop-0.1.21.dist-info → cmdop-0.1.23.dist-info}/METADATA +107 -59
  26. {cmdop-0.1.21.dist-info → cmdop-0.1.23.dist-info}/RECORD +29 -24
  27. cmdop/services/browser/aio/__init__.py +0 -6
  28. cmdop/services/browser/aio/service.py +0 -420
  29. cmdop/services/browser/aio/session.py +0 -407
  30. cmdop/services/browser/base/__init__.py +0 -6
  31. cmdop/services/browser/base/session.py +0 -124
  32. cmdop/services/browser/sync/__init__.py +0 -6
  33. cmdop/services/browser/sync/session.py +0 -644
  34. /cmdop/services/browser/{base/service.py → service/_helpers.py} +0 -0
  35. {cmdop-0.1.21.dist-info → cmdop-0.1.23.dist-info}/WHEEL +0 -0
  36. {cmdop-0.1.21.dist-info → cmdop-0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -1,420 +0,0 @@
1
- """Asynchronous browser service."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- from typing import TYPE_CHECKING, Any
7
-
8
- from cmdop.services.base import BaseService
9
- from cmdop.services.browser.base.service import BaseServiceMixin, cookie_to_pb, pb_to_cookie
10
- from cmdop.services.browser.models import BrowserCookie, BrowserState, PageInfo, ScrollResult, raise_browser_error
11
-
12
- if TYPE_CHECKING:
13
- from cmdop.transport.base import BaseTransport
14
-
15
-
16
- class AsyncBrowserService(BaseService, BaseServiceMixin):
17
- """
18
- Asynchronous browser service.
19
-
20
- Example:
21
- async with client.browser.create_session() as session:
22
- await session.navigate("https://google.com")
23
- await session.type("input[name='q']", "Python")
24
- results = await session.extract(".result-title")
25
- """
26
-
27
- def __init__(self, transport: BaseTransport) -> None:
28
- super().__init__(transport)
29
- self._stub: Any = None
30
-
31
- @property
32
- def _get_stub(self) -> Any:
33
- if self._stub is None:
34
- from cmdop._generated.service_pb2_grpc import TerminalStreamingServiceStub
35
- self._stub = TerminalStreamingServiceStub(self._async_channel)
36
- return self._stub
37
-
38
- # === Session Management ===
39
-
40
- async def create_session(
41
- self,
42
- start_url: str | None = None,
43
- provider: str = "camoufox",
44
- profile_id: str | None = None,
45
- headless: bool = False,
46
- width: int = 1280,
47
- height: int = 800,
48
- ) -> "AsyncBrowserSession":
49
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserCreateSessionRequest
50
- from cmdop.services.browser.aio.session import AsyncBrowserSession
51
-
52
- request = BrowserCreateSessionRequest(
53
- provider=provider,
54
- profile_id=profile_id or "",
55
- start_url=start_url or "",
56
- headless=headless,
57
- width=width,
58
- height=height,
59
- )
60
- response = await self._call_async(self._get_stub.BrowserCreateSession, request)
61
-
62
- if not response.success:
63
- raise_browser_error(response.error, "create_session")
64
-
65
- return AsyncBrowserSession(self, response.browser_session_id)
66
-
67
- async def close_session(self, session_id: str) -> None:
68
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserCloseSessionRequest
69
-
70
- request = BrowserCloseSessionRequest(browser_session_id=session_id)
71
- response = await self._call_async(self._get_stub.BrowserCloseSession, request)
72
-
73
- if not response.success:
74
- raise RuntimeError(f"Failed to close browser session: {response.error}")
75
-
76
- # === Navigation & Interaction ===
77
-
78
- async def navigate(self, session_id: str, url: str, timeout_ms: int = 30000) -> str:
79
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserNavigateRequest
80
-
81
- request = BrowserNavigateRequest(
82
- browser_session_id=session_id, url=url, timeout_ms=timeout_ms
83
- )
84
- response = await self._call_async(self._get_stub.BrowserNavigate, request)
85
-
86
- if not response.success:
87
- raise_browser_error(response.error, "navigate", url=url)
88
-
89
- return response.final_url
90
-
91
- async def click(
92
- self, session_id: str, selector: str, timeout_ms: int = 5000, move_cursor: bool = False
93
- ) -> None:
94
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserClickRequest
95
-
96
- request = BrowserClickRequest(
97
- browser_session_id=session_id,
98
- selector=selector,
99
- timeout_ms=timeout_ms,
100
- move_cursor=move_cursor,
101
- )
102
- response = await self._call_async(self._get_stub.BrowserClick, request)
103
-
104
- if not response.success:
105
- raise_browser_error(response.error, "click", selector=selector)
106
-
107
- async def type(
108
- self,
109
- session_id: str,
110
- selector: str,
111
- text: str,
112
- human_like: bool = False,
113
- clear_first: bool = True,
114
- ) -> None:
115
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserTypeRequest
116
-
117
- request = BrowserTypeRequest(
118
- browser_session_id=session_id,
119
- selector=selector,
120
- text=text,
121
- human_like=human_like,
122
- clear_first=clear_first,
123
- )
124
- response = await self._call_async(self._get_stub.BrowserType, request)
125
-
126
- if not response.success:
127
- raise_browser_error(response.error, "type", selector=selector)
128
-
129
- async def wait_for(
130
- self, session_id: str, selector: str, timeout_ms: int = 30000
131
- ) -> bool:
132
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserWaitRequest
133
-
134
- request = BrowserWaitRequest(
135
- browser_session_id=session_id, selector=selector, timeout_ms=timeout_ms
136
- )
137
- response = await self._call_async(self._get_stub.BrowserWait, request)
138
-
139
- if not response.success:
140
- raise_browser_error(response.error, "wait_for", selector=selector)
141
-
142
- return response.found
143
-
144
- # === Extraction ===
145
-
146
- async def extract(
147
- self, session_id: str, selector: str, attr: str | None = None, limit: int = 100
148
- ) -> list[str]:
149
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserExtractRequest
150
-
151
- request = BrowserExtractRequest(
152
- browser_session_id=session_id,
153
- selector=selector,
154
- attribute=attr or "",
155
- limit=limit,
156
- )
157
- response = await self._call_async(self._get_stub.BrowserExtract, request)
158
-
159
- if not response.success:
160
- raise RuntimeError(f"Extract failed: {response.error}")
161
-
162
- return list(response.values)
163
-
164
- async def extract_regex(
165
- self, session_id: str, pattern: str, from_html: bool = False, limit: int = 100
166
- ) -> list[str]:
167
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserExtractRegexRequest
168
-
169
- request = BrowserExtractRegexRequest(
170
- browser_session_id=session_id,
171
- pattern=pattern,
172
- from_html=from_html,
173
- limit=limit,
174
- )
175
- response = await self._call_async(self._get_stub.BrowserExtractRegex, request)
176
-
177
- if not response.success:
178
- raise RuntimeError(f"ExtractRegex failed: {response.error}")
179
-
180
- return list(response.matches)
181
-
182
- async def get_html(self, session_id: str, selector: str | None = None) -> str:
183
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetHTMLRequest
184
-
185
- request = BrowserGetHTMLRequest(
186
- browser_session_id=session_id, selector=selector or ""
187
- )
188
- response = await self._call_async(self._get_stub.BrowserGetHTML, request)
189
-
190
- if not response.success:
191
- raise RuntimeError(f"GetHTML failed: {response.error}")
192
-
193
- return response.html
194
-
195
- async def get_text(self, session_id: str, selector: str | None = None) -> str:
196
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetTextRequest
197
-
198
- request = BrowserGetTextRequest(
199
- browser_session_id=session_id, selector=selector or ""
200
- )
201
- response = await self._call_async(self._get_stub.BrowserGetText, request)
202
-
203
- if not response.success:
204
- raise RuntimeError(f"GetText failed: {response.error}")
205
-
206
- return response.text
207
-
208
- # === JavaScript ===
209
-
210
- async def execute_script(self, session_id: str, script: str) -> str:
211
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserExecuteScriptRequest
212
-
213
- request = BrowserExecuteScriptRequest(
214
- browser_session_id=session_id, script=script
215
- )
216
- response = await self._call_async(
217
- self._get_stub.BrowserExecuteScript, request
218
- )
219
-
220
- if not response.success:
221
- raise RuntimeError(f"ExecuteScript failed: {response.error}")
222
-
223
- return response.result
224
-
225
- # === State & Cookies ===
226
-
227
- async def screenshot(self, session_id: str, full_page: bool = False) -> bytes:
228
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserScreenshotRequest
229
-
230
- request = BrowserScreenshotRequest(
231
- browser_session_id=session_id, full_page=full_page
232
- )
233
- response = await self._call_async(self._get_stub.BrowserScreenshot, request)
234
-
235
- if not response.success:
236
- raise RuntimeError(f"Screenshot failed: {response.error}")
237
-
238
- return response.data
239
-
240
- async def get_state(self, session_id: str) -> BrowserState:
241
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetStateRequest
242
-
243
- request = BrowserGetStateRequest(browser_session_id=session_id)
244
- response = await self._call_async(self._get_stub.BrowserGetState, request)
245
-
246
- if not response.success:
247
- raise RuntimeError(f"GetState failed: {response.error}")
248
-
249
- return BrowserState(url=response.url, title=response.title)
250
-
251
- async def set_cookies(
252
- self, session_id: str, cookies: list[BrowserCookie | dict]
253
- ) -> None:
254
- from cmdop._generated.rpc_messages.browser_pb2 import (
255
- BrowserCookie as PbCookie,
256
- BrowserSetCookiesRequest,
257
- )
258
-
259
- pb_cookies = [cookie_to_pb(c, PbCookie) for c in cookies]
260
- request = BrowserSetCookiesRequest(
261
- browser_session_id=session_id, cookies=pb_cookies
262
- )
263
- response = await self._call_async(self._get_stub.BrowserSetCookies, request)
264
-
265
- if not response.success:
266
- raise RuntimeError(f"SetCookies failed: {response.error}")
267
-
268
- async def get_cookies(
269
- self, session_id: str, domain: str = ""
270
- ) -> list[BrowserCookie]:
271
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetCookiesRequest
272
-
273
- request = BrowserGetCookiesRequest(
274
- browser_session_id=session_id, domain=domain
275
- )
276
- response = await self._call_async(self._get_stub.BrowserGetCookies, request)
277
-
278
- if not response.success:
279
- raise RuntimeError(f"GetCookies failed: {response.error}")
280
-
281
- return [pb_to_cookie(c) for c in response.cookies]
282
-
283
- # === Parser helpers ===
284
-
285
- async def validate_selectors(
286
- self, session_id: str, item: str, fields: dict[str, str]
287
- ) -> dict:
288
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserValidateSelectorsRequest
289
-
290
- request = BrowserValidateSelectorsRequest(
291
- browser_session_id=session_id,
292
- item=item,
293
- fields=fields,
294
- )
295
- response = await self._call_async(
296
- self._get_stub.BrowserValidateSelectors, request
297
- )
298
-
299
- if not response.success:
300
- raise RuntimeError(f"ValidateSelectors failed: {response.error}")
301
-
302
- return {
303
- "valid": response.valid,
304
- "counts": dict(response.counts),
305
- "samples": dict(response.samples),
306
- "errors": list(response.errors),
307
- }
308
-
309
- async def extract_data(
310
- self, session_id: str, item: str, fields_json: str, limit: int = 100
311
- ) -> dict:
312
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserExtractDataRequest
313
-
314
- request = BrowserExtractDataRequest(
315
- browser_session_id=session_id,
316
- item=item,
317
- fields_json=fields_json,
318
- limit=limit,
319
- )
320
- response = await self._call_async(self._get_stub.BrowserExtractData, request)
321
-
322
- if not response.success:
323
- raise RuntimeError(f"ExtractData failed: {response.error}")
324
-
325
- return {
326
- "items": json.loads(response.items_json) if response.items_json else [],
327
- "count": response.count,
328
- }
329
-
330
- # === Mouse & Scroll (v2.18.0) ===
331
-
332
- async def mouse_move(
333
- self, session_id: str, x: int, y: int, steps: int = 10
334
- ) -> None:
335
- """Move cursor to coordinates with smooth movement."""
336
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserMouseMoveRequest
337
-
338
- request = BrowserMouseMoveRequest(
339
- browser_session_id=session_id, x=x, y=y, steps=steps
340
- )
341
- response = await self._call_async(self._get_stub.BrowserMouseMove, request)
342
-
343
- if not response.success:
344
- raise RuntimeError(f"MouseMove failed: {response.error}")
345
-
346
- async def hover(self, session_id: str, selector: str, timeout_ms: int = 5000) -> None:
347
- """Hover over element by selector."""
348
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserHoverRequest
349
-
350
- request = BrowserHoverRequest(
351
- browser_session_id=session_id, selector=selector, timeout_ms=timeout_ms
352
- )
353
- response = await self._call_async(self._get_stub.BrowserHover, request)
354
-
355
- if not response.success:
356
- raise_browser_error(response.error, "hover", selector=selector)
357
-
358
- async def scroll(
359
- self,
360
- session_id: str,
361
- direction: str = "down",
362
- amount: int = 500,
363
- selector: str = "",
364
- smooth: bool = True,
365
- ) -> ScrollResult:
366
- """Scroll page or element into view."""
367
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserScrollRequest
368
-
369
- request = BrowserScrollRequest(
370
- browser_session_id=session_id,
371
- direction=direction,
372
- amount=amount,
373
- selector=selector,
374
- smooth=smooth,
375
- )
376
- response = await self._call_async(self._get_stub.BrowserScroll, request)
377
-
378
- if not response.success:
379
- return ScrollResult(success=False, error=response.error)
380
-
381
- return ScrollResult(
382
- success=True,
383
- scroll_x=response.scroll_x,
384
- scroll_y=response.scroll_y,
385
- scrolled_by=response.scrolled_by,
386
- page_height=response.page_height,
387
- viewport_height=response.viewport_height,
388
- at_bottom=response.at_bottom,
389
- )
390
-
391
- async def get_page_info(self, session_id: str) -> PageInfo:
392
- """Get comprehensive page information."""
393
- from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetPageInfoRequest
394
-
395
- request = BrowserGetPageInfoRequest(browser_session_id=session_id)
396
- response = await self._call_async(self._get_stub.BrowserGetPageInfo, request)
397
-
398
- if not response.success:
399
- raise RuntimeError(f"GetPageInfo failed: {response.error}")
400
-
401
- return PageInfo(
402
- url=response.url,
403
- title=response.title,
404
- page_height=response.page_height,
405
- viewport_height=response.viewport_height,
406
- viewport_width=response.viewport_width,
407
- scroll_x=response.scroll_x,
408
- scroll_y=response.scroll_y,
409
- at_top=response.at_top,
410
- at_bottom=response.at_bottom,
411
- load_time_ms=response.load_time_ms,
412
- cookies_count=response.cookies_count,
413
- is_https=response.is_https,
414
- has_iframes=response.has_iframes,
415
- dom_nodes_raw=response.dom_nodes_raw,
416
- dom_nodes_cleaned=response.dom_nodes_cleaned,
417
- tokens_estimate=response.tokens_estimate,
418
- cloudflare_detected=response.cloudflare_detected,
419
- captcha_detected=response.captcha_detected,
420
- )