Source code for neofoam.framework.initialization.init_step
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 NeoFOAM authors
"""
Lazy Initialization
Provides the InitStep dataclass for deferred initialization with dependency tracking.
"""
from dataclasses import dataclass, field
from typing import Any, Callable
# ``InitCategory`` used to be a closed ``Literal``; routing is now extensible
# via :class:`CategoryRouter` so categories are plain strings. The alias is
# kept as ``str`` to limit churn on annotations that reference the name.
InitCategory = str
[docs]
class InitStepExecutionError(RuntimeError):
"""Raised when an InitStep's initializer fails during execution.
Attributes:
step_name: Name of the InitStep that failed.
depends_on: Dependencies declared by the failed step.
"""
def __init__(self, step_name: str, depends_on: list[str], cause: Exception) -> None:
self.step_name = step_name
self.depends_on = list(depends_on)
deps = ", ".join(depends_on) if depends_on else "(none)"
super().__init__(
f"InitStep '{step_name}' failed during execution "
f"(depends_on: [{deps}]): {cause}"
)
[docs]
@dataclass
class InitStep:
"""
A deferred initialization step that declares its dependencies.
InitStep objects are returned from BUILD stage methods and collected
by the initializer. They are then topologically sorted by dependencies
and executed in the correct order.
Attributes:
name: Unique identifier (e.g., "fields.U", "operators.momentum")
depends_on: List of dependency names that must be initialized first
initializer: Lazy function that produces the runtime object using context
category: Optional category for grouping (e.g., "fields", "operators")
Example:
InitStep(
name="fields.U",
depends_on=["mesh"],
initializer=lambda ctx: create_vector_field(ctx["mesh"], U0)
)
"""
name: str
depends_on: list[str] = field(default_factory=list)
initializer: Callable[[dict[str, Any]], Any] = None # type: ignore[assignment]
category: InitCategory = "resource"
def __post_init__(self) -> None:
"""Validate InitStep after creation."""
if not self.name:
raise ValueError("InitStep must have a non-empty name")
if self.initializer is None:
raise ValueError(
f"InitStep '{self.name}' must have an initializer function"
)
if not isinstance(self.category, str) or not self.category:
raise ValueError(
f"InitStep '{self.name}' must have a non-empty category string"
)