fix(plugins): discovery must not instantiate an imported base class - #4319
Open
warksit wants to merge 1 commit into
Open
fix(plugins): discovery must not instantiate an imported base class#4319warksit wants to merge 1 commit into
warksit wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes Predbat’s plugin discovery so it won’t instantiate an imported base class (e.g., PredBatPlugin) just because it alphabetically sorts first in inspect.getmembers(), preventing silently “loaded” but non-functional plugins.
Changes:
- Tighten name-based plugin discovery to only consider classes defined in the plugin module (
obj.__module__ == plugin_module.__name__). - Add a regression test that creates a temp plugin whose class name sorts after
PredBatPluginand asserts the correct class is instantiated. - Register the new test in
unit_test.pyso it runs with the suite.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| apps/predbat/plugin_system.py | Restricts name-based class discovery to classes defined in the plugin file to avoid instantiating imported base classes. |
| apps/predbat/tests/test_plugin_startup.py | Adds a regression test for the discovery bug using a temporary plugin module. |
| apps/predbat/unit_test.py | Registers the new plugin discovery regression test in the main test list. |
Comment on lines
132
to
134
| for name, obj in inspect.getmembers(plugin_module, inspect.isclass): | ||
| if name.endswith("Plugin"): | ||
| if name.endswith("Plugin") and obj.__module__ == plugin_module.__name__: | ||
| try: |
inspect.getmembers() returns members ALPHABETICALLY, and the name-based detection strategy takes the first class ending in 'Plugin'. Every plugin does 'from plugin_system import PredBatPlugin', so the abstract base sits in the module namespace — and any plugin class sorting after 'PredBatPlugin' gets the base instantiated instead of itself. The plugin then logs as loaded successfully and silently does nothing: no hooks registered, no behaviour. Whether a plugin works depends on the luck of its class name. Hit in the wild with a class named SocKeepPublishPlugin; ColdWeatherPlugin and CurtailmentPlugin sort before the base, so they were unaffected. Fix: require obj.__module__ == plugin_module.__name__, so only classes actually DEFINED in the plugin file are candidates. The two fallback strategies (PREDBAT_PLUGIN marker, initialize_plugin function) are unchanged. Test discovers a temp plugin whose class name sorts after PredBatPlugin and asserts the right class was instantiated. Verified it fails without the fix: ERROR: discovery instantiated PredBatPlugin, expected ZzSortsLastPlugin
warksit
force-pushed
the
fix/plugin-discovery-base-class
branch
from
August 20, 2026 07:52
4ebf9a4 to
0666c8c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
load_plugin()'s preferred detection strategy takes the first class whose name ends inPlugin:inspect.getmembers()returns members alphabetically. Every plugin doesfrom plugin_system import PredBatPlugin, so the abstract base class is in the module namespace and is itself a match. Any plugin whose class name sorts afterPredBatPlugintherefore gets the base instantiated instead of itself.When that happens the plugin looks completely healthy in the log:
…but
PredBatPlugin.register_hooks()is a no-op, so no hooks are registered and the plugin silently does nothing. There is no error, no warning, and the entity it was supposed to publish simply goes stale.Whether a plugin works depends on the luck of its class name. I hit this with a plugin class called
SocKeepPublishPlugin;ColdWeatherPluginandCurtailmentPluginhappen to sort beforePredBatPlugin, which is the only reason they were unaffected.The fix
Require the candidate class to be defined in the plugin file, not merely imported into it:
This also covers the general case of a plugin importing any other helper class ending in
Plugin.The two fallback strategies (the
PREDBAT_PLUGINmarker and theinitialize_plugin()function) are unchanged.Compatibility
No API change and no behaviour change for any plugin that was already working — a plugin class defined in its own file still matches exactly as before. The only plugins affected are ones that were silently broken.
Testing
Added
test_plugin_discovery_skips_imported_base_classto the existingtests/test_plugin_startup.py(no new files). It writes a temp plugin whose class name deliberately sorts afterPredBatPlugin, runs real discovery over it, and asserts the correct class was instantiated.Confirmed it fails without the fix:
and passes with it. Registered in
unit_test.pyasplugin_discovery. Fullunit_test.py --quickpasses,pre-commit runclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01WY52NvNyVdznsF4VcVG8Q8