Skip to content

refactor: plugin.py is 3,038 lines carrying six responsibilities — split it behind the XML wiring #146

Description

@simons-plugins

plugin.py is 3,038 lines, the largest file in the repo by 1.7×
(export_bridge.py is next at 1,766). Every other module has one job; this one
has six. Filing the shape now, while the seams are fresh, rather than
discovering them again later.

What is actually in there

Measured by line band, not by impression:

Lines Band Roughly
246 Module-level helpers server_location, sanitize_host, _escape, _pairing_html (a 70-line HTML template living in the plugin module)
914 Lifecycle, health, device sync startup/shutdown/runConcurrentThread, the device-subscription machinery, deviceUpdated/deviceDeleted, _health_tick, config-UI validate/close
152 IWS HTTP handlers The Domio API surface — http_status/http_commission/http_decommission/http_diagnostics and their sync bodies
289 matter-server menus Install/reinstall/restart/logs, manual commissioning, decommission, folder + node pickers
574 Export dialog callbacks The whole Manage Matter Exports master-detail: candidates, roles, add/update/remove, summaries, warnings
406 Export recovery + bridge agent Rebuild map, reset pairings, agent start/stop/diagnose, bridge install
457 Pairing + fabric management Pair/unpair, fabric cache, the IWS pairing page, backup/restore

The last four bands — 1,726 lines, 57% of the file — are menu and dialog
callbacks. They are in plugin.py only because Indigo resolves
<CallbackMethod> against the Plugin class, not because they belong beside
startup.

The constraint that shapes any split

Indigo resolves XML callbacks by name, as attributes on the Plugin class.
MenuItems.xml, Actions.xml, Devices.xml and PluginConfig.xml between
them name every menu*, get*, export*, validate* and actionControl*
method. Anything extracted must stay reachable as Plugin.<name> or the menu
silently breaks at runtime — Indigo logs a missing-callback error, no test
fails, and nothing at import time complains.

tests/test_plugin_module.py already guards exactly this
(test_menu_callbacks_exist_on_plugin, test_action_callbacks_exist_on_plugin,
test_dynamic_list_methods_exist_on_plugin), so the guardrail is in place
before the refactor rather than after. That is the reason to do this now.

So the split is mixins/delegation, not free-standing modules: the callback stays
a thin Plugin method, the body moves. Mixin classes (ExportDialogMixin,
PairingMenuMixin, ServerMenuMixin, HttpApiMixin) keep every name on the
class via MRO and need no change to a single XML file.

Why this is cheap right now

  • 421 tests across 5 files already import plugin and drive these methods
    directly (test_plugin_behaviour.py, test_plugin_module.py,
    test_export_menu.py, test_export_wiring.py, test_pairing_menu.py).
    Behaviour is pinned; a move that changes behaviour fails loudly.
  • No module imports plugin.py back — verified, zero back-imports. The
    dependency arrows all point away from it, so extraction cannot create a cycle.
  • The repo runs 1.6:1 tests to code overall. This is the file where that
    investment pays out.

Suggested order, smallest risk first

  1. _pairing_html → its own module. A 70-line HTML template with no plugin
    state; pure function, already tested through http_pairing. Free.
  2. IWS handlers → HttpApiMixin (152 lines). Smallest real band, clearest
    boundary — it is the Domio contract in docs/API.md, which is versioned
    separately anyway.
  3. Export dialog → ExportDialogMixin (574 lines). Biggest win. Pairs
    naturally with export_store / export_catalog, which it already delegates
    to.
  4. Pairing + fabric → PairingMenuMixin (457 lines).
  5. Server + bridge menus → ServerMenuMixin (695 lines across two bands).

Leaves plugin.py at roughly 900–1,100 lines of genuine lifecycle glue,
which is defensible for an Indigo plugin's entry point.

Explicitly not in scope

  • No behaviour change, no renamed callbacks, no XML edits. If any <CallbackMethod>
    string changes, the refactor has gone wrong.
  • Not a test reorganisation. The five test files keep importing plugin; they
    are testing the composed class, which is what Indigo instantiates.
  • endpoints.ts (1,662 lines) is not included. It is one factory per §4.2
    role — a cohesive table, not six jobs in a trenchcoat.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions