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
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
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