Building a Lightweight State Machine with Python Enum
This article demonstrates how to implement a clear, maintainable state machine for a publishing workflow using Python's Enum type, covering state definitions, forward and backward transitions, validation, entry actions, and visualisation without external dependencies.
State management is a frequent requirement in software development, but scattered if‑else logic can become tangled. Python's Enum type provides a concise foundation for building a state machine without heavyweight libraries.
Why a state machine?
Consider a simple article publishing process that moves through the stages DRAFT → REVIEW → PUBLISHED → ARCHIVED. The rules are:
Publishing is prohibited until the article has been reviewed.
Returning directly from PUBLISHED to DRAFT is disallowed.
Any state can be archived at any time.
Define states with Enum
from enum import Enum, auto
class ArticleState(Enum):
DRAFT = auto()
REVIEW = auto()
PUBLISHED = auto()
ARCHIVED = auto()The enumeration creates the four possible states.
Add forward transition: next()
class ArticleState(Enum):
DRAFT = auto()
REVIEW = auto()
PUBLISHED = auto()
ARCHIVED = auto()
def next(self):
transitions = {
ArticleState.DRAFT: ArticleState.REVIEW,
ArticleState.REVIEW: ArticleState.PUBLISHED,
}
return transitions.get(self, None)Usage:
state = ArticleState.DRAFT
print(state.next()) # ArticleState.REVIEWBidirectional workflow: previous()
def previous(self):
transitions = {
ArticleState.REVIEW: ArticleState.DRAFT,
ArticleState.PUBLISHED: ArticleState.REVIEW,
}
return transitions.get(self, None)Now the state can move forward and backward within the allowed range.
Transition validation
def can_transition_to(self, target):
allowed = {
ArticleState.DRAFT: [ArticleState.REVIEW],
ArticleState.REVIEW: [ArticleState.PUBLISHED],
ArticleState.PUBLISHED: [ArticleState.ARCHIVED],
}
return target in allowed.get(self, [])Example:
if current_state.can_transition_to(next_state):
current_state = next_state
else:
raise ValueError("Illegal state transition")State‑driven actions
def on_enter(self):
actions = {
ArticleState.REVIEW: lambda: print("Notify reviewer"),
ArticleState.PUBLISHED: lambda: print("Deploy to production"),
ArticleState.ARCHIVED: lambda: print("Move to archive folder"),
}
action = actions.get(self)
if action:
action()Usage:
next_state = current_state.next()
next_state.on_enter()Visualising the flow
for state in ArticleState:
print(f"{state.name} → {state.next().name if state.next() else '---'}")Sample output:
DRAFT → REVIEW
REVIEW → PUBLISHED
PUBLISHED → ---
ARCHIVED → ---The output can be fed into Graphviz or Mermaid to generate a diagram.
When to switch to a dedicated library
If a state machine grows very complex—requiring conditional guards, asynchronous events, or extensive callbacks—libraries such as transitions provide advanced features like entry/exit hooks and visualisation. However, for roughly 80 % of typical use cases, the simple Enum‑based pattern is sufficient and avoids extra dependencies.
Conclusion
Python's Enum may appear to be just a tool for avoiding magic numbers, but embedding behavior, logic, and transition rules inside an enumeration yields a clean, maintainable state machine without scattered conditionals or additional classes.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Data STUDIO
Click to receive the "Python Study Handbook"; reply "benefit" in the chat to get it. Data STUDIO focuses on original data science articles, centered on Python, covering machine learning, data analysis, visualization, MySQL and other practical knowledge and project case studies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
