diff --git a/statemachine/dispatcher.py b/statemachine/dispatcher.py index 51eb9e37..399ed846 100644 --- a/statemachine/dispatcher.py +++ b/statemachine/dispatcher.py @@ -16,6 +16,7 @@ if TYPE_CHECKING: from .callbacks import CallbackSpec from .callbacks import CallbackSpecList + from .callbacks import CallbacksRegistry @dataclass @@ -58,7 +59,7 @@ def from_listeners(cls, listeners: Iterable["Listener"]) -> "Listeners": def resolve( self, specs: "CallbackSpecList", - registry, + registry: "CallbacksRegistry", allowed_references: SpecReference = SPECS_ALL, ): found_convention_specs = specs.conventional_specs & self.all_attrs diff --git a/statemachine/event.py b/statemachine/event.py index d1dddcef..f80f8112 100644 --- a/statemachine/event.py +++ b/statemachine/event.py @@ -6,6 +6,7 @@ from statemachine.utils import run_async_from_sync from .event_data import TriggerData +from .i18n import _ if TYPE_CHECKING: from .statemachine import StateMachine @@ -103,8 +104,10 @@ def __call__(self, *args, **kwargs): # can be called as a method. But it is not meant to be called without # an SM instance. Such SM instance is provided by `__get__` method when # used as a property descriptor. - machine = self._sm + if machine is None: + raise RuntimeError(_("Event {} cannot be called without a SM instance").format(self)) + kwargs = {k: v for k, v in kwargs.items() if k not in _event_data_kwargs} trigger_data = TriggerData( machine=machine, diff --git a/statemachine/statemachine.py b/statemachine/statemachine.py index 253b996d..32f654ce 100644 --- a/statemachine/statemachine.py +++ b/statemachine/statemachine.py @@ -30,6 +30,7 @@ from .utils import run_async_from_sync if TYPE_CHECKING: + from .event import Event from .state import State @@ -295,11 +296,11 @@ def current_state(self, value): self.current_state_value = value.value @property - def events(self): - return self.__class__.events + def events(self) -> "List[Event]": + return [getattr(self, event) for event in self.__class__._events] @property - def allowed_events(self): + def allowed_events(self) -> "List[Event]": """List of the current allowed events.""" return [getattr(self, event) for event in self.current_state.transitions.unique_events] diff --git a/tests/test_events.py b/tests/test_events.py index 3cfcab42..51015eea 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -232,3 +232,28 @@ def on_cycle(self, event_data, event: str): assert sm.send("cycle") == "Running cycle from green to yellow" assert sm.send("cycle") == "Running cycle from yellow to red" assert sm.send("cycle") == "Running cycle from red to green" + + def test_allow_using_events_as_commands(self): + class StartMachine(StateMachine): + created = State(initial=True) + started = State() + + created.to(started, event=Event("launch_rocket")) + + sm = StartMachine() + event = next(iter(sm.events)) + + event() # events on an instance machine are "bounded events" + + assert sm.started.is_active + + def test_event_commands_fail_when_unbound_to_instance(self): + class StartMachine(StateMachine): + created = State(initial=True) + started = State() + + created.to(started, event=Event("launch_rocket")) + + event = next(iter(StartMachine.events)) + with pytest.raises(RuntimeError): + event()