From f34c805b84702922ef1765dddf4e90f5b9ee8a49 Mon Sep 17 00:00:00 2001 From: agatha Date: Sat, 14 Mar 2026 15:14:11 -0400 Subject: [PATCH] feat: add plugin auto-discovery via importlib --- src/proxy_pool/plugins/discovery.py | 85 +++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/proxy_pool/plugins/discovery.py diff --git a/src/proxy_pool/plugins/discovery.py b/src/proxy_pool/plugins/discovery.py new file mode 100644 index 0000000..80795e5 --- /dev/null +++ b/src/proxy_pool/plugins/discovery.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import importlib +import logging +from pathlib import Path + +from proxy_pool.config import Settings +from proxy_pool.plugins.protocols import Notifier, ProxyChecker, SourceParser +from proxy_pool.plugins.registry import PluginRegistry + +logger = logging.getLogger(__name__) + + +def discover_plugins( + package: str, + registry: PluginRegistry, + settings: Settings, +) -> None: + try: + mod = importlib.import_module(package) + except ImportError: + logger.warning("Plugin package '%s' not found, skipping", package) + return + + if hasattr(mod, "__path__"): + logger.warning( + "Plugin package '%s' has no __path__ attribute, skipping", package + ) + return + + package_path = Path(mod.__path__[0]) + + for path in sorted(package_path.rglob("*.py")): + if path.name.startswith("_"): + continue + + relative = path.relative_to(package_path) + module_parts = relative.with_suffix("").parts + module_name = f"{package}.{'.'.join(module_parts)}" + + try: + plugin_module = importlib.import_module(module_name) + except Exception: + logger.exception("Failed to import plugin module '%s'", module_name) + continue + + if not hasattr(plugin_module, "create_plugin"): + continue + + try: + plugin = plugin_module.create_plugin(settings) + except Exception: + logger.exception( + "Failed to create plugin from '%s'", + module_name, + ) + continue + + if plugin is None: + logger.debug( + "Plugin '%s' returned None (disabled by config)", + module_name, + ) + continue + + _register_plugin(registry, plugin, module_name) + + +def _register_plugin( + registry: PluginRegistry, + plugin: object, + module_name: str, +) -> None: + if isinstance(plugin, SourceParser): + registry.register_parser(plugin) + elif isinstance(plugin, ProxyChecker): + registry.register_checker(plugin) + elif isinstance(plugin, Notifier): + registry.register_notifier(plugin) + else: + logger.warning( + "Plugin from '%s' has unknown type: %s", + module_name, + type(plugin).__name__, + )