from collections.abc import Awaitable, Callable from dataclasses import dataclass, field @dataclass class Plugin: """A self-contained dashboard plugin. Each plugin registers a router (mounted in create_app) and reports itself here with a display title/description. `open`/`close` are optional lifecycle hooks run from the app lifespan; `skeleton_fn` renders the plugin's initial fragment for the Plugins tab. """ id: str title: str description: str = "" skeleton_fn: Callable[[], Awaitable[str]] | None = field(default=None) open_fn: Callable[[], Awaitable[None]] | None = field(default=None) close_fn: Callable[[], Awaitable[None]] | None = field(default=None) async def skeleton(self) -> str: """Render the plugin's initial fragment. Returns: The HTML fragment for the Plugins tab. Raises: NotImplementedError: if no skeleton_fn was provided. """ if self.skeleton_fn is None: raise NotImplementedError return await self.skeleton_fn() async def open(self) -> None: """Run the plugin's startup hook (no-op when not provided).""" if self.open_fn is not None: await self.open_fn() async def close(self) -> None: """Run the plugin's shutdown hook (no-op when not provided).""" if self.close_fn is not None: await self.close_fn()