pygenfsm


Namepygenfsm JSON
Version 1.0.1 PyPI version JSON
download
home_pageNone
SummaryA minimal, clean, typed and async-native FSM (Finite State Machine) implementation inspired by Erlang's gen_fsm
upload_time2025-08-14 07:43:28
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseNone
keywords async asyncio automation erlang event-driven finite-state-machine fsm gen_fsm state-machine state-management typed workflow
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # ๐Ÿ”„ pygenfsm

<div align="center">

[![PyPI version](https://badge.fury.io/py/pygenfsm.svg)](https://badge.fury.io/py/pygenfsm)
[![Python](https://img.shields.io/pypi/pyversions/pygenfsm.svg)](https://pypi.org/project/pygenfsm/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
[![Type checked: pyright](https://img.shields.io/badge/type%20checked-pyright-blue.svg)](https://github.com/microsoft/pyright)
[![Test Coverage](https://img.shields.io/badge/coverage-83%25-green.svg)](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[![PyPI version](https://badge.fury.io/py/pygenfsm.svg)](https://badge.fury.io/py/pygenfsm)\n[![Python](https://img.shields.io/pypi/pyversions/pygenfsm.svg)](https://pypi.org/project/pygenfsm/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)\n[![Type checked: pyright](https://img.shields.io/badge/type%20checked-pyright-blue.svg)](https://github.com/microsoft/pyright)\n[![Test Coverage](https://img.shields.io/badge/coverage-83%25-green.svg)](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"
}
        
Elapsed time: 1.38496s