Source code for modelly.scenarios.exceptions
"""Exceptions for the scenarios module."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Any
from modelly.core.exceptions import ModellyException
if TYPE_CHECKING:
from modelly.scenarios.scenario import Scenario
from modelly.scenarios.store import RunId
# ---------------------------------------------------------------------------
# Run-stage exceptions
#
# Raised inside RunConfiguration to attribute a failure to a specific stage
# of a run. Each carries a FailureOrigin via the class-level `origin`, so the
# runner can record where a run broke without inspecting which method raised.
# Always raised with `raise ... from e` so the underlying exception is
# preserved as __cause__ and the recorded failure reports the real error type.
# ---------------------------------------------------------------------------
[docs]
class FailureOrigin(Enum):
"""Enum describing where a scenario run failed."""
INSTANTIATING = "instantiating"
RUNNING = "running"
EXTRACTING = "extracting"
WRITING = "writing"
ABORTED = "aborted"
[docs]
@dataclass(frozen=True)
class FailureInfo:
"""Structured diagnostics for a failed run.
ensures traceback information can be safely sent from workers to root.
"""
origin: FailureOrigin
exception_type: str
message: str
traceback: str
[docs]
class RunStageException(ModellyException):
"""Base for failures attributable to a stage of a single run."""
#: The stage this exception represents. Set by each subclass.
origin: FailureOrigin
def __init__(self, message: str):
"""Initialize a run-stage exception.
Args:
message: human-readable description of the stage failure. The
underlying cause should be attached via ``raise ... from e``.
"""
super().__init__(message)
[docs]
class ModelInstantiationException(RunStageException):
"""Raised when a model cannot be instantiated for a scenario."""
origin: FailureOrigin = FailureOrigin.INSTANTIATING
def __init__(
self,
model_class: type,
model_args: list[Any],
model_kwargs: dict[str, Any],
scenario: Scenario,
):
"""Initialize a model instantiation exception.
Args:
model_class: the model class that failed to instantiate
model_args: the positional args passed to the model
model_kwargs: the keyword args passed to the model
scenario: the scenario being instantiated
"""
msg = (
f"Failed to instantiate {model_class.__name__}. "
f"Please check your model_args and model_kwargs.\n"
f" - Passed args: {model_args}\n"
f" - Passed kwargs: {model_kwargs}\n"
f" - for scenario_id: {scenario.scenario_id}, "
f"replication_id: {scenario.replication_id}"
)
super().__init__(msg)
self.model_class = model_class
self.model_args = model_args
self.model_kwargs = model_kwargs
self.scenario = scenario
[docs]
class ModelRunException(RunStageException):
"""Raised when a model fails while advancing (run_model)."""
origin: FailureOrigin = FailureOrigin.RUNNING
def __init__(self, run_id: RunId):
"""Initialize a model run exception.
Args:
run_id: identifier of the run that failed during execution
"""
super().__init__(f"Model run failed for {run_id}")
self.run_id = run_id
# ---------------------------------------------------------------------------
# Store-side (read path) exceptions
#
# Raised by the store when querying run results, unrelated to run stages.
# ---------------------------------------------------------------------------
[docs]
class ScenarioNotFoundException(ModellyException):
"""Raised when no run is recorded for a given RunId."""
def __init__(self, run_id: RunId | None = None):
"""Initialize a scenario-not-found exception.
Args:
run_id: the RunId that could not be found, if known
"""
msg = f"No run found for {run_id}" if run_id else "Run not found"
super().__init__(msg)
self.run_id = run_id
[docs]
class ScenarioNotReadyException(ModellyException):
"""Raised when a run's output is requested before it has completed."""
def __init__(self, run_id: RunId | None = None):
"""Initialize a scenario-not-ready exception.
Args:
run_id: the RunId whose output is not yet available
"""
super().__init__(f"Run for {run_id} is not ready")
self.run_id = run_id
[docs]
class ScenarioFailedException(ModellyException):
"""Raised when a failed run's output is requested."""
def __init__(self, run_id: RunId | None = None, failure: FailureInfo | None = None):
"""Initialize a scenario-failed exception.
Args:
run_id: the RunId of the failed run
failure: structured failure diagnostics for the run, if available
"""
msg = f"Run {run_id} failed"
if failure is not None:
msg += (
f": {failure.exception_type} in {failure.origin.value}: "
f"{failure.message}"
)
super().__init__(msg)
self.run_id = run_id
self.failure = failure
[docs]
class ScenarioAbortedException(ModellyException):
"""Raised when an aborted run's output is requested."""
def __init__(self, run_id: RunId | None = None, failure: FailureInfo | None = None):
"""Initialize a scenario-aborted exception.
Args:
run_id: the RunId of the aborted run
failure: structured failure diagnostics for the run, if available
"""
msg = f"Run {run_id} aborted"
if failure is not None:
msg += (
f": {failure.exception_type} in {failure.origin.value}: "
f"{failure.message}"
)
super().__init__(msg)
self.run_id = run_id
self.failure = failure