Skip to main content

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 states
  • hsm.initial(hsm.target(path)): Defines the default entry point
  • hsm.final(name): Terminal states for completion handling
  • hsm.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 state
  • hsm.exit(action): Executed when exiting a state
  • hsm.effect(action): Executed during transitions
  • hsm.guard(guard_func): Returns boolean to allow/block transitions
  • hsm.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 model
  • hsm.start(ctx, instance, model): Start a state machine instance
  • hsm.stop(instance): Stop and cleanup a state machine

State Elements

  • hsm.state(name, ...children): Define a state
  • hsm.initial(target, ...effects): Define initial state
  • hsm.final(name): Define final state
  • hsm.choice(name, ...transitions): Define choice pseudostate

Transitions

  • hsm.transition(...conditions): Define a transition
  • hsm.on(event_name): Event condition
  • hsm.after(duration_func): Timer condition (one-shot)
  • hsm.every(duration_func): Timer condition (recurring)
  • hsm.target(path): Target state
  • hsm.guard(func): Guard condition
  • hsm.effect(func): Transition effect

Actions

  • hsm.entry(func): Entry action
  • hsm.exit(func): Exit action
  • hsm.activity(func): Long-running activity

Runtime

  • hsm.Instance: Base class for state machine instances
  • hsm.Context: Execution context with cancellation support
  • hsm.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

stateforward_hsm-0.2.0.tar.gz (589.0 kB view details)

Uploaded Source

Built Distribution

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

stateforward_hsm-0.2.0-py3-none-any.whl (20.2 kB view details)

Uploaded Python 3

File details

Details for the file stateforward_hsm-0.2.0.tar.gz.

File metadata

  • Download URL: stateforward_hsm-0.2.0.tar.gz
  • Upload date:
  • Size: 589.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for stateforward_hsm-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9779d958614c0a857ddb298baf4c4ed16fae5ae79096461c5d46b0d149d539f7
MD5 2ef40095f73b2d8e5f9318afc30adb87
BLAKE2b-256 47656c31f141b39ace2d0726ceb6d63f59126c7e8a89d6ac8840592d76d89a4a

See more details on using hashes here.

File details

Details for the file stateforward_hsm-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: stateforward_hsm-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 20.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for stateforward_hsm-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b4f53c7671eb6657dcc25b5bedb5bb8c942c912916e152534e068ec534f248dc
MD5 0953a0c7395b33f9382b269222c46dc3
BLAKE2b-256 c4631e2756c98e7c1bd652a506b39121c48bc3a75b010d2e9e39119ed744b787

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