Skip to main content

A flexible, async-friendly state machine implementation for Python with thread-safe operations and decorator-based transitions.

Project description

aiostate

A flexible, async-friendly state machine implementation for Python with thread-safe operations and decorator-based transitions.

Features

  • Thread-safe: Uses asyncio locks for concurrent access
  • Decorator-based: Clean, intuitive API for defining transitions
  • Guard conditions: Conditional transitions with custom logic
  • Entry/Exit handlers: Execute code when entering or leaving states
  • Wildcard transitions: Define transitions from any state
  • Type-safe: Full typing support with generics
  • Flexible: Works with any hashable type for states and events

Installation

pip install aiostate

Or using Poetry:

poetry add aiostate

Quick Start

import asyncio
from aiostate import AsyncStateMachine

# Create a state machine for a simple traffic light
fsm = AsyncStateMachine('red')


@fsm.transition('red', 'timer', 'green')
async def red_to_green():
    print("Light turns green")


@fsm.transition('green', 'timer', 'yellow')
async def green_to_yellow():
    print("Light turns yellow")


@fsm.transition('yellow', 'timer', 'red')
async def yellow_to_red():
    print("Light turns red")


async def main():
    print(f"Current state: {fsm.state}")  # red

    await fsm.trigger('timer')  # red -> green
    print(f"Current state: {fsm.state}")  # green

    await fsm.trigger('timer')  # green -> yellow
    print(f"Current state: {fsm.state}")  # yellow

    await fsm.trigger('timer')  # yellow -> red
    print(f"Current state: {fsm.state}")  # red


asyncio.run(main())

Advanced Usage

Entry and Exit Handlers

fsm = AsyncStateMachine('idle')


@fsm.on_enter('running')
async def on_enter_running():
    print("System is now running")
    # Initialize resources, start monitoring, etc.


@fsm.on_exit('running')
async def on_exit_running():
    print("System is stopping")
    # Cleanup resources, save state, etc.


@fsm.transition('idle', 'start', 'running')
async def start_system():
    print("Starting system...")
    # Perform startup logic

Guard Conditions

fsm = AsyncStateMachine('locked')


def has_valid_key(key):
    return key == "secret123"


@fsm.transition('locked', 'unlock', 'unlocked', guard=has_valid_key)
async def unlock_door(key):
    print(f"Door unlocked with key: {key}")


# Usage
success = await fsm.trigger('unlock', key="wrong_key")
print(success)  # False - guard condition failed

success = await fsm.trigger('unlock', key="secret123")
print(success)  # True - transition successful

Multiple Source States

fsm = AsyncStateMachine('idle')


# Transition from either 'running' or 'paused' to 'stopped'
@fsm.transition({'running', 'paused'}, 'stop', 'stopped')
async def stop_process():
    print("Process stopped")


# Wildcard transition - from any state to 'error'
@fsm.transition('*', 'error', 'error')
async def handle_error():
    print("Error occurred, transitioning to error state")

Async Guard Conditions

async def async_guard(user_id):
    # Simulate async database check
    await asyncio.sleep(0.1)
    return user_id in ['admin', 'user123']


@fsm.transition('pending', 'approve', 'approved', guard=async_guard)
async def approve_request(user_id):
    print(f"Request approved by {user_id}")

API Reference

AsyncStateMachine

Constructor

AsyncStateMachine(initial_state: T)

Creates a new state machine with the specified initial state.

Properties

  • state: T - Current state (read-only)
  • all_states: Set[T] - All registered states (read-only)

Methods

  • is_state(state: T) -> bool - Check if currently in specified state
  • can_trigger(evt: T) -> bool - Check if event can be triggered
  • add_state(state: T) -> None - Add state without transitions
  • get_valid_events() -> Set[T] - Get valid events for current state
  • get_transition_graph() -> Dict[T, Dict[T, T]] - Get complete transition graph
  • trigger(evt: T, **kwargs) -> bool - Trigger an event

Decorators

  • @transition(from_states, evt, to_state, guard=None) - Define a transition
  • @on_enter(state) - Register enter handler
  • @on_exit(state) - Register exit handler

Error Handling

The library raises StateTransitionError when:

  • No transition is defined for the current state and event
  • A guard condition fails during execution
  • An exit handler fails
  • A transition action fails
from aiostate import StateTransitionError

try:
    await fsm.trigger('invalid_event')
except StateTransitionError as e:
    print(f"Transition failed: {e}")

License

This project is licensed under the MIT License.

Development

Setup

poetry install

Running Tests

poetry run pytest

Building

poetry build

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiostate-0.1.0.tar.gz (6.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiostate-0.1.0-py3-none-any.whl (8.4 kB view details)

Uploaded Python 3

File details

Details for the file aiostate-0.1.0.tar.gz.

File metadata

  • Download URL: aiostate-0.1.0.tar.gz
  • Upload date:
  • Size: 6.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.11.13 Linux/6.11.0-1018-azure

File hashes

Hashes for aiostate-0.1.0.tar.gz
Algorithm Hash digest
SHA256 67efa35fef7d162704a32599b7a4f42f4156727d73b85c96d64be21ac66e21e6
MD5 29fc51036dd7f5a2787153a084f584cd
BLAKE2b-256 af32bd9917f1b5a741942179de60e880d129765d68271e0502130b51fdb08254

See more details on using hashes here.

File details

Details for the file aiostate-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: aiostate-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.11.13 Linux/6.11.0-1018-azure

File hashes

Hashes for aiostate-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d8f9295610d84ed42225a0ab92535eda1d28f259a383b4e7af43800139e019f3
MD5 9b5f22565ae042b09398d07a4fed5fe5
BLAKE2b-256 e76278fdd414e0a839b30131b83ffcde4e61c9f0c89f2a0e57e464dfd724af32

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page