first commit
This commit is contained in:
148
core/runtime.py
Normal file
148
core/runtime.py
Normal file
@@ -0,0 +1,148 @@
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user