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 statecan_trigger(evt: T) -> bool- Check if event can be triggeredadd_state(state: T) -> None- Add state without transitionsget_valid_events() -> Set[T]- Get valid events for current stateget_transition_graph() -> Dict[T, Dict[T, T]]- Get complete transition graphtrigger(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
Metadata
Release files for aiostate 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aiostate-0.1.0.tar.gz | 6.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aiostate-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 14.7 kB
Release files / aiostate-0.1.0.tar.gz
| Download URL | aiostate-0.1.0.tar.gz |
|---|---|
| Size | 6.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
67efa35fef7d162704a32599b7a4f42f4156727d73b85c96d64be21ac66e21e6
|
|
BLAKE2b-256 checksum How to use checksums |
af32bd9917f1b5a741942179de60e880d129765d68271e0502130b51fdb08254
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.11.13 Linux/6.11.0-1018-azure
|
Release files / aiostate-0.1.0-py3-none-any.whl
| Download URL | aiostate-0.1.0-py3-none-any.whl |
|---|---|
| Size | 8.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d8f9295610d84ed42225a0ab92535eda1d28f259a383b4e7af43800139e019f3
|
|
BLAKE2b-256 checksum How to use checksums |
e76278fdd414e0a839b30131b83ffcde4e61c9f0c89f2a0e57e464dfd724af32
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.11.13 Linux/6.11.0-1018-azure
|