fd5447cbb9
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Package Extension / package-extension (push) Successful in 43s
Build & Publish Package / publish (push) Successful in 43s
Testing / test (push) Successful in 45s
Restructure the Python API and internals around composable namespaces and a standalone transport/endpoint layer. Bump to 0.12.0. Python API: - Replace flat methods (b.tabs_list(), b.group_list()) with namespaces: b.nav, b.tabs, b.groups, b.windows, b.dom, b.extract, b.page, b.storage, b.cookies, b.session, b.perf, b.extension. - Shrink browser_cli/__init__.py to a thin composition root; move all behaviour into browser_cli/sdk/ (one module per namespace + factories, base, routing). Internals: - Add browser_cli/transport.py and remote_transport.py to isolate IPC from command logic; client.py now delegates instead of owning transport. - Add browser_cli/endpoints.py for endpoint resolution and browser_cli/errors.py for shared error types. - Extract markdown rendering into browser_cli/markdown.py (out of extract). - Add USER_AGENT to version_manager. Tooling & tests: - Add justfile with common dev tasks. - Update CLI commands and demo to the namespaced API. - Rework tests for the new layout; add test_transport.py and test_refactor_boundaries.py to lock in module boundaries. BREAKING CHANGE: flat API methods are removed in favour of namespaces (e.g. b.tabs_list() -> b.tabs.list(), b.group_list() -> b.groups.list()).
95 lines
3.4 KiB
Python
95 lines
3.4 KiB
Python
"""Object-factory mixin for :class:`~browser_cli.BrowserCLI`.
|
|
|
|
Builds the typed :class:`~browser_cli.models.Tab` / :class:`~browser_cli.models.Group`
|
|
dataclasses from raw command responses and binds each one to the client that
|
|
should run its actions. In multi-browser mode an object is bound to a sibling
|
|
client targeting the browser it came from, so ``tab.close()`` routes correctly.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from browser_cli.models import Group, Tab
|
|
|
|
class FactoryMixin:
|
|
"""Turn raw response dicts into bound ``Tab``/``Group`` objects.
|
|
|
|
Mixed into :class:`~browser_cli.BrowserCLI`; relies on the client providing
|
|
``_browser``/``_remote``/``_key`` and being constructible via ``type(self)``.
|
|
"""
|
|
|
|
def _make_tab(
|
|
self,
|
|
data: dict,
|
|
*,
|
|
browser_profile: str | None = None,
|
|
browser_name: str | None = None,
|
|
browser_remote: str | None = None,
|
|
) -> Tab:
|
|
tab = Tab(
|
|
id=data["id"],
|
|
window_id=data.get("windowId", 0),
|
|
active=data.get("active", False),
|
|
muted=data.get("muted", False),
|
|
title=data.get("title") or "",
|
|
url=data.get("url") or "",
|
|
group_id=data.get("groupId") or None,
|
|
browser=browser_name,
|
|
)
|
|
tab._browser = self if browser_profile is None else type(self)(
|
|
browser=browser_profile,
|
|
remote=browser_remote,
|
|
key=self._key,
|
|
)
|
|
return tab
|
|
|
|
def _require_tab(self, data, error: str) -> Tab:
|
|
"""Build a bound Tab from a tab-shaped response, or raise ``RuntimeError(error)``."""
|
|
if not isinstance(data, dict) or "id" not in data:
|
|
raise RuntimeError(error)
|
|
return self._make_tab(data)
|
|
|
|
def _make_group(
|
|
self,
|
|
data: dict,
|
|
*,
|
|
browser_profile: str | None = None,
|
|
browser_name: str | None = None,
|
|
browser_remote: str | None = None,
|
|
) -> Group:
|
|
group = Group(
|
|
id=data["id"],
|
|
title=data.get("title") or "",
|
|
color=data.get("color") or "",
|
|
collapsed=data.get("collapsed", False),
|
|
tab_count=data.get("tabCount", 0),
|
|
browser=browser_name,
|
|
)
|
|
group._browser = self if browser_profile is None else type(self)(
|
|
browser=browser_profile,
|
|
remote=browser_remote,
|
|
key=self._key,
|
|
)
|
|
return group
|
|
|
|
def _make_tab_for(self, data: dict, target) -> Tab:
|
|
"""Build a Tab, tagging it with *target* in multi-browser mode (``None`` = local)."""
|
|
return self._make_tab(
|
|
data,
|
|
browser_profile=target.profile if target else None,
|
|
browser_name=target.display_name if target else None,
|
|
browser_remote=target.remote if target else None,
|
|
)
|
|
|
|
def _make_group_for(self, data: dict, target) -> Group:
|
|
"""Build a Group, tagging it with *target* in multi-browser mode (``None`` = local)."""
|
|
return self._make_group(
|
|
data,
|
|
browser_profile=target.profile if target else None,
|
|
browser_name=target.display_name if target else None,
|
|
browser_remote=target.remote if target else None,
|
|
)
|
|
|
|
@staticmethod
|
|
def _tag_browser(item: dict, target) -> dict:
|
|
"""Return *item* as-is locally, or with a ``browser`` key in multi-browser mode."""
|
|
return item if target is None else {**item, "browser": target.display_name}
|