Skip to content

Commit de0476f

Browse files
committed
refactor: simplify browser routing cache
Remove the public cache priming helpers, keep jwt-required routes, and rename the example and tests so the python browser routing diff stays focused on cache-backed direct-to-VM behavior. Made-with: Cursor
1 parent dba503e commit de0476f

8 files changed

Lines changed: 171 additions & 278 deletions

File tree

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ def main() -> None:
77
with Kernel(browser_routing=BrowserRoutingConfig(enabled=True, direct_to_vm_subresources=("process",))) as client:
88
browser = client.browsers.create(headless=True)
99
try:
10-
client.prime_browser_route_cache(browser)
11-
1210
client.browsers.process.exec(browser.session_id, command="uname", args=["-a"])
1311

1412
response = client.browsers.request(browser.session_id, "GET", "https://example.com")

src/kernel/_client.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,6 @@ def copy(
343343
# client.with_options(timeout=10).foo.create(...)
344344
with_options = copy
345345

346-
def prime_browser_route_cache(self, browser: Any) -> None:
347-
self.browser_route_cache.prime(browser)
348-
349346
@override
350347
def _make_status_error(
351348
self,
@@ -640,9 +637,6 @@ def copy(
640637
# client.with_options(timeout=10).foo.create(...)
641638
with_options = copy
642639

643-
def prime_browser_route_cache(self, browser: Any) -> None:
644-
self.browser_route_cache.prime(browser)
645-
646640
@override
647641
def _make_status_error(
648642
self,

src/kernel/lib/browser_scoped/raw_http.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ def request_via_browser_route(
2727
if json is not None and content is not None:
2828
raise TypeError("Passing both `json` and `content` is not supported")
2929
q: dict[str, object] = {**sanitize_curl_raw_params(params), "url": url}
30-
if route.jwt:
31-
q["jwt"] = route.jwt
30+
q["jwt"] = route.jwt
3231
opts = FinalRequestOptions.construct(
3332
method=method.upper(),
3433
url=route.base_url.rstrip("/") + "/curl/raw",
@@ -54,8 +53,7 @@ def stream_via_browser_route(
5453
timeout: float | Timeout | None | NotGiven = not_given,
5554
) -> Iterator[httpx.Response]:
5655
q: dict[str, Any] = sanitize_curl_raw_params(params)
57-
if route.jwt:
58-
q["jwt"] = route.jwt
56+
q["jwt"] = route.jwt
5957
q["url"] = url
6058
h = {k: v for k, v in parent.default_headers.items() if isinstance(v, str)}
6159
if content is None:
@@ -90,8 +88,7 @@ async def async_request_via_browser_route(
9088
if json is not None and content is not None:
9189
raise TypeError("Passing both `json` and `content` is not supported")
9290
q: dict[str, object] = {**sanitize_curl_raw_params(params), "url": url}
93-
if route.jwt:
94-
q["jwt"] = route.jwt
91+
q["jwt"] = route.jwt
9592
opts = FinalRequestOptions.construct(
9693
method=method.upper(),
9794
url=route.base_url.rstrip("/") + "/curl/raw",
@@ -117,8 +114,7 @@ async def async_stream_via_browser_route(
117114
timeout: float | Timeout | None | NotGiven = not_given,
118115
) -> AsyncIterator[httpx.Response]:
119116
q: dict[str, Any] = sanitize_curl_raw_params(params)
120-
if route.jwt:
121-
q["jwt"] = route.jwt
117+
q["jwt"] = route.jwt
122118
q["url"] = url
123119
h = {k: v for k, v in parent.default_headers.items() if isinstance(v, str)}
124120
if content is None:

src/kernel/lib/browser_scoped/routing.py

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77

88
from ..._compat import model_copy
99
from ..._models import FinalRequestOptions
10-
from ..._types import Headers
1110
from .util import base_url_from_browser_like, jwt_from_cdp_ws_url, cdp_ws_url_from_browser_like, session_id_from_browser_like
1211

1312

1413
@dataclass
1514
class BrowserRoute:
1615
session_id: str
1716
base_url: str
18-
jwt: str | None = None
17+
jwt: str
1918

2019

2120
@dataclass
@@ -35,30 +34,33 @@ def set(self, route: BrowserRoute) -> None:
3534
self._routes[route.session_id] = BrowserRoute(
3635
session_id=route.session_id.strip(),
3736
base_url=route.base_url.strip().rstrip("/") + "/",
38-
jwt=route.jwt.strip() if isinstance(route.jwt, str) and route.jwt.strip() else None,
37+
jwt=route.jwt.strip(),
3938
)
4039

4140
def delete(self, session_id: str) -> None:
4241
self._routes.pop(session_id, None)
4342

44-
def prime(self, browser: Any) -> BrowserRoute:
45-
session_id = session_id_from_browser_like(browser)
46-
base_url = base_url_from_browser_like(browser)
47-
if not base_url:
48-
raise ValueError("browser.base_url is required to prime the browser route cache")
49-
jwt = None
50-
try:
51-
jwt = jwt_from_cdp_ws_url(cdp_ws_url_from_browser_like(browser))
52-
except Exception:
53-
jwt = None
54-
route = BrowserRoute(session_id=session_id, base_url=base_url, jwt=jwt)
55-
self.set(route)
56-
return route
57-
5843
def values(self) -> list[BrowserRoute]:
5944
return list(self._routes.values())
6045

6146

47+
def browser_route_from_browser(browser: Any) -> BrowserRoute | None:
48+
session_id = session_id_from_browser_like(browser)
49+
base_url = base_url_from_browser_like(browser)
50+
if not base_url:
51+
return None
52+
53+
jwt = None
54+
try:
55+
jwt = jwt_from_cdp_ws_url(cdp_ws_url_from_browser_like(browser))
56+
except Exception:
57+
jwt = None
58+
if not jwt:
59+
return None
60+
61+
return BrowserRoute(session_id=session_id, base_url=base_url, jwt=jwt)
62+
63+
6264
def rewrite_direct_vm_options(
6365
options: FinalRequestOptions,
6466
*,
@@ -86,8 +88,7 @@ def rewrite_direct_vm_options(
8688
params: dict[str, object] = {}
8789
if isinstance(options.params, Mapping):
8890
params.update(cast(Mapping[str, object], options.params))
89-
if route.jwt:
90-
params["jwt"] = route.jwt
91+
params["jwt"] = route.jwt
9192
rewritten.params = params or options.params
9293
return rewritten
9394

@@ -119,9 +120,3 @@ def match_direct_vm_path(path: str) -> tuple[str, str, str] | None:
119120
return None
120121

121122

122-
def build_direct_vm_headers(headers: Mapping[str, str] | None) -> Headers | None:
123-
if headers is None:
124-
return {"Authorization": None}
125-
out: dict[str, str | None] = {"Authorization": None}
126-
out.update(headers)
127-
return out

src/kernel/lib/browser_scoped/util.py

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

3-
import inspect
43
from typing import Any, Mapping, cast
54
from urllib.parse import parse_qs, urlparse
65

@@ -59,29 +58,3 @@ def cdp_ws_url_from_browser_like(browser: Any) -> str:
5958
raise TypeError("browser object must have a non-empty cdp_ws_url")
6059

6160

62-
class ScopedResourceProxy:
63-
"""Delegates to a generated resource; injects `id` for callables that still expose it."""
64-
65-
def __init__(self, inner: Any, session_id: str) -> None:
66-
object.__setattr__(self, "_inner", inner)
67-
object.__setattr__(self, "_session_id", session_id)
68-
69-
def __getattr__(self, name: str) -> Any:
70-
if name.startswith("_"):
71-
raise AttributeError(name)
72-
attr = getattr(self._inner, name)
73-
if name.startswith("with_") or not callable(attr):
74-
return attr
75-
try:
76-
sig = inspect.signature(attr)
77-
except (TypeError, ValueError):
78-
return attr
79-
if "id" not in sig.parameters:
80-
return attr
81-
82-
def bound(*args: Any, **kwargs: Any) -> Any:
83-
kw = dict(kwargs)
84-
kw["id"] = self._session_id
85-
return attr(*args, **kw)
86-
87-
return bound

src/kernel/resources/browsers/browsers.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
request_via_browser_route,
9595
stream_via_browser_route,
9696
)
97+
from ...lib.browser_scoped.routing import browser_route_from_browser
9798

9899
__all__ = ["BrowsersResource", "AsyncBrowsersResource"]
99100

@@ -249,7 +250,9 @@ def create(
249250
),
250251
cast_to=BrowserCreateResponse,
251252
)
252-
self._client.prime_browser_route_cache(result)
253+
route = browser_route_from_browser(result)
254+
if route is not None:
255+
self._client.browser_route_cache.set(route)
253256
return result
254257

255258
def retrieve(
@@ -293,7 +296,9 @@ def retrieve(
293296
),
294297
cast_to=BrowserRetrieveResponse,
295298
)
296-
self._client.prime_browser_route_cache(result)
299+
route = browser_route_from_browser(result)
300+
if route is not None:
301+
self._client.browser_route_cache.set(route)
297302
return result
298303

299304
def update(
@@ -352,7 +357,9 @@ def update(
352357
),
353358
cast_to=BrowserUpdateResponse,
354359
)
355-
self._client.prime_browser_route_cache(result)
360+
route = browser_route_from_browser(result)
361+
if route is not None:
362+
self._client.browser_route_cache.set(route)
356363
return result
357364

358365
def list(
@@ -418,7 +425,9 @@ def list(
418425
model=BrowserListResponse,
419426
)
420427
for item in page.items:
421-
self._client.prime_browser_route_cache(item)
428+
route = browser_route_from_browser(item)
429+
if route is not None:
430+
self._client.browser_route_cache.set(route)
422431
return page
423432

424433
@typing_extensions.deprecated("deprecated")
@@ -816,7 +825,9 @@ async def create(
816825
),
817826
cast_to=BrowserCreateResponse,
818827
)
819-
self._client.prime_browser_route_cache(result)
828+
route = browser_route_from_browser(result)
829+
if route is not None:
830+
self._client.browser_route_cache.set(route)
820831
return result
821832

822833
async def retrieve(
@@ -860,7 +871,9 @@ async def retrieve(
860871
),
861872
cast_to=BrowserRetrieveResponse,
862873
)
863-
self._client.prime_browser_route_cache(result)
874+
route = browser_route_from_browser(result)
875+
if route is not None:
876+
self._client.browser_route_cache.set(route)
864877
return result
865878

866879
async def update(
@@ -919,7 +932,9 @@ async def update(
919932
),
920933
cast_to=BrowserUpdateResponse,
921934
)
922-
self._client.prime_browser_route_cache(result)
935+
route = browser_route_from_browser(result)
936+
if route is not None:
937+
self._client.browser_route_cache.set(route)
923938
return result
924939

925940
def list(

0 commit comments

Comments
 (0)