from __future__ import annotations import logging from pydantic import BaseModel, ValidationError from core.base import BaseRenderer, BaseTransport from core.plugin_types import PluginLoadError, ensure_plugin_config_model from core.schemas.config import Config, ResolvedConfig, ResolvedPluginConfig, ResolvedReportOutput from core.schemas.modules import LoadedModule from reporting.models import PluginRuntimeError from reporting.registry import RendererRegistry from reporting.transport_registry import TransportRegistry logger = logging.getLogger(__name__) def resolve_config( config: Config, loaded_modules: list[LoadedModule], renderer_registry: RendererRegistry, transport_registry: TransportRegistry, plugin_errors: list[PluginLoadError] | None = None, ) -> tuple[ResolvedConfig, list[PluginRuntimeError]]: runtime_errors = [_plugin_load_error_to_runtime_error(error) for error in plugin_errors or ()] resolved_modules: dict[str, BaseModel] = {} seen_module_ids: set[str] = set() for module in loaded_modules: if module.manifest.identifier in seen_module_ids: runtime_errors.append( PluginRuntimeError( plugin_kind="module", plugin_name=module.manifest.identifier, stage="discovery", message="duplicate module identifier", ) ) continue seen_module_ids.add(module.manifest.identifier) raw_module_config = config.modules.get(module.manifest.identifier, {}) try: resolved_modules[module.manifest.identifier] = module.config_model.model_validate( raw_module_config ) except ValidationError as exc: runtime_errors.append( PluginRuntimeError( plugin_kind="module", plugin_name=module.manifest.identifier, stage="config_validation", message=str(exc), ) ) resolved_outputs: list[ResolvedReportOutput] = [] for output in config.reports.outputs: try: renderer = renderer_registry.get(output.renderer.type) transport = transport_registry.get(output.transport.type) resolved_outputs.append( ResolvedReportOutput( renderer=_resolve_plugin_config( output.renderer.type, output.renderer.payload(), renderer ), transport=_resolve_plugin_config( output.transport.type, output.transport.payload(), transport ), ) ) except (ValidationError, ValueError, TypeError) as exc: runtime_errors.append( PluginRuntimeError( plugin_kind="report_output", plugin_name=f"{output.renderer.type}->{output.transport.type}", stage="config_validation", message=str(exc), ) ) resolved_config = ResolvedConfig( exclude_categories=config.exclude_categories, allow_failures_below=config.allow_failures_below, strict_mode=config.strict_mode, fail_fast=config.fail_fast, dry_run=config.dry_run, max_concurrency=config.max_concurrency, logging=config.logging, plugin_packages=config.plugin_packages, reports=resolved_outputs, modules=resolved_modules, ) return resolved_config, runtime_errors def validate_reporting_plugins( renderer_registry: RendererRegistry, transport_registry: TransportRegistry, ) -> list[PluginRuntimeError]: errors: list[PluginRuntimeError] = [] errors.extend(_validate_registry_plugins("renderer", renderer_registry._renderers.values())) errors.extend(_validate_registry_plugins("transport", transport_registry._transports.values())) return errors def _validate_registry_plugins(plugin_kind: str, plugins: object) -> list[PluginRuntimeError]: seen: set[type[object]] = set() errors: list[PluginRuntimeError] = [] for plugin in plugins: plugin_type = type(plugin) if plugin_type in seen: continue seen.add(plugin_type) try: ensure_plugin_config_model(plugin) except TypeError as exc: errors.append( PluginRuntimeError( plugin_kind=plugin_kind, plugin_name=plugin_type.__name__, stage="plugin_validation", message=str(exc), ) ) return errors def _resolve_plugin_config( plugin_type: str, payload: dict[str, object], plugin: BaseRenderer | BaseTransport, ) -> ResolvedPluginConfig: config_model = ensure_plugin_config_model(plugin) return ResolvedPluginConfig(type=plugin_type, config=config_model.model_validate(payload)) def _plugin_load_error_to_runtime_error(error: PluginLoadError) -> PluginRuntimeError: return PluginRuntimeError( plugin_kind=error.plugin_kind, plugin_name=error.source, stage="discovery", message=error.message, )