Of course! The Python statemachine library is a fantastic and powerful tool for managing complex state transitions in your applications. It provides a clean, readable, and Pythonic way to define states, transitions, and the logic that goes with them.

Let's break down what it is, why you'd use it, and how to use it with a clear example.
What is a State Machine?
A state machine is a computational model that consists of:
- States: A finite number of distinct conditions or modes the system can be in (e.g.,
locked,unlocked,processing). - Transitions: Rules that define how the system moves from one state to another (e.g., from
lockedtounlockedwhen a correct code is entered). - Actions: Code that is executed when a state is entered, exited, or when a transition occurs.
Using a state machine prevents your code from getting cluttered with complex if/elif/else or switch/case blocks that check the current state and try to figure out what to do next.
Why Use the statemachine Library?
- Clarity: Your state logic is defined in a single, easy-to-read place.
- Validation: It prevents invalid transitions. You can't, for example, try to transition from
lockedtoprocessingif that's not defined. - Callbacks: It allows you to hook into the lifecycle of states and transitions (e.g., run code when entering a state).
- Visualization: It can generate a visual diagram of your state machine, which is invaluable for documentation and debugging.
Installation
First, you need to install the library. It's available on PyPI.

pip install statemachine
Core Concepts and a Simple Example
Let's model a simple garage door.
States:
closedopenopening(a transitional state)closing(a transitional state)
Transitions:
- If
closed, pressing the button makes itopening. - If
opening, it becomesopen. - If
open, pressing the button makes itclosing. - If
closing, it becomesclosed.
Here is the code to model this:

from statemachine import State, StateMachine
# Define the states
class GarageDoor(StateMachine):
# The initial state is defined with `initial=True`
closed = State(initial=True)
opening = State()
open = State()
closing = State()
# Define the transitions
# The `on` keyword specifies the trigger method name
# The `from_` and `to` keywords specify the source and target states
press_button = (
closed.to(opening)
| opening.to(open)
| open.to(closing)
| closing.to(closed)
)
# You can add callbacks to states and transitions
# `on_enter_` and `on_exit_` prefixes for state callbacks
# `on_` prefix for transition callbacks
def on_enter_opening(self):
print("Motor is starting to move the door up...")
def on_enter_closing(self):
print("Motor is starting to move the door down...")
def on_enter_open(self):
print("Door is now fully open.")
def on_enter_closed(self):
print("Door is now fully closed.")
# You can add logic to the transition itself
@press_button.before
def check_if_door_is_moving(self):
# This will run before any transition
if self.current_state in [self.opening, self.closing]:
print("Cannot press button while door is moving!")
# Returning False cancels the transition
return False
return True
# --- Let's use the state machine ---
door = GarageDoor()
print(f"Initial state: {door.current_state}")
# Initial state: closed
# Press the button
door.press_button()
# Motor is starting to move the door up...
# Door is now fully open.
print(f"Current state: {door.current_state}")
# Current state: open
# Press the button again
door.press_button()
# Motor is starting to move the door down...
# Door is now fully closed.
print(f"Current state: {door.current_state}")
# Current state: closed
# Try to press the button while it's opening (we'll simulate this)
# Let's manually change the state to opening for the demo
door.current_state = door.opening
print(f"\nCurrent state: {door.current_state}")
# Current state: opening
door.press_button()
# Cannot press button while door is moving!
print(f"Current state after failed press: {door.current_state}")
# Current state after failed press: opening
Advanced Features
Conditional Transitions
You can make transitions conditional based on certain criteria. Let's extend the garage door with a sensor that detects if something is blocking the door.
from statemachine import State, StateMachine
class SmartGarageDoor(StateMachine):
closed = State(initial=True)
opening = State()
open = State()
closing = State()
blocked = State() # New state for when the sensor is triggered
press_button = (
closed.to(opening)
| opening.to(open)
| open.to(closing)
| closing.to(closed)
)
# New transition for the sensor
sensor_triggered = opening.to(blocked)
# A conditional transition
# The `cond` keyword takes a callable that returns True or False
is_path_clear = closing.to(closed, cond=lambda sm: sm.is_path_clear)
# A transition that can go to one of two states
finish_opening = (
opening.to(open) |
opening.to(blocked, cond=lambda sm: not sm.is_path_clear)
)
def __init__(self):
super().__init__()
self.is_path_clear = True # Assume the path is clear initially
def on_enter_blocked(self):
print("ALERT! Door is blocked. Stopping motor.")
self.is_path_clear = False # Reset for next time
def on_enter_open(self):
print("Door is now fully open.")
self.is_path_clear = True # Path is clear when open
# --- Using the smart door ---
smart_door = SmartGarageDoor()
print("--- Scenario 1: Normal operation ---")
smart_door.press_button() # closed -> opening -> open
smart_door.press_button() # open -> closing -> closed
print("\n--- Scenario 2: Obstacle detected ---")
# Simulate an obstacle appearing
smart_door.is_path_clear = False
# Start opening the door
smart_door.press_button() # closed -> opening
# Simulate the sensor detecting the obstacle
smart_door.sensor_triggered() # opening -> blocked
print(f"Current state: {smart_door.current_state}")
# Now, clear the obstacle and try again
smart_door.is_path_clear = True
smart_door.press_button() # blocked -> opening (via finish_opening)
smart_door.press_button() # opening -> open (via finish_opening)
print(f"Final state: {smart_door.current_state}")
Visualizing the State Machine
This is one of the best features. You can generate a visual representation of your state machine as a .png file.
# Use the first simple GarageDoor example
from statemachine import State, StateMachine
import os
class GarageDoor(StateMachine):
closed = State(initial=True)
opening = State()
open = State()
closing = State()
press_button = (
closed.to(opening)
| opening.to(open)
| open.to(closing)
| closing.to(closed)
)
# Create an instance to generate the model
door = GarageDoor()
# Generate the diagram
# This will create a file named 'garage_door_state_machine.png' in your current directory
door.show_graph('garage_door_state_machine')
print(f"State machine diagram saved to {os.path.abspath('garage_door_state_machine.png')}")
When you run this, it will produce a file like this:
When to Use a State Machine
- User Interfaces: A button can be in
enabled,disabled, orloadingstates. - Order Processing: An order can be
pending,confirmed,shipped,delivered, orcancelled. - Game Development: A character can be
idle,walking,attacking, ordead. - Workflows: A document can be
draft,in_review,approved, orarchived.
When NOT to Use a State Machine
- If your state logic is very simple and can be handled by a few
ifstatements, adding a state machine might be overkill. - If the number of states and transitions is extremely large and complex, it can become difficult to manage, though the
statemachinelibrary helps mitigate this.
In summary, the statemachine library is an excellent choice for modeling any system with clear, discrete states and well-defined rules for transitioning between them. It leads to more robust, maintainable, and understandable code.
