Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion statemachine/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
if TYPE_CHECKING:
from .callbacks import CallbackSpec
from .callbacks import CallbackSpecList
from .callbacks import CallbacksRegistry


@dataclass
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion statemachine/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions statemachine/statemachine.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .utils import run_async_from_sync

if TYPE_CHECKING:
from .event import Event
from .state import State


Expand Down Expand Up @@ -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]

Expand Down
25 changes: 25 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()