StateChart

See also

New to statecharts? See Core concepts for an overview of how states, transitions, events, and actions fit together.

Once you define a StateChart class with states, transitions, and events, you work with instances of that class. Each instance is a live, running machine with its own configuration, event queues, and listeners. This page documents what you can do with that instance at runtime.

Creating an instance

>>> from statemachine import State, StateChart

>>> class TrafficLight(StateChart):
...     green = State(initial=True)
...     yellow = State()
...     red = State()
...
...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)

>>> sm = TrafficLight()

The constructor activates the initial state and runs any on_enter callbacks. You can pass a model object to store state externally (see Domain models) and listeners to observe the machine (see Listeners).

Sending events

The primary way to drive a state machine is by sending events.

send(event, **kwargs) places the event on the external queue. External events are processed after the current macrostep completes:

>>> sm.send("cycle")
>>> sm.yellow.is_active
True

Events can also be called as methods — sm.cycle() is equivalent to sm.send("cycle"):

>>> sm.cycle()
>>> sm.red.is_active
True

raise_(event, **kwargs) places the event on the internal queue. Internal events are processed within the current macrostep, before any pending external events. This is useful inside callbacks when you need to trigger follow-up transitions immediately:

>>> from statemachine import State, StateChart

>>> class WithInternalEvent(StateChart):
...     a = State(initial=True)
...     b = State()
...     c = State(final=True)
...
...     go = a.to(b)
...     finish = b.to(c)
...
...     def on_enter_b(self):
...         self.raise_("finish")

>>> sm = WithInternalEvent()
>>> sm.send("go")
>>> sm.c.is_active
True

Both methods accept arbitrary keyword arguments that are forwarded to all callbacks via dependency injection.

See also

See Processing model for the full macrostep/microstep lifecycle and how internal and external queues interact.

Delayed events

Events can be scheduled to fire after a delay (in milliseconds):

sm.send("timeout", delay=5000)

Delayed events can be cancelled before firing by providing a send_id:

sm.send("timeout", delay=5000, send_id="my_timeout")
sm.cancel_event("my_timeout")

Note

The delay is blocking in the sync engine — the processing loop sleeps until the delay elapses, holding the calling thread. In the async engine, delays are scheduled with asyncio and do not block the event loop.

Querying events

Not every event is relevant in every state. The instance provides two levels of event introspection:

allowed_events — events that have at least one transition from the current configuration, regardless of whether guards pass:

>>> from statemachine import State, StateChart

>>> class Turnstile(StateChart):
...     locked = State(initial=True)
...     unlocked = State()
...
...     coin = locked.to(unlocked)
...     push = unlocked.to(locked)

>>> sm = Turnstile()
>>> [e.id for e in sm.allowed_events]
['coin']

enabled_events(**kwargs) — a subset of allowed_events where at least one transition’s guards are satisfied given the provided arguments. Use this when guards depend on runtime data:

>>> from statemachine import State, StateChart

>>> class Gate(StateChart):
...     closed = State(initial=True)
...     open = State()
...
...     enter = closed.to(open, cond="has_badge")
...     close = open.to(closed)
...
...     def has_badge(self, badge: bool = False):
...         return badge

>>> sm = Gate()
>>> [e.id for e in sm.allowed_events]
['enter']

>>> [e.id for e in sm.enabled_events()]
[]

>>> [e.id for e in sm.enabled_events(badge=True)]
['enter']

allowed_events is cheap — it only checks the state topology. enabled_events evaluates guards, so pass the same keyword arguments you would pass to send().

Querying the configuration

The configuration is the set of currently active states. In a flat machine this is a single state; with compound and parallel states, multiple states are active simultaneously.

configuration returns the active states as an OrderedSet[State]:

>>> from statemachine import State, StateChart

>>> class Journey(StateChart):
...     class shire(State.Compound):
...         bag_end = State(initial=True)
...         green_dragon = State()
...         visit_pub = bag_end.to(green_dragon)
...     road = State(final=True)
...     depart = shire.to(road)

>>> sm = Journey()
>>> {s.id for s in sm.configuration} == {"shire", "bag_end"}
True

configuration_values returns the values (or IDs when no custom value is set) instead of State objects — useful for serialization or quick checks:

>>> set(sm.configuration_values) == {"shire", "bag_end"}
True

Checking termination

is_terminated returns True when the machine has completed its work. In a flat machine this means a final state is active. With compound and parallel states, the condition is structural — all parallel regions must have completed, nested compounds must have reached their final children, and so on:

>>> sm.send("visit_pub")
>>> sm.is_terminated
False

>>> sm.send("depart")
>>> sm.is_terminated
True

Use is_terminated instead of checking individual states — it handles arbitrarily nested structures for you.

final_states lists all top-level states marked as final:

>>> sm.final_states
[State('Road', id='road', value='road', initial=False, final=True, parallel=False)]

Managing listeners at runtime

Class-level listeners are declared on the class (see Listeners). You can also add listeners to a running instance:

>>> class Logger:
...     def after_transition(self, source: State, target: State, event: str):
...         print(f"[log] {source.id} →({event})→ {target.id}")

>>> sm = Turnstile()
>>> _ = sm.add_listener(Logger())
>>> sm.send("coin")
[log] locked →(coin)→ unlocked

add_listener() returns the instance for chaining. Use active_listeners to inspect all currently attached listeners.

Class-level attributes

These are set by the metaclass at class definition time and are available on both the class and its instances:

Attribute

Type

Description

name

str

The class name (e.g., "TrafficLight")

states

States

Collection of all top-level states

final_states

list[State]

Top-level states marked as final

events

list[Event]

All events declared on the class

initial_state

State

The top-level initial state

states_map

dict

Mapping from state values to State objects (all nesting levels)