Skip to content

Commit 3c6cb7b

Browse files
csentisfgmacedo
authored andcommitted
fix(io): materialize transition validators from dict definitions
create_machine_class_from_definition threaded cond/unless/on/before/after into Transition(), but dropped validators — so a 'validators' entry in a dict/JSON definition was silently ignored and never ran at send(). The TransitionDict TypedDict also mistyped validators as bool. Pass validators through to Transition() and fix the type to the same callback-spec union as cond/unless. Adds a regression test proving a definition-supplied validator runs (and aborts) on send(). Closes #<issue>. Signed-off-by: Christian Sentis <christian.sentis@icloud.com>
1 parent ab38143 commit 3c6cb7b

2 files changed

Lines changed: 34 additions & 1 deletion

File tree

statemachine/io/class_factory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class TransitionDict(TypedDict, total=False):
3535
event: "str | None"
3636
internal: bool
3737
initial: bool
38-
validators: bool
38+
validators: "str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]"
3939
cond: "str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]"
4040
unless: "str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]"
4141
on: "str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]"
@@ -204,6 +204,7 @@ def create_machine_class_from_definition(
204204
"event": transition_event_name,
205205
"internal": transition_data.get("internal"),
206206
"initial": transition_data.get("initial"),
207+
"validators": transition_data.get("validators"),
207208
"cond": transition_data.get("cond"),
208209
"unless": transition_data.get("unless"),
209210
"on": transition_data.get("on"),

tests/test_io.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for statemachine.io module (dictionary-based state machine definitions)."""
22

3+
import pytest
34
from statemachine.io import create_machine_class_from_definition
45
from statemachine.io.class_factory import _parse_history
56

@@ -44,3 +45,34 @@ def test_transition_with_both_parent_and_own_event_name(self):
4445
event_ids = sorted(e.id for e in sm.events)
4546
assert "parent_evt" in event_ids
4647
assert "sub_evt" in event_ids
48+
49+
50+
class TestTransitionValidators:
51+
"""A ``validators`` entry in a transition definition must be materialized
52+
onto the Transition (the explicit-rejection channel), not silently dropped.
53+
"""
54+
55+
def test_validators_from_definition_run_on_send(self):
56+
class Rejected(Exception):
57+
pass
58+
59+
def reject(*args, **kwargs):
60+
raise Rejected("not allowed")
61+
62+
sm_cls = create_machine_class_from_definition(
63+
"ValidatedMachine",
64+
states={
65+
"s1": {
66+
"initial": True,
67+
"on": {"go": [{"target": "s2", "validators": reject}]},
68+
},
69+
"s2": {"final": True},
70+
},
71+
)
72+
sm = sm_cls()
73+
74+
# Without the validator being materialized, send() would succeed and
75+
# the machine would land in s2. With it, the validator aborts.
76+
with pytest.raises(Rejected):
77+
sm.send("go")
78+
assert sm.current_state.id == "s1"

0 commit comments

Comments
 (0)