Spaces:
Running on Zero
Running on Zero
| from __future__ import annotations | |
| import asyncio | |
| import inspect | |
| from collections.abc import Awaitable, Callable | |
| from typing import Protocol | |
| from pydantic import BaseModel | |
| from .event import EventEnvelope | |
| EventHandler = Callable[[BaseModel], Awaitable[None]] | |
| QueuedEvent = tuple[EventEnvelope[BaseModel], EventHandler] | |
| class PluginManagerProtocol(Protocol): | |
| def register_event(self, name: str, contract: type[BaseModel]) -> None: ... | |
| async def emit( | |
| self, | |
| name: str, | |
| data: BaseModel, | |
| *, | |
| source: str = "space", | |
| ) -> EventEnvelope[BaseModel]: ... | |
| async def wait_for_idle(self, *, timeout: float = 5) -> None: ... | |
| async def report_plugin_error( | |
| self, | |
| plugin: Plugin, | |
| envelope: EventEnvelope[BaseModel], | |
| error: Exception, | |
| ) -> None: ... | |
| class Plugin: | |
| def __init__(self, name: str, *, receive_self_events: bool = False) -> None: | |
| if not name: | |
| raise ValueError("plugin name must not be empty") | |
| self.name: str = name | |
| self.capabilities: tuple[str, ...] = () | |
| self.receive_self_events: bool = receive_self_events | |
| self.manager: PluginManagerProtocol | None = None | |
| self.enabled: bool = False | |
| self.queue: asyncio.Queue[QueuedEvent] = asyncio.Queue() | |
| self._initialized: bool = False | |
| self._worker: asyncio.Task[None] | None = None | |
| async def initialize(self) -> None: | |
| return None | |
| def iter_event_handlers(self) -> dict[str, EventHandler]: | |
| return { | |
| name.removeprefix("on_"): getattr(self, name) | |
| for name in dir(self) | |
| if name.startswith("on_") and inspect.iscoroutinefunction(getattr(self, name)) | |
| } | |
| async def _enqueue(self, envelope: EventEnvelope[BaseModel], handler: EventHandler) -> None: | |
| if self.enabled: | |
| await self.queue.put((envelope, handler)) | |
| def _start_worker(self) -> None: | |
| self._worker = asyncio.create_task(self._run(), name=f"space-plugin-{self.name}") | |
| async def _stop_worker(self) -> None: | |
| worker = self._worker | |
| self._worker = None | |
| if worker is not None: | |
| worker.cancel() | |
| try: | |
| await worker | |
| except asyncio.CancelledError: | |
| pass | |
| self.clear_queue() | |
| def clear_queue(self) -> None: | |
| while not self.queue.empty(): | |
| self.queue.get_nowait() | |
| self.queue.task_done() | |
| async def _run(self) -> None: | |
| while True: | |
| envelope, handler = await self.queue.get() | |
| try: | |
| await handler(envelope.data) | |
| except Exception as error: | |
| if self.manager is not None: | |
| await self.manager.report_plugin_error(self, envelope, error) | |
| finally: | |
| self.queue.task_done() | |