vention-state-machine 0.4__tar.gz → 0.4.4__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.
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/PKG-INFO +23 -7
- vention_state_machine-0.4.4/pyproject.toml +130 -0
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/core.py +7 -24
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/decorator_manager.py +3 -9
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/decorators.py +2 -8
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/defs.py +1 -3
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/machine_protocols.py +3 -9
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/utils.py +2 -5
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/vention_communication.py +13 -19
- vention_state_machine-0.4/pyproject.toml +0 -21
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/README.md +0 -0
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/__init__.py +0 -0
- {vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/decorator_protocols.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vention-state-machine
|
|
3
|
-
Version: 0.4
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Declarative state machine framework for machine apps
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: VentionCo
|
|
@@ -8,12 +8,28 @@ Requires-Python: >=3.10,<3.11
|
|
|
8
8
|
Classifier: License :: Other/Proprietary License
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
-
Requires-Dist:
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
11
|
+
Requires-Dist: annotated-doc (==0.0.4) ; python_version == "3.10"
|
|
12
|
+
Requires-Dist: annotated-types (==0.7.0) ; python_version == "3.10"
|
|
13
|
+
Requires-Dist: anyio (==4.11.0) ; python_version == "3.10"
|
|
14
|
+
Requires-Dist: asyncio (==3.4.3) ; python_version == "3.10"
|
|
15
|
+
Requires-Dist: click (==8.1.8) ; python_version == "3.10"
|
|
16
|
+
Requires-Dist: colorama (==0.4.6) ; python_version == "3.10" and platform_system == "Windows"
|
|
17
|
+
Requires-Dist: coverage (==7.10.7) ; python_version == "3.10"
|
|
18
|
+
Requires-Dist: exceptiongroup (==1.3.0) ; python_version == "3.10"
|
|
19
|
+
Requires-Dist: fastapi (==0.121.1) ; python_version == "3.10"
|
|
20
|
+
Requires-Dist: graphviz (==0.21) ; python_version == "3.10"
|
|
21
|
+
Requires-Dist: h11 (==0.16.0) ; python_version == "3.10"
|
|
22
|
+
Requires-Dist: idna (==3.11) ; python_version == "3.10"
|
|
23
|
+
Requires-Dist: pydantic (==2.12.3) ; python_version == "3.10"
|
|
24
|
+
Requires-Dist: pydantic-core (==2.41.4) ; python_version == "3.10"
|
|
25
|
+
Requires-Dist: six (==1.17.0) ; python_version == "3.10"
|
|
26
|
+
Requires-Dist: sniffio (==1.3.1) ; python_version == "3.10"
|
|
27
|
+
Requires-Dist: starlette (==0.48.0) ; python_version == "3.10"
|
|
28
|
+
Requires-Dist: transitions (==0.9.3) ; python_version == "3.10"
|
|
29
|
+
Requires-Dist: typing-extensions (==4.15.0) ; python_version == "3.10"
|
|
30
|
+
Requires-Dist: typing-inspection (==0.4.2) ; python_version == "3.10"
|
|
31
|
+
Requires-Dist: uvicorn (==0.35.0) ; python_version == "3.10"
|
|
32
|
+
Requires-Dist: vention-communication (==0.3.0) ; python_version == "3.10"
|
|
17
33
|
Description-Content-Type: text/markdown
|
|
18
34
|
|
|
19
35
|
# vention-state-machine
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "vention-state-machine"
|
|
3
|
+
version = "0.4.4"
|
|
4
|
+
description = "Declarative state machine framework for machine apps"
|
|
5
|
+
authors = [ "VentionCo" ]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "Proprietary"
|
|
8
|
+
|
|
9
|
+
[[tool.poetry.packages]]
|
|
10
|
+
include = "state_machine"
|
|
11
|
+
from = "src"
|
|
12
|
+
|
|
13
|
+
[tool.poetry.build]
|
|
14
|
+
generate-setup-file = false
|
|
15
|
+
|
|
16
|
+
[tool.poetry.dependencies]
|
|
17
|
+
python = ">=3.10,<3.11"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.dependencies.annotated-doc]
|
|
20
|
+
version = "0.0.4"
|
|
21
|
+
markers = 'python_version == "3.10"'
|
|
22
|
+
optional = false
|
|
23
|
+
|
|
24
|
+
[tool.poetry.dependencies.annotated-types]
|
|
25
|
+
version = "0.7.0"
|
|
26
|
+
markers = 'python_version == "3.10"'
|
|
27
|
+
optional = false
|
|
28
|
+
|
|
29
|
+
[tool.poetry.dependencies.anyio]
|
|
30
|
+
version = "4.11.0"
|
|
31
|
+
markers = 'python_version == "3.10"'
|
|
32
|
+
optional = false
|
|
33
|
+
|
|
34
|
+
[tool.poetry.dependencies.asyncio]
|
|
35
|
+
version = "3.4.3"
|
|
36
|
+
markers = 'python_version == "3.10"'
|
|
37
|
+
optional = false
|
|
38
|
+
|
|
39
|
+
[tool.poetry.dependencies.click]
|
|
40
|
+
version = "8.1.8"
|
|
41
|
+
markers = 'python_version == "3.10"'
|
|
42
|
+
optional = false
|
|
43
|
+
|
|
44
|
+
[tool.poetry.dependencies.colorama]
|
|
45
|
+
version = "0.4.6"
|
|
46
|
+
markers = 'python_version == "3.10" and platform_system == "Windows"'
|
|
47
|
+
optional = false
|
|
48
|
+
|
|
49
|
+
[tool.poetry.dependencies.coverage]
|
|
50
|
+
version = "7.10.7"
|
|
51
|
+
markers = 'python_version == "3.10"'
|
|
52
|
+
optional = false
|
|
53
|
+
|
|
54
|
+
[tool.poetry.dependencies.exceptiongroup]
|
|
55
|
+
version = "1.3.0"
|
|
56
|
+
markers = 'python_version == "3.10"'
|
|
57
|
+
optional = false
|
|
58
|
+
|
|
59
|
+
[tool.poetry.dependencies.fastapi]
|
|
60
|
+
version = "0.121.1"
|
|
61
|
+
markers = 'python_version == "3.10"'
|
|
62
|
+
optional = false
|
|
63
|
+
|
|
64
|
+
[tool.poetry.dependencies.graphviz]
|
|
65
|
+
version = "0.21"
|
|
66
|
+
markers = 'python_version == "3.10"'
|
|
67
|
+
optional = false
|
|
68
|
+
|
|
69
|
+
[tool.poetry.dependencies.h11]
|
|
70
|
+
version = "0.16.0"
|
|
71
|
+
markers = 'python_version == "3.10"'
|
|
72
|
+
optional = false
|
|
73
|
+
|
|
74
|
+
[tool.poetry.dependencies.idna]
|
|
75
|
+
version = "3.11"
|
|
76
|
+
markers = 'python_version == "3.10"'
|
|
77
|
+
optional = false
|
|
78
|
+
|
|
79
|
+
[tool.poetry.dependencies.pydantic-core]
|
|
80
|
+
version = "2.41.4"
|
|
81
|
+
markers = 'python_version == "3.10"'
|
|
82
|
+
optional = false
|
|
83
|
+
|
|
84
|
+
[tool.poetry.dependencies.pydantic]
|
|
85
|
+
version = "2.12.3"
|
|
86
|
+
markers = 'python_version == "3.10"'
|
|
87
|
+
optional = false
|
|
88
|
+
|
|
89
|
+
[tool.poetry.dependencies.six]
|
|
90
|
+
version = "1.17.0"
|
|
91
|
+
markers = 'python_version == "3.10"'
|
|
92
|
+
optional = false
|
|
93
|
+
|
|
94
|
+
[tool.poetry.dependencies.sniffio]
|
|
95
|
+
version = "1.3.1"
|
|
96
|
+
markers = 'python_version == "3.10"'
|
|
97
|
+
optional = false
|
|
98
|
+
|
|
99
|
+
[tool.poetry.dependencies.starlette]
|
|
100
|
+
version = "0.48.0"
|
|
101
|
+
markers = 'python_version == "3.10"'
|
|
102
|
+
optional = false
|
|
103
|
+
|
|
104
|
+
[tool.poetry.dependencies.transitions]
|
|
105
|
+
version = "0.9.3"
|
|
106
|
+
markers = 'python_version == "3.10"'
|
|
107
|
+
optional = false
|
|
108
|
+
|
|
109
|
+
[tool.poetry.dependencies.typing-extensions]
|
|
110
|
+
version = "4.15.0"
|
|
111
|
+
markers = 'python_version == "3.10"'
|
|
112
|
+
optional = false
|
|
113
|
+
|
|
114
|
+
[tool.poetry.dependencies.typing-inspection]
|
|
115
|
+
version = "0.4.2"
|
|
116
|
+
markers = 'python_version == "3.10"'
|
|
117
|
+
optional = false
|
|
118
|
+
|
|
119
|
+
[tool.poetry.dependencies.uvicorn]
|
|
120
|
+
version = "0.35.0"
|
|
121
|
+
markers = 'python_version == "3.10"'
|
|
122
|
+
optional = false
|
|
123
|
+
|
|
124
|
+
[tool.poetry.dependencies.vention-communication]
|
|
125
|
+
version = "0.3.0"
|
|
126
|
+
markers = 'python_version == "3.10"'
|
|
127
|
+
optional = false
|
|
128
|
+
|
|
129
|
+
[tool.poetry.group.dev]
|
|
130
|
+
dependencies = { }
|
|
@@ -58,19 +58,12 @@ class StateMachine(HierarchicalGraphMachine):
|
|
|
58
58
|
) -> None:
|
|
59
59
|
# Normalize state definitions
|
|
60
60
|
if is_state_container(states):
|
|
61
|
-
state_groups = [
|
|
62
|
-
value
|
|
63
|
-
for value in vars(states).values()
|
|
64
|
-
if isinstance(value, StateGroup)
|
|
65
|
-
]
|
|
61
|
+
state_groups = [value for value in vars(states).values() if isinstance(value, StateGroup)]
|
|
66
62
|
resolved_states = [group.to_state_list()[0] for group in state_groups]
|
|
67
63
|
elif isinstance(states, list):
|
|
68
64
|
resolved_states = states
|
|
69
65
|
else:
|
|
70
|
-
raise TypeError(
|
|
71
|
-
f"`states` must be either a StateGroup container or a list of state dicts. "
|
|
72
|
-
f"Got: {type(states).__name__}"
|
|
73
|
-
)
|
|
66
|
+
raise TypeError(f"`states` must be either a StateGroup container or a list of state dicts. " f"Got: {type(states).__name__}")
|
|
74
67
|
|
|
75
68
|
self._declared_states: list[dict[str, Any]] = resolved_states
|
|
76
69
|
|
|
@@ -95,9 +88,7 @@ class StateMachine(HierarchicalGraphMachine):
|
|
|
95
88
|
self._timeouts: dict[str, asyncio.Task[Any]] = {}
|
|
96
89
|
self._last_state: Optional[str] = None
|
|
97
90
|
self._enable_recovery: bool = enable_last_state_recovery
|
|
98
|
-
self._history: deque[dict[str, Any]] = deque(
|
|
99
|
-
maxlen=history_size or self.DEFAULT_HISTORY_SIZE
|
|
100
|
-
)
|
|
91
|
+
self._history: deque[dict[str, Any]] = deque(maxlen=history_size or self.DEFAULT_HISTORY_SIZE)
|
|
101
92
|
self._current_start: Optional[datetime] = None
|
|
102
93
|
self._guard_conditions: dict[str, List[Callable[[], bool]]] = {}
|
|
103
94
|
self._state_change_callbacks: List[Callable[[str, str, str], None]] = []
|
|
@@ -110,9 +101,7 @@ class StateMachine(HierarchicalGraphMachine):
|
|
|
110
101
|
BaseStates.FAULT.value,
|
|
111
102
|
before="cancel_tasks",
|
|
112
103
|
)
|
|
113
|
-
self.add_transition(
|
|
114
|
-
BaseTriggers.RESET.value, BaseStates.FAULT.value, BaseStates.READY.value
|
|
115
|
-
)
|
|
104
|
+
self.add_transition(BaseTriggers.RESET.value, BaseStates.FAULT.value, BaseStates.READY.value)
|
|
116
105
|
self._attach_after_hooks()
|
|
117
106
|
self._add_guard_conditions_to_transitions()
|
|
118
107
|
|
|
@@ -137,9 +126,7 @@ class StateMachine(HierarchicalGraphMachine):
|
|
|
137
126
|
except asyncio.CancelledError:
|
|
138
127
|
pass
|
|
139
128
|
|
|
140
|
-
def set_timeout(
|
|
141
|
-
self, state_name: str, seconds: float, trigger_fn: Callable[[], str]
|
|
142
|
-
) -> None:
|
|
129
|
+
def set_timeout(self, state_name: str, seconds: float, trigger_fn: Callable[[], str]) -> None:
|
|
143
130
|
"""
|
|
144
131
|
Schedule a timeout: if `state_name` remains active after `seconds`, fire `trigger_fn()`.
|
|
145
132
|
"""
|
|
@@ -191,9 +178,7 @@ class StateMachine(HierarchicalGraphMachine):
|
|
|
191
178
|
"""Get the last `n` history entries."""
|
|
192
179
|
return list(self._history)[-n:] if n > 0 else []
|
|
193
180
|
|
|
194
|
-
def add_transition_condition(
|
|
195
|
-
self, trigger_name: str, condition_fn: Callable[[], bool]
|
|
196
|
-
) -> None:
|
|
181
|
+
def add_transition_condition(self, trigger_name: str, condition_fn: Callable[[], bool]) -> None:
|
|
197
182
|
"""
|
|
198
183
|
Add a guard condition to a transition trigger.
|
|
199
184
|
Multiple conditions can be added for the same trigger - ALL must pass for the transition to be allowed.
|
|
@@ -202,9 +187,7 @@ class StateMachine(HierarchicalGraphMachine):
|
|
|
202
187
|
self._guard_conditions[trigger_name] = []
|
|
203
188
|
self._guard_conditions[trigger_name].append(condition_fn)
|
|
204
189
|
|
|
205
|
-
def add_state_change_callback(
|
|
206
|
-
self, callback: Callable[[str, str, str], None]
|
|
207
|
-
) -> None:
|
|
190
|
+
def add_state_change_callback(self, callback: Callable[[str, str, str], None]) -> None:
|
|
208
191
|
"""
|
|
209
192
|
Add a callback that fires on any state change.
|
|
210
193
|
|
{vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/decorator_manager.py
RENAMED
|
@@ -38,14 +38,10 @@ class DecoratorManager:
|
|
|
38
38
|
def _discover_state_callbacks(self, callback_fn: Any) -> None:
|
|
39
39
|
"""Discover on_enter_state and on_exit_state decorators."""
|
|
40
40
|
if hasattr(callback_fn, "_on_enter_state"):
|
|
41
|
-
self._decorator_bindings.append(
|
|
42
|
-
(callback_fn._on_enter_state, "enter", callback_fn)
|
|
43
|
-
)
|
|
41
|
+
self._decorator_bindings.append((callback_fn._on_enter_state, "enter", callback_fn))
|
|
44
42
|
|
|
45
43
|
if hasattr(callback_fn, "_on_exit_state"):
|
|
46
|
-
self._exit_decorator_bindings.append(
|
|
47
|
-
(callback_fn._on_exit_state, "exit", callback_fn)
|
|
48
|
-
)
|
|
44
|
+
self._exit_decorator_bindings.append((callback_fn._on_exit_state, "exit", callback_fn))
|
|
49
45
|
|
|
50
46
|
def _discover_guard_conditions(self, callback_fn: Any) -> None:
|
|
51
47
|
"""Discover guard decorators."""
|
|
@@ -70,9 +66,7 @@ class DecoratorManager:
|
|
|
70
66
|
|
|
71
67
|
def _bind_state_callbacks(self, instance: Any) -> None:
|
|
72
68
|
"""Bind state entry/exit callbacks."""
|
|
73
|
-
for state_name, hook_type, callback_fn in
|
|
74
|
-
self._decorator_bindings + self._exit_decorator_bindings
|
|
75
|
-
):
|
|
69
|
+
for state_name, hook_type, callback_fn in self._decorator_bindings + self._exit_decorator_bindings:
|
|
76
70
|
bound_fn = callback_fn.__get__(instance)
|
|
77
71
|
|
|
78
72
|
if hook_type == "enter" and hasattr(callback_fn, "_timeout_config"):
|
|
@@ -54,9 +54,7 @@ def on_exit_state(
|
|
|
54
54
|
return decorator
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def auto_timeout(
|
|
58
|
-
seconds: float, trigger: Union[str, Callable[[], str]] = "to_fault"
|
|
59
|
-
) -> Callable[[CallableType], CallableType]:
|
|
57
|
+
def auto_timeout(seconds: float, trigger: Union[str, Callable[[], str]] = "to_fault") -> Callable[[CallableType], CallableType]:
|
|
60
58
|
"""
|
|
61
59
|
Decorator that applies an auto-timeout configuration to a state entry handler.
|
|
62
60
|
"""
|
|
@@ -90,11 +88,7 @@ def guard(
|
|
|
90
88
|
guard_fn._guard_conditions = {}
|
|
91
89
|
|
|
92
90
|
for trigger in triggers:
|
|
93
|
-
trigger_name = (
|
|
94
|
-
getattr(trigger, "name", trigger)
|
|
95
|
-
if hasattr(trigger, "name")
|
|
96
|
-
else trigger
|
|
97
|
-
)
|
|
91
|
+
trigger_name = getattr(trigger, "name", trigger) if hasattr(trigger, "name") else trigger
|
|
98
92
|
if not isinstance(trigger_name, str):
|
|
99
93
|
raise TypeError(f"Expected a Trigger or str, got {type(trigger)}")
|
|
100
94
|
|
|
@@ -88,9 +88,7 @@ class Trigger:
|
|
|
88
88
|
def __call__(self) -> str:
|
|
89
89
|
return self.name
|
|
90
90
|
|
|
91
|
-
def transition(
|
|
92
|
-
self, source: Union[str, State], dest: Union[str, State]
|
|
93
|
-
) -> Dict[str, str]:
|
|
91
|
+
def transition(self, source: Union[str, State], dest: Union[str, State]) -> Dict[str, str]:
|
|
94
92
|
"""
|
|
95
93
|
Build {"trigger": self.name, "source": <src>, "dest": <dst>}.
|
|
96
94
|
`source` / `dest` may be raw strings or StateKey instances.
|
{vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/machine_protocols.py
RENAMED
|
@@ -2,9 +2,7 @@ from typing import Protocol, Callable, Any
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class SupportsTimeout(Protocol):
|
|
5
|
-
def set_timeout(
|
|
6
|
-
self, state_name: str, seconds: float, trigger_fn: Callable[[], str]
|
|
7
|
-
) -> None: ...
|
|
5
|
+
def set_timeout(self, state_name: str, seconds: float, trigger_fn: Callable[[], str]) -> None: ...
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
class SupportsStateCallbacks(Protocol):
|
|
@@ -12,15 +10,11 @@ class SupportsStateCallbacks(Protocol):
|
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class SupportsGuardConditions(Protocol):
|
|
15
|
-
def add_transition_condition(
|
|
16
|
-
self, trigger_name: str, condition_fn: Callable[[], bool]
|
|
17
|
-
) -> None: ...
|
|
13
|
+
def add_transition_condition(self, trigger_name: str, condition_fn: Callable[[], bool]) -> None: ...
|
|
18
14
|
|
|
19
15
|
|
|
20
16
|
class SupportsStateChangeCallbacks(Protocol):
|
|
21
|
-
def add_state_change_callback(
|
|
22
|
-
self, callback: Callable[[str, str, str], None]
|
|
23
|
-
) -> None: ...
|
|
17
|
+
def add_state_change_callback(self, callback: Callable[[str, str, str], None]) -> None: ...
|
|
24
18
|
|
|
25
19
|
|
|
26
20
|
class StateMachineProtocol(
|
|
@@ -48,11 +48,8 @@ def is_state_container(state_source: object) -> bool:
|
|
|
48
48
|
Return True if `obj` is an instance or class that contains StateGroup(s).
|
|
49
49
|
"""
|
|
50
50
|
try:
|
|
51
|
-
return any(
|
|
52
|
-
isinstance(value, StateGroup) for value in vars(state_source).values()
|
|
53
|
-
) or any(
|
|
54
|
-
isinstance(value, StateGroup)
|
|
55
|
-
for value in vars(state_source.__class__).values()
|
|
51
|
+
return any(isinstance(value, StateGroup) for value in vars(state_source).values()) or any(
|
|
52
|
+
isinstance(value, StateGroup) for value in vars(state_source.__class__).values()
|
|
56
53
|
)
|
|
57
54
|
except TypeError:
|
|
58
55
|
return False
|
{vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/vention_communication.py
RENAMED
|
@@ -15,11 +15,11 @@ from state_machine.utils import (
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
__all__ = ["
|
|
18
|
+
__all__ = ["build_state_machine_rpc_bundle"]
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
|
|
21
|
+
def build_state_machine_rpc_bundle(
|
|
22
|
+
state_machine: StateMachine,
|
|
23
23
|
*,
|
|
24
24
|
include_state_actions: bool = True,
|
|
25
25
|
include_history_action: bool = True,
|
|
@@ -44,8 +44,8 @@ def build_state_machine_bundle(
|
|
|
44
44
|
if include_state_actions:
|
|
45
45
|
|
|
46
46
|
async def get_state() -> StateResponse:
|
|
47
|
-
last = getattr(
|
|
48
|
-
return StateResponse(state=
|
|
47
|
+
last = getattr(state_machine, "get_last_state", lambda: None)()
|
|
48
|
+
return StateResponse(state=state_machine.state, last_state=last)
|
|
49
49
|
|
|
50
50
|
bundle.actions.append(
|
|
51
51
|
ActionEntry(
|
|
@@ -62,17 +62,15 @@ def build_state_machine_bundle(
|
|
|
62
62
|
if include_history_action:
|
|
63
63
|
|
|
64
64
|
async def get_history() -> HistoryResponse:
|
|
65
|
-
raw = getattr(
|
|
65
|
+
raw = getattr(state_machine, "history", [])
|
|
66
66
|
out = []
|
|
67
67
|
|
|
68
68
|
for item in raw:
|
|
69
69
|
if isinstance(item, dict):
|
|
70
|
-
# Handle dict items from history
|
|
71
70
|
state_str = str(item.get("state", ""))
|
|
72
71
|
duration_ms: Optional[int] = item.get("duration_ms")
|
|
73
72
|
if duration_ms is not None and not isinstance(duration_ms, int):
|
|
74
73
|
duration_ms = None
|
|
75
|
-
# Add timestamp if present, otherwise use current time
|
|
76
74
|
timestamp: datetime
|
|
77
75
|
if "timestamp" in item and isinstance(item["timestamp"], datetime):
|
|
78
76
|
timestamp = item["timestamp"]
|
|
@@ -86,7 +84,6 @@ def build_state_machine_bundle(
|
|
|
86
84
|
)
|
|
87
85
|
)
|
|
88
86
|
else:
|
|
89
|
-
# Handle simple state strings
|
|
90
87
|
out.append(HistoryEntry(state=str(item), timestamp=datetime.now()))
|
|
91
88
|
|
|
92
89
|
return HistoryResponse(history=out, buffer_size=len(raw))
|
|
@@ -103,8 +100,8 @@ def build_state_machine_bundle(
|
|
|
103
100
|
# ======================================================
|
|
104
101
|
# Triggers
|
|
105
102
|
# ======================================================
|
|
106
|
-
# Source of truth: transitions registered on
|
|
107
|
-
all_triggers = sorted(
|
|
103
|
+
# Source of truth: transitions registered on state machine
|
|
104
|
+
all_triggers = sorted(state_machine.events.keys())
|
|
108
105
|
selected = all_triggers if triggers is None else list(triggers)
|
|
109
106
|
|
|
110
107
|
# ---------- factory function (fixes closure bug) ----------
|
|
@@ -112,22 +109,19 @@ def build_state_machine_bundle(
|
|
|
112
109
|
trigger_name: str,
|
|
113
110
|
) -> Callable[[], Coroutine[Any, Any, TriggerResponse]]:
|
|
114
111
|
async def trigger_call() -> TriggerResponse:
|
|
115
|
-
current =
|
|
116
|
-
allowed =
|
|
112
|
+
current = state_machine.state
|
|
113
|
+
allowed = state_machine.get_triggers(current)
|
|
117
114
|
|
|
118
115
|
# Precondition error if invalid in current state
|
|
119
116
|
if trigger_name not in allowed:
|
|
120
117
|
raise ConnectError(
|
|
121
118
|
code="failed_precondition",
|
|
122
|
-
message=(
|
|
123
|
-
f"Trigger '{trigger_name}' cannot run from '{current}'. "
|
|
124
|
-
f"Allowed: {sorted(allowed)}"
|
|
125
|
-
),
|
|
119
|
+
message=(f"Trigger '{trigger_name}' cannot run from '{current}'. " f"Allowed: {sorted(allowed)}"),
|
|
126
120
|
)
|
|
127
121
|
|
|
128
122
|
# Fetch bound trigger method
|
|
129
123
|
try:
|
|
130
|
-
method = getattr(
|
|
124
|
+
method = getattr(state_machine, trigger_name)
|
|
131
125
|
except AttributeError as e:
|
|
132
126
|
raise ConnectError(
|
|
133
127
|
code="internal",
|
|
@@ -148,7 +142,7 @@ def build_state_machine_bundle(
|
|
|
148
142
|
return TriggerResponse(
|
|
149
143
|
result=trigger_name,
|
|
150
144
|
previous_state=current,
|
|
151
|
-
new_state=
|
|
145
|
+
new_state=state_machine.state,
|
|
152
146
|
)
|
|
153
147
|
|
|
154
148
|
return trigger_call
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "vention-state-machine"
|
|
3
|
-
version = "0.4"
|
|
4
|
-
description = "Declarative state machine framework for machine apps"
|
|
5
|
-
authors = [ "VentionCo" ]
|
|
6
|
-
readme = "README.md"
|
|
7
|
-
license = "Proprietary"
|
|
8
|
-
packages = [{ include = "state_machine", from = "src" }]
|
|
9
|
-
|
|
10
|
-
[tool.poetry.dependencies]
|
|
11
|
-
asyncio = "^3.4.3"
|
|
12
|
-
python = ">=3.10,<3.11"
|
|
13
|
-
uvicorn = "^0.35.0"
|
|
14
|
-
transitions = "^0.9.3"
|
|
15
|
-
graphviz = "^0.21"
|
|
16
|
-
coverage = "^7.10.1"
|
|
17
|
-
vention-communication = "^0.2.2"
|
|
18
|
-
|
|
19
|
-
[tool.poetry.group.dev.dependencies]
|
|
20
|
-
pytest = "^8.3.4"
|
|
21
|
-
ruff = "^0.8.0"
|
|
File without changes
|
|
File without changes
|
{vention_state_machine-0.4 → vention_state_machine-0.4.4}/src/state_machine/decorator_protocols.py
RENAMED
|
File without changes
|