Files
rengine/shared/plugins.py
T
Yogesh OjhaandClaude Opus 5 6edc153876 refactor(plugins): one discovery, not seven near-copies of it
Every plug-and-play surface carried its own "import the modules beside
me and collect the subclasses" loop: mcp tools, toolbox tools, the two
scanner directories, and the stage, section, provider and connector
registries. shared/plugins.py holds it once, as classes_in_modules for
a directory of modules and classes_in_packages for a directory of
sub-packages; each registry keeps its own naming and duplicate rules.

The copies had drifted. stages/dast_scan checked obj.__module__ so a
scanner imported from another module is not registered twice;
stages/vulnerability_scan did not, and would have registered the
wrong class the day a second scanner lands. Both now hold the guard.

The scanner copies also suppressed ModuleNotFoundError in silence, so
a typo inside a scanner read as "not installed" rather than an error.
Every skipped module is logged.

Registry contents are unchanged: stages 27, sections 30, providers 3,
connectors 1, mcp tools 26, toolbox tools 6, vuln scanners 1, dast
scanners 2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DafWxfoWZXVDgr9KroJBDZ
2026-09-16 08:44:59 +05:30

86 lines
2.7 KiB
Python

"""Plug-and-play discovery: the classes a directory of modules or sub-packages declares."""
from __future__ import annotations
import importlib
from collections.abc import Collection, Sequence
from pathlib import Path
from types import ModuleType
from shared.logging import get_logger
logger = get_logger(__name__)
SKIP = ("base",)
def _import(name: str) -> ModuleType | None:
try:
return importlib.import_module(name)
except Exception as exc:
logger.warning("plugin module skipped", module=name, error=str(exc))
return None
def _declared[T](namespace: ModuleType, base: type[T]) -> list[type[T]]:
"""The subclasses this module declares itself, never the ones it imports."""
out = []
for obj in vars(namespace).values():
if (
isinstance(obj, type)
and issubclass(obj, base)
and obj is not base
and obj.__module__ == namespace.__name__
and not getattr(obj, "__abstractmethods__", None)
):
out.append(obj)
return out
def classes_in_modules[T](
package: str, directory: Path, base: type[T], *, skip: Collection[str] = SKIP
) -> list[type[T]]:
"""Every `base` subclass declared by a module in `directory`."""
found: list[type[T]] = []
for module in sorted(directory.glob("*.py")):
if module.stem.startswith("_") or module.stem in skip:
continue
namespace = _import(f"{package}.{module.stem}")
if namespace is not None:
found.extend(_declared(namespace, base))
return found
def classes_in_packages[T](
package: str,
directory: Path,
base: type[T],
*,
submodules: Sequence[str] = (),
) -> list[type[T]]:
"""Every `base` subclass declared by a sub-package of `directory`, or by `submodules` of one."""
found: list[type[T]] = []
for entry in sorted(directory.iterdir()):
if not entry.is_dir() or entry.name.startswith(("_", ".")):
continue
for suffix in ("", *submodules):
name = f"{package}.{entry.name}{'.' + suffix if suffix else ''}"
namespace = _import_optional(name)
if namespace is not None:
found.extend(_declared(namespace, base))
return found
def _import_optional(name: str) -> ModuleType | None:
"""A sub-package need not carry every module name a registry looks for."""
try:
return importlib.import_module(name)
except ModuleNotFoundError as exc:
if exc.name == name:
return None
logger.warning("plugin module skipped", module=name, error=str(exc))
return None
except Exception as exc:
logger.warning("plugin module skipped", module=name, error=str(exc))
return None