v0.8.0 — Fortify is out

State machines for Python, done right.

Run your XState or Stately JSON unmodified, or define machines in plain Python. Async and sync engines share one algorithm. A CLI that proves its generated code rebuilds your machine before it writes a file. Zero dependencies.

$ pip install xstate-statemachine
  • 3,183 tests
  • 3.9–3.14 Python
  • 0 dependencies
  • MIT licensed
order.machine.json live
pending submitted open filled cancelled
state pending event

Everything a statechart runtime should do.
Nothing it shouldn't.

XState JSON, unmodified

Export from Stately and run it. Nested, parallel, history, guards, delays, invoke, actors, tags, meta.

from xstate_statemachine import create_machine, Interpreter
machine = create_machine(json.load(open("order.json")))
interp  = await Interpreter(machine).start()

One algorithm, two engines

Interpreter for asyncio, SyncInterpreter for everything else. Same core; cannot drift.

Time you control

Inject a Clock. SimulatedClock fires a 30‑second timeout in microseconds.

Ask, don't poll

await send("SUBMIT", wait=True) hands you a Receipt the moment the step has run. send_priority jumps the queue; the inbox is bounded with backpressure.

Receipt state_ids={'submitted'} changed=True error=None

Persistence that resumes

Versioned snapshots that refuse a structurally different machine. Pending events survive restarts.

Typed context

context_type=MyCtx flows a TypedDict to interp.context. A typo'd key is a checker error.

Docs that run

Every Python block in this site is executed in CI. Every link is resolved. 34 runnable examples.

Four ways to define a machine

Pick the style that matches your team. All compile to the same runtime.

from xstate_statemachine import (
    State, StateMachine, SyncInterpreter, action, guard
)

class TrafficLight(StateMachine):
    machine_id = "trafficLight"

    # States
    green  = State("green",  initial=True)
    yellow = State("yellow")
    red    = State("red")

    # Transitions
    slow_down = green.to(yellow, event="TIMER")
    stop      = yellow.to(red,   event="TIMER")
    go        = red.to(green,    event="TIMER")

    @action
    def log_change(self, interpreter, context, event, action_def):
        print(f"Light: {interpreter.active_state_ids}")

# Run it
machine = TrafficLight.create_machine()
interp = SyncInterpreter(machine).start()
interp.send("TIMER")  # green → yellow
interp.send("TIMER")  # yellow → red
interp.stop()
from xstate_statemachine import MachineBuilder, SyncInterpreter

machine = (
    MachineBuilder("trafficLight")
    .state("green",  initial=True)
    .state("yellow")
    .state("red")
    .transition("green",  "TIMER", "yellow")
    .transition("yellow", "TIMER", "red")
    .transition("red",    "TIMER", "green")
    .build()
)

interp = SyncInterpreter(machine).start()
interp.send("TIMER")  # green → yellow
interp.send("TIMER")  # yellow → red
interp.stop()
from xstate_statemachine import State, build_machine, SyncInterpreter

green  = State("green",  initial=True,
                on={"TIMER": "yellow"})
yellow = State("yellow", on={"TIMER": "red"})
red    = State("red",    on={"TIMER": "green"})

machine = build_machine(
    id="trafficLight",
    states=[green, yellow, red],
)

interp = SyncInterpreter(machine).start()
interp.send("TIMER")  # green → yellow
interp.send("TIMER")  # yellow → red
interp.stop()
from xstate_statemachine import create_machine, SyncInterpreter

config = {
    "id": "trafficLight",
    "initial": "green",
    "states": {
        "green":  {"on": {"TIMER": "yellow"}},
        "yellow": {"on": {"TIMER": "red"}},
        "red":    {"on": {"TIMER": "green"}},
    }
}

machine = create_machine(config)
interp = SyncInterpreter(machine).start()
interp.send("TIMER")  # green → yellow
interp.send("TIMER")  # yellow → red
interp.stop()

How it compares

Against the three other Python state-machine libraries in common use — transitions 0.9.3, python-statemachine 3.2.1, sismic 1.6.11, checked against the installed packages on 2026-09-19. A dash means the capability exists in a limited form (a thread-based timeout extension, an optional-dependency diagram, listener callbacks rather than a plugin API).

Feature XState-StateMachine transitions python-statemachine sismic
Hierarchical states
Parallel regions
Delayed transitions (after)
Invoked services / child machines
Actor model (spawn, sendTo, escalate)
XState v5 JSON compatibility
Declarative definition (JSON / YAML / SCXML)
Async and sync engines
Virtual-time clock for tests
Deep snapshot / restore
Bounded inbox with overflow policy
Build-time validation
Plugin / inspection hooks
CLI code generator
Diagram export
Zero runtime dependencies

How It Performs

Same machine shape, each library through its own idiomatic API. Higher is better; bold is fastest in the row.

Scenario XState-StateMachine transitions python-statemachine sismic
Flat toggle events/s82,562173,28711,85016,155
Guard + action events/s75,926146,36510,17014,978
3-level nested events/s34,02310,4853,3026,248
Parallel regions events/s56,2397,4684,7515,930
Delayed transitions timers/s11,609724,6646,805
1,000 instances instances/s51,92347,3264,8529,307
Construction machines/s12,44210,4751,813362
Native asyncio events/s32,10747,6208,339

transitions is faster on flat machines because it is a transition table, not a statechart engine — no configuration set, no entry/exit ordering, no internal queue. The moment states nest or run in parallel, that algorithm is what the others have to emulate, and XState-StateMachine is 3.2–7.5× faster than every library that does implement statecharts. Since 0.8.1 it is also the fastest of the four to construct (1.19× transitions) and to fan out to 1,000 instances (1.10×) — a single-pass parser and slotted interpreters — while still running the full build-time validator on every create_machine(). Measured 2026-09-19 on 0.8.1, all libraries in one session: Python 3.14, median of 7 runs, GC disabled, setup excluded. Reproduce with benchmarks/competitors/run.py; full table and caveats in the benchmark README, our own scaling curves in Production Characteristics.

Ship state you can reason about.

Start with the guide, or read the whole API on one page.