-
Notifications
You must be signed in to change notification settings - Fork 878
Description
KolibriPluginBase takes string properties pointing to module names which it then imports. However, when there are issues with this import, the Django error messages are basically worthless - pointing to an entirely different issue.
So - there should be a try/catch earlier on in the instantiation of a class inheriting KolibriPluginBase that tries to import the module and complains directly if that module does not exist or if it has an error itself.
For example, if a class assigns "api_urls" to the property untrainslated_view_urls and that api_urls.py file itself has a broken import, the dev will see an error completely unrelated to that file and that import. Ideally, this will be caught far earlier and the user can be told in this case a more contextually helpful error message.
Current State of Error Handling
The KolibriPluginBase class (in kolibri/plugins/init.py) uses the _return_module() method (lines 296-309) to import modules specified by plugin properties. The current implementation:
def _return_module(self, module_name):
if module_has_submodule(sys.modules[self.module_path], module_name):
models_module_name = "%s.%s" % (self.module_path, module_name)
try:
return import_module(models_module_name)
except Exception as e:
logging.warning(
"Tried to import module {module_name} from {plugin} but an error was raised".format(
plugin=self.module_path, module_name=module_name
)
)
logging.exception(e)
return NoneThe problem: While this logs a generic warning, it:
- Swallows the actual exception details (only logs to console, not shown to developer in most cases)
- Returns
None, which causes errors to surface much later in unrelated parts of the codebase - Doesn't distinguish between different failure scenarios
Specific Misconfiguration Scenarios
Here are common mistakes that produce confusing error messages:
-
Typo in the module name:
class MyPlugin(KolibriPluginBase): untranslated_view_urls = "api_url" # Should be "api_urls"
-
Module exists but has a broken import:
# In api_urls.py from nonexistent_module import something # ImportError
-
Module file doesn't exist:
class MyPlugin(KolibriPluginBase): untranslated_view_urls = "urls" # File doesn't exist
-
Syntax errors in the module:
# In api_urls.py urlpatterns = [ # Missing closing bracket
Where Errors Surface
When _return_module() returns None, the error manifests in different places depending on which property was misconfigured:
-
URL registration (kolibri/plugins/utils/urls.py:44-45):
url_module = plugin_instance.url_module # Returns None api_url_module = plugin_instance.api_url_module # Returns None
Later when trying to access
url_module.urlpatterns(line 67), you get:AttributeError: 'NoneType' object has no attribute 'urlpatterns' -
Settings application (kolibri/plugins/utils/settings.py:134):
plugin_settings_module = plugin_instance.settings_module # Returns None
If settings are expected, subsequent code may fail with obscure errors.
-
Options loading (kolibri/plugins/utils/options.py:111-117):
plugin_options = plugin_instance.options_module # Returns None if plugin_options and hasattr(plugin_options, "option_spec"): # Safely handles None
This one is better handled, but still doesn't tell the developer what went wrong.
Desired Behavior
The improved error handling should:
-
Provide clear, actionable error messages that include:
- Which plugin caused the error (module path)
- Which property was misconfigured (e.g.,
untranslated_view_urls) - What went wrong (module not found vs. import error)
-
Include full exception information when a module exists but has import errors:
Error in plugin 'kolibri.plugins.learn': Failed to import module 'api_urls' specified in 'untranslated_view_urls' property. ImportError: cannot import name 'something' from 'nonexistent_module' [full traceback] Please check: - The module file exists at: kolibri/plugins/learn/api_urls.py - All imports in that file are correct - The module name is spelled correctly in the plugin definition -
Distinguish between error types:
- Module doesn't exist: "Module 'api_urls' not found. Expected file: kolibri/plugins/learn/api_urls.py"
- Import failed: Show the actual ImportError with traceback
- Syntax error: Show the SyntaxError with traceback
-
Fail early and explicitly rather than returning
Noneand causing confusion downstream
Understanding the Plugin Architecture
For students new to Kolibri, here's how the plugin system works:
Plugin Properties: When you create a plugin class, you can specify module names as string properties:
untranslated_view_urls: Module containing URL patterns for API endpoints (no language prefix)translated_view_urls: Module containing URL patterns for views with translated contentroot_view_urls: Module containing URL patterns attached to the domain rootdjango_settings: Module containing Django settings to augmentkolibri_options: Module containing configuration optionskolibri_option_defaults: Module containing default value overrides
Example:
class Learn(KolibriPluginBase):
untranslated_view_urls = "api_urls" # Points to kolibri/plugins/learn/api_urls.py
translated_view_urls = "urls" # Points to kolibri/plugins/learn/urls.py
kolibri_options = "options" # Points to kolibri/plugins/learn/options.pyModule Loading Process:
- Django startup triggers plugin registration
- For each enabled plugin, the system accesses properties like
.url_module(lines 311-336) - These properties call
_return_module()to import the specified module - The imported modules are used by various parts of the system (URL routing, settings, etc.)
Implementation Hints
When implementing the fix, consider:
- The
_return_module()method is the central place to add better error handling - You might want to create custom exception classes for different error scenarios
- Check Django's error messages for import failures as inspiration for helpful messages
- Consider using the existing development mode flag to provide even more detailed debugging info (see here for example: https://github.com/learningequality/kolibri/blob/develop/kolibri/core/webpack/hooks.py#L87 )
- Look at how the properties (
url_module,settings_module, etc.) use_return_module()- they currently have their own warning messages that could be improved too
Testing Your Changes
To test your improvements, you can:
- Create a test plugin with intentional errors
- Try each misconfiguration scenario listed above
- Verify that error messages clearly identify the problem
- Ensure the error appears immediately at startup (fail fast)
- Check that correct configurations still work normally
Additional Resources:
- Plugin Architecture Documentation
- Django's
import_modulefunction: Python docs - Django's
module_has_submodule: Used to check if a module exists before importing