Python State Machines with Transitions — Core Concepts

What the Transitions Library Does

transitions is a lightweight Python library that adds state machine behavior to any object. You define states, transitions between them, and optional conditions and callbacks. The library then:

  • Tracks the current state
  • Only allows valid transitions
  • Runs callbacks before, during, and after transitions
  • Provides convenience methods for checking state (is_<state>())
  • Generates trigger methods automatically (<trigger_name>())

Basic Setup

A state machine needs three things: states, transitions, and a model (the object whose state is being tracked).

states = ["draft", "review", "approved", "published"]

transitions = [
    {"trigger": "submit",  "source": "draft",    "dest": "review"},
    {"trigger": "approve", "source": "review",   "dest": "approved"},
    {"trigger": "reject",  "source": "review",   "dest": "draft"},
    {"trigger": "publish", "source": "approved",  "dest": "published"},
]

Each transition has:

  • trigger — the method name that causes the transition
  • source — the state you must be in to use this trigger
  • dest — the state you move to

Callbacks: Running Code During Transitions

Transitions supports callbacks at multiple points in the transition lifecycle:

CallbackWhen it runsUse case
beforeBefore the transition startsValidation, logging
prepareBefore conditions are checkedData preparation
conditionsAfter prepare, before transitionGuard clauses
afterAfter the transition completesSide effects (emails, logs)
on_enter_<state>When entering a specific stateState-specific setup
on_exit_<state>When leaving a specific stateState-specific cleanup

Example: sending an email when an article is published:

after publish → send_notification_email
on_enter_published → update_publish_timestamp

Conditions (Guards)

Conditions are functions that return True or False. A transition only proceeds if all conditions pass:

{"trigger": "approve", "source": "review", "dest": "approved",
 "conditions": ["has_reviewer_approval", "passes_quality_check"]}

If has_reviewer_approval returns False, calling approve() does nothing (or raises an exception, depending on configuration).

Multiple Sources and Wildcards

A trigger can apply to multiple source states:

{"trigger": "cancel", "source": ["draft", "review", "approved"], "dest": "cancelled"}

Or use the wildcard "*" to allow a transition from any state:

{"trigger": "emergency_stop", "source": "*", "dest": "halted"}

State Features

States aren’t just strings. They can carry behavior:

  • on_enter callbacks — run when entering the state
  • on_exit callbacks — run when leaving the state
  • ignore_invalid_triggers — silently ignore triggers that don’t apply instead of raising errors
  • Tags — group states for bulk operations (e.g., all “active” states)

Querying State

The library adds convenience methods to your model:

  • model.state — current state name
  • model.is_draft() — returns True if in “draft” state
  • model.may_submit() — returns True if the “submit” trigger is valid from current state

may_<trigger>() is especially useful for UI code — show or hide buttons based on available actions.

Common Misconception

“State machines are overkill for simple workflows.” A state machine with 3-4 states and 4-5 transitions is barely more code than the equivalent if/else chain, but it centralizes the rules, prevents invalid states, and makes the workflow self-documenting. The overhead isn’t in the code — it’s in the concept. Once you think in states and transitions, even small workflows benefit.

When to Use Transitions

  • Order processing — placed → paid → shipped → delivered
  • Document workflows — draft → review → approved → published
  • User account lifecycle — active → suspended → banned
  • IoT device control — idle → running → error → maintenance
  • Game entity behavior — idle → patrolling → chasing → attacking

When Not to Use It

  • Simple boolean flags — on/off doesn’t need a state machine
  • Unbounded state spaces — if possible states are dynamic or infinite, a state machine isn’t the right model
  • Performance-critical inner loops — the library adds method call overhead per transition

One thing to remember: The transitions library takes messy if/else state management and replaces it with a declarative table of states, triggers, and rules — making your workflow’s logic visible, testable, and impossible to violate accidentally.

pythonstate-machinestransitions

See Also

  • Python Event Emitter Patterns How Python programs shout 'something happened!' so other parts of the code can react — like a school bell that tells everyone it's recess.
  • Python Observer Vs Pubsub Two ways Python code can share news — one is like telling your friends directly, the other is like posting on a bulletin board for anyone to read.
  • Python Rxpy Reactive Programming How RxPY lets Python code react to streams of data the way a news ticker reacts to breaking stories — automatically and in real time.
  • Ci Cd Why big apps can ship updates every day without turning your phone into a glitchy mess — CI/CD is the behind-the-scenes quality gate and delivery truck.
  • Containerization Why does software that works on your computer break on everyone else's? Containers fix that — and they're why Netflix can deploy 100 updates a day without the site going down.