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 in send(), guards, and comparisons.

  • name — a human-readable label for display purposes. Auto-generated from the id by replacing _ and . with spaces and capitalizing the first word. You can override the automatic name by passing name= 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.

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

done_state_quest

"done_state_quest" and "done.state.quest"

done_state_lonely_mountain

"done_state_lonely_mountain" and "done.state.lonely_mountain"

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

error_execution

"error_execution" and "error.execution"

Note

If you provide an explicit id= parameter on the Event, it takes precedence and the naming convention is not applied.