# Tutorial
This tutorial walks you through python-statemachine from your first flat state
machine all the way to full statecharts â compound states, parallel regions,
history, and async. Each section builds on the previous one using the same
domain: **a coffee shop order system**.
By the end you will be comfortable defining states, transitions, guards,
actions, and listeners, and you will see how the same declarative API scales
from a five-state FSM to a production-grade statechart â no new concepts
required.
## Your first state machine
A coffee order goes through a few stages: the customer places it, the barista
prepares it, and the customer picks it up.
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... # Define the states
... pending = State(initial=True)
... preparing = State()
... ready = State()
... picked_up = State(final=True)
...
... # Define events â each one groups one or more transitions
... start = pending.to(preparing)
... finish = preparing.to(ready)
... pick_up = ready.to(picked_up)
```
That's it â states are class attributes, transitions are built with
`state.to(target)`, and events are the names you assign them to.
Create an instance and start sending events:
```py
>>> order = CoffeeOrder()
>>> order.pending.is_active
True
>>> order.send("start")
>>> order.preparing.is_active
True
>>> order.send("finish")
>>> order.send("pick_up")
>>> order.picked_up.is_active
True
```
You can also call events as methods â `order.start()` is equivalent to
`order.send("start")`:
```py
>>> order = CoffeeOrder()
>>> order.start()
>>> order.preparing.is_active
True
```
```{tip}
Use `sm.send("event_name")` when the event name is dynamic (e.g., comes from
user input or a message queue). Use `sm.event_name()` when writing
application code where the event is known at development time.
```
## Adding behavior with actions
A state machine without side effects is just a diagram. Actions let you
attach behavior to state entries, exits, and transitions.
Define actions by naming convention â the library discovers them
automatically:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
... preparing = State()
... ready = State()
... picked_up = State(final=True)
...
... start = pending.to(preparing)
... finish = preparing.to(ready)
... pick_up = ready.to(picked_up)
...
... # Called when entering the "preparing" state
... def on_enter_preparing(self):
... print("Barista starts making the drink.")
...
... # Called when the "finish" event fires
... def on_finish(self):
... print("Drink is ready!")
...
... # Called when entering the "picked_up" state
... def on_enter_picked_up(self):
... print("Customer picked up the order. Enjoy!")
>>> order = CoffeeOrder()
>>> order.send("start")
Barista starts making the drink.
>>> order.send("finish")
Drink is ready!
>>> order.send("pick_up")
Customer picked up the order. Enjoy!
```
The naming conventions are:
| Pattern | When it runs |
|---------------------------|---------------------------------------|
| `on_enter_()` | Every time `` is entered |
| `on_exit_()` | Every time `` is exited |
| `before_()` | Before any transition for `` |
| `on_()` | During the transition for `` |
| `after_()` | After the transition for `` |
```{seealso}
The full list of action callbacks and their execution order is in
[](actions.md).
```
### Dependency injection in callbacks
Callbacks don't need to accept a fixed signature. Declare only the
parameters you need, and the library injects them automatically:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
... preparing = State()
... ready = State()
... picked_up = State(final=True)
...
... start = pending.to(preparing)
... finish = preparing.to(ready)
... pick_up = ready.to(picked_up)
...
... def on_enter_preparing(self, source: State, target: State):
... print(f"{source.id} â {target.id}")
...
... def on_finish(self):
... print("Done!")
>>> order = CoffeeOrder()
>>> order.send("start")
pending â preparing
>>> order.send("finish")
Done!
```
`on_enter_preparing` asks for `source` and `target` â it gets them.
`on_finish` asks for nothing extra â that's fine too.
Available parameters include `event`, `source`, `target`, `state`, and any
keyword arguments you pass to `send()`:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
... preparing = State()
... ready = State(final=True)
...
... start = pending.to(preparing)
... finish = preparing.to(ready)
...
... def on_start(self, drink: str = "coffee"):
... print(f"Making a {drink}.")
>>> order = CoffeeOrder()
>>> order.send("start", drink="cappuccino")
Making a cappuccino.
```
## Guards: conditional transitions
Not every transition should always be allowed. Guards are conditions that
must be satisfied for a transition to fire.
A coffee order shouldn't move to `preparing` unless it has been paid for:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
... preparing = State()
... ready = State()
... picked_up = State(final=True)
...
... # Two transitions on the same event â checked in declaration order.
... # The first whose guard passes wins.
... start = (
... pending.to(preparing, cond="is_paid")
... | pending.to(pending) # fallback: stay in pending
... )
... finish = preparing.to(ready)
... pick_up = ready.to(picked_up)
...
... paid: bool = False
...
... def is_paid(self):
... return self.paid
>>> order = CoffeeOrder()
>>> order.send("start") # not paid â stays in pending
>>> order.pending.is_active
True
>>> order.paid = True
>>> order.send("start") # paid â moves to preparing
>>> order.preparing.is_active
True
```
Guards receive the same dependency injection as actions â you can
accept `event`, `source`, `target`, and any extra keyword arguments:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
... preparing = State(final=True)
...
... start = (
... pending.to(preparing, cond="is_paid")
... | pending.to(pending)
... )
...
... def is_paid(self, amount: float = 0):
... return amount >= 5.0
>>> order = CoffeeOrder()
>>> order.send("start", amount=3.0)
>>> order.pending.is_active
True
>>> order.send("start", amount=5.0)
>>> order.preparing.is_active
True
```
```{seealso}
See [](guards.md) for `unless=`, validators, boolean expressions
in condition strings, and evaluation order details.
```
## Observing from outside with listeners
Listeners let external objects react to state changes without touching the
state machine definition. Any object with methods matching the callback
naming conventions works as a listener.
The preferred way is to declare listeners at the class level â they are
automatically attached to every instance:
```py
>>> from statemachine import StateChart, State
>>> class NotificationService:
... def on_enter_state(self, target: State):
... print(f"[notify] Order is now: {target.id}")
>>> class CoffeeOrder(StateChart):
... listeners = [NotificationService]
...
... pending = State(initial=True)
... preparing = State()
... ready = State(final=True)
...
... start = pending.to(preparing)
... finish = preparing.to(ready)
>>> order = CoffeeOrder()
[notify] Order is now: pending
>>> order.send("start")
[notify] Order is now: preparing
>>> order.send("finish")
[notify] Order is now: ready
```
When the `listeners` list contains a **class** (like `NotificationService`
above), it acts as a factory â a fresh instance is created for each state
machine. Pass an already-built **instance** instead if you want a shared,
stateless listener (e.g., a global logger).
You can also add listeners at runtime, either via the constructor or on an
already running machine:
```py
>>> class AuditLog:
... def after_transition(self, source: State, target: State, event: str):
... print(f"[audit] {source.id} â({event})â {target.id}")
>>> order = CoffeeOrder()
[notify] Order is now: pending
>>> _ = order.add_listener(AuditLog())
>>> order.send("start")
[notify] Order is now: preparing
[audit] pending â(start)â preparing
```
The machine knows nothing about the listener, and the listener knows
nothing about the machine's internals â only the callback protocol.
```{seealso}
See [](listeners.md) for class-level listener configuration, `functools.partial`
factories, and the full list of listener callbacks.
```
## Generating diagrams
Visualize any state machine as a diagram:
```{statemachine-diagram} tests.machines.tutorial_coffee_order.CoffeeOrder
:alt: CoffeeOrder diagram
```
Generate diagrams programmatically with `_graph()`:
```python
order = CoffeeOrder()
order._graph().write_png("order.png")
```
Or from the command line:
```bash
python -m statemachine.contrib.diagram my_module.CoffeeOrder order.png
```
### Text representations with `format()`
You can also get text representations of any state machine using Python's built-in
`format()` or f-strings â no Graphviz needed:
```py
>>> from tests.machines.tutorial_coffee_order import CoffeeOrder
>>> print(f"{CoffeeOrder:md}")
| State | Event | Guard | Target |
| --------- | ------- | ----- | --------- |
| Pending | Start | | Preparing |
| Preparing | Finish | | Ready |
| Ready | Pick up | | Picked up |
```
Supported formats include `mermaid`, `md` (markdown table), `rst`, `dot`, and `svg`.
Works on both classes and instances:
```py
>>> print(f"{CoffeeOrder:mermaid}")
stateDiagram-v2
direction LR
state "Pending" as pending
state "Preparing" as preparing
state "Ready" as ready
state "Picked up" as picked_up
[*] --> pending
picked_up --> [*]
pending --> preparing : Start
preparing --> ready : Finish
ready --> picked_up : Pick up
```
```{tip}
Graphviz diagram generation requires [Graphviz](https://graphviz.org/) (`dot` command)
and the `diagrams` extra:
pip install python-statemachine[diagrams]
Text formats (`md`, `rst`, `mermaid`) work without any extra dependencies.
```
```{seealso}
See [](diagram.md) for all formats, highlighting active states, auto-expanding
docstrings, Jupyter integration, Sphinx directive, and the `quickchart_write_svg`
alternative that doesn't require Graphviz.
```
## Scaling up with statecharts
So far our coffee order has been a flat sequence of states. Real systems
are rarely that simple â what happens when preparing a drink involves
multiple steps? What if the order includes both a drink *and* a snack
prepared in parallel?
This is where python-statemachine shines: you scale from a flat FSM to a
full statechart using the **exact same API**. No new base class, no
configuration flags â just nest your states.
### Compound states: breaking complexity into levels
Preparing a drink isn't a single step. Let's model it as a compound state
with sub-steps â grinding, brewing, and serving:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
...
... class preparing(State.Compound):
... """Drink preparation with internal steps."""
... grinding = State(initial=True)
... brewing = State()
... serving = State(final=True)
...
... grind = grinding.to(brewing)
... brew = brewing.to(serving)
...
... picked_up = State(final=True)
...
... start = pending.to(preparing)
... done_state_preparing = preparing.to(picked_up)
>>> order = CoffeeOrder()
>>> order.send("start")
>>> set(order.configuration_values) == {"preparing", "grinding"}
True
>>> order.send("grind")
>>> "brewing" in order.configuration_values
True
>>> order.send("brew")
>>> order.picked_up.is_active
True
```
Entering `preparing` activates both the compound parent and its initial
child (`grinding`). When `serving` â a final child â is reached,
`done.state.preparing` fires automatically and transitions to `picked_up`.
Notice how nothing changed about the outer API. You still `send("start")`
to begin â the compound structure is an internal detail.
### Parallel states: concurrent regions
Now let's say the order includes both a drink and a snack, prepared at the
same time by different stations:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
...
... class preparing(State.Parallel):
... class drink(State.Compound):
... brewing = State(initial=True)
... drink_done = State(final=True)
... brew = brewing.to(drink_done)
... class snack(State.Compound):
... heating = State(initial=True)
... snack_done = State(final=True)
... heat = heating.to(snack_done)
...
... picked_up = State(final=True)
...
... start = pending.to(preparing)
... done_state_preparing = preparing.to(picked_up)
>>> order = CoffeeOrder()
>>> order.send("start")
>>> "brewing" in order.configuration_values and "heating" in order.configuration_values
True
>>> order.send("brew") # drink done, snack still heating
>>> "drink_done" in order.configuration_values and "heating" in order.configuration_values
True
>>> order.is_terminated # drink region finished, but snack hasn't
False
>>> order.send("heat") # both done â auto-transitions to picked_up
>>> order.picked_up.is_active
True
>>> order.is_terminated
True
```
`State.Parallel` activates all child regions at once. Each region
processes events independently. The machine only transitions out when
**every** region reaches a final state.
### Checking completion with `is_terminated`
In a flat state machine, checking whether you've reached a specific
final state is enough. But with compound and parallel states, completion
depends on the structure â all regions of a parallel must finish, nested
compounds must reach their own final children, and so on. The
`is_terminated` property handles this for you: it returns `True` only
when the entire machine has completed its work, regardless of how deeply
nested the structure is. Use it instead of checking individual states.
A common pattern is to consume events from a queue or stream, feeding
them to the machine until it terminates:
```py
>>> from collections import deque
>>> order = CoffeeOrder()
>>> queue = deque(["start", "brew", "heat"])
>>> while not order.is_terminated and queue:
... order.send(queue.popleft())
>>> order.is_terminated
True
```
This decouples event production from consumption â the queue could come
from a message broker, a file, user input, or any other source.
### History states: remember where you left off
What if the barista needs to pause preparation (e.g., to handle a rush)
and resume later? A history state remembers which child was active when a
compound was exited:
```py
>>> from statemachine import HistoryState, StateChart, State
>>> class CoffeeOrder(StateChart):
... class preparing(State.Compound):
... grinding = State(initial=True)
... brewing = State()
... done = State(final=True)
... h = HistoryState()
...
... grind = grinding.to(brewing)
... brew = brewing.to(done)
...
... paused = State()
... finished = State(final=True)
...
... pause = preparing.to(paused)
... resume = paused.to(preparing.h) # â return via history
... done_state_preparing = preparing.to(finished)
>>> order = CoffeeOrder()
>>> order.send("grind") # now in "brewing"
>>> "brewing" in order.configuration_values
True
>>> order.send("pause") # leave preparing
>>> order.send("resume") # history restores "brewing", not "grinding"
>>> "brewing" in order.configuration_values
True
>>> order.send("brew") # finish preparation
>>> order.finished.is_active
True
```
Use `HistoryState(type="deep")` for deep history that remembers the exact
leaf state across nested compounds.
### Eventless transitions: react automatically
Eventless transitions fire without an explicit event â they trigger
automatically when their guard condition is met:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
... preparing = State()
... ready = State()
... picked_up = State(final=True)
...
... # Eventless: fires automatically when the guard is satisfied
... pending.to(preparing, cond="is_paid")
... ready.to(picked_up, cond="was_picked_up")
...
... finish = preparing.to(ready)
...
... # A no-op event to re-enter the processing loop
... check = (
... pending.to.itself(internal=True)
... | ready.to.itself(internal=True)
... )
...
... paid: bool = False
... picked: bool = False
...
... def is_paid(self):
... return self.paid
... def was_picked_up(self):
... return self.picked
>>> order = CoffeeOrder()
>>> order.paid = True
>>> order.send("check") # triggers the eventless transition
>>> order.preparing.is_active
True
>>> order.send("finish")
>>> order.picked = True
>>> order.send("check")
>>> order.picked_up.is_active
True
```
Eventless transitions are evaluated after every macrostep. Combined with
guards, they let the machine react to changes in its own data without
requiring the outside world to name every event.
### Error handling as events
With `StateChart`, runtime exceptions in callbacks don't crash the
machine â they become `error.execution` events that you can handle with
regular transitions:
```py
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... preparing = State(initial=True)
... out_of_stock = State(final=True)
...
... make_drink = preparing.to(preparing, on="do_make_drink")
... error_execution = preparing.to(out_of_stock)
...
... def do_make_drink(self):
... raise RuntimeError("Out of oat milk!")
...
... def on_enter_out_of_stock(self, error=None):
... if error:
... print(f"Problem: {error}")
>>> order = CoffeeOrder()
>>> order.send("make_drink")
Problem: Out of oat milk!
>>> order.out_of_stock.is_active
True
```
The exception is caught, dispatched as an internal `error.execution`
event, and handled by the `error_execution` transition â no try/except
needed in your application code.
```{seealso}
See [](error_handling.md) for the full `error.execution` lifecycle,
block-level error catching, and the cleanup/finalize pattern.
```
### Async: same API, no changes needed
Every example above works with async callbacks too. Just use `async def`
and the engine switches automatically:
```py
>>> import asyncio
>>> from statemachine import StateChart, State
>>> class CoffeeOrder(StateChart):
... pending = State(initial=True)
... preparing = State()
... ready = State(final=True)
...
... start = pending.to(preparing)
... finish = preparing.to(ready)
...
... async def on_start(self, drink: str = "coffee"):
... return f"Started making {drink}"
...
... async def on_finish(self):
... return "Drink is ready!"
>>> async def main():
... order = CoffeeOrder()
... result = await order.send("start", drink="latte")
... print(result)
... result = await order.send("finish")
... print(result)
>>> asyncio.run(main())
Started making latte
Drink is ready!
```
No special async base class. No configuration. The same `StateChart`
class, the same `send()` method, the same naming conventions â just
`async def` and `await`.
```{seealso}
See [](async.md) for the sync vs. async engine selection table,
initial state activation in async contexts, and concurrent event sending.
```
## Next steps
You now have a solid foundation. Here are the most useful pages to
explore next:
- **[States](states.md)** â final states, compound states, parallel states, history, `DoneData`
- **[Transitions](transitions.md)** â self-transitions, internal transitions, cross-boundary, delayed events
- **[Actions](actions.md)** â the full callback execution order, `prepare_event()`
- **[Guards](guards.md)** â `unless=`, validators, boolean expressions, `In()` for cross-region checks
- **[Listeners](listeners.md)** â the observer pattern in depth
- **[Error handling](error_handling.md)** â `error.execution` events, block-level catching, cleanup patterns
- **[Processing model](processing_model.md)** â `send()` vs `raise_()`, microstep/macrostep, run-to-completion
- **[Behaviour](behaviour.md)** â `StateChart` vs `StateMachine`, behavioral flags, and migration guide
- **[Django integration](integrations.md)** â auto-discovery, `MachineMixin` with Django models
- **[Diagrams](diagram.md)** â CLI generation, Jupyter, SVG, DPI settings
- **[API reference](api.md)** â full class and method reference