(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 InvalidDefinitionThese checks run as soon as the class body is evaluated by the
StateMachineMetaclass. If any check fails, the class itself is not
created.
Statecharts loaded via {func}`statemachine.io.load` (SCXML/JSON/YAML) disable the three
**structural** checks below — unreachable states, trap states and final-state
reachability — because declarative documents legitimately express configurations they
would reject. See [](io/index.md#validation-of-loaded-documents).
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, bFinal 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)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 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 (automatically generated for the initial state) cannot carry conditions or events — they always fire unconditionally.
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.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)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.
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.
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.
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
TrueExpressions support and, or, not, and parentheses. See
{ref}guards for the full syntax.
| Validation | When | Configurable |
|---|---|---|
| Exactly one initial state | Class definition | No |
| No transitions from final states | Class definition | No |
| Unreachable states | Class definition | validate_disconnected_states |
| Trap states | Class definition | validate_trap_states |
| Final state reachability | Class definition | validate_final_reachability |
| Internal transition targets | Class definition | No |
| Initial transitions have no cond | Class definition | No |
donedata on final states only |
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.