Skip to content

Errors

Exception hierarchy for FSM configuration and runtime errors.

FSMError

FSMError(
    message: str,
    context: dict[str, Any] | None = None,
    *,
    code: ErrorCode | None = None
)

Bases: Exception

Base exception for all PyStator errors.

Attributes:

Name Type Description
message

Human-readable error description.

context

Additional context about the error.

code

Machine-readable error code for programmatic handling.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    context: dict[str, Any] | None = None,
    *,
    code: ErrorCode | None = None,
) -> None:
    self.message = message
    self.context = context or {}
    self.code = code
    super().__init__(message)

to_dict

to_dict() -> dict[str, Any]

Serialize error to dictionary.

Source code in src/pystator/errors.py
def to_dict(self) -> dict[str, Any]:
    """Serialize error to dictionary."""
    return {
        "type": self.__class__.__name__,
        "message": self.message,
        "code": self.code.value if self.code else None,
        "context": self.context,
    }

ConfigurationError

ConfigurationError(
    message: str,
    path: str | None = None,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

Error in FSM configuration (YAML/JSON schema, invalid definition).

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    path: str | None = None,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    if path:
        ctx["path"] = path
    super().__init__(message, ctx)
    self.path = path
    self.code = ErrorCode.CONFIG

InvalidTransitionError

InvalidTransitionError(
    message: str,
    current_state: str,
    trigger: str,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

Transition is not allowed from the current state.

Attributes:

Name Type Description
current_state

The state from which the transition was attempted.

trigger

The event that triggered the attempted transition.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    current_state: str,
    trigger: str,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx.update({"current_state": current_state, "trigger": trigger})
    super().__init__(message, ctx)
    self.current_state = current_state
    self.trigger = trigger
    self.code = ErrorCode.INVALID_TRIGGER

GuardRejectedError

GuardRejectedError(
    message: str,
    current_state: str,
    trigger: str,
    guard_name: str,
    guard_result: Any = None,
    context: dict[str, Any] | None = None,
)

Bases: InvalidTransitionError

Transition blocked by a guard condition.

Attributes:

Name Type Description
guard_name

The name of the guard that rejected the transition.

guard_result

Additional information about the guard evaluation.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    current_state: str,
    trigger: str,
    guard_name: str,
    guard_result: Any = None,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx.update({"guard_name": guard_name, "guard_result": guard_result})
    super().__init__(message, current_state, trigger, ctx)
    self.guard_name = guard_name
    self.guard_result = guard_result
    self.code = ErrorCode.GUARD_REJECTED

UndefinedStateError

UndefinedStateError(
    message: str,
    state_name: str,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

Reference to a state that is not defined in the machine.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    state_name: str,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx["state_name"] = state_name
    super().__init__(message, ctx)
    self.state_name = state_name
    self.code = ErrorCode.UNDEFINED_STATE

UndefinedTriggerError

UndefinedTriggerError(
    message: str,
    trigger: str,
    available_triggers: list[str] | None = None,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

Event trigger not defined in the machine (strict mode only).

Attributes:

Name Type Description
trigger

The undefined event trigger.

available_triggers

List of valid triggers for reference.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    trigger: str,
    available_triggers: list[str] | None = None,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx["trigger"] = trigger
    if available_triggers:
        ctx["available_triggers"] = available_triggers
    super().__init__(message, ctx)
    self.trigger = trigger
    self.available_triggers = available_triggers or []
    self.code = ErrorCode.INVALID_TRIGGER

GuardNotFoundError

GuardNotFoundError(
    message: str,
    guard_name: str,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

Guard function not registered.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    guard_name: str,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx["guard_name"] = guard_name
    super().__init__(message, ctx)
    self.guard_name = guard_name
    self.code = ErrorCode.GUARD_NOT_FOUND

ActionNotFoundError

ActionNotFoundError(
    message: str,
    action_name: str,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

Action function not registered.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    action_name: str,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx["action_name"] = action_name
    super().__init__(message, ctx)
    self.action_name = action_name
    self.code = ErrorCode.ACTION_NOT_FOUND

TimeoutExpiredError

TimeoutExpiredError(
    message: str,
    state_name: str,
    timeout_seconds: float,
    elapsed_seconds: float,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

State timeout has expired.

Source code in src/pystator/errors.py
def __init__(
    self,
    message: str,
    state_name: str,
    timeout_seconds: float,
    elapsed_seconds: float,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx.update(
        {
            "state_name": state_name,
            "timeout_seconds": timeout_seconds,
            "elapsed_seconds": elapsed_seconds,
        }
    )
    super().__init__(message, ctx)
    self.state_name = state_name
    self.timeout_seconds = timeout_seconds
    self.elapsed_seconds = elapsed_seconds
    self.code = ErrorCode.TIMEOUT

TerminalStateError

TerminalStateError(
    current_state: str,
    trigger: str,
    context: dict[str, Any] | None = None,
)

Bases: InvalidTransitionError

Attempted to transition from a terminal state.

Source code in src/pystator/errors.py
def __init__(
    self,
    current_state: str,
    trigger: str,
    context: dict[str, Any] | None = None,
) -> None:
    message = f"Cannot transition from terminal state '{current_state}'"
    super().__init__(message, current_state, trigger, context)
    self.code = ErrorCode.TERMINAL_STATE

StaleVersionError

StaleVersionError(
    entity_id: str,
    expected_version: int,
    actual_version: int,
    context: dict[str, Any] | None = None,
)

Bases: FSMError

Optimistic locking conflict -- entity was modified concurrently.

Attributes:

Name Type Description
entity_id

The entity whose state was stale.

expected_version

The version the caller expected.

actual_version

The current version in the store.

Source code in src/pystator/errors.py
def __init__(
    self,
    entity_id: str,
    expected_version: int,
    actual_version: int,
    context: dict[str, Any] | None = None,
) -> None:
    ctx = context or {}
    ctx.update(
        {
            "entity_id": entity_id,
            "expected_version": expected_version,
            "actual_version": actual_version,
        }
    )
    message = (
        f"Stale version for entity '{entity_id}': "
        f"expected {expected_version}, got {actual_version}"
    )
    super().__init__(message, ctx)
    self.entity_id = entity_id
    self.expected_version = expected_version
    self.actual_version = actual_version
    self.code = ErrorCode.STALE_VERSION