A pure python small Mealy state machine library
Reason this release was yanked:
Deprecated
Project description
SNanoSM
A pure Python, minimalistic, and fully typed library for the implementation of Mealy Finite State Machines.
Part of Nobody Industry's MFFP (Made From First Principles) set of libraries.
Table of Contents
- Overview & Core Concept
- Installation
- Quick Start (Binary Inverter)
- Advanced Usage (Sequence Detector & Fallback Matching)
- API Reference
- Testing & Verification
Overview & Core Concept
A Mealy State Machine is a finite-state machine whose output values are determined both by its current state and its current inputs.
In mealy.py, this is modeled by:
- States: Unique nodes in the machine.
- Transitions: Directed connections between states triggered by specific inputs.
- Action Functions (Outputs): Arbitrary callbacks associated with transitions that receive a mutable user-defined context object.
[!NOTE] By passing a mutable context down to transition actions, you can build rich state-dependent behavior while keeping the machine's state logic simple and decoupled.
Installation
Since the library uses pyproject.toml with the Hatchling build backend, you can install it locally in editable mode or build it using standard tools:
# Install in editable mode
pip install -e .
# Or build the package
python -m build
Quick Start (Binary Inverter)
Here is a simple example demonstrating how to invert a binary string ("0" becomes "1", "1" becomes "0") using inverter.py.
from typing import Tuple, TypedDict
from snanosm.mealy import Machine
# Define a context to hold our state machine's output and metadata
class Context(TypedDict):
result: str
n_chars: int
def add_to_result(context: Context, c: str) -> None:
context["result"] += c
context["n_chars"] += 1
def inverter(input_string: str) -> Tuple[str, int]:
# Initialize the mutable context
context: Context = {
"result": "",
"n_chars": 0
}
# Create the machine with the context
m = Machine(context)
# Add a start state "S"
m.add_state("S", is_start_state=True)
# Define transitions: when in state "S" and input is "0", execute action and stay in "S"
m.add_transition("0", "S", "S", lambda ctx: add_to_result(ctx, "1"))
m.add_transition("1", "S", "S", lambda ctx: add_to_result(ctx, "0"))
# Process inputs sequentially
for c in input_string:
m.process_input(c)
return context["result"], context["n_chars"]
if __name__ == '__main__':
res, count = inverter("000011110010")
print(f"Result: {res}, Characters processed: {count}")
# Output: Result: 111100001101, Characters processed: 12
Advanced Usage (Sequence Detector & Fallback Matching)
The sequence detector in detector.py searches for the substring "AB" within a stream of characters. It illustrates the use of TransitionInputEnum to define catch-all transitions when no specific input matches.
from typing import TypedDict
from snanosm.mealy import Machine, TransitionInputEnum
class Context(TypedDict):
count: int
position: int
matches: int
def dinc(context: Context):
context["count"] += 1
def dset(context: Context):
context["position"] = context["count"]
context["count"] += 1
def dprn(context: Context):
context["count"] += 1
print(f"SUBSTRING FOUND AT POSITION: {context['position']}")
context["matches"] += 1
def detector(input_string: str) -> int:
initial_context: Context = {
"count": 0,
"position": 0,
"matches": 0,
}
m = Machine(initial_context)
m.add_state("Q0", is_start_state=True)
m.add_state("Q1")
# Q0 -> Q1 on 'A', saving the start position
m.add_transition("A", "Q0", "Q1", lambda context: dset(context))
# Catch-all transition: Q0 -> Q0 for any input other than 'A'
m.add_transition(TransitionInputEnum.MATCH_REST, "Q0", "Q0", lambda context: dinc(context))
# Q1 -> Q0 on 'B', printing match information
m.add_transition("B", "Q1", "Q0", lambda context: dprn(context))
# Catch-all transition: Q1 -> Q0 for any input other than 'B'
m.add_transition(TransitionInputEnum.MATCH_REST, "Q1", "Q0", lambda context: dinc(context))
for c in input_string:
m.process_input(c)
return initial_context["matches"]
API Reference
Core Abstractions
| Class/Type | Description |
|---|---|
InputProtocol |
A typing protocol requiring __eq__ and __hash__. Any hashable, equatable Python object can serve as machine input. |
TransitionInputEnum |
Enum containing special transition inputs (e.g., MATCH_REST). |
State |
Represents a state node in the state machine. |
Transition |
Represents an edge between states triggered by a transition input. |
Machine |
The core finite state machine runner. |
API Details
InputProtocol
class InputProtocol(Protocol):
def __eq__(self, __o: Self) -> bool: ...
def __hash__(self) -> int: ...
Any custom object used as an input to Machine.process_input must implement this protocol (or be natively hashable and equatable, e.g. strings, integers, frozen dataclasses).
TransitionInputEnum
MATCH_REST: Activates if no matching transition input is found for the current state. Useful for defining default fallback transitions.
State
get_name() -> str: Returns the state's name.__str__() -> str: Returns[State <state_name>].
Transition
execute_output(context): Executes the output callback if it was supplied.get_destination_name_hash() -> int: Returns the hash of the destination state name.__str__() -> str: Returns[Transition (<origin>, <destination>, <input>)].
Machine
__init__(initial_context: object = None): Initializes the machine. Sets up the configuration using an optional initial context. If not provided, an empty dict{}is instantiated.add_state(state_name: str, is_start_state: bool = False, is_end_state: bool = False) -> None: Registers a new state node in the machine.[!WARNING] State names must not start with
#(reserved for internal configurations). A machine cannot have multiple start states.add_transition(transition_input: TransitionInput, origin_name: str, destination_name: str, output_function: Optional[Callable[[Optional[object]], None]]) -> None: Registers a transition edge between two existing states.transition_input: An input conforming toInputProtocolorTransitionInputEnum.output_function: A callable accepting context, run upon transitioning.
process_input(i: Input) -> None: Processes a single input token. It evaluates transitions registered under the current state.- If a transition matching
iis registered, it will be executed. - If no matching transition is found but a
MATCH_RESTtransition is registered, that fallback is executed. - If no valid transition is found, it raises a
ValueError.
- If a transition matching
reset() -> None: Resets the state machine's active state back to the start state, and re-assigns the context back toinitial_context.get_current_state() -> Optional[State]: Returns the currentStateobject, orNoneif the machine has not started or processed any inputs.is_in_final_state() -> bool: ReturnsTrueif the machine's current state is registered as an end state.__str__() -> str: Returns a structured string layout of the machine structure, lists of states, and transitions.
Testing & Verification
Unit tests are located in test_mealy.py. To run the test suite, navigate to the project directory and execute:
PYTHONPATH=src python -m unittest discover -s tests
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 snanosm-1.0.0.tar.gz.
File metadata
- Download URL: snanosm-1.0.0.tar.gz
- Upload date:
- Size: 7.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8355f06b80db71a2d8d733e2694ede80c6913498be8f73d8582127e37e2096b1
|
|
| MD5 |
f4c358f44a585acead7ab050b94146e9
|
|
| BLAKE2b-256 |
e6138b1966ab6c3af5b9912ce7d6c3dd53b811d1785efb6b2e59dbddab9fb5ae
|
File details
Details for the file snanosm-1.0.0-py3-none-any.whl.
File metadata
- Download URL: snanosm-1.0.0-py3-none-any.whl
- Upload date:
- Size: 6.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ded75f6ea76d3b97257725b0ecf8f3b4413d5b08c9c3d6672bde89c2ea51f87d
|
|
| MD5 |
70c56092b32be047f3674b67c28e2a3c
|
|
| BLAKE2b-256 |
ccb38b1cdd79322b1967256c179d7b28faadff67e6a48cd80054e4cc543f7f3f
|