first commit

This commit is contained in:
2026-07-11 23:54:36 +07:00
commit b07562921b
40 changed files with 3813 additions and 0 deletions

148
core/runtime.py Normal file
View 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,
)