Skip to content

Commit 8985849

Browse files
authored
feat: Events as commands on 'allowed_events' and 'events' SM properties lists (#489)
1 parent cc55e08 commit 8985849

4 files changed

Lines changed: 35 additions & 5 deletions

File tree

statemachine/dispatcher.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
if TYPE_CHECKING:
1717
from .callbacks import CallbackSpec
1818
from .callbacks import CallbackSpecList
19+
from .callbacks import CallbacksRegistry
1920

2021

2122
@dataclass
@@ -58,7 +59,7 @@ def from_listeners(cls, listeners: Iterable["Listener"]) -> "Listeners":
5859
def resolve(
5960
self,
6061
specs: "CallbackSpecList",
61-
registry,
62+
registry: "CallbacksRegistry",
6263
allowed_references: SpecReference = SPECS_ALL,
6364
):
6465
found_convention_specs = specs.conventional_specs & self.all_attrs

statemachine/event.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from statemachine.utils import run_async_from_sync
77

88
from .event_data import TriggerData
9+
from .i18n import _
910

1011
if TYPE_CHECKING:
1112
from .statemachine import StateMachine
@@ -103,8 +104,10 @@ def __call__(self, *args, **kwargs):
103104
# can be called as a method. But it is not meant to be called without
104105
# an SM instance. Such SM instance is provided by `__get__` method when
105106
# used as a property descriptor.
106-
107107
machine = self._sm
108+
if machine is None:
109+
raise RuntimeError(_("Event {} cannot be called without a SM instance").format(self))
110+
108111
kwargs = {k: v for k, v in kwargs.items() if k not in _event_data_kwargs}
109112
trigger_data = TriggerData(
110113
machine=machine,

statemachine/statemachine.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from .utils import run_async_from_sync
3131

3232
if TYPE_CHECKING:
33+
from .event import Event
3334
from .state import State
3435

3536

@@ -295,11 +296,11 @@ def current_state(self, value):
295296
self.current_state_value = value.value
296297

297298
@property
298-
def events(self):
299-
return self.__class__.events
299+
def events(self) -> "List[Event]":
300+
return [getattr(self, event) for event in self.__class__._events]
300301

301302
@property
302-
def allowed_events(self):
303+
def allowed_events(self) -> "List[Event]":
303304
"""List of the current allowed events."""
304305
return [getattr(self, event) for event in self.current_state.transitions.unique_events]
305306

tests/test_events.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,28 @@ def on_cycle(self, event_data, event: str):
232232
assert sm.send("cycle") == "Running cycle from green to yellow"
233233
assert sm.send("cycle") == "Running cycle from yellow to red"
234234
assert sm.send("cycle") == "Running cycle from red to green"
235+
236+
def test_allow_using_events_as_commands(self):
237+
class StartMachine(StateMachine):
238+
created = State(initial=True)
239+
started = State()
240+
241+
created.to(started, event=Event("launch_rocket"))
242+
243+
sm = StartMachine()
244+
event = next(iter(sm.events))
245+
246+
event() # events on an instance machine are "bounded events"
247+
248+
assert sm.started.is_active
249+
250+
def test_event_commands_fail_when_unbound_to_instance(self):
251+
class StartMachine(StateMachine):
252+
created = State(initial=True)
253+
started = State()
254+
255+
created.to(started, event=Event("launch_rocket"))
256+
257+
event = next(iter(StartMachine.events))
258+
with pytest.raises(RuntimeError):
259+
event()

0 commit comments

Comments
 (0)