Validations¶
The library validates your statechart structure at two stages: class definition time (when the Python class body is evaluated) and instance creation time (when you call the constructor). These checks catch common mistakes early — before any event is ever processed.
All validation errors raise InvalidDefinition.
>>> from statemachine import StateChart, State
>>> from statemachine.exceptions import InvalidDefinition
Class definition validations¶
These checks run as soon as the class body is evaluated by the
StateMachineMetaclass. If any check fails, the class itself is not
created.
Exactly one initial state¶
Every statechart must have exactly one initial state at the root level:
>>> try:
... class Bad(StateChart):
... a = State(initial=True)
... b = State(initial=True)
... go = a.to(b)
... except InvalidDefinition as e:
... print(e)
There should be one and only one initial state. Your currently have these: a, b
No transitions from final states¶
Final states represent completion — outgoing transitions are not allowed:
>>> try:
... class Bad(StateChart):
... draft = State(initial=True)
... closed = State(final=True)
... reopen = closed.to(draft)
... close = draft.to(closed)
... except InvalidDefinition as e:
... print(e)
Cannot declare transitions from final state. Invalid state(s): ['closed']
Unreachable states¶
Every state must be reachable from the initial state. Isolated states indicate a wiring mistake:
>>> try:
... class Bad(StateChart):
... red = State(initial=True)
... green = State()
... hazard = State()
... cycle = red.to(green) | green.to(red)
... blink = hazard.to.itself()
... except InvalidDefinition as e:
... print(e)
There are unreachable states. The statemachine graph should have a single component. Disconnected states: ['hazard']
Disable with validate_disconnected_states = False.
Trap states¶
Every non-final state must have at least one outgoing transition. A state with no way out is a “trap” — likely a forgotten transition:
>>> try:
... class Bad(StateChart):
... red = State(initial=True)
... green = State()
... hazard = State()
... cycle = red.to(green) | green.to(red)
... fault = red.to(hazard) | green.to(hazard)
... except InvalidDefinition as e:
... print(e)
All non-final states should have at least one outgoing transition. These states have no outgoing transition: ['hazard']
Disable with validate_trap_states = False:
>>> class Accepted(StateChart):
... validate_trap_states = False
... red = State(initial=True)
... green = State()
... hazard = State()
... cycle = red.to(green) | green.to(red)
... fault = red.to(hazard) | green.to(hazard)
Final state reachability¶
When final states exist, every non-final state must have at least one path to a final state:
>>> try:
... class Bad(StateChart):
... draft = State(initial=True)
... abandoned = State()
... closed = State(final=True)
... produce = draft.to(abandoned) | abandoned.to(abandoned)
... close = draft.to(closed)
... except InvalidDefinition as e:
... print(e)
All non-final states should have at least one path to a final state. These states have no path to a final state: ['abandoned']
Disable with validate_final_reachability = False.
Internal transition targets¶
Internal transitions must target the same state (self) or a descendant — they cannot cross to external states:
>>> try:
... class Bad(StateChart):
... a = State(initial=True)
... b = State(final=True)
... go = a.to(b, internal=True)
... except InvalidDefinition as e:
... assert "Not a valid internal transition" in str(e)
Initial transitions have no conditions¶
Initial transitions (automatically generated for the initial state) cannot carry conditions or events — they always fire unconditionally.
donedata on final states only¶
The donedata parameter can only be used on states marked as final=True:
>>> try:
... class Bad(StateChart):
... a = State(initial=True, donedata="get_data")
... b = State(final=True)
... go = a.to(b)
... except InvalidDefinition as e:
... print(e)
'donedata' can only be specified on final states.
Invalid listener entries¶
Entries in the listeners class attribute must be classes, callables, or
object instances — not primitives like strings or numbers:
>>> try:
... class Bad(StateChart):
... listeners = ["not_a_listener"]
... a = State(initial=True)
... b = State(final=True)
... go = a.to(b)
... except InvalidDefinition as e:
... assert "Invalid entry in 'listeners'" in str(e)
Instance creation validations¶
These checks run when you instantiate a statechart (call MyChart()).
They verify that the runtime wiring is correct — callbacks resolve to
actual methods, boolean expressions parse, etc.
Callback resolution¶
Every callback name declared on a transition or state (via on, before,
after, enter, exit, cond, etc.) must resolve to an actual attribute
on the statechart, model, or one of the registered listeners.
>>> class MyChart(StateChart):
... a = State(initial=True)
... b = State(final=True)
... go = a.to(b, on="nonexistent_method")
>>> try:
... MyChart()
... except InvalidDefinition as e:
... assert "Did not found name 'nonexistent_method'" in str(e)
This validation ensures there are no typos in callback names. It checks all sources in order: the statechart class itself, then the model (if provided), then each listener.
Note
Convention-based callbacks (like on_enter_<state> or before_<event>)
are not validated — they are optional by design. Only explicitly
declared callback names (passed as strings to on, cond, etc.) are
checked.
Boolean expression parsing¶
Guard conditions written as boolean expressions must be syntactically valid:
>>> try:
... class MyChart(StateChart):
... a = State(initial=True)
... b = State(final=True)
... go = a.to(b, cond="valid_a and valid_b")
... def valid_a(self):
... return True
... def valid_b(self):
... return True
... sm = MyChart()
... sm.send("go")
... except InvalidDefinition:
... pass # would fail if expression didn't parse
>>> "b" in sm.configuration_values
True
Expressions support and, or, not, and parentheses. See
Conditions for the full syntax.
Summary¶
Validation |
When |
Configurable |
|---|---|---|
Exactly one initial state |
Class definition |
No |
No transitions from final states |
Class definition |
No |
Unreachable states |
Class definition |
|
Trap states |
Class definition |
|
Final state reachability |
Class definition |
|
Internal transition targets |
Class definition |
No |
Initial transitions have no cond |
Class definition |
No |
|
Class definition |
No |
Invalid listener entries |
Class definition |
No |
Callback resolution |
Instance creation |
No |
Boolean expression parsing |
Instance creation |
No |
All configurable flags default to True. Set them to False on the class
to disable the corresponding check.