Plugin System¶
Why Plugins Are Separate from Services¶
Services own the connection lifecycle and routing. Plugins own domain logic and hardware interaction. This separation means:
- A plugin can be tested without a NATS connection
- Multiple plugin types can coexist in a single service
- The same plugin class can be used with different service configurations
Declarative Registration via Meta¶
Plugins declare their commands and metrics in an inner Meta class. The
PluginMetaclass processes this at class definition time, collecting items
from the full MRO chain so subclasses automatically inherit base commands:
class MyPlugin(SensorPlugin):
class Meta:
commands = [MyCommand] # added to base SensorPlugin commands
out_metrics = [Metric("v", "V")] # added to any inherited metrics
For each of commands, in_metrics, and out_metrics, PluginMetaclass
delegates to a shared collector (ItemCollector and its subclasses
CommandsCollector/InputMetricCollector/OutputMetricCollector in
tolomeo/collectors.py), which assembles the final list in this order:
- Each ancestor's already-computed
_commands/_in_metrics/_out_metrics(walked over the MRO) - Entries from the current class's own
Metaclass only - Entries declared directly on the class body (outside
Meta)
The combined list is then deduplicated by identity, preserving order.
Lifecycle: attach → detach¶
Every plugin goes through a two-phase lifecycle:
attach()
└─ before_connect() → connect() → after_connect()
└─ before_setup() → setup() → after_setup()
detach()
└─ before_disconnect() → disconnect() → after_disconnect()
setup() is implemented by PluginBase and automatically registers all
declared commands and metrics. Use after_setup to start background tasks
— all commands will be registered by then.
Hook Methods¶
The six hook methods (before_connect, after_connect, before_setup,
after_setup, before_disconnect, after_disconnect) are called by
_call_with_hooks. They default to no-ops. Override them to add logic
without replacing the lifecycle contract.
OTAPlugin uses before_setup to wire task manager callbacks into the
FSM, and after_setup to bootstrap the state machine. This pattern keeps
initialization logic isolated and testable.