State Machine API Reference
StateMachine
from specwright import StateMachine
Base class for state machines with validated transitions.
Class Attributes (Required)
| Attribute |
Type |
Description |
states |
list[str] |
List of valid state names |
initial_state |
str |
Starting state (must be in states) |
Class Attributes (Optional)
| Attribute |
Type |
Default |
Description |
track_history |
bool |
False |
Record every state visited |
Properties
| Property |
Type |
Description |
state |
str |
Current state |
state_history |
list[str] |
Copy of all states visited (empty if track_history=False) |
Class Validation
Subclasses are validated at definition time (__init_subclass__):
| Check |
Exception |
states is non-empty |
InvalidStateError |
initial_state is defined |
InvalidStateError |
initial_state is in states |
InvalidStateError |
All transition from_state values are in states |
InvalidStateError |
All transition to_state values are in states |
InvalidStateError |
Lifecycle Hooks
Define methods matching these patterns to run code on state changes:
| Method Pattern |
Called When |
on_exit_{state_name}(self) |
After transition method succeeds, before state updates |
on_enter_{state_name}(self) |
After state updates to the new value |
Example
class Order(StateMachine):
states = ["pending", "paid", "shipped"]
initial_state = "pending"
track_history = True
@transition(from_state="pending", to_state="paid")
def pay(self, amount: float) -> str:
return f"Paid ${amount:.2f}"
@transition(from_state="paid", to_state="shipped")
def ship(self, tracking: str) -> str:
return f"Shipped: {tracking}"
def on_enter_paid(self):
print("Payment received")
order = Order()
order.pay(99.99) # "Paid $99.99"
order.state # "paid"
order.state_history # ["pending", "paid"]
@transition
from specwright import transition
Decorator that marks a method as a state transition.
Signature
@transition(from_state: str | list[str], to_state: str)
def method(self, ...):
...
Parameters
| Parameter |
Type |
Description |
from_state |
str \| list[str] |
Valid source state(s) for this transition |
to_state |
str |
Target state after successful execution |
Behavior
- Check that
self.state is in from_state — raise InvalidTransitionError if not
- Execute the decorated method
- If the method raises, state is unchanged (no hooks run)
- If the method succeeds:
- Call
on_exit_{old_state}() if defined
- Update state to
to_state
- Append to history if
track_history=True
- Call
on_enter_{new_state}() if defined
- Return the method's return value
Raises
| Exception |
When |
InvalidTransitionError |
Current state is not in from_state |
Decorated methods gain a __transition__ attribute containing a TransitionMeta instance.
from specwright import TransitionMeta
Frozen dataclass stored on @transition-decorated methods.
Fields
| Field |
Type |
Description |
from_states |
frozenset[str] |
Valid source states |
to_state |
str |
Target state |
Example
class Machine(StateMachine):
states = ["a", "b"]
initial_state = "a"
@transition(from_state="a", to_state="b")
def go(self): pass
meta = Machine.go.__transition__
assert meta.from_states == frozenset({"a"})
assert meta.to_state == "b"