mirror of
https://github.com/samjage/metro-warden.git
synced 2026-06-06 04:00:42 +00:00
Initial commit: Metro Warden TUI network operations center
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Metro Warden Base Plugin — abstract base class for all plugins.
|
||||
|
||||
Every plugin must subclass :class:`BasePlugin` and define the class-level
|
||||
attributes ``name``, ``version``, and ``description``. Lifecycle hooks
|
||||
``on_load``, ``on_unload``, and ``on_event`` can be overridden as needed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BasePlugin(abc.ABC):
|
||||
"""
|
||||
Abstract base class for Metro Warden plugins.
|
||||
|
||||
Subclasses must declare:
|
||||
name = "my-plugin" # unique identifier
|
||||
version = "1.0.0"
|
||||
description = "Does something useful"
|
||||
tags = ["category"] # optional
|
||||
|
||||
Lifecycle::
|
||||
|
||||
on_load() — called once after instantiation
|
||||
on_unload() — called before the plugin is removed
|
||||
on_event(topic, data) — called for bus events the plugin subscribes to
|
||||
"""
|
||||
|
||||
# Class-level attributes — subclasses MUST override these
|
||||
name: str = ""
|
||||
version: str = "0.0.0"
|
||||
description: str = ""
|
||||
tags: list = []
|
||||
|
||||
def __init__(self, bus=None, state=None) -> None:
|
||||
if not self.name:
|
||||
raise ValueError(f"{type(self).__name__} must define a 'name' attribute")
|
||||
self._bus = bus
|
||||
self._state = state
|
||||
self._sub_ids: list[str] = []
|
||||
self._log = logging.getLogger(f"plugin.{self.name}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lifecycle hooks
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def on_load(self) -> None:
|
||||
"""Called once when the plugin is loaded by the registry."""
|
||||
self._log.info("plugin %r loaded (v%s)", self.name, self.version)
|
||||
|
||||
def on_unload(self) -> None:
|
||||
"""Called when the plugin is being unloaded. Clean up resources here."""
|
||||
self._unsubscribe_all()
|
||||
self._log.info("plugin %r unloaded", self.name)
|
||||
|
||||
def on_event(self, topic: str, data: Any) -> None:
|
||||
"""
|
||||
Called for every bus event whose topic this plugin has subscribed to.
|
||||
Override in subclasses to handle specific events.
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helper utilities
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def subscribe(self, topic_pattern: str) -> Optional[str]:
|
||||
"""Subscribe this plugin's on_event handler to *topic_pattern*."""
|
||||
if self._bus is None:
|
||||
self._log.warning("no bus — cannot subscribe to %r", topic_pattern)
|
||||
return None
|
||||
sub_id = self._bus.subscribe(topic_pattern, self.on_event)
|
||||
self._sub_ids.append(sub_id)
|
||||
return sub_id
|
||||
|
||||
def publish(self, topic: str, data: Any = None) -> None:
|
||||
"""Publish an event to the bus."""
|
||||
if self._bus is None:
|
||||
return
|
||||
self._bus.publish_sync(topic, data)
|
||||
|
||||
def state_get(self, key: str, default: Any = None) -> Any:
|
||||
if self._state is None:
|
||||
return default
|
||||
return self._state.get(key, default)
|
||||
|
||||
def state_set(self, key: str, value: Any) -> None:
|
||||
if self._state is not None:
|
||||
self._state.set(key, value)
|
||||
|
||||
def _unsubscribe_all(self) -> None:
|
||||
if self._bus is None:
|
||||
return
|
||||
for sid in self._sub_ids:
|
||||
self._bus.unsubscribe(sid)
|
||||
self._sub_ids.clear()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{type(self).__name__} name={self.name!r} v{self.version}>"
|
||||
Reference in New Issue
Block a user