cmdop 0.1.13__py3-none-any.whl → 0.1.15__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.
cmdop/__init__.py CHANGED
@@ -116,8 +116,14 @@ from cmdop.discovery import (
116
116
  get_online_agents,
117
117
  list_agents,
118
118
  )
119
+ from cmdop.helpers import (
120
+ JSONFormatter,
121
+ flatten_json,
122
+ json_to_yaml,
123
+ json_to_text,
124
+ )
119
125
 
120
- __version__ = "0.1.13"
126
+ __version__ = "0.1.15"
121
127
 
122
128
  __all__ = [
123
129
  # Version
@@ -210,4 +216,9 @@ __all__ = [
210
216
  "BrowserSessionClosedError",
211
217
  "BrowserNavigationError",
212
218
  "BrowserElementNotFoundError",
219
+ # Helpers
220
+ "JSONFormatter",
221
+ "flatten_json",
222
+ "json_to_yaml",
223
+ "json_to_text",
213
224
  ]
@@ -0,0 +1,15 @@
1
+ """CMDOP SDK helpers."""
2
+
3
+ from cmdop.helpers.formatting import (
4
+ JSONFormatter,
5
+ flatten_json,
6
+ json_to_yaml,
7
+ json_to_text,
8
+ )
9
+
10
+ __all__ = [
11
+ "JSONFormatter",
12
+ "flatten_json",
13
+ "json_to_yaml",
14
+ "json_to_text",
15
+ ]
@@ -0,0 +1,70 @@
1
+ """JSON formatting utilities."""
2
+
3
+ import json
4
+
5
+ import yaml
6
+ from flatten_json import flatten
7
+
8
+
9
+ class JSONFormatter:
10
+ """
11
+ JSON to various formats converter.
12
+
13
+ Usage:
14
+ fmt = JSONFormatter()
15
+
16
+ flat = fmt.flatten({"a": {"b": 1}}) # {"a.b": 1}
17
+ yaml_str = fmt.to_yaml(data)
18
+ text = fmt.to_text(data) # key: value lines
19
+ """
20
+
21
+ def __init__(self, separator: str = "."):
22
+ self.separator = separator
23
+
24
+ def flatten(self, data: dict | list) -> dict:
25
+ """
26
+ Flatten nested JSON to flat dict with dot notation.
27
+
28
+ {"a": {"b": 1}} -> {"a.b": 1}
29
+ """
30
+ if isinstance(data, list):
31
+ data = {"items": data}
32
+ return flatten(data, self.separator)
33
+
34
+ def to_yaml(self, data: dict | list) -> str:
35
+ """Convert to YAML string."""
36
+ return yaml.dump(
37
+ data,
38
+ allow_unicode=True,
39
+ default_flow_style=False,
40
+ sort_keys=False,
41
+ indent=2,
42
+ )
43
+
44
+ def to_text(self, data: dict | list) -> str:
45
+ """Convert to flat text (key: value per line)."""
46
+ flat = self.flatten(data)
47
+ lines = []
48
+ for key, value in flat.items():
49
+ if isinstance(value, str):
50
+ val_str = value
51
+ elif value is None:
52
+ val_str = "null"
53
+ elif isinstance(value, bool):
54
+ val_str = "true" if value else "false"
55
+ else:
56
+ val_str = str(value)
57
+ lines.append(f"{key}: {val_str}")
58
+ return "\n".join(lines)
59
+
60
+ def to_json(self, data: dict | list, indent: int = 2) -> str:
61
+ """Convert to formatted JSON string."""
62
+ return json.dumps(data, indent=indent, ensure_ascii=False)
63
+
64
+
65
+ # Default instance
66
+ _fmt = JSONFormatter()
67
+
68
+ flatten_json = _fmt.flatten
69
+ json_to_yaml = _fmt.to_yaml
70
+ json_to_text = _fmt.to_text
@@ -0,0 +1,17 @@
1
+ """Browser service module."""
2
+
3
+ from cmdop.services.browser.models import BrowserCookie, BrowserState, raise_browser_error
4
+ from cmdop.services.browser.session import BrowserSession
5
+ from cmdop.services.browser.service import BrowserService
6
+ from cmdop.services.browser.async_session import AsyncBrowserSession
7
+ from cmdop.services.browser.async_service import AsyncBrowserService
8
+
9
+ __all__ = [
10
+ "BrowserCookie",
11
+ "BrowserState",
12
+ "BrowserSession",
13
+ "BrowserService",
14
+ "AsyncBrowserSession",
15
+ "AsyncBrowserService",
16
+ "raise_browser_error",
17
+ ]
@@ -0,0 +1,109 @@
1
+ """Shared browser utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ from cmdop.services.browser.models import BrowserCookie
9
+
10
+
11
+ def build_fetch_js(url: str) -> str:
12
+ """Build JS for single fetch."""
13
+ return f"""
14
+ (async function() {{
15
+ try {{
16
+ const resp = await fetch("{url}");
17
+ if (!resp.ok) return JSON.stringify({{__error: resp.status}});
18
+ return JSON.stringify(await resp.json());
19
+ }} catch(e) {{
20
+ return JSON.stringify({{__error: e.message}});
21
+ }}
22
+ }})()
23
+ """
24
+
25
+
26
+ def build_fetch_all_js(urls: dict[str, str]) -> tuple[str, list[str]]:
27
+ """Build JS for parallel fetch. Returns (js_code, keys)."""
28
+ keys = list(urls.keys())
29
+ fetches = [
30
+ f'fetch("{urls[k]}").then(r => r.ok ? r.json() : null).catch(() => null)'
31
+ for k in keys
32
+ ]
33
+
34
+ js = f"""
35
+ (async function() {{
36
+ try {{
37
+ const results = await Promise.all([{", ".join(fetches)}]);
38
+ return JSON.stringify(results);
39
+ }} catch(e) {{
40
+ return JSON.stringify({{__error: e.message}});
41
+ }}
42
+ }})()
43
+ """
44
+ return js, keys
45
+
46
+
47
+ def parse_fetch_result(result: str) -> dict | list | None:
48
+ """Parse single fetch result."""
49
+ if result:
50
+ try:
51
+ data = json.loads(result)
52
+ if isinstance(data, dict) and "__error" in data:
53
+ return None
54
+ return data
55
+ except json.JSONDecodeError:
56
+ return None
57
+ return None
58
+
59
+
60
+ def parse_fetch_all_result(result: str, keys: list[str]) -> dict[str, Any]:
61
+ """Parse parallel fetch result."""
62
+ if result:
63
+ try:
64
+ data = json.loads(result)
65
+ if isinstance(data, dict) and "__error" in data:
66
+ return {k: None for k in keys}
67
+ return {k: data[i] for i, k in enumerate(keys)}
68
+ except json.JSONDecodeError:
69
+ return {k: None for k in keys}
70
+ return {k: None for k in keys}
71
+
72
+
73
+ def cookie_to_pb(c: BrowserCookie | dict, PbCookie: type) -> Any:
74
+ """Convert cookie to protobuf."""
75
+ if isinstance(c, dict):
76
+ return PbCookie(
77
+ name=c.get("name", ""),
78
+ value=c.get("value", ""),
79
+ domain=c.get("domain", ""),
80
+ path=c.get("path", "/"),
81
+ secure=c.get("secure", False),
82
+ http_only=c.get("http_only", False),
83
+ same_site=c.get("same_site", ""),
84
+ expires=c.get("expires", 0),
85
+ )
86
+ return PbCookie(
87
+ name=c.name,
88
+ value=c.value,
89
+ domain=c.domain,
90
+ path=c.path,
91
+ secure=c.secure,
92
+ http_only=c.http_only,
93
+ same_site=c.same_site,
94
+ expires=c.expires,
95
+ )
96
+
97
+
98
+ def pb_to_cookie(c: Any) -> BrowserCookie:
99
+ """Convert protobuf to cookie."""
100
+ return BrowserCookie(
101
+ name=c.name,
102
+ value=c.value,
103
+ domain=c.domain,
104
+ path=c.path,
105
+ secure=c.secure,
106
+ http_only=c.http_only,
107
+ same_site=c.same_site,
108
+ expires=c.expires,
109
+ )
@@ -0,0 +1,311 @@
1
+ """Browser service (async)."""
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.models import BrowserCookie, BrowserState, raise_browser_error
10
+ from cmdop.services.browser.async_session import AsyncBrowserSession
11
+ from cmdop.services.browser._base import cookie_to_pb, pb_to_cookie
12
+
13
+ if TYPE_CHECKING:
14
+ from cmdop.transport.base import BaseTransport
15
+
16
+
17
+ class AsyncBrowserService(BaseService):
18
+ """
19
+ Async browser service.
20
+
21
+ Example:
22
+ async with client.browser.create_session() as session:
23
+ await session.navigate("https://google.com")
24
+ await session.type("input[name='q']", "Python")
25
+ results = await session.extract(".result-title")
26
+ """
27
+
28
+ def __init__(self, transport: BaseTransport) -> None:
29
+ super().__init__(transport)
30
+ self._stub: Any = None
31
+
32
+ @property
33
+ def _get_stub(self) -> Any:
34
+ if self._stub is None:
35
+ from cmdop._generated.service_pb2_grpc import TerminalStreamingServiceStub
36
+ self._stub = TerminalStreamingServiceStub(self._async_channel)
37
+ return self._stub
38
+
39
+ async def create_session(
40
+ self,
41
+ start_url: str | None = None,
42
+ provider: str = "camoufox",
43
+ profile_id: str | None = None,
44
+ headless: bool = False,
45
+ width: int = 1280,
46
+ height: int = 800,
47
+ ) -> AsyncBrowserSession:
48
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserCreateSessionRequest
49
+
50
+ request = BrowserCreateSessionRequest(
51
+ provider=provider,
52
+ profile_id=profile_id or "",
53
+ start_url=start_url or "",
54
+ headless=headless,
55
+ width=width,
56
+ height=height,
57
+ )
58
+ response = await self._call_async(self._get_stub.BrowserCreateSession, request)
59
+
60
+ if not response.success:
61
+ raise_browser_error(response.error, "create_session")
62
+
63
+ return AsyncBrowserSession(self, response.browser_session_id)
64
+
65
+ async def close_session(self, session_id: str) -> None:
66
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserCloseSessionRequest
67
+
68
+ request = BrowserCloseSessionRequest(browser_session_id=session_id)
69
+ response = await self._call_async(self._get_stub.BrowserCloseSession, request)
70
+
71
+ if not response.success:
72
+ raise RuntimeError(f"Failed to close browser session: {response.error}")
73
+
74
+ async def navigate(self, session_id: str, url: str, timeout_ms: int = 30000) -> str:
75
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserNavigateRequest
76
+
77
+ request = BrowserNavigateRequest(
78
+ browser_session_id=session_id, url=url, timeout_ms=timeout_ms
79
+ )
80
+ response = await self._call_async(self._get_stub.BrowserNavigate, request)
81
+
82
+ if not response.success:
83
+ raise_browser_error(response.error, "navigate", url=url)
84
+
85
+ return response.final_url
86
+
87
+ async def click(self, session_id: str, selector: str, timeout_ms: int = 5000) -> None:
88
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserClickRequest
89
+
90
+ request = BrowserClickRequest(
91
+ browser_session_id=session_id, selector=selector, timeout_ms=timeout_ms
92
+ )
93
+ response = await self._call_async(self._get_stub.BrowserClick, request)
94
+
95
+ if not response.success:
96
+ raise_browser_error(response.error, "click", selector=selector)
97
+
98
+ async def type(
99
+ self,
100
+ session_id: str,
101
+ selector: str,
102
+ text: str,
103
+ human_like: bool = False,
104
+ clear_first: bool = True,
105
+ ) -> None:
106
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserTypeRequest
107
+
108
+ request = BrowserTypeRequest(
109
+ browser_session_id=session_id,
110
+ selector=selector,
111
+ text=text,
112
+ human_like=human_like,
113
+ clear_first=clear_first,
114
+ )
115
+ response = await self._call_async(self._get_stub.BrowserType, request)
116
+
117
+ if not response.success:
118
+ raise_browser_error(response.error, "type", selector=selector)
119
+
120
+ async def wait_for(
121
+ self, session_id: str, selector: str, timeout_ms: int = 30000
122
+ ) -> bool:
123
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserWaitRequest
124
+
125
+ request = BrowserWaitRequest(
126
+ browser_session_id=session_id, selector=selector, timeout_ms=timeout_ms
127
+ )
128
+ response = await self._call_async(self._get_stub.BrowserWait, request)
129
+
130
+ if not response.success:
131
+ raise_browser_error(response.error, "wait_for", selector=selector)
132
+
133
+ return response.found
134
+
135
+ async def extract(
136
+ self, session_id: str, selector: str, attr: str | None = None, limit: int = 100
137
+ ) -> list[str]:
138
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserExtractRequest
139
+
140
+ request = BrowserExtractRequest(
141
+ browser_session_id=session_id,
142
+ selector=selector,
143
+ attribute=attr or "",
144
+ limit=limit,
145
+ )
146
+ response = await self._call_async(self._get_stub.BrowserExtract, request)
147
+
148
+ if not response.success:
149
+ raise RuntimeError(f"Extract failed: {response.error}")
150
+
151
+ return list(response.values)
152
+
153
+ async def extract_regex(
154
+ self, session_id: str, pattern: str, from_html: bool = False, limit: int = 100
155
+ ) -> list[str]:
156
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserExtractRegexRequest
157
+
158
+ request = BrowserExtractRegexRequest(
159
+ browser_session_id=session_id,
160
+ pattern=pattern,
161
+ from_html=from_html,
162
+ limit=limit,
163
+ )
164
+ response = await self._call_async(self._get_stub.BrowserExtractRegex, request)
165
+
166
+ if not response.success:
167
+ raise RuntimeError(f"ExtractRegex failed: {response.error}")
168
+
169
+ return list(response.matches)
170
+
171
+ async def get_html(self, session_id: str, selector: str | None = None) -> str:
172
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetHTMLRequest
173
+
174
+ request = BrowserGetHTMLRequest(
175
+ browser_session_id=session_id, selector=selector or ""
176
+ )
177
+ response = await self._call_async(self._get_stub.BrowserGetHTML, request)
178
+
179
+ if not response.success:
180
+ raise RuntimeError(f"GetHTML failed: {response.error}")
181
+
182
+ return response.html
183
+
184
+ async def get_text(self, session_id: str, selector: str | None = None) -> str:
185
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetTextRequest
186
+
187
+ request = BrowserGetTextRequest(
188
+ browser_session_id=session_id, selector=selector or ""
189
+ )
190
+ response = await self._call_async(self._get_stub.BrowserGetText, request)
191
+
192
+ if not response.success:
193
+ raise RuntimeError(f"GetText failed: {response.error}")
194
+
195
+ return response.text
196
+
197
+ async def execute_script(self, session_id: str, script: str) -> str:
198
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserExecuteScriptRequest
199
+
200
+ request = BrowserExecuteScriptRequest(
201
+ browser_session_id=session_id, script=script
202
+ )
203
+ response = await self._call_async(
204
+ self._get_stub.BrowserExecuteScript, request
205
+ )
206
+
207
+ if not response.success:
208
+ raise RuntimeError(f"ExecuteScript failed: {response.error}")
209
+
210
+ return response.result
211
+
212
+ async def screenshot(self, session_id: str, full_page: bool = False) -> bytes:
213
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserScreenshotRequest
214
+
215
+ request = BrowserScreenshotRequest(
216
+ browser_session_id=session_id, full_page=full_page
217
+ )
218
+ response = await self._call_async(self._get_stub.BrowserScreenshot, request)
219
+
220
+ if not response.success:
221
+ raise RuntimeError(f"Screenshot failed: {response.error}")
222
+
223
+ return response.data
224
+
225
+ async def get_state(self, session_id: str) -> BrowserState:
226
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetStateRequest
227
+
228
+ request = BrowserGetStateRequest(browser_session_id=session_id)
229
+ response = await self._call_async(self._get_stub.BrowserGetState, request)
230
+
231
+ if not response.success:
232
+ raise RuntimeError(f"GetState failed: {response.error}")
233
+
234
+ return BrowserState(url=response.url, title=response.title)
235
+
236
+ async def set_cookies(
237
+ self, session_id: str, cookies: list[BrowserCookie | dict]
238
+ ) -> None:
239
+ from cmdop._generated.rpc_messages.browser_pb2 import (
240
+ BrowserCookie as PbCookie,
241
+ BrowserSetCookiesRequest,
242
+ )
243
+
244
+ pb_cookies = [cookie_to_pb(c, PbCookie) for c in cookies]
245
+ request = BrowserSetCookiesRequest(
246
+ browser_session_id=session_id, cookies=pb_cookies
247
+ )
248
+ response = await self._call_async(self._get_stub.BrowserSetCookies, request)
249
+
250
+ if not response.success:
251
+ raise RuntimeError(f"SetCookies failed: {response.error}")
252
+
253
+ async def get_cookies(
254
+ self, session_id: str, domain: str = ""
255
+ ) -> list[BrowserCookie]:
256
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserGetCookiesRequest
257
+
258
+ request = BrowserGetCookiesRequest(
259
+ browser_session_id=session_id, domain=domain
260
+ )
261
+ response = await self._call_async(self._get_stub.BrowserGetCookies, request)
262
+
263
+ if not response.success:
264
+ raise RuntimeError(f"GetCookies failed: {response.error}")
265
+
266
+ return [pb_to_cookie(c) for c in response.cookies]
267
+
268
+ async def validate_selectors(
269
+ self, session_id: str, item: str, fields: dict[str, str]
270
+ ) -> dict:
271
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserValidateSelectorsRequest
272
+
273
+ request = BrowserValidateSelectorsRequest(
274
+ browser_session_id=session_id,
275
+ item=item,
276
+ fields=fields,
277
+ )
278
+ response = await self._call_async(
279
+ self._get_stub.BrowserValidateSelectors, request
280
+ )
281
+
282
+ if not response.success:
283
+ raise RuntimeError(f"ValidateSelectors failed: {response.error}")
284
+
285
+ return {
286
+ "valid": response.valid,
287
+ "counts": dict(response.counts),
288
+ "samples": dict(response.samples),
289
+ "errors": list(response.errors),
290
+ }
291
+
292
+ async def extract_data(
293
+ self, session_id: str, item: str, fields_json: str, limit: int = 100
294
+ ) -> dict:
295
+ from cmdop._generated.rpc_messages.browser_pb2 import BrowserExtractDataRequest
296
+
297
+ request = BrowserExtractDataRequest(
298
+ browser_session_id=session_id,
299
+ item=item,
300
+ fields_json=fields_json,
301
+ limit=limit,
302
+ )
303
+ response = await self._call_async(self._get_stub.BrowserExtractData, request)
304
+
305
+ if not response.success:
306
+ raise RuntimeError(f"ExtractData failed: {response.error}")
307
+
308
+ return {
309
+ "items": json.loads(response.items_json) if response.items_json else [],
310
+ "count": response.count,
311
+ }
@@ -0,0 +1,119 @@
1
+ """Browser session (async)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from cmdop.services.browser.models import BrowserCookie, BrowserState
8
+ from cmdop.services.browser._base import (
9
+ build_fetch_js,
10
+ build_fetch_all_js,
11
+ parse_fetch_result,
12
+ parse_fetch_all_result,
13
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ from cmdop.services.browser.async_service import AsyncBrowserService
17
+
18
+
19
+ class AsyncBrowserSession:
20
+ """
21
+ Async browser session with fluent API.
22
+
23
+ Use as async context manager:
24
+ async with client.browser.create_session() as session:
25
+ await session.navigate("https://example.com")
26
+ await session.click("button.submit")
27
+ """
28
+
29
+ def __init__(self, service: AsyncBrowserService, session_id: str) -> None:
30
+ self._service = service
31
+ self._session_id = session_id
32
+
33
+ @property
34
+ def session_id(self) -> str:
35
+ return self._session_id
36
+
37
+ async def navigate(self, url: str, timeout_ms: int = 30000) -> str:
38
+ return await self._service.navigate(self._session_id, url, timeout_ms)
39
+
40
+ async def click(self, selector: str, timeout_ms: int = 5000) -> None:
41
+ await self._service.click(self._session_id, selector, timeout_ms)
42
+
43
+ async def type(
44
+ self,
45
+ selector: str,
46
+ text: str,
47
+ human_like: bool = False,
48
+ clear_first: bool = True,
49
+ ) -> None:
50
+ await self._service.type(
51
+ self._session_id, selector, text, human_like, clear_first
52
+ )
53
+
54
+ async def wait_for(self, selector: str, timeout_ms: int = 30000) -> bool:
55
+ return await self._service.wait_for(self._session_id, selector, timeout_ms)
56
+
57
+ async def extract(
58
+ self, selector: str, attr: str | None = None, limit: int = 100
59
+ ) -> list[str]:
60
+ return await self._service.extract(self._session_id, selector, attr, limit)
61
+
62
+ async def extract_regex(
63
+ self, pattern: str, from_html: bool = False, limit: int = 100
64
+ ) -> list[str]:
65
+ return await self._service.extract_regex(
66
+ self._session_id, pattern, from_html, limit
67
+ )
68
+
69
+ async def get_html(self, selector: str | None = None) -> str:
70
+ return await self._service.get_html(self._session_id, selector)
71
+
72
+ async def get_text(self, selector: str | None = None) -> str:
73
+ return await self._service.get_text(self._session_id, selector)
74
+
75
+ async def execute_script(self, script: str) -> str:
76
+ return await self._service.execute_script(self._session_id, script)
77
+
78
+ async def fetch_json(self, url: str) -> dict | list | None:
79
+ """Fetch JSON from URL using JS fetch()."""
80
+ js = build_fetch_js(url)
81
+ result = await self.execute_script(js)
82
+ return parse_fetch_result(result)
83
+
84
+ async def fetch_all(self, urls: dict[str, str]) -> dict[str, Any]:
85
+ """Fetch multiple URLs in parallel using Promise.all."""
86
+ if not urls:
87
+ return {}
88
+ js, keys = build_fetch_all_js(urls)
89
+ result = await self.execute_script(js)
90
+ return parse_fetch_all_result(result, keys)
91
+
92
+ async def screenshot(self, full_page: bool = False) -> bytes:
93
+ return await self._service.screenshot(self._session_id, full_page)
94
+
95
+ async def get_state(self) -> BrowserState:
96
+ return await self._service.get_state(self._session_id)
97
+
98
+ async def set_cookies(self, cookies: list[BrowserCookie | dict]) -> None:
99
+ await self._service.set_cookies(self._session_id, cookies)
100
+
101
+ async def get_cookies(self, domain: str = "") -> list[BrowserCookie]:
102
+ return await self._service.get_cookies(self._session_id, domain)
103
+
104
+ async def validate_selectors(self, item: str, fields: dict[str, str]) -> dict:
105
+ return await self._service.validate_selectors(self._session_id, item, fields)
106
+
107
+ async def extract_data(self, item: str, fields_json: str, limit: int = 100) -> dict:
108
+ return await self._service.extract_data(
109
+ self._session_id, item, fields_json, limit
110
+ )
111
+
112
+ async def close(self) -> None:
113
+ await self._service.close_session(self._session_id)
114
+
115
+ async def __aenter__(self) -> "AsyncBrowserSession":
116
+ return self
117
+
118
+ async def __aexit__(self, *args: Any) -> None:
119
+ await self.close()