# ๐ pygenfsm
<div align="center">
[](https://badge.fury.io/py/pygenfsm)
[](https://pypi.org/project/pygenfsm/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/astral-sh/ruff)
[](https://github.com/microsoft/pyright)
[](https://github.com/serialx/pygenfsm)
**A minimal, clean, typed and async-native FSM (Finite State Machine) implementation for Python, inspired by Erlang's gen_fsm**
[Installation](#-installation) โข
[Quick Start](#-quick-start) โข
[Features](#-features) โข
[Examples](#-examples) โข
[API Reference](#-api-reference) โข
[Contributing](#-contributing)
</div>
---
## ๐ฏ Why pygenfsm?
Building robust state machines in Python often involves:
- ๐คฏ Complex if/elif chains that grow unmaintainable
- ๐ Implicit state that's hard to reason about
- ๐ Scattered transition logic across your codebase
- โ No type safety for states and events
- ๐ซ Mixing sync and async code awkwardly
**pygenfsm** solves these problems with a minimal, elegant API that leverages Python's type system and async capabilities.
## โจ Features
<table>
<tr>
<td>
### ๐จ Clean API
```python
@fsm.on(State.IDLE, StartEvent)
def handle_start(fsm, event):
return State.RUNNING
```
</td>
<td>
### ๐ Async Native
```python
@fsm.on(State.RUNNING, DataEvent)
async def handle_data(fsm, event):
await process_data(event.data)
return State.DONE
```
</td>
</tr>
<tr>
<td>
### ๐ฏ Type Safe
```python
# Full typing with generics
FSM[StateEnum, EventType, ContextType]
```
</td>
<td>
### ๐ Zero Dependencies
```bash
# Minimal and fast
pip install pygenfsm
```
</td>
</tr>
</table>
### Key Benefits
- **๐ Type-safe**: Full typing support with generics for states, events, and context
- **๐ญ Flexible**: Mix sync and async handlers in the same FSM
- **๐ฆ Minimal**: Zero dependencies, clean API surface
- **๐ Pythonic**: Decorator-based, intuitive design
- **๐ Async-native**: Built for modern async Python
- **๐ Context-aware**: Carry data between transitions
- **๐งฌ Cloneable**: Fork FSM instances for testing scenarios
- **๐๏ธ Builder pattern**: Late context injection support
## ๐ฆ Installation
```bash
# Using pip
pip install pygenfsm
# Using uv (recommended)
uv add pygenfsm
# Using poetry
poetry add pygenfsm
```
## ๐ Quick Start
### Basic Example
```python
import asyncio
from dataclasses import dataclass
from enum import Enum, auto
from pygenfsm import FSM
# 1. Define states as an enum
class State(Enum):
IDLE = auto()
RUNNING = auto()
DONE = auto()
# 2. Define events as dataclasses
@dataclass
class StartEvent:
task_id: str
@dataclass
class CompleteEvent:
result: str
# 3. Create FSM with initial state
fsm = FSM[State, StartEvent | CompleteEvent, None](
state=State.IDLE,
context=None, # No context needed for simple FSM
)
# 4. Define handlers with decorators
@fsm.on(State.IDLE, StartEvent)
def start_handler(fsm, event: StartEvent) -> State:
print(f"Starting task {event.task_id}")
return State.RUNNING
@fsm.on(State.RUNNING, CompleteEvent)
def complete_handler(fsm, event: CompleteEvent) -> State:
print(f"Task completed: {event.result}")
return State.DONE
# 5. Run the FSM
async def main():
await fsm.send(StartEvent(task_id="123"))
await fsm.send(CompleteEvent(result="Success!"))
print(f"Final state: {fsm.state}")
asyncio.run(main())
```
## ๐ฏ Core Concepts
### States, Events, and Context
pygenfsm is built on three core concepts:
| Concept | Purpose | Implementation |
|---------|---------|----------------|
| **States** | The finite set of states your system can be in | Python Enum |
| **Events** | Things that happen to trigger transitions | Dataclasses |
| **Context** | Data that persists across transitions | Any Python type |
### Handler Types
pygenfsm seamlessly supports both sync and async handlers:
```python
# Sync handler - for simple state transitions
@fsm.on(State.IDLE, SimpleEvent)
def sync_handler(fsm, event) -> State:
# Fast, synchronous logic
return State.NEXT
# Async handler - for I/O operations
@fsm.on(State.LOADING, DataEvent)
async def async_handler(fsm, event) -> State:
# Async I/O, network calls, etc.
data = await fetch_data(event.url)
fsm.context.data = data
return State.READY
```
## ๐ Examples
### Traffic Light System
```python
from enum import Enum, auto
from dataclasses import dataclass
from pygenfsm import FSM
class Color(Enum):
RED = auto()
YELLOW = auto()
GREEN = auto()
@dataclass
class TimerEvent:
"""Timer expired event"""
pass
@dataclass
class EmergencyEvent:
"""Emergency button pressed"""
pass
# Create FSM
traffic_light = FSM[Color, TimerEvent | EmergencyEvent, None](
state=Color.RED,
context=None,
)
@traffic_light.on(Color.RED, TimerEvent)
def red_to_green(fsm, event) -> Color:
print("๐ด โ ๐ข")
return Color.GREEN
@traffic_light.on(Color.GREEN, TimerEvent)
def green_to_yellow(fsm, event) -> Color:
print("๐ข โ ๐ก")
return Color.YELLOW
@traffic_light.on(Color.YELLOW, TimerEvent)
def yellow_to_red(fsm, event) -> Color:
print("๐ก โ ๐ด")
return Color.RED
# Emergency overrides from any state
for color in Color:
@traffic_light.on(color, EmergencyEvent)
def emergency(fsm, event) -> Color:
print("๐จ EMERGENCY โ RED")
return Color.RED
```
### Connection Manager with Retry Logic
```python
import asyncio
from dataclasses import dataclass, field
from enum import Enum, auto
from pygenfsm import FSM
class ConnState(Enum):
DISCONNECTED = auto()
CONNECTING = auto()
CONNECTED = auto()
ERROR = auto()
@dataclass
class ConnectEvent:
host: str
port: int
@dataclass
class ConnectionContext:
retries: int = 0
max_retries: int = 3
last_error: str = ""
fsm = FSM[ConnState, ConnectEvent, ConnectionContext](
state=ConnState.DISCONNECTED,
context=ConnectionContext(),
)
@fsm.on(ConnState.DISCONNECTED, ConnectEvent)
async def start_connection(fsm, event: ConnectEvent) -> ConnState:
print(f"๐ Connecting to {event.host}:{event.port}")
return ConnState.CONNECTING
@fsm.on(ConnState.CONNECTING, ConnectEvent)
async def attempt_connect(fsm, event: ConnectEvent) -> ConnState:
try:
# Simulate connection attempt
await asyncio.sleep(1)
if fsm.context.retries < 2: # Simulate failures
raise ConnectionError("Network timeout")
print("โ
Connected!")
fsm.context.retries = 0
return ConnState.CONNECTED
except ConnectionError as e:
fsm.context.retries += 1
fsm.context.last_error = str(e)
if fsm.context.retries >= fsm.context.max_retries:
print(f"โ Max retries reached: {e}")
return ConnState.ERROR
print(f"๐ Retry {fsm.context.retries}/{fsm.context.max_retries}")
return ConnState.CONNECTING
```
## ๐๏ธ Advanced Patterns
### Late Context Injection with FSMBuilder
Perfect for dependency injection and testing:
```python
from pygenfsm import FSMBuilder
# Define builder without context
builder = FSMBuilder[State, Event, AppContext](
initial_state=State.INIT
)
@builder.on(State.INIT, StartEvent)
async def initialize(fsm, event) -> State:
# Access context that will be injected later
await fsm.context.database.connect()
return State.READY
# Later, when dependencies are ready...
database = Database(connection_string)
logger = Logger(level="INFO")
# Build FSM with context
fsm = builder.build(AppContext(
database=database,
logger=logger,
))
```
### Cloning for Testing Scenarios
Test different paths without affecting the original:
```python
# Create base FSM
original_fsm = FSM[State, Event, Context](
state=State.INITIAL,
context=Context(data=[]),
)
# Clone for testing
test_scenario_1 = original_fsm.clone()
test_scenario_2 = original_fsm.clone()
# Run different scenarios
await test_scenario_1.send(SuccessEvent())
await test_scenario_2.send(FailureEvent())
# Original remains unchanged
assert original_fsm.state == State.INITIAL
```
## ๐ API Reference
### Core Classes
#### `FSM[S, E, C]`
The main FSM class with generic parameters:
- `S`: State enum type
- `E`: Event type (can be a Union)
- `C`: Context type
**Methods:**
- `on(state: S, event_type: type[E])`: Decorator to register handlers
- `async send(event: E) -> S`: Send event and transition state
- `send_sync(event: E) -> S`: Synchronous send (only for sync handlers)
- `clone() -> FSM[S, E, C]`: Create independent copy
- `replace_context(context: C) -> None`: Replace context
#### `FSMBuilder[S, E, C]`
Builder for late context injection:
- `on(state: S, event_type: type[E])`: Register handlers
- `build(context: C) -> FSM[S, E, C]`: Create FSM with context
### Best Practices
1. **Use sync handlers for:**
- Simple state transitions
- Pure computations
- Context updates
2. **Use async handlers for:**
- Network I/O
- Database operations
- File system access
- Long computations
3. **Event Design:**
- Make events immutable (use frozen dataclasses)
- Include all necessary data in events
- Use Union types for multiple events per state
4. **Context Design:**
- Keep context focused and minimal
- Use dataclasses for structure
- Avoid circular references
## ๐ค Contributing
We love contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
```bash
# Setup development environment
git clone https://github.com/serialx/pygenfsm
cd pygenfsm
uv sync
# Run tests
uv run pytest
# Run linting
uv run ruff check .
uv run pyright .
```
## ๐ Comparison with transitions
### Feature Comparison
| Feature | pygenfsm | transitions |
|---------|----------|-------------|
| **Event Data** | โ
First-class with dataclasses | โ Limited (callbacks, conditions) |
| **Async Support** | โ
Native async/await | โ No built-in support |
| **Type Safety** | โ
Full generics | โ ๏ธ Runtime checks only |
| **State Definition** | โ
Enums (type-safe) | โ ๏ธ Strings/objects |
| **Handler Registration** | โ
Decorators | โ Configuration dicts |
| **Context/Model** | โ
Explicit, typed | โ ๏ธ Implicit on model |
| **Dependencies** | โ
Zero | โ Multiple (six, etc.) |
| **Visualization** | โ Not built-in | โ
GraphViz support |
| **Hierarchical States** | โ No | โ
Yes (HSM) |
| **Parallel States** | โ No | โ
Yes |
| **State History** | โ No | โ
Yes |
| **Guards/Conditions** | โ ๏ธ In handler logic | โ
Built-in |
| **Callbacks** | โ ๏ธ In handlers | โ
before/after/prepare |
| **Size** | ~300 LOC | ~3000 LOC |
### When to Use Each
**Use pygenfsm when you need:**
- ๐ Strong type safety with IDE support
- ๐ Native async/await support
- ๐ฆ Zero dependencies
- ๐ฏ Event-driven architecture with rich data
- ๐ Modern Python patterns (3.11+)
- ๐งช Easy testing with full typing
**Use transitions when you need:**
- ๐ State diagram visualization
- ๐ Hierarchical states (HSM)
- โก Parallel state machines
- ๐ State history tracking
- ๐ Complex transition guards/conditions
- ๐๏ธ Legacy Python support
## ๐ Links
- **GitHub**: [github.com/serialx/pygenfsm](https://github.com/serialx/pygenfsm)
- **PyPI**: [pypi.org/project/pygenfsm](https://pypi.org/project/pygenfsm)
- **Documentation**: [Full API Docs](https://github.com/serialx/pygenfsm/wiki)
- **Issues**: [Report bugs or request features](https://github.com/serialx/pygenfsm/issues)
## ๐ License
MIT License - see [LICENSE](LICENSE) file for details.
---
<div align="center">
Made with โค๏ธ by developers who love clean state machines
</div>
Raw data
{
"_id": null,
"home_page": null,
"name": "pygenfsm",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": "SerialX <serialx@serialx.net>",
"keywords": "async, asyncio, automation, erlang, event-driven, finite-state-machine, fsm, gen_fsm, state-machine, state-management, typed, workflow",
"author": null,
"author_email": "SerialX <serialx@serialx.net>",
"download_url": "https://files.pythonhosted.org/packages/4f/74/9b753640b212553d35d1b8e553903243edd794ba9b31403226f282b20293/pygenfsm-1.0.1.tar.gz",
"platform": null,
"description": "# \ud83d\udd04 pygenfsm\n\n<div align=\"center\">\n\n[](https://badge.fury.io/py/pygenfsm)\n[](https://pypi.org/project/pygenfsm/)\n[](https://opensource.org/licenses/MIT)\n[](https://github.com/astral-sh/ruff)\n[](https://github.com/microsoft/pyright)\n[](https://github.com/serialx/pygenfsm)\n\n**A minimal, clean, typed and async-native FSM (Finite State Machine) implementation for Python, inspired by Erlang's gen_fsm**\n\n[Installation](#-installation) \u2022\n[Quick Start](#-quick-start) \u2022\n[Features](#-features) \u2022\n[Examples](#-examples) \u2022\n[API Reference](#-api-reference) \u2022\n[Contributing](#-contributing)\n\n</div>\n\n---\n\n## \ud83c\udfaf Why pygenfsm?\n\nBuilding robust state machines in Python often involves:\n- \ud83e\udd2f Complex if/elif chains that grow unmaintainable\n- \ud83d\udc1b Implicit state that's hard to reason about\n- \ud83d\udd00 Scattered transition logic across your codebase\n- \u274c No type safety for states and events\n- \ud83d\udeab Mixing sync and async code awkwardly\n\n**pygenfsm** solves these problems with a minimal, elegant API that leverages Python's type system and async capabilities.\n\n## \u2728 Features\n\n<table>\n<tr>\n<td>\n\n### \ud83c\udfa8 Clean API\n```python\n@fsm.on(State.IDLE, StartEvent)\ndef handle_start(fsm, event):\n return State.RUNNING\n```\n\n</td>\n<td>\n\n### \ud83d\udd04 Async Native\n```python\n@fsm.on(State.RUNNING, DataEvent)\nasync def handle_data(fsm, event):\n await process_data(event.data)\n return State.DONE\n```\n\n</td>\n</tr>\n<tr>\n<td>\n\n### \ud83c\udfaf Type Safe\n```python\n# Full typing with generics\nFSM[StateEnum, EventType, ContextType]\n```\n\n</td>\n<td>\n\n### \ud83d\ude80 Zero Dependencies\n```bash\n# Minimal and fast\npip install pygenfsm\n```\n\n</td>\n</tr>\n</table>\n\n### Key Benefits\n\n- **\ud83d\udd12 Type-safe**: Full typing support with generics for states, events, and context\n- **\ud83c\udfad Flexible**: Mix sync and async handlers in the same FSM\n- **\ud83d\udce6 Minimal**: Zero dependencies, clean API surface\n- **\ud83d\udc0d Pythonic**: Decorator-based, intuitive design\n- **\ud83d\udd04 Async-native**: Built for modern async Python\n- **\ud83d\udcca Context-aware**: Carry data between transitions\n- **\ud83e\uddec Cloneable**: Fork FSM instances for testing scenarios\n- **\ud83c\udfd7\ufe0f Builder pattern**: Late context injection support\n\n## \ud83d\udce6 Installation\n\n```bash\n# Using pip\npip install pygenfsm\n\n# Using uv (recommended)\nuv add pygenfsm\n\n# Using poetry\npoetry add pygenfsm\n```\n\n## \ud83d\ude80 Quick Start\n\n### Basic Example\n\n```python\nimport asyncio\nfrom dataclasses import dataclass\nfrom enum import Enum, auto\nfrom pygenfsm import FSM\n\n# 1. Define states as an enum\nclass State(Enum):\n IDLE = auto()\n RUNNING = auto()\n DONE = auto()\n\n# 2. Define events as dataclasses\n@dataclass\nclass StartEvent:\n task_id: str\n\n@dataclass\nclass CompleteEvent:\n result: str\n\n# 3. Create FSM with initial state\nfsm = FSM[State, StartEvent | CompleteEvent, None](\n state=State.IDLE,\n context=None, # No context needed for simple FSM\n)\n\n# 4. Define handlers with decorators\n@fsm.on(State.IDLE, StartEvent)\ndef start_handler(fsm, event: StartEvent) -> State:\n print(f\"Starting task {event.task_id}\")\n return State.RUNNING\n\n@fsm.on(State.RUNNING, CompleteEvent)\ndef complete_handler(fsm, event: CompleteEvent) -> State:\n print(f\"Task completed: {event.result}\")\n return State.DONE\n\n# 5. Run the FSM\nasync def main():\n await fsm.send(StartEvent(task_id=\"123\"))\n await fsm.send(CompleteEvent(result=\"Success!\"))\n print(f\"Final state: {fsm.state}\")\n\nasyncio.run(main())\n```\n\n## \ud83c\udfaf Core Concepts\n\n### States, Events, and Context\n\npygenfsm is built on three core concepts:\n\n| Concept | Purpose | Implementation |\n|---------|---------|----------------|\n| **States** | The finite set of states your system can be in | Python Enum |\n| **Events** | Things that happen to trigger transitions | Dataclasses |\n| **Context** | Data that persists across transitions | Any Python type |\n\n### Handler Types\n\npygenfsm seamlessly supports both sync and async handlers:\n\n```python\n# Sync handler - for simple state transitions\n@fsm.on(State.IDLE, SimpleEvent)\ndef sync_handler(fsm, event) -> State:\n # Fast, synchronous logic\n return State.NEXT\n\n# Async handler - for I/O operations\n@fsm.on(State.LOADING, DataEvent)\nasync def async_handler(fsm, event) -> State:\n # Async I/O, network calls, etc.\n data = await fetch_data(event.url)\n fsm.context.data = data\n return State.READY\n```\n\n## \ud83d\udcda Examples\n\n### Traffic Light System\n\n```python\nfrom enum import Enum, auto\nfrom dataclasses import dataclass\nfrom pygenfsm import FSM\n\nclass Color(Enum):\n RED = auto()\n YELLOW = auto()\n GREEN = auto()\n\n@dataclass\nclass TimerEvent:\n \"\"\"Timer expired event\"\"\"\n pass\n\n@dataclass\nclass EmergencyEvent:\n \"\"\"Emergency button pressed\"\"\"\n pass\n\n# Create FSM\ntraffic_light = FSM[Color, TimerEvent | EmergencyEvent, None](\n state=Color.RED,\n context=None,\n)\n\n@traffic_light.on(Color.RED, TimerEvent)\ndef red_to_green(fsm, event) -> Color:\n print(\"\ud83d\udd34 \u2192 \ud83d\udfe2\")\n return Color.GREEN\n\n@traffic_light.on(Color.GREEN, TimerEvent)\ndef green_to_yellow(fsm, event) -> Color:\n print(\"\ud83d\udfe2 \u2192 \ud83d\udfe1\")\n return Color.YELLOW\n\n@traffic_light.on(Color.YELLOW, TimerEvent)\ndef yellow_to_red(fsm, event) -> Color:\n print(\"\ud83d\udfe1 \u2192 \ud83d\udd34\")\n return Color.RED\n\n# Emergency overrides from any state\nfor color in Color:\n @traffic_light.on(color, EmergencyEvent)\n def emergency(fsm, event) -> Color:\n print(\"\ud83d\udea8 EMERGENCY \u2192 RED\")\n return Color.RED\n```\n\n### Connection Manager with Retry Logic\n\n```python\nimport asyncio\nfrom dataclasses import dataclass, field\nfrom enum import Enum, auto\nfrom pygenfsm import FSM\n\nclass ConnState(Enum):\n DISCONNECTED = auto()\n CONNECTING = auto()\n CONNECTED = auto()\n ERROR = auto()\n\n@dataclass\nclass ConnectEvent:\n host: str\n port: int\n\n@dataclass\nclass ConnectionContext:\n retries: int = 0\n max_retries: int = 3\n last_error: str = \"\"\n\nfsm = FSM[ConnState, ConnectEvent, ConnectionContext](\n state=ConnState.DISCONNECTED,\n context=ConnectionContext(),\n)\n\n@fsm.on(ConnState.DISCONNECTED, ConnectEvent)\nasync def start_connection(fsm, event: ConnectEvent) -> ConnState:\n print(f\"\ud83d\udd0c Connecting to {event.host}:{event.port}\")\n return ConnState.CONNECTING\n\n@fsm.on(ConnState.CONNECTING, ConnectEvent)\nasync def attempt_connect(fsm, event: ConnectEvent) -> ConnState:\n try:\n # Simulate connection attempt\n await asyncio.sleep(1)\n if fsm.context.retries < 2: # Simulate failures\n raise ConnectionError(\"Network timeout\")\n \n print(\"\u2705 Connected!\")\n fsm.context.retries = 0\n return ConnState.CONNECTED\n \n except ConnectionError as e:\n fsm.context.retries += 1\n fsm.context.last_error = str(e)\n \n if fsm.context.retries >= fsm.context.max_retries:\n print(f\"\u274c Max retries reached: {e}\")\n return ConnState.ERROR\n \n print(f\"\ud83d\udd04 Retry {fsm.context.retries}/{fsm.context.max_retries}\")\n return ConnState.CONNECTING\n```\n\n## \ud83c\udfd7\ufe0f Advanced Patterns\n\n### Late Context Injection with FSMBuilder\n\nPerfect for dependency injection and testing:\n\n```python\nfrom pygenfsm import FSMBuilder\n\n# Define builder without context\nbuilder = FSMBuilder[State, Event, AppContext](\n initial_state=State.INIT\n)\n\n@builder.on(State.INIT, StartEvent)\nasync def initialize(fsm, event) -> State:\n # Access context that will be injected later\n await fsm.context.database.connect()\n return State.READY\n\n# Later, when dependencies are ready...\ndatabase = Database(connection_string)\nlogger = Logger(level=\"INFO\")\n\n# Build FSM with context\nfsm = builder.build(AppContext(\n database=database,\n logger=logger,\n))\n```\n\n### Cloning for Testing Scenarios\n\nTest different paths without affecting the original:\n\n```python\n# Create base FSM\noriginal_fsm = FSM[State, Event, Context](\n state=State.INITIAL,\n context=Context(data=[]),\n)\n\n# Clone for testing\ntest_scenario_1 = original_fsm.clone()\ntest_scenario_2 = original_fsm.clone()\n\n# Run different scenarios\nawait test_scenario_1.send(SuccessEvent())\nawait test_scenario_2.send(FailureEvent())\n\n# Original remains unchanged\nassert original_fsm.state == State.INITIAL\n```\n\n## \ud83d\udd0c API Reference\n\n### Core Classes\n\n#### `FSM[S, E, C]`\n\nThe main FSM class with generic parameters:\n- `S`: State enum type\n- `E`: Event type (can be a Union)\n- `C`: Context type\n\n**Methods:**\n- `on(state: S, event_type: type[E])`: Decorator to register handlers\n- `async send(event: E) -> S`: Send event and transition state\n- `send_sync(event: E) -> S`: Synchronous send (only for sync handlers)\n- `clone() -> FSM[S, E, C]`: Create independent copy\n- `replace_context(context: C) -> None`: Replace context\n\n#### `FSMBuilder[S, E, C]`\n\nBuilder for late context injection:\n- `on(state: S, event_type: type[E])`: Register handlers\n- `build(context: C) -> FSM[S, E, C]`: Create FSM with context\n\n### Best Practices\n\n1. **Use sync handlers for:**\n - Simple state transitions\n - Pure computations\n - Context updates\n\n2. **Use async handlers for:**\n - Network I/O\n - Database operations\n - File system access\n - Long computations\n\n3. **Event Design:**\n - Make events immutable (use frozen dataclasses)\n - Include all necessary data in events\n - Use Union types for multiple events per state\n\n4. **Context Design:**\n - Keep context focused and minimal\n - Use dataclasses for structure\n - Avoid circular references\n\n## \ud83e\udd1d Contributing\n\nWe love contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n```bash\n# Setup development environment\ngit clone https://github.com/serialx/pygenfsm\ncd pygenfsm\nuv sync\n\n# Run tests\nuv run pytest\n\n# Run linting\nuv run ruff check .\nuv run pyright .\n```\n\n## \ud83d\udcca Comparison with transitions\n\n### Feature Comparison\n\n| Feature | pygenfsm | transitions |\n|---------|----------|-------------|\n| **Event Data** | \u2705 First-class with dataclasses | \u274c Limited (callbacks, conditions) |\n| **Async Support** | \u2705 Native async/await | \u274c No built-in support |\n| **Type Safety** | \u2705 Full generics | \u26a0\ufe0f Runtime checks only |\n| **State Definition** | \u2705 Enums (type-safe) | \u26a0\ufe0f Strings/objects |\n| **Handler Registration** | \u2705 Decorators | \u274c Configuration dicts |\n| **Context/Model** | \u2705 Explicit, typed | \u26a0\ufe0f Implicit on model |\n| **Dependencies** | \u2705 Zero | \u274c Multiple (six, etc.) |\n| **Visualization** | \u274c Not built-in | \u2705 GraphViz support |\n| **Hierarchical States** | \u274c No | \u2705 Yes (HSM) |\n| **Parallel States** | \u274c No | \u2705 Yes |\n| **State History** | \u274c No | \u2705 Yes |\n| **Guards/Conditions** | \u26a0\ufe0f In handler logic | \u2705 Built-in |\n| **Callbacks** | \u26a0\ufe0f In handlers | \u2705 before/after/prepare |\n| **Size** | ~300 LOC | ~3000 LOC |\n\n### When to Use Each\n\n**Use pygenfsm when you need:**\n- \ud83d\udd12 Strong type safety with IDE support\n- \ud83d\udd04 Native async/await support\n- \ud83d\udce6 Zero dependencies\n- \ud83c\udfaf Event-driven architecture with rich data\n- \ud83d\ude80 Modern Python patterns (3.11+)\n- \ud83e\uddea Easy testing with full typing\n\n**Use transitions when you need:**\n- \ud83d\udcca State diagram visualization\n- \ud83c\udf84 Hierarchical states (HSM)\n- \u26a1 Parallel state machines\n- \ud83d\udcdc State history tracking\n- \ud83d\udd04 Complex transition guards/conditions\n- \ud83c\udfd7\ufe0f Legacy Python support\n\n## \ud83d\udd17 Links\n\n- **GitHub**: [github.com/serialx/pygenfsm](https://github.com/serialx/pygenfsm)\n- **PyPI**: [pypi.org/project/pygenfsm](https://pypi.org/project/pygenfsm)\n- **Documentation**: [Full API Docs](https://github.com/serialx/pygenfsm/wiki)\n- **Issues**: [Report bugs or request features](https://github.com/serialx/pygenfsm/issues)\n\n## \ud83d\udcdc License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n---\n\n<div align=\"center\">\nMade with \u2764\ufe0f by developers who love clean state machines\n</div>",
"bugtrack_url": null,
"license": null,
"summary": "A minimal, clean, typed and async-native FSM (Finite State Machine) implementation inspired by Erlang's gen_fsm",
"version": "1.0.1",
"project_urls": {
"Homepage": "https://github.com/serialx/pygenfsm",
"Issues": "https://github.com/serialx/pygenfsm/issues",
"Repository": "https://github.com/serialx/pygenfsm"
},
"split_keywords": [
"async",
" asyncio",
" automation",
" erlang",
" event-driven",
" finite-state-machine",
" fsm",
" gen_fsm",
" state-machine",
" state-management",
" typed",
" workflow"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "50bdeca879a1a8e4cddbef3118a307297ff65097bbafddf6220b89c24be4fca6",
"md5": "32d879e8eee262a9a6d2eb9b9a00a676",
"sha256": "75703efc82bffb60e386ac44901d782c5ce49edcb789bdd2922fcc8831ae617f"
},
"downloads": -1,
"filename": "pygenfsm-1.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "32d879e8eee262a9a6d2eb9b9a00a676",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 8932,
"upload_time": "2025-08-14T07:43:27",
"upload_time_iso_8601": "2025-08-14T07:43:27.489969Z",
"url": "https://files.pythonhosted.org/packages/50/bd/eca879a1a8e4cddbef3118a307297ff65097bbafddf6220b89c24be4fca6/pygenfsm-1.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "4f749b753640b212553d35d1b8e553903243edd794ba9b31403226f282b20293",
"md5": "f314a030a9e99a793b7cbfca2eab93bb",
"sha256": "cee9facd628fcb382f9c8af847d1c15ac9a1164ee293ec1ca7532bd8d1133e93"
},
"downloads": -1,
"filename": "pygenfsm-1.0.1.tar.gz",
"has_sig": false,
"md5_digest": "f314a030a9e99a793b7cbfca2eab93bb",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 41779,
"upload_time": "2025-08-14T07:43:28",
"upload_time_iso_8601": "2025-08-14T07:43:28.586554Z",
"url": "https://files.pythonhosted.org/packages/4f/74/9b753640b212553d35d1b8e553903243edd794ba9b31403226f282b20293/pygenfsm-1.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-14 07:43:28",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "serialx",
"github_project": "pygenfsm",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pygenfsm"
}