ReMe/reme/components/client/base_client.py
jinliyl b9caae1e50
feat(service): support explicit wildcard network binds (#547)
* feat(service): bind network services to all interfaces

* fix(service): keep network listeners local by default

* fix(service): make remote access explicit
2026-09-15 14:26:33 +08:00

62 lines
2.2 KiB
Python

"""Base client abstraction."""
import json
import os
from abc import abstractmethod
from collections.abc import AsyncGenerator
from ..base_component import BaseComponent
from ...constants import (
REME_DEFAULT_HOST,
REME_DEFAULT_PORT,
REME_SERVICE_INFO,
normalize_connect_host,
)
from ...enumeration import ComponentEnum
class BaseClient(BaseComponent):
"""Abstract base for clients that communicate with ReMe services."""
component_type = ComponentEnum.CLIENT
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.client = None
def _resolve_service_address(self, host: str | None, port: int | None) -> tuple[str, int]:
"""Resolve explicit, discovered, or default network coordinates."""
if not (host and port):
if service_info := os.environ.get(REME_SERVICE_INFO):
try:
data = json.loads(service_info)
host, port = data["host"], data["port"]
except Exception:
self.logger.warning(f"Invalid service info: {service_info}")
host, port = REME_DEFAULT_HOST, REME_DEFAULT_PORT
else:
host, port = REME_DEFAULT_HOST, REME_DEFAULT_PORT
return normalize_connect_host(host), port
async def _start(self) -> None:
"""Initialize the client."""
async def _close(self) -> None:
"""Close the client and release resources."""
@abstractmethod
def _execute(self, action: str, payload: dict) -> AsyncGenerator[str, None]:
"""Backend-specific execution; yield text chunks (single yield for non-streaming backends)."""
@abstractmethod
async def list_actions(self) -> list[dict]:
"""Discover available actions on the server; each dict is the raw backend descriptor."""
async def __call__(self, action: str, **kwargs) -> AsyncGenerator[str, None]:
"""Dispatch: action='list' returns the action catalog; otherwise delegate to _execute()."""
if action == "list":
actions = await self.list_actions()
yield json.dumps(actions, indent=2, ensure_ascii=False)
return
async for chunk in self._execute(action, kwargs):
yield chunk