p-e-w/heretic · error · ValueError
Import-based plugin must use the form 'fully.qualified.modul
Error message
Import-based plugin must use the form 'fully.qualified.module.ClassName'
What it means
The name had no ':' (so it is treated as an import path), but it contains no '.', meaning there is no way to split it into module and class parts. load_plugin() throws this to enforce the 'fully.qualified.module.ClassName' form for import-based plugins.
Source
Thrown at src/heretic/plugin.py:134
f"Could not load plugin '{name}' (invalid module spec)"
)
module = importlib.util.module_from_spec(spec)
# Cache before executing to match normal import semantics and allow
# circular imports. If execution fails, remove the entry.
sys.modules[module_name] = module
try:
spec.loader.exec_module(module)
except Exception:
sys.modules.pop(module_name, None)
raise
plugin_cls = validate_class(module, class_name)
# Fully-qualified import path, e.g "heretic.scorers.keyword_rate.KeywordRate".
else:
if "." not in name:
raise ValueError(
"Import-based plugin must use the form 'fully.qualified.module.ClassName'"
)
module_name, class_name = name.rsplit(".", 1)
try:
module = importlib.import_module(module_name)
except ImportError as e:
raise ImportError(f"Error loading plugin '{name}': {e}") from e
plugin_cls = validate_class(module, class_name)
if not issubclass(plugin_cls, base_class):
raise TypeError(f"Plugin '{name}' must subclass {base_class.__name__}")
return plugin_cls
class Context:
"""
Runtime context passed to pluginsView on GitHub (pinned to bedb94ef11)
Solutions
- Use the full dotted path including module, e.g. 'myproject.scorers.my_scorer.MyScorer'.
- Ensure the module is on sys.path (installed or PYTHONPATH set).
- If the class lives in a file, use the 'path/to/file.py:ClassName' form instead.
Example fix
# before scorer = "KeywordRate" # after scorer = "heretic.scorers.keyword_rate.KeywordRate"
Defensive patterns
Strategy: validation
Validate before calling
def valid_import_plugin(name: str) -> bool:
return "." in name and ":" not in name Try / catch
try:
cls = load_plugin(name, Scorer)
except ValueError as e:
sys.exit(f"{e} (got '{name}')") Prevention
- Always use fully-qualified dotted paths for import plugins.
- Copy plugin names from documentation verbatim, including module prefix.
When it happens
Trigger: load_plugin('MyScorer', Scorer) — just a class name; passing a bare top-level symbol expecting heretic to guess the module; config value missing the dotted module prefix.
Common situations: Users familiar with entry-point style plugins passing only a name; copying only the class portion out of documentation; abbreviating 'heretic.scorers.keyword_rate.KeywordRate' to 'KeywordRate'.
Related errors
- Plugin '{name}' does not export a class named '{class_name}'
- You must append the plugin class name to the filepath like t
- File-based plugin must use the form 'path/to/plugin.py:Class
- Plugin file '{plugin_path}' does not exist
- Could not load plugin '{name}' (invalid module spec)
AI-assisted analysis of p-e-w/heretic@bedb94ef11 (2026-08-29).
Data as JSON: /api/errors/eae0127a2a075ebf.
Report an issue: GitHub.