Skip to content

Commit 7cc2ccb

Browse files
committed
fix: type-check browser-scoped helpers
Keep the browser-scoped request helpers aligned with repo linting and reserve internal raw-request query keys without exposing implementation details. Made-with: Cursor
1 parent cfa07c9 commit 7cc2ccb

3 files changed

Lines changed: 47 additions & 21 deletions

File tree

src/kernel/lib/browser_scoped/client.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@ def request(
133133
method=method.upper(),
134134
url="/curl/raw",
135135
params=q,
136-
headers=headers if headers is not None else not_given,
137-
content=content,
136+
headers=_normalize_headers(headers),
137+
content=_normalize_binary_content(content),
138138
json_data=json,
139-
timeout=timeout,
139+
timeout=_normalize_timeout(timeout),
140140
)
141141
return self._http.request(httpx.Response, opts)
142142

@@ -165,8 +165,8 @@ def stream(
165165
self._http._prepare_url("/curl/raw"),
166166
params=q,
167167
headers=h,
168-
content=content,
169-
timeout=eff_timeout,
168+
content=_normalize_binary_content(content),
169+
timeout=_normalize_timeout(eff_timeout),
170170
)
171171
with cm as resp:
172172
yield resp
@@ -248,10 +248,10 @@ async def request(
248248
method=method.upper(),
249249
url="/curl/raw",
250250
params=q,
251-
headers=headers if headers is not None else not_given,
252-
content=content,
251+
headers=_normalize_headers(headers),
252+
content=_normalize_binary_content(content),
253253
json_data=json,
254-
timeout=timeout,
254+
timeout=_normalize_timeout(timeout),
255255
)
256256
return await self._http.request(httpx.Response, opts)
257257

@@ -280,8 +280,8 @@ async def stream(
280280
self._http._prepare_url("/curl/raw"),
281281
params=q,
282282
headers=h,
283-
content=content,
284-
timeout=eff_timeout,
283+
content=_normalize_binary_content(content),
284+
timeout=_normalize_timeout(eff_timeout),
285285
) as resp:
286286
yield resp
287287

@@ -306,3 +306,21 @@ def async_browser_scoped_from_browser(parent: AsyncKernel, browser: Any) -> Asyn
306306
if not jwt:
307307
raise ValueError("could not parse jwt from browser.cdp_ws_url; required for browser session HTTP")
308308
return AsyncBrowserScopedClient(parent, session_id=session_id, session_base_url=session_base, jwt=jwt)
309+
310+
311+
def _normalize_headers(headers: Mapping[str, str] | None) -> Mapping[str, str]:
312+
return headers if headers is not None else {}
313+
314+
315+
def _normalize_timeout(timeout: float | Timeout | None | NotGiven) -> float | Timeout | None:
316+
return None if isinstance(timeout, NotGiven) else timeout
317+
318+
319+
def _normalize_binary_content(content: BinaryTypes | None) -> httpx._types.RequestContent | None:
320+
if content is None:
321+
return None
322+
if isinstance(content, bytearray):
323+
return bytes(content)
324+
if isinstance(content, memoryview):
325+
return content.tobytes()
326+
return cast(httpx._types.RequestContent, content)

src/kernel/lib/browser_scoped/util.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import Any, Mapping
3+
from typing import Any, Mapping, cast
44
from urllib.parse import parse_qs, urlparse
55

66
# Query keys reserved for /curl/raw; user-supplied `params` must not override these.
@@ -27,7 +27,8 @@ def session_id_from_browser_like(browser: Any) -> str:
2727
if isinstance(sid, str) and sid:
2828
return sid
2929
if isinstance(browser, Mapping):
30-
m = browser.get("session_id")
30+
mapping = cast(Mapping[str, object], browser)
31+
m = mapping.get("session_id")
3132
if isinstance(m, str) and m:
3233
return m
3334
raise TypeError("browser object must have a non-empty session_id")
@@ -38,7 +39,8 @@ def base_url_from_browser_like(browser: Any) -> str | None:
3839
if isinstance(bu, str) and bu.strip():
3940
return bu.strip().rstrip("/") + "/"
4041
if isinstance(browser, Mapping):
41-
raw = browser.get("base_url")
42+
mapping = cast(Mapping[str, object], browser)
43+
raw = mapping.get("base_url")
4244
if isinstance(raw, str) and raw.strip():
4345
return raw.strip().rstrip("/") + "/"
4446
return None
@@ -49,7 +51,8 @@ def cdp_ws_url_from_browser_like(browser: Any) -> str:
4951
if isinstance(u, str) and u:
5052
return u
5153
if isinstance(browser, Mapping):
52-
m = browser.get("cdp_ws_url")
54+
mapping = cast(Mapping[str, object], browser)
55+
m = mapping.get("cdp_ws_url")
5356
if isinstance(m, str) and m:
5457
return m
5558
raise TypeError("browser object must have a non-empty cdp_ws_url")

tests/test_browser_scoped.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from __future__ import annotations
22

3-
import os
43
import json
4+
import os
5+
from typing import cast
56

67
import httpx
78
import respx
@@ -14,7 +15,7 @@
1415
api_key = "sk-123"
1516

1617

17-
def _fake_browser() -> dict[str, str]:
18+
def _fake_browser() -> dict[str, object]:
1819
return {
1920
"session_id": "sess-1",
2021
"base_url": "http://browser-session.test/browser/kernel",
@@ -47,7 +48,8 @@ def test_for_browser_process_exec_routes_to_session_base() -> None:
4748
b = client.for_browser(_fake_browser())
4849
out = b.process.exec(command="echo", args=["hi"])
4950
assert route.called
50-
sent = route.calls[0].request.read().decode()
51+
request = cast(httpx.Request, route.calls[0].request)
52+
sent = request.read().decode()
5153
body = json.loads(sent)
5254
assert body["command"] == "echo"
5355
assert body["args"] == ["hi"]
@@ -65,8 +67,9 @@ def test_browser_request_uses_curl_raw() -> None:
6567
assert r.status_code == 200
6668
assert r.content == b"ok"
6769
assert route.called
68-
assert "curl/raw" in str(route.calls[0].request.url)
69-
assert "jwt=token-abc" in str(route.calls[0].request.url)
70+
request = cast(httpx.Request, route.calls[0].request)
71+
assert "curl/raw" in str(request.url)
72+
assert "jwt=token-abc" in str(request.url)
7073

7174

7275
@respx.mock
@@ -82,7 +85,8 @@ def test_browser_request_params_cannot_override_target_url_or_jwt() -> None:
8285
params={"url": "https://evil.example", "jwt": "other", "timeout_ms": 1},
8386
)
8487
assert route.called
85-
req_url = route.calls[0].request.url
88+
request = cast(httpx.Request, route.calls[0].request)
89+
req_url = request.url
8690
assert str(req_url.params.get("url")) == "https://example.com"
8791
assert str(req_url.params.get("jwt")) == "token-abc"
8892
assert str(req_url.params.get("timeout_ms")) == "1"
@@ -103,7 +107,8 @@ def test_browser_stream_params_cannot_override_target_url_or_jwt() -> None:
103107
assert resp.status_code == 200
104108
assert resp.read() == b"streamed"
105109
assert route.called
106-
req_url = route.calls[0].request.url
110+
request = cast(httpx.Request, route.calls[0].request)
111+
req_url = request.url
107112
assert str(req_url.params.get("url")) == "https://example.com"
108113
assert str(req_url.params.get("jwt")) == "token-abc"
109114

0 commit comments

Comments
 (0)