Skip to content

Commit

Permalink
feat: Events as commands on 'allowed_events' and 'events' SM properti…
Browse files Browse the repository at this point in the history
…es lists
  • Loading branch information
fgmacedo committed Nov 7, 2024
1 parent cc55e08 commit 46d69f0
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 5 deletions.
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()

0 comments on commit 46d69f0

Please sign in to comment.