-
Notifications
You must be signed in to change notification settings - Fork 5
Plugin registry improvements #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughThe PR refactors PluginRegistryBase to enforce a true singleton pattern via Changes
Sequence DiagramsequenceDiagram
participant User as User Code
participant Registry as PluginRegistry
participant Discoverer as Plugin Discoverer
participant Module as Plugin Module
participant Validator as Validator
participant Plugin as Plugin Instance
User->>Registry: collect_plugins()
activate Registry
Registry->>Discoverer: scan for modules matching module_prefix
activate Discoverer
Discoverer->>Module: import module
activate Module
Module-->>Discoverer: return module
deactivate Module
Discoverer-->>Registry: yield module
deactivate Discoverer
loop For each discovered module
Registry->>Validator: validate_plugin(name, module)
activate Validator
Validator->>Registry: expected_attributes()
activate Registry
Registry-->>Validator: attribute requirements
deactivate Registry
Validator->>Module: check attributes exist & types match
activate Module
Module-->>Validator: attribute values
deactivate Module
alt Validation succeeds
Validator-->>Registry: valid
else Validation fails
Validator-->>Registry: InvalidPluginException
end
deactivate Validator
alt Validation passed
Registry->>Registry: register_plugin(name, module)
Registry->>Registry: load_plugin(name, module)
activate Registry
Registry->>Plugin: construct plugin from module
activate Plugin
Plugin-->>Registry: plugin instance
deactivate Plugin
Registry->>Registry: store plugin
deactivate Registry
end
end
User->>Registry: get_plugin(plugin_name)
Registry-->>User: return TPlugin or InvalidPluginException
deactivate Registry
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
tests/example_plugin.py (1)
49-58: Consider defensive handling ofmodule.__file__.Line 56 assumes
module.__file__is not None, which could raise a TypeError if the module is a built-in or namespace package. While test fixtures are file-based, adding a defensive check or assertion would improve robustness.Consider adding a guard:
def load_plugin(self, name: str, module: ModuleType) -> ExamplePlugin: settings_cls = getattr(module, "ExampleSettings", None) string_attr = module.example_string + if module.__file__ is None: + raise ValueError(f"Plugin module {name} has no __file__ attribute") + return ExamplePlugin( _name=name, _settings_cls=settings_cls, file=Path(module.__file__), string_attr=string_attr, )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pyproject.tomlis excluded by!pyproject.toml
📒 Files selected for processing (9)
src/snakemake_interface_common/plugin_registry/__init__.py(5 hunks)tests/__init__.py(1 hunks)tests/example_plugin.py(1 hunks)tests/plugins/invalid-class/snakemake_example_plugin_invalid_class/__init__.py(1 hunks)tests/plugins/invalid-object/snakemake_example_plugin_invalid_object/__init__.py(1 hunks)tests/plugins/missing-attr/snakemake_example_plugin_missing_attr/__init__.py(1 hunks)tests/plugins/valid/snakemake_example_plugin_valid_1/__init__.py(1 hunks)tests/plugins/valid/snakemake_example_plugin_valid_2/__init__.py(1 hunks)tests/test_registry.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py
⚙️ CodeRabbit configuration file
**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of theselfargument of methods.
Do not suggest type annotation of theclsargument of classmethods.
Do not suggest return type annotation if a function or method does not contain areturnstatement.
Files:
tests/plugins/missing-attr/snakemake_example_plugin_missing_attr/__init__.pytests/plugins/invalid-object/snakemake_example_plugin_invalid_object/__init__.pytests/plugins/valid/snakemake_example_plugin_valid_2/__init__.pytests/plugins/valid/snakemake_example_plugin_valid_1/__init__.pytests/__init__.pytests/example_plugin.pysrc/snakemake_interface_common/plugin_registry/__init__.pytests/plugins/invalid-class/snakemake_example_plugin_invalid_class/__init__.pytests/test_registry.py
🧬 Code graph analysis (5)
tests/plugins/valid/snakemake_example_plugin_valid_1/__init__.py (2)
src/snakemake_interface_common/plugin_registry/plugin.py (1)
SettingsBase(41-50)tests/plugins/invalid-class/snakemake_example_plugin_invalid_class/__init__.py (1)
ExampleSettings(6-7)
tests/example_plugin.py (3)
src/snakemake_interface_common/plugin_registry/__init__.py (1)
PluginRegistryBase(23-206)src/snakemake_interface_common/plugin_registry/plugin.py (2)
PluginBase(82-360)SettingsBase(41-50)src/snakemake_interface_common/plugin_registry/attribute_types.py (3)
AttributeType(17-31)AttributeMode(6-8)AttributeKind(11-13)
src/snakemake_interface_common/plugin_registry/__init__.py (3)
src/snakemake_interface_common/plugin_registry/plugin.py (2)
name(85-85)register_cli_args(134-198)src/snakemake_interface_common/plugin_registry/attribute_types.py (1)
AttributeType(17-31)src/snakemake_interface_common/exceptions.py (1)
InvalidPluginException(77-79)
tests/plugins/invalid-class/snakemake_example_plugin_invalid_class/__init__.py (1)
tests/plugins/valid/snakemake_example_plugin_valid_1/__init__.py (1)
ExampleSettings(9-10)
tests/test_registry.py (4)
src/snakemake_interface_common/plugin_registry/plugin.py (1)
SettingsBase(41-50)src/snakemake_interface_common/exceptions.py (1)
InvalidPluginException(77-79)tests/example_plugin.py (5)
ExamplePlugin(18-34)ExamplePluginRegistry(37-72)new(39-43)name(25-26)settings_cls(33-34)src/snakemake_interface_common/plugin_registry/__init__.py (4)
get_plugin_type(113-125)is_installed(79-81)get_plugin(83-98)get_registered_plugins(75-77)
🔇 Additional comments (23)
tests/__init__.py (1)
1-1: LGTM!Clear comment explaining the purpose of this
__init__.pyfile.src/snakemake_interface_common/plugin_registry/__init__.py (7)
23-42: Excellent documentation.The docstring clearly explains the singleton pattern, naming conventions, discovery mechanism, and includes a concrete example. The note about editable installs is a helpful caveat.
44-56: LGTM!The singleton pattern using
__new__with an initialization guard in__init__is well-implemented. Each subclass will maintain its own_instanceclass attribute due to Python's attribute assignment semantics.
58-72: LGTM!Good organization moving abstract methods to the top. The contract for subclasses is clear and well-documented.
83-98: LGTM!Good improvement to the error message using the pip-installable package name, making it actionable for users.
127-139: LGTM!The plugin discovery logic correctly filters by prefix, transforms underscores to dashes in plugin names, and delegates to
register_pluginfor validation and registration.
141-206: LGTM!The
register_pluginandvalidate_pluginmethods provide thorough validation with clear error messages. The handling of optional vs required attributes and class vs object types is correct.
11-11: Minimum Python version is already properly set to 3.11+.Verification confirms that
snakemake-interface-commonrequires Python >=3.11 in its package metadata (both PyPI and Bioconda). TheSelfimport fromtypingon line 11 is therefore appropriately supported and no additional configuration changes are needed.tests/plugins/valid/snakemake_example_plugin_valid_2/__init__.py (1)
1-3: LGTM!Good test fixture for validating plugin discovery with only required attributes (no optional settings class).
tests/plugins/missing-attr/snakemake_example_plugin_missing_attr/__init__.py (1)
1-1: LGTM!Appropriate negative test fixture for validating error handling when required plugin attributes are missing.
tests/plugins/invalid-class/snakemake_example_plugin_invalid_class/__init__.py (1)
1-7: LGTM!Appropriate negative test fixture for validating error handling when a plugin's class attribute doesn't inherit from the expected base class.
tests/plugins/invalid-object/snakemake_example_plugin_invalid_object/__init__.py (1)
1-3: LGTM! Test fixture correctly implements invalid scenario.This test fixture appropriately provides an integer value for
example_stringto enable validation of type-checking error handling in the registry.tests/plugins/valid/snakemake_example_plugin_valid_1/__init__.py (2)
1-6: LGTM! Valid test fixture structure.The module correctly defines the required
example_stringattribute with the expected string type.
9-10: LGTM! Minimal settings class is appropriate for testing.The empty
ExampleSettingsclass correctly inherits fromSettingsBaseand is sufficient to test the optional settings class attribute validation. The TODO can remain as this fixture validates that minimal settings classes are properly recognized.tests/test_registry.py (5)
1-14: LGTM! Clean test setup.Imports and test directory setup are correct.
16-28: LGTM! Comprehensive basic functionality tests.The test correctly validates registry type inference, singleton behavior, and plugin-not-found error handling.
30-59: LGTM! Thorough plugin discovery and validation tests.The test comprehensively validates plugin discovery, instantiation, and attribute checking for both plugins with and without optional settings classes.
61-72: LGTM! Proper validation of missing attribute handling.The test correctly verifies that the registry raises
InvalidPluginExceptionwith an appropriate error message when a required attribute is missing.
74-98: LGTM! Comprehensive type validation tests.Both
test_invalid_objectandtest_invalid_classproperly validate that the registry rejects plugins with incorrect attribute types and generates informative error messages.tests/example_plugin.py (4)
1-16: LGTM! Clean import structure.All necessary imports are present and correctly organized.
17-35: LGTM! ExamplePlugin correctly implements PluginBase.The dataclass structure and property implementations appropriately fulfill the
PluginBasecontract for testing purposes.
38-48: LGTM! Test registry setup is correct.The
new()classmethod appropriately bypasses singleton behavior for isolated testing, andmodule_prefixcorrectly identifies test plugins.
60-72: LGTM! Attribute specifications are correct.The
expected_attributes()implementation properly defines the validation rules for test plugins, specifyingExampleSettingsas an optional class attribute andexample_stringas a required object attribute.
PluginRegsitryBase:__new__()- previous annotation allowed for returning an instance of a different registry class as long as the type parameter was consistent. This leads to problems with type checking tools. See caveat below.register_plugin()method: Fixed issue where name conflicts were checked using the full module name with prefix instead of the registry name. Changed argument name to be a bit more clear.get_plugin()method: Fix error message to properly display expected package name usingget_plugin_package_name(). Also corrected return type annotation.tests/to PixiformattaskNote: fix to type of annotation of
PluginRegistryBase.__new__()requirestyping.Self, which requires Python 3.11. I think a type annotation is required here due to returningcls._instance, tools will otherwise infer an unknown type. Since Snakemake itself now requires 3.11, I don't think there should be an issue with increasing the minimum version of this package to match.Summary by CodeRabbit
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.