"""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 typing import Any, Protocol, cast from browser_cli.models import Group, Tab class _FactoryClient(Protocol): _key: str | None 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 tab_from( 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, index=data.get("index", 0) or 0, browser=browser_name, ) client = cast(_FactoryClient, self) tab._browser = self if browser_profile is None else cast(Any, type(self))( browser=browser_profile, remote=browser_remote, key=client._key, _command_sender=getattr(self, "_command_sender", None), ) return tab def require_tab_response(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.tab_from(data) def group_from( 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), window_id=data.get("windowId"), browser=browser_name, ) client = cast(_FactoryClient, self) group._browser = self if browser_profile is None else cast(Any, type(self))( browser=browser_profile, remote=browser_remote, key=client._key, _command_sender=getattr(self, "_command_sender", None), ) return group def tab_from_target(self, data: dict, target) -> Tab: """Build a Tab, tagging it with *target* in multi-browser mode (``None`` = local).""" return self.tab_from( 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 group_from_target(self, data: dict, target) -> Group: """Build a Group, tagging it with *target* in multi-browser mode (``None`` = local).""" return self.group_from( 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}