semantic-state-machine 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.3
2
+ Name: semantic-state-machine
3
+ Version: 0.1.0
4
+ Summary: A lightweight, type-safe Python state machine implementation using modern Python 3.13+ features.
5
+ Keywords: state-machine,finite-state-machine,type-safety,audit
6
+ Author: Richard West
7
+ Author-email: Richard West <dopplereffect.us@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.13
15
+ Project-URL: Homepage, https://github.com/rjdw/semantic-state-machine
16
+ Project-URL: Repository, https://github.com/rjdw/semantic-state-machine
17
+ Project-URL: Issues, https://github.com/rjdw/semantic-state-machine/issues
18
+ Description-Content-Type: text/markdown
19
+
20
+ # State Machine
21
+
22
+ A lightweight, type-safe Python state machine implementation using modern Python 3.13+ features like PEP 695 type parameters.
23
+
24
+ ## Features
25
+
26
+ - **Type Safety**: Leverage Python's type system to ensure consistent states, events, and contexts.
27
+ - **Decorator Support**: Easily define transitions using the `@sm.transition` decorator.
28
+ - **Auditing**: Built-in support for recording transition history via `AuditedStateMachine` and `AuditContext`.
29
+ - **Flexible Context**: Pass a custom context object to transition actions to maintain state outside the machine.
30
+
31
+ ## Installation
32
+
33
+ This project uses `uv` for dependency management. To get started, ensure you have `uv` installed and run:
34
+
35
+ ```bash
36
+ uv sync
37
+ ```
38
+
39
+ You can install the package via pip:
40
+
41
+ ```bash
42
+ pip install semantic-state-machine
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ### Basic State Machine
48
+
49
+ Define your states and events as `Enum`s and create a context class.
50
+
51
+ ```python
52
+ from enum import Enum
53
+ from semantic_state_machine import StateMachine
54
+
55
+ class State(Enum):
56
+ LOCKED = 1
57
+ UNLOCKED = 2
58
+
59
+ class Event(Enum):
60
+ PUSH = 1
61
+ COIN = 2
62
+
63
+ class TurnstileContext:
64
+ def __init__(self):
65
+ self.total_coins = 0
66
+
67
+ sm = StateMachine[State, Event, TurnstileContext]()
68
+
69
+ @sm.transition(State.LOCKED, Event.COIN, State.UNLOCKED)
70
+ def insert_coin(ctx: TurnstileContext):
71
+ ctx.total_coins += 1
72
+ print("Coin inserted. Turnstile unlocked.")
73
+
74
+ @sm.transition(State.UNLOCKED, Event.PUSH, State.LOCKED)
75
+ def push_turnstile(ctx: TurnstileContext):
76
+ print("Turnstile pushed. Turnstile locked.")
77
+
78
+ # Usage
79
+ ctx = TurnstileContext()
80
+ current_state = State.LOCKED
81
+ current_state = sm.handle_transition(ctx, current_state, Event.COIN)
82
+ # current_state is now State.UNLOCKED
83
+ ```
84
+
85
+ ### Audited State Machine
86
+
87
+ Use `AuditedStateMachine` and `AuditContext` to automatically track the history of transitions.
88
+
89
+ ```python
90
+ from semantic_state_machine import AuditedStateMachine, AuditContext
91
+
92
+ class MyContext(AuditContext[State, Event]):
93
+ pass
94
+
95
+ sm = AuditedStateMachine[State, Event, MyContext]()
96
+
97
+ # Transitions are defined the same way
98
+ @sm.transition(State.LOCKED, Event.COIN, State.UNLOCKED)
99
+ def insert_coin(ctx: MyContext):
100
+ pass
101
+
102
+ ctx = MyContext()
103
+ sm.handle_transition(ctx, State.LOCKED, Event.COIN)
104
+
105
+ # The transition is automatically recorded
106
+ print(ctx._audit) # [(State.LOCKED, Event.COIN)]
107
+ ```
108
+
109
+ ## Testing
110
+
111
+ The project includes a comprehensive suite of unit and integration tests.
112
+
113
+ ### Run All Tests
114
+ ```bash
115
+ uv run pytest tests/
116
+ ```
117
+
118
+ ### Run with Coverage
119
+ ```bash
120
+ uv run pytest --cov=src tests/
121
+ ```
122
+
123
+ The current implementation maintains **100% code coverage** across the core logic and integration workflows.
124
+
125
+ ## Project Structure
126
+
127
+ - `src/semantic_state_machine/`: Core implementation.
128
+ - `tests/unit/`: Focused unit tests for individual components.
129
+ - `tests/integration/`: End-to-end workflow simulations.
@@ -0,0 +1,110 @@
1
+ # State Machine
2
+
3
+ A lightweight, type-safe Python state machine implementation using modern Python 3.13+ features like PEP 695 type parameters.
4
+
5
+ ## Features
6
+
7
+ - **Type Safety**: Leverage Python's type system to ensure consistent states, events, and contexts.
8
+ - **Decorator Support**: Easily define transitions using the `@sm.transition` decorator.
9
+ - **Auditing**: Built-in support for recording transition history via `AuditedStateMachine` and `AuditContext`.
10
+ - **Flexible Context**: Pass a custom context object to transition actions to maintain state outside the machine.
11
+
12
+ ## Installation
13
+
14
+ This project uses `uv` for dependency management. To get started, ensure you have `uv` installed and run:
15
+
16
+ ```bash
17
+ uv sync
18
+ ```
19
+
20
+ You can install the package via pip:
21
+
22
+ ```bash
23
+ pip install semantic-state-machine
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### Basic State Machine
29
+
30
+ Define your states and events as `Enum`s and create a context class.
31
+
32
+ ```python
33
+ from enum import Enum
34
+ from semantic_state_machine import StateMachine
35
+
36
+ class State(Enum):
37
+ LOCKED = 1
38
+ UNLOCKED = 2
39
+
40
+ class Event(Enum):
41
+ PUSH = 1
42
+ COIN = 2
43
+
44
+ class TurnstileContext:
45
+ def __init__(self):
46
+ self.total_coins = 0
47
+
48
+ sm = StateMachine[State, Event, TurnstileContext]()
49
+
50
+ @sm.transition(State.LOCKED, Event.COIN, State.UNLOCKED)
51
+ def insert_coin(ctx: TurnstileContext):
52
+ ctx.total_coins += 1
53
+ print("Coin inserted. Turnstile unlocked.")
54
+
55
+ @sm.transition(State.UNLOCKED, Event.PUSH, State.LOCKED)
56
+ def push_turnstile(ctx: TurnstileContext):
57
+ print("Turnstile pushed. Turnstile locked.")
58
+
59
+ # Usage
60
+ ctx = TurnstileContext()
61
+ current_state = State.LOCKED
62
+ current_state = sm.handle_transition(ctx, current_state, Event.COIN)
63
+ # current_state is now State.UNLOCKED
64
+ ```
65
+
66
+ ### Audited State Machine
67
+
68
+ Use `AuditedStateMachine` and `AuditContext` to automatically track the history of transitions.
69
+
70
+ ```python
71
+ from semantic_state_machine import AuditedStateMachine, AuditContext
72
+
73
+ class MyContext(AuditContext[State, Event]):
74
+ pass
75
+
76
+ sm = AuditedStateMachine[State, Event, MyContext]()
77
+
78
+ # Transitions are defined the same way
79
+ @sm.transition(State.LOCKED, Event.COIN, State.UNLOCKED)
80
+ def insert_coin(ctx: MyContext):
81
+ pass
82
+
83
+ ctx = MyContext()
84
+ sm.handle_transition(ctx, State.LOCKED, Event.COIN)
85
+
86
+ # The transition is automatically recorded
87
+ print(ctx._audit) # [(State.LOCKED, Event.COIN)]
88
+ ```
89
+
90
+ ## Testing
91
+
92
+ The project includes a comprehensive suite of unit and integration tests.
93
+
94
+ ### Run All Tests
95
+ ```bash
96
+ uv run pytest tests/
97
+ ```
98
+
99
+ ### Run with Coverage
100
+ ```bash
101
+ uv run pytest --cov=src tests/
102
+ ```
103
+
104
+ The current implementation maintains **100% code coverage** across the core logic and integration workflows.
105
+
106
+ ## Project Structure
107
+
108
+ - `src/semantic_state_machine/`: Core implementation.
109
+ - `tests/unit/`: Focused unit tests for individual components.
110
+ - `tests/integration/`: End-to-end workflow simulations.
@@ -0,0 +1,42 @@
1
+ [project]
2
+ name = "semantic-state-machine"
3
+ version = "0.1.0"
4
+ description = "A lightweight, type-safe Python state machine implementation using modern Python 3.13+ features."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Richard West", email = "dopplereffect.us@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.13"
10
+ dependencies = []
11
+ keywords = ["state-machine", "finite-state-machine", "type-safety", "audit"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ ]
19
+ license = { text = "MIT" }
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/rjdw/semantic-state-machine"
23
+ Repository = "https://github.com/rjdw/semantic-state-machine"
24
+ Issues = "https://github.com/rjdw/semantic-state-machine/issues"
25
+
26
+ [build-system]
27
+ requires = ["uv_build>=0.9.26,<0.10.0"]
28
+ build-backend = "uv_build"
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "pytest>=9.0.3",
33
+ "pytest-cov>=7.1.0",
34
+ "pytest-gitignore>=1.3",
35
+ "pytest-html-plus>=0.5.2",
36
+ "pytest-isort>=4.0.0",
37
+ "pytest-randomly>=4.0.1",
38
+ "pytest-timeout>=2.4.0",
39
+ "pytest-xdist>=3.8.0",
40
+ "ruff>=0.15.10",
41
+ "ty>=0.0.29",
42
+ ]
@@ -0,0 +1,17 @@
1
+ from .state_machine import (
2
+ Action,
3
+ AuditContext,
4
+ AuditedStateMachine,
5
+ InvalidTransition,
6
+ StateMachine,
7
+ Transition,
8
+ )
9
+
10
+ __all__ = [
11
+ "Action",
12
+ "AuditContext",
13
+ "AuditedStateMachine",
14
+ "InvalidTransition",
15
+ "StateMachine",
16
+ "Transition",
17
+ ]
@@ -0,0 +1,180 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import Enum
3
+ from typing import Callable, Iterable, cast
4
+
5
+ type Action[C] = Callable[[C], None]
6
+ """A callable that performs an action on a context object."""
7
+
8
+ type Transition[S: Enum, E: Enum, C] = dict[tuple[S, E], tuple[S, Action[C]]]
9
+ """A mapping of (state, event) to (target_state, action)."""
10
+
11
+
12
+ @dataclass
13
+ class AuditContext[S: Enum, E: Enum]:
14
+ """A context class that records the history of state transitions.
15
+
16
+ Args:
17
+ S: The Enum type representing the states.
18
+ E: The Enum type representing the events.
19
+
20
+ Notes:
21
+ Architectural Intent: Provides a standardized mixin for state machine
22
+ contexts that require auditing capabilities. It is designed to be
23
+ inherited by user-defined context classes.
24
+ """
25
+
26
+ _audit: list[tuple[S, E]] = field(default_factory=list)
27
+
28
+ def record_transition(self, from_state: S, event: E) -> None:
29
+ """Records a transition from a given state triggered by an event.
30
+
31
+ Args:
32
+ from_state: The state the machine is transitioning from.
33
+ event: The event that triggered the transition.
34
+ """
35
+ self._audit.append((from_state, event))
36
+
37
+
38
+ class InvalidTransition(Exception):
39
+ """Raised when a transition is not defined for a given state and event."""
40
+
41
+ pass
42
+
43
+
44
+ @dataclass
45
+ class StateMachine[S: Enum, E: Enum, C]:
46
+ """A lightweight, type-safe state machine.
47
+
48
+ The state machine is stateless; the current state must be managed externally
49
+ and passed to the `handle_transition` method.
50
+
51
+ Args:
52
+ S: The Enum type representing the states.
53
+ E: The Enum type representing the events.
54
+ C: The type of the context object passed to actions.
55
+
56
+ Notes:
57
+ Architectural Intent: Leverages PEP 695 type parameters to ensure
58
+ strict type safety across states, events, and context objects without
59
+ sacrificing flexibility.
60
+ """
61
+
62
+ _transitions: Transition[S, E, C] = field(default_factory=dict)
63
+
64
+ def add_transition(
65
+ self, from_state: S, event: E, to_state: S, func: Action[C]
66
+ ) -> None:
67
+ """Manually registers a transition between states.
68
+
69
+ Args:
70
+ from_state: The starting state for the transition.
71
+ event: The event that triggers the transition.
72
+ to_state: The target state after the transition.
73
+ func: The action to execute during the transition.
74
+ """
75
+ self._transitions[(from_state, event)] = (to_state, func)
76
+
77
+ def handle_transition(self, ctx: C, from_state: S, event: E) -> S:
78
+ """Executes a transition and returns the new state.
79
+
80
+ Args:
81
+ ctx: The context object to pass to the transition's action.
82
+ from_state: The current state of the machine.
83
+ event: The event to process.
84
+
85
+ Returns:
86
+ S: The new state of the machine.
87
+
88
+ Raises:
89
+ InvalidTransition: If the transition is not defined for the
90
+ given state and event.
91
+ """
92
+ to_state, action = self._next_transition(from_state, event)
93
+ action(ctx)
94
+ return to_state
95
+
96
+ def _next_transition(self, from_state: S, event: E) -> tuple[S, Action[C]]:
97
+ """Internal helper to retrieve the next transition.
98
+
99
+ Args:
100
+ from_state: The current state.
101
+ event: The event.
102
+
103
+ Returns:
104
+ tuple[S, Action[C]]: A tuple of (target_state, action).
105
+
106
+ Raises:
107
+ InvalidTransition: If the transition key is missing.
108
+ """
109
+ try:
110
+ return self._transitions[(from_state, event)]
111
+ except KeyError as e:
112
+ raise InvalidTransition(
113
+ f"Cannot {event.name} when {from_state.name}"
114
+ ) from e
115
+
116
+ def transition(
117
+ self, from_state: S | Iterable[S], event: E, to_state: S
118
+ ) -> Callable[[Action[C]], Action[C]]:
119
+ """A decorator to register a transition for a function.
120
+
121
+ Args:
122
+ from_state: A single state or an iterable of states that can
123
+ trigger this transition.
124
+ event: The event that triggers the transition.
125
+ to_state: The target state after the transition.
126
+
127
+ Returns:
128
+ Callable[[Action[C]], Action[C]]: The decorator function.
129
+
130
+ Notes:
131
+ Architectural Intent: Allows for a declarative way to map
132
+ multiple source states to a single target state and action.
133
+ """
134
+ states: list[S]
135
+ if isinstance(from_state, Enum):
136
+ states = [cast(S, from_state)]
137
+ else:
138
+ states = list(cast(Iterable[S], from_state))
139
+
140
+ def decorator(func: Action[C]) -> Action[C]:
141
+ for s in states:
142
+ self.add_transition(s, event, to_state, func)
143
+ return func
144
+
145
+ return decorator
146
+
147
+
148
+ @dataclass
149
+ class AuditedStateMachine[S: Enum, E: Enum, C: AuditContext](StateMachine[S, E, C]):
150
+ """A state machine that automatically records transitions in the context.
151
+
152
+ Requires a context that inherits from `AuditContext`.
153
+
154
+ Args:
155
+ S: The Enum type representing the states.
156
+ E: The Enum type representing the events.
157
+ C: A context type that implements `AuditContext`.
158
+
159
+ Notes:
160
+ Architectural Intent: Simplifies auditing by automatically calling
161
+ `record_transition` on the context before executing the standard
162
+ transition logic.
163
+ """
164
+
165
+ def handle_transition(self, ctx: C, from_state: S, event: E) -> S:
166
+ """Records the transition and then executes it.
167
+
168
+ Args:
169
+ ctx: The audit context.
170
+ from_state: The current state.
171
+ event: The event to process.
172
+
173
+ Returns:
174
+ S: The new state.
175
+
176
+ Raises:
177
+ InvalidTransition: If the transition is not defined.
178
+ """
179
+ ctx.record_transition(from_state, event)
180
+ return super().handle_transition(ctx, from_state, event)