Events¶
See also
New to statecharts? See Core concepts for an overview of how states, transitions, events, and actions fit together.
An event is a named signal that drives the state machine forward. When you
assign a transition to a class-level name, that name becomes an event — the
library creates an Event object automatically. Events are the external
interface of your machine: callers send event names, and the machine decides
which transitions to take.
Declaring events¶
The simplest way to declare an event is by assigning a transition to a name:
>>> from statemachine import Event, State, StateChart
>>> class SimpleSM(StateChart):
... initial = State(initial=True)
... final = State(final=True)
...
... start = initial.to(final)
>>> isinstance(SimpleSM.start, Event)
True
The name start is automatically converted to an Event with
id="start". Multiple transitions can share the same event using
the | operator:
>>> class TrafficLight(StateChart):
... green = State(initial=True)
... yellow = State()
... red = State()
...
... cycle = green.to(yellow) | yellow.to(red) | red.to(green)
>>> sm = TrafficLight()
>>> sm.send("cycle")
>>> sm.yellow.is_active
True
For better IDE support (autocompletion, type checking) or to set a
human-readable display name, use the Event class explicitly:
>>> class SimpleSM(StateChart):
... initial = State(initial=True)
... final = State(final=True)
...
... start = Event(initial.to(final), name="Start the machine")
>>> SimpleSM.start.name
'Start the machine'
>>> SimpleSM.start.id
'start'
Event identity: id vs name¶
Every event has two string properties:
id— the programmatic identifier, derived from the class attribute name. Use this insend(), guards, and comparisons.name— a human-readable label for display purposes. Auto-generated from theidby replacing_and.with spaces and capitalizing the first word. You can override the automatic name by passingname=explicitly when declaring the event:
>>> TrafficLight.cycle.id
'cycle'
>>> TrafficLight.cycle.name
'Cycle'
>>> class Example(StateChart):
... on = State(initial=True)
... off = State(final=True)
... shut_down = Event(on.to(off), name="Shut the system down")
>>> Example.shut_down.name
'Shut the system down'
Tip
Always use event.id for programmatic checks. The name property is intended
for UI display and may differ from the id.
Triggering events¶
Once declared, events are triggered on a StateChart instance in two ways:
As a method call:
sm.cycle()— when the event name is known at development time.Via
send():sm.send("cycle")— when the event name is dynamic (e.g., from user input, a message queue, or a data file).
Both styles produce the same result. The machine evaluates guard conditions, executes Actions, and updates the configuration.
See also
See Sending events for the full runtime API — send(), raise_(),
delayed events, and cancellation.
Events as a method-call protocol¶
Because every event is a callable method, a state machine can act as a guard
for the order in which your object’s methods may be called. Set
allow_event_without_transition = False so that any call that doesn’t match an
enabled transition raises TransitionNotAllowed instead of being silently
ignored. The transitions then describe the legal
sequence, and the machine enforces it for you:
>>> from statemachine import State, StateChart
>>> class Connection(StateChart):
... "A client whose methods can only be called in a valid order."
... allow_event_without_transition = False # reject any unexpected call
...
... disconnected = State(initial=True)
... connected = State()
... authenticated = State()
...
... connect = disconnected.to(connected)
... authenticate = connected.to(authenticated)
... disconnect = connected.to(disconnected) | authenticated.to(disconnected)
...
... def on_connect(self):
... print("socket opened")
...
... def on_authenticate(self):
... print("credentials accepted")
Calling the methods in a valid order works:
>>> conn = Connection()
>>> conn.connect()
socket opened
>>> conn.authenticate()
credentials accepted
>>> "authenticated" in conn.configuration_values
True
A call that isn’t valid in the current state raises, so an out-of-order method call fails loudly instead of doing nothing:
>>> fresh = Connection()
>>> fresh.authenticate() # must connect first
Traceback (most recent call last):
...
statemachine.exceptions.TransitionNotAllowed: Can't Authenticate when in Disconnected.
The same protection applies to unknown event names sent dynamically:
>>> fresh.send("teleport")
Traceback (most recent call last):
...
statemachine.exceptions.TransitionNotAllowed: Can't teleport when in Disconnected.
See also
Behaviour explains allow_event_without_transition and the other
class-level flags that switch between this strict mode and the tolerant,
event-driven default.
The event parameter on transitions¶
Each transition accepts an optional event parameter that binds it to a
specific event, overriding the default (which is the class-level attribute
name). This lets individual transitions within a group respond to their own
event identifiers:
>>> from statemachine import Event, State, StateChart
>>> class TrafficLightMachine(StateChart):
... green = State(initial=True)
... yellow = State()
... red = State()
...
... slowdown = Event(name="Slowing down")
...
... cycle = Event(
... green.to(yellow, event=slowdown)
... | yellow.to(red, event="stop")
... | red.to(green, event="go"),
... name="Loop",
... )
>>> sm = TrafficLightMachine()
>>> sm.send("cycle") # umbrella event — dispatches green→yellow
>>> sm.yellow.is_active
True
>>> sm.send("stop") # individual event — dispatches yellow→red
>>> sm.red.is_active
True
>>> sm.send("go") # individual event — dispatches red→green
>>> sm.green.is_active
True
The event parameter accepts a string, an Event instance, a reference
to a previously declared Event (like slowdown above), or a list of
any of these. A space-separated string is also accepted and split into
individual events automatically:
>>> class MultiEvent(StateChart):
... a = State(initial=True)
... b = State(final=True)
...
... # Both forms are equivalent — the transition responds to "move", "go" and "start"
... move = a.to(b, event=["go", "start"])
>>> sm = MultiEvent()
>>> sm.send("move")
>>> sm.b.is_active
True
>>> sm = MultiEvent()
>>> sm.send("go")
>>> sm.b.is_active
True
>>> sm = MultiEvent()
>>> sm.send("start")
>>> sm.b.is_active
True
Tip
This is an advanced feature. Most state machines only need the simple
name = source.to(target) form. Use the event parameter when you need
fine-grained control over event routing within a composite transition group.
Automatic events¶
The engine generates certain events automatically in response to structural changes. You don’t send these yourself — you define transitions that react to them.
done.state events¶
Added in version 3.0.0.
When a compound state’s final child is entered, the
engine queues a done.state.{parent_id} internal event. Define a transition
for this event to react when a compound’s work is complete:
>>> from statemachine import State, StateChart
>>> class QuestWithDone(StateChart):
... class quest(State.Compound):
... traveling = State(initial=True)
... arrived = State(final=True)
... finish = traveling.to(arrived)
... celebration = State(final=True)
... done_state_quest = quest.to(celebration)
>>> sm = QuestWithDone()
>>> sm.send("finish")
>>> set(sm.configuration_values) == {"celebration"}
True
For parallel states, the done.state event fires
only when all regions have reached a final state:
>>> from statemachine import State, StateChart
>>> class WarWithDone(StateChart):
... class war(State.Parallel):
... class quest(State.Compound):
... start_q = State(initial=True)
... end_q = State(final=True)
... finish_q = start_q.to(end_q)
... class battle(State.Compound):
... start_b = State(initial=True)
... end_b = State(final=True)
... finish_b = start_b.to(end_b)
... peace = State(final=True)
... done_state_war = war.to(peace)
>>> sm = WarWithDone()
>>> sm.send("finish_q")
>>> "war" in sm.configuration_values
True
>>> sm.send("finish_b")
>>> set(sm.configuration_values) == {"peace"}
True
DoneData¶
Final states can carry data to their done.state handlers via the donedata
parameter. The value should be a callable (or method name string) that returns
a dict, which is forwarded as keyword arguments to the transition handler:
>>> from statemachine import Event, State, StateChart
>>> class QuestCompletion(StateChart):
... class quest(State.Compound):
... traveling = State(initial=True)
... completed = State(final=True, donedata="get_result")
... finish = traveling.to(completed)
... def get_result(self):
... return {"hero": "frodo", "outcome": "victory"}
... epilogue = State(final=True)
... done_state_quest = Event(quest.to(epilogue, on="capture_result"))
... def capture_result(self, hero=None, outcome=None, **kwargs):
... self.result = f"{hero}: {outcome}"
>>> sm = QuestCompletion()
>>> sm.send("finish")
>>> sm.result
'frodo: victory'
Note
donedata can only be specified on final=True states. Attempting to use it
on a non-final state raises InvalidDefinition.
error.execution events¶
When a callback raises during a macrostep and
catch_errors_as_events is enabled, the engine dispatches an
error.execution internal event. Define a transition for this event to
recover from errors within the statechart:
>>> from statemachine import State, StateChart
>>> class ResilientChart(StateChart):
... working = State(initial=True)
... failed = State(final=True)
...
... go = working.to.itself(on="do_work")
... error_execution = working.to(failed)
...
... def do_work(self):
... raise RuntimeError("something went wrong")
>>> sm = ResilientChart()
>>> sm.send("go")
>>> "failed" in sm.configuration_values
True
See also
See How errors are caught for the full error handling reference: recovery
patterns, after as a finalize hook, and nested error scenarios.
Dot-notation naming conventions¶
SCXML uses dot-separated event names (done.state.quest, error.execution),
but Python identifiers cannot contain dots. The library provides prefix-based
naming conventions that automatically register both forms:
done_state_ prefix¶
Any event attribute starting with done_state_ matches both the underscore
form and the dot-notation form. Only the prefix is replaced — the suffix is
kept as-is, preserving multi-word state names:
Attribute name |
Matches event names |
|---|---|
|
|
|
|
error_ prefix¶
Any event attribute starting with error_ matches both the underscore form
and the dot-notation form. Unlike done_state_, all underscores after
the prefix are replaced with dots:
Attribute name |
Matches event names |
|---|---|
|
|
Note
If you provide an explicit id= parameter on the Event, it takes precedence
and the naming convention is not applied.