refactor: reorganize client transport and extension internals

- Split client, native, remote, serve, markdown, and SDK internals into focused packages with direct imports.
- Move local and remote transport framing/protocol helpers behind clearer module boundaries.
- Break up the extension injected DOM logic into a separate content dispatch bundle and dedicated content modules.
- Add explicit client handling for passive remote discovery without noisy PQ warnings.
- Keep behavior covered with updated unit, integration, and extension tests.
This commit is contained in:
2026-06-13 23:31:24 +02:00
parent fd5447cbb9
commit 076914e5b7
88 changed files with 7491 additions and 5228 deletions
+106 -9
View File
@@ -1,19 +1,116 @@
"""Base class for SDK command namespaces.
"""Base helpers for SDK command namespaces.
Each namespace (``b.tabs``, ``b.dom``, ...) is a thin object bound to its
:class:`~browser_cli.BrowserCLI` client. Namespaces hold no state of their own;
they delegate to the client's shared infrastructure (``_cmd``, the multi-browser
helpers, and the ``Tab``/``Group`` factories).
they delegate to the client's shared infrastructure (command dispatch, the
multi-browser helpers, and the ``Tab``/``Group`` factories).
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from collections.abc import Callable
from functools import wraps
from typing import Any, TypeVar
if TYPE_CHECKING:
from browser_cli import BrowserCLI
F = TypeVar("F", bound=Callable)
_MISSING = object()
def _clone_default(value):
if isinstance(value, (dict, list, set)):
return value.copy()
return value
def sdk_command(
name: str,
args: Callable | None = None,
*,
default=_MISSING,
field: str | None = None,
fallback=_MISSING,
mapper: Callable | None = None,
return_result: bool = True,
):
"""Decorate a namespace method as a browser wire command.
This keeps the public method signature/docstring on the normal Python
method, while moving repetitive command-dispatch plumbing into one place.
The wrapped function body is intentionally unused; it exists only for
signature, docs, and type hints.
"""
def decorator(func: F) -> F:
@wraps(func)
def wrapper(self, *method_args, **method_kwargs):
payload = args(self, *method_args, **method_kwargs) if args is not None else {}
result = self.command(name, payload)
if not return_result:
return None
if mapper is not None:
return mapper(self, result, *method_args, **method_kwargs)
if field is not None:
if fallback is _MISSING:
return self.field(result, field, default)
return self.field(result, field, default, fallback=fallback)
if default is not _MISSING and not result:
return _clone_default(default)
return result
wrapper._browser_cli_command = name # type: ignore[attr-defined]
return wrapper # type: ignore[return-value]
return decorator
class Namespace:
"""A group of related SDK methods, bound to a BrowserCLI client."""
"""A group of related SDK methods, bound to a BrowserCLI client."""
def __init__(self, client: "BrowserCLI"):
self._c = client
def __init__(self, client: Any):
self._c = client
def command(self, name: str, args: dict | None = None):
"""Dispatch a browser command through the owning client."""
return self._c.dispatch(name, args)
def tab_from(self, data: dict):
"""Build a bound Tab from a raw command response dict."""
return self._c.tab_from(data)
def group_from(self, data: dict):
"""Build a bound Group from a raw command response dict."""
return self._c.group_from(data)
def tab_from_target(self, data: dict, target):
"""Build a bound Tab for a multi-browser target."""
return self._c.tab_from_target(data, target)
def group_from_target(self, data: dict, target):
"""Build a bound Group for a multi-browser target."""
return self._c.group_from_target(data, target)
def tag_browser(self, item: dict, target):
"""Annotate a raw dict with its browser in multi-browser mode."""
return self._c.tag_browser(item, target)
def multi_list(self, name: str, args: dict | None, mapper: Callable):
"""Run a list command with multi-browser fan-out support."""
return self._c.multi_list(name, args, mapper)
def multi_count(self, name: str, args: dict | None = None):
"""Run a count command with multi-browser fan-out support."""
return self._c.multi_count(name, args)
def apply_tab_filter(self, filter_fn: Callable):
"""Apply a Python-side tab filter using client semantics."""
return self._c.apply_tab_filter(filter_fn)
def toggle_tab(self, name: str, tab_id: int | None):
"""Run a tab toggle command and return the affected tab ID."""
return self._c.toggle_tab(name, tab_id)
def require_tab(self, data, error: str):
"""Convert a tab-like response into a bound Tab or raise a clean error."""
return self._c.require_tab(data, error)
def field(self, result, key, default=None, *, fallback=_MISSING):
"""Read a field from command output using the client's SDK semantics."""
if fallback is _MISSING:
return self._c.field(result, key, default)
return self._c.field(result, key, default, fallback=fallback)