Hierarchical State Machine implementation for Python with asyncio support
Project description
stateforward.hsm
A high-performance, hierarchical state machine (HSM) implementation for Python with asyncio support. This library provides a declarative, async-first approach to building complex state machines with efficient O(1) runtime performance.
Features
- Hierarchical States: Support for nested state machines with proper scoping
- Async/Await: Full asyncio support for actions, guards, effects, and activities
- Precomputed Lookups: O(1) performance through precomputed transition and element maps
- Event-Driven: Deterministic event processing with guards and effects
- Timers: Built-in support for one-shot and recurring timers
- Activities: Long-running async operations that auto-cancel on state exit
- Error Handling: Automatic error event dispatching for robust error recovery
- Type Safety: Strong typing with comprehensive type hints
Installation
pip install stateforward.hsm
Quick Start
import asyncio
import hsm
class Counter(hsm.Instance):
def __init__(self):
super().__init__()
self.value = 0
@staticmethod
async def increment(ctx, self, event):
self.value += 1
print(f"Count: {self.value}")
model = hsm.define('Counter',
hsm.initial(hsm.target('counting')),
hsm.state('counting',
hsm.transition(
hsm.on('inc'),
hsm.target('.'), # Self-transition
hsm.effect(increment)
)
)
)
async def main():
instance = Counter()
ctx = hsm.Context()
# Start the state machine
sm = await hsm.start(ctx, instance, Counter.model)
print(f"Initial state: {sm.state()}")
# Dispatch events
await sm.dispatch(hsm.Event('inc'))
await sm.dispatch(hsm.Event('inc'))
print(f"Final count: {instance.value}")
# Clean shutdown
await hsm.stop(sm)
if __name__ == "__main__":
asyncio.run(main())
Core Concepts
Declarative Definition
State machines are defined using a declarative API that precomputes all lookups for optimal runtime performance:
model = hsm.define(
"MyMachine",
hsm.initial(hsm.target("ready")),
hsm.state("ready",
hsm.transition(hsm.on("start"), hsm.target("running"))
),
hsm.state("running",
hsm.transition(hsm.on("stop"), hsm.target("ready"))
)
)
States and Hierarchy
hsm.state(name, ...children): Regular states that can contain nested stateshsm.initial(hsm.target(path)): Defines the default entry pointhsm.final(name): Terminal states for completion handlinghsm.choice(name, ...transitions): Conditional branching logic
Transitions
Transitions define how the machine responds to events:
hsm.transition(
hsm.on("event_name"), # Event trigger
hsm.source("current_state"), # Optional explicit source
hsm.target("next_state"), # Destination state
hsm.guard(async_guard_func), # Optional condition
hsm.effect(async_effect_func) # Optional side effect
)
Actions and Behaviors
All behavioral functions are async and receive (ctx, instance, event):
hsm.entry(action): Executed when entering a statehsm.exit(action): Executed when exiting a statehsm.effect(action): Executed during transitionshsm.guard(guard_func): Returns boolean to allow/block transitionshsm.activity(activity_func): Long-running operations that auto-cancel
Timers
Built-in timer support for time-based transitions:
from datetime import timedelta
@staticmethod
async def one_second(ctx, self, event):
return timedelta(seconds=1)
hsm.transition(
hsm.after(one_second), # One-shot timer
hsm.target("next_state")
)
hsm.transition(
hsm.every(one_second), # Recurring timer
hsm.target(".")
)
Error Handling
Exceptions in actions automatically dispatch hsm_error events:
class RobustMachine(hsm.Instance):
@staticmethod
async def risky_action(ctx, self, event):
if random.random() < 0.1:
raise ValueError("Something went wrong!")
@staticmethod
async def handle_error(ctx, self, event):
print(f"Error occurred: {event.data}")
# Recovery logic here
model = hsm.define('RobustMachine',
hsm.state('working',
hsm.effect(risky_action),
hsm.transition(hsm.on('hsm_error'), hsm.target('error_state'))
),
hsm.state('error_state',
hsm.entry(handle_error)
)
)
Advanced Usage
Class-Based Pattern
The recommended pattern is to define your state machine as a class:
class TrafficLight(hsm.Instance):
def __init__(self):
super().__init__()
self.cycles = 0
@staticmethod
async def change_to_green(ctx, self, event):
print("🟢 Green light")
self.cycles += 1
model = hsm.define('TrafficLight',
hsm.initial(hsm.target('red')),
hsm.state('red',
hsm.transition(hsm.on('timer'), hsm.target('green'))
),
hsm.state('green',
hsm.entry(change_to_green),
hsm.transition(hsm.on('timer'), hsm.target('yellow'))
),
hsm.state('yellow',
hsm.transition(hsm.on('timer'), hsm.target('red'))
)
)
Path Resolution
State paths are resolved relative to the transition's source state:
- Child:
'child_state' - Parent:
'..' - Sibling:
'../sibling_state' - Absolute:
'/MachineName/state' - Self:
'.'
Runtime Lifecycle
# Create instance and context
instance = MyMachine()
ctx = hsm.Context()
# Start the machine
sm = await hsm.start(ctx, instance, MyMachine.model)
# Check current state
current_state = sm.state()
# Dispatch events
await sm.dispatch(hsm.Event('my_event', data={'key': 'value'}))
# Stop the machine
await hsm.stop(sm)
API Reference
Core Functions
hsm.define(name, ...elements): Create a state machine modelhsm.start(ctx, instance, model): Start a state machine instancehsm.stop(instance): Stop and cleanup a state machine
State Elements
hsm.state(name, ...children): Define a statehsm.initial(target, ...effects): Define initial statehsm.final(name): Define final statehsm.choice(name, ...transitions): Define choice pseudostate
Transitions
hsm.transition(...conditions): Define a transitionhsm.on(event_name): Event conditionhsm.after(duration_func): Timer condition (one-shot)hsm.every(duration_func): Timer condition (recurring)hsm.target(path): Target statehsm.guard(func): Guard conditionhsm.effect(func): Transition effect
Actions
hsm.entry(func): Entry actionhsm.exit(func): Exit actionhsm.activity(func): Long-running activity
Runtime
hsm.Instance: Base class for state machine instanceshsm.Context: Execution context with cancellation supporthsm.Event(name, data=None): Event object
Performance
This implementation uses precomputed lookup tables for O(1) transition resolution, making it suitable for high-performance applications. All behavioral functions are async-first, enabling efficient concurrent execution.
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please see the main HSM repository for contribution guidelines.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stateforward_hsm-0.1.0.tar.gz.
File metadata
- Download URL: stateforward_hsm-0.1.0.tar.gz
- Upload date:
- Size: 575.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.4.29
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2c6322948fcf4a5e1cdd717d7f6e57fcaea709d113b8082becd5d7d471b15da
|
|
| MD5 |
3020bd23f63e94bf182bbf8e5ee242f7
|
|
| BLAKE2b-256 |
c009f426dae6259cb36d1ad69b89df5dc5953c6bf3a60ecef38cb5ca37ce60c4
|
File details
Details for the file stateforward_hsm-0.1.0-py3-none-any.whl.
File metadata
- Download URL: stateforward_hsm-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.4.29
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7489db945a2e98b80893c1ef87465cc0079f323e62fe0d68e8169ba63c10f13
|
|
| MD5 |
e1b51036a084c88df0cf8be6f1db6804
|
|
| BLAKE2b-256 |
60a369a01a135bdafbf51778145e16d28cc315a15db269e6733aea95435c8a64
|