Skip to content

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:

  1. Each ancestor's already-computed _commands/_in_metrics/_out_metrics (walked over the MRO)
  2. Entries from the current class's own Meta class only
  3. 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.