Source code for neofoam.framework.initialization.execution.context_builder

# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2026 NeoFOAM authors

"""Route :class:`InitResult` values into a :class:`Context`.

The routing layer is open for extension: register additional category
handlers via :class:`CategoryRouter.register`. Unknown categories fall
through to ``models`` with a logged warning.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Any, Callable

from ...context import Context
from .init_result import InitResult


logger = logging.getLogger(__name__)


def _strip_prefix(name: str, prefix: str) -> str:
    if name.startswith(f"{prefix}."):
        return name[len(prefix) + 1 :]
    return name


[docs] @dataclass class ContextBuilder: """Mutable accumulator routed values are written into. Calling :meth:`to_context` produces the immutable :class:`Context`. """ fields: dict[str, Any] = field(default_factory=dict) models: dict[str, Any] = field(default_factory=dict) mesh: Any = None runtime: Any = None def to_context(self) -> Context: return Context( fields=self.fields, models=self.models, mesh=self.mesh, runtime=self.runtime )
Handler = Callable[[ContextBuilder, str, Any], None] def _route_fields(builder: ContextBuilder, name: str, value: Any) -> None: builder.fields[_strip_prefix(name, "fields")] = value def _route_models(builder: ContextBuilder, name: str, value: Any) -> None: builder.models[_strip_prefix(name, "models")] = value def _route_operators(builder: ContextBuilder, name: str, value: Any) -> None: builder.models[_strip_prefix(name, "operators")] = value def _route_resource(builder: ContextBuilder, name: str, value: Any) -> None: if name == "mesh": builder.mesh = value elif name == "runtime": builder.runtime = value else: _fallback_to_models(builder, name, value) def _fallback_to_models(builder: ContextBuilder, name: str, value: Any) -> None: logger.warning( "InitStep '%s' has no special context slot — routing to models", name, ) builder.models[name] = value
[docs] class CategoryRouter: """Registry mapping :attr:`InitResult.category` to handlers.""" def __init__(self) -> None: self._handlers: dict[str, Handler] = {} def register(self, category: str, handler: Handler) -> None: self._handlers[category] = handler def route(self, builder: ContextBuilder, result: InitResult) -> None: handler = self._handlers.get(result.category, _fallback_to_models) handler(builder, result.name, result.value)
[docs] def default_router() -> CategoryRouter: """Router populated with the four built-in category handlers.""" router = CategoryRouter() router.register("fields", _route_fields) router.register("models", _route_models) router.register("operators", _route_operators) router.register("resource", _route_resource) return router
[docs] def build_context_from_results( init_results: list[InitResult], *, router: CategoryRouter | None = None, ) -> Context: """Build a :class:`Context` by routing ``init_results`` through ``router``.""" builder = ContextBuilder() active_router = router or default_router() for result in init_results: active_router.route(builder, result) return builder.to_context()