Skip to main content

A tiny generic state machine with implicit states and C#-style events.

Project description

micro-statemachine logo

micro-statemachine

A tiny generic state machine with implicit states.

  • No boilerplate — states are never declared. Calling sm.to_<state>() moves into that state, creating it on the fly.
  • Everything allowed by default — every transition is permitted unless you explicitly forbid it.
  • Event hooks — subscribe to enter, exit, and remain per state with the += / -= operator, just like C# events.
  • Zero dependencies, fully type-hinted, single small module.

Install

pip install micro-statemachine

Quick start

import logging
from micro_statemachine import u_states

logger = logging.getLogger(__name__)

sm = u_states["connection"]

# Subscribe handlers with +=
sm.happy_enter += lambda: logger.info("entered happy state")
sm.failed_enter += lambda: logger.warning("Failed!")
# `remain` fires when you transition into the state you're already in.
sm.failed_remain += lambda: logger.warning("still in failed")

sm.to_failed()   # fires failed_enter
sm.to_failed()   # already failed -> fires failed_remain
sm.to_happy()    # fires failed_exit, then happy_enter

Restricting transitions

By default any transition is allowed. Forbid specific ones with set_transitions, using the <source>_to_<target> naming convention:

sm.set_transitions(happy_to_failed=True, failed_to_happy=False)

sm.to_failed()   # ok
sm.to_happy()    # raises TransitionError

A forbidden transition raises TransitionError.

Current state

Read the active state with sm.state or the sm.current_state alias (both are None until the first transition):

sm.to_happy()
sm.current_state  # "happy"

Assigning to current_state sets the state directly — no transition is run, no enter/exit/remain events fire, and blocking rules are ignored. Handy for restoring a machine to a known state:

sm.current_state = "happy"   # jump straight to happy, silently
sm.current_state = None      # reset to the initial state

none and any in transition rules

Either side of a <source>_to_<target> rule may also be one of two reserved words:

  • none — the initial (pre-transition) state, so you can constrain the very first move.
  • any — a wildcard matching every state (including the initial one).

Explicit rules always override wildcard ones, which lets you flip the default from "everything allowed" to an allowlist:

sm.set_transitions(
    state_1_to_state_2=True,   # plain explicit rule
    none_to_state_1=True,      # the first move may go to state_1...
    none_to_any=False,         # ...and nowhere else
)

sm.to_state_2()   # raises TransitionError (none -> any is forbidden)
sm.to_state_1()   # ok — the explicit rule wins over the wildcard

When several rules could apply, the most specific wins: <source>_to_<target> > <source>_to_any > any_to_<target> > any_to_any.

Events

Each state exposes three events, resolved lazily on first access:

Attribute Fires when…
sm.<state>_enter a transition into <state> from a different state
sm.<state>_exit a transition out of <state>
sm.<state>_remain to_<state>() is called while already in <state>

Handlers are plain zero-argument callables. Add with +=, remove with -=:

def on_enter():
    print("hi")

sm.happy_enter += on_enter
sm.happy_enter -= on_enter

Registry

u_states is a lazy registry — indexing it creates the machine on first access, so different modules can share one machine by name:

from micro_statemachine import u_states

u_states["connection"]  # same StateMachine everywhere

You can also construct machines directly with StateMachine(name).

Diagram

sm.generate_chart() returns a Mermaid state diagram as a fenced markdown block — paste it into a README or GitHub issue and it renders, with the current state highlighted and transition rules drawn as labelled edges (none becomes the initial marker, any a wildcard node):

sm.happy_enter += lambda: None
sm.failed_enter += lambda: None
sm.set_transitions(happy_to_failed=False)
sm.to_happy()
print(sm.generate_chart())
```mermaid
stateDiagram-v2
    [*] --> happy
    failed
    happy
    happy --> failed: ✗ forbidden
    classDef current fill:#ffd54f,stroke:#333,font-weight:bold;
    class happy current
```

States are inferred from subscribed handlers, transition rules, and the current state (they're never declared explicitly).

Naming rules

State names may contain _ but must not be empty or contain the reserved words to, enter, exit, or remain (they'd collide with the transition and event syntax). none and any are reserved for transition rules, so they can't be a state name on their own — but they're fine as parts of one (any_state, none_selected). Invalid names raise ValueError.

License

MIT

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

micro_statemachine-0.1.0.tar.gz (3.5 MB view details)

Uploaded Source

Built Distribution

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

micro_statemachine-0.1.0-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: micro_statemachine-0.1.0.tar.gz
  • Upload date:
  • Size: 3.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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 micro_statemachine-0.1.0.tar.gz
Algorithm Hash digest
SHA256 edac18564ea7e170aee9f62350b7ff41fa997f50334fdf50f1f6c60c107fc258
MD5 8ce8c997ded0e4675a79cbccc6f51d12
BLAKE2b-256 e1b6f025044b3fabfc97d41c22f4f1a51af97aa4781f35089d490142c781c1e5

See more details on using hashes here.

File details

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

File metadata

  • Download URL: micro_statemachine-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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 micro_statemachine-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d7e0062ca73843e47cdb3b68e5e963a3f37e5330f6c63ca7e40f371b4445d2cb
MD5 dd215e61d05ebe7bddaf7a7510d8f8ea
BLAKE2b-256 682d3dc982e5dfd0c8d4f6029e0f91fea68037403020463b026c948805b4eb22

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