(events)= (event)=
New to statecharts? See [](concepts.md) 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)
TrueThe 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
TrueFor 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)=
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'Always use `event.id` for programmatic checks. The `name` property is intended
for UI display and may differ from the `id`.
(triggering-events)= (triggering events)=
Once declared, events are triggered on a {ref}StateChart <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
{ref}guard conditions <validators and guards>, executes {ref}actions, and
updates the {ref}configuration <querying-configuration>.
See {ref}`sending-events` for the full runtime API — `send()`, `raise_()`,
delayed events, and cancellation.
(events-as-method-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
TrueA 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.{ref}`behaviour` explains `allow_event_without_transition` and the other
class-level flags that switch between this strict mode and the tolerant,
event-driven default.
(event-parameter)=
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
TrueThe 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
TrueThis 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.
(done-state-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.
When a {ref}compound state's <compound-states> 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"}
TrueFor {ref}parallel states <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'`donedata` can only be specified on `final=True` states. Attempting to use it
on a non-final state raises `InvalidDefinition`.
When a callback raises during a macrostep and
{ref}catch_errors_as_events <behaviour> 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
TrueSee {ref}`error-execution` for the full error handling reference: recovery
patterns, `after` as a finalize hook, and nested error scenarios.
(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-convention)=
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 |
|---|---|
done_state_quest |
"done_state_quest" and "done.state.quest" |
done_state_lonely_mountain |
"done_state_lonely_mountain" and "done.state.lonely_mountain" |
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 |
|---|---|
error_execution |
"error_execution" and "error.execution" |
If you provide an explicit `id=` parameter on the `Event`, it takes precedence
and the naming convention is not applied.