Skip to content

Commit 490d981

Browse files
authored
feat: Improved dispatcher of methods, now with protected attributes; Example with rich models. (#305)
1 parent bf3f7d9 commit 490d981

38 files changed

Lines changed: 2305 additions & 395 deletions

AUTHORS.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Contributors
1515
* Raphael Schrader <raphael@schradercloud.de>
1616

1717

18-
Credits
19-
-------
18+
Scaffolding
19+
-----------
2020

2121
This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
2222

docs/actions.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
(actions)=
2-
31
# Actions
42

53

@@ -34,6 +32,9 @@ For each defined state, you can register `on_enter_<state>` and `on_exit_<state>
3432

3533
- `on_exit_<state_identifier>(event_data)`
3634

35+
36+
The initial {ref}`state` is entered when the machine starts and the corresponding actions `on_enter_state` or `on_enter_<state>` are called if defined.
37+
3738
## Event actions
3839

3940
For each event, you can register `before_<event>` and `after_<event>`
@@ -82,12 +83,17 @@ Actions and Guards will be executed in the following order:
8283
- `after_transition(event_data)`
8384

8485

86+
(dynamic-dispatch)=
8587
## Dynamic dispatch
8688

8789
python-statemachine implements a custom dispatch mechanism on all those available Actions and
8890
Guards, this means that you can declare an arbitrary number of `*args` and `**kwargs`, and the
89-
library will to it's best to match your method signature of what's expect to receive with the
90-
provided arguments.
91+
library will match your method signature of what's expect to receive with the provided arguments.
92+
93+
This means that if on your `on_enter_<state>()` or `on_execute_<event>()` method, you also
94+
need to know the `source` ({ref}`state`), or the `event` ({ref}`event`), or access a keyword
95+
argument passed with the trigger, just add this parameter to the method and It will be passed
96+
by the dispatch mechanics.
9197

9298
In other words, if you implement a method to handle an event and don't declare any parameter,
9399
you'll be fine, if you declare an expected parameter, you'll also be covered.
@@ -110,7 +116,8 @@ For your convenience, all these parameters are available for you on any Action o
110116

111117
- `transition`: The {ref}`Transition` instance that was activated by the {ref}`Event`.
112118

113-
So, you can implement Actions and Guards like these:
119+
So, you can implement Actions and Guards like these, but this list is not exaustive, it's only to give you a few examples... any combination of parameters will work, including extra parameters
120+
that you may inform when triggering an {ref}`event`:
114121

115122
```py
116123
def action_or_guard_method_name(self):
@@ -127,12 +134,7 @@ def action_or_guard_method_name(self, *args, event_data, event, source, state, m
127134

128135
```
129136

130-
131-
## Example
132-
133-
See this test for a complete example of order resolution of callbacks.
134-
135-
```{literalinclude} ../tests/test_actions.py
136-
:language: python
137-
:linenos:
137+
```{seealso}
138+
See the example {ref}`sphx_glr_auto_examples_all_actions_machine.py` for a complete example of
139+
order resolution of callbacks.
138140
```

docs/api.rst

Lines changed: 0 additions & 10 deletions
This file was deleted.

docs/auto_examples/all_actions_machine.ipynb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,60 @@
2828
"source": [
2929
"import mock\n\nfrom statemachine import StateMachine, State\n\n\nclass AllActionsMachine(StateMachine):\n\n initial = State(\"Initial\", initial=True)\n final = State(\"Final\", final=True)\n\n go = initial.to(\n final,\n validators=[\"validation_1\", \"validation_2\"],\n conditions=[\"condition_1\", \"condition_2\"],\n unless=[\"unless_1\", \"unless_2\"],\n on_execute=[\"on_execute_1\", \"on_execute_2\"],\n before=[\"before_go_inline_1\", \"before_go_inline_2\"],\n after=[\"after_go_inline_1\", \"after_go_inline_2\"],\n )\n\n def __init__(self, *args, **kwargs):\n self.spy = mock.Mock(side_effect=lambda x: x)\n super(AllActionsMachine, self).__init__(*args, **kwargs)\n\n # validations and conditions\n\n def validation_1(self):\n # this method may raise an exception\n return self.spy(\"validation_1\")\n\n def validation_2(self):\n # this method may raise an exception\n return self.spy(\"validation_2\")\n\n def condition_1(self):\n self.spy(\"condition_1\")\n return True\n\n def condition_2(self):\n self.spy(\"condition_2\")\n return True\n\n def unless_1(self):\n self.spy(\"unless_1\")\n return False\n\n def unless_2(self):\n self.spy(\"unless_2\")\n return False\n\n # generics state\n\n def on_enter_state(self):\n return self.spy(\"on_enter_state\")\n\n def on_exit_state(self):\n return self.spy(\"on_exit_state\")\n\n # generics transition\n\n def before_transition(self):\n return self.spy(\"before_transition\")\n\n def after_transition(self):\n return self.spy(\"after_transition\")\n\n # before / after specific\n\n def on_execute_1(self):\n return self.spy(\"on_execute_1\")\n\n def on_execute_2(self):\n return self.spy(\"on_execute_2\")\n\n def before_go_inline_1(self):\n return self.spy(\"before_go_inline_1\")\n\n def before_go_inline_2(self):\n return self.spy(\"before_go_inline_2\")\n\n def before_go(self):\n return self.spy(\"before_go\")\n\n def on_go(self):\n return self.spy(\"on_go\")\n\n def after_go_inline_1(self):\n return self.spy(\"after_go_inline_1\")\n\n def after_go_inline_2(self):\n return self.spy(\"after_go_inline_2\")\n\n def after_go(self):\n return self.spy(\"after_go\")\n\n # enter / exit specific\n\n def on_enter_initial(self):\n return self.spy(\"on_enter_initial\")\n\n def on_exit_initial(self):\n return self.spy(\"on_exit_initial\")\n\n def on_enter_final(self):\n return self.spy(\"on_enter_final\")\n\n def on_exit_final(self):\n \"hopefully this will not be called\"\n return self.spy(\"on_exit_final\")"
3030
]
31+
},
32+
{
33+
"cell_type": "markdown",
34+
"metadata": {},
35+
"source": [
36+
"# Testing\n\n"
37+
]
38+
},
39+
{
40+
"cell_type": "code",
41+
"execution_count": null,
42+
"metadata": {
43+
"collapsed": false
44+
},
45+
"outputs": [],
46+
"source": [
47+
"machine = AllActionsMachine()\nspy = machine.spy"
48+
]
49+
},
50+
{
51+
"cell_type": "markdown",
52+
"metadata": {},
53+
"source": [
54+
"Only before actions have their result collected.\n\n"
55+
]
56+
},
57+
{
58+
"cell_type": "code",
59+
"execution_count": null,
60+
"metadata": {
61+
"collapsed": false
62+
},
63+
"outputs": [],
64+
"source": [
65+
"result = machine.go()\nassert result == [\n \"before_transition\",\n \"before_go_inline_1\",\n \"before_go_inline_2\",\n \"on_execute_1\",\n \"on_execute_2\",\n \"before_go\",\n \"on_go\",\n]"
66+
]
67+
},
68+
{
69+
"cell_type": "markdown",
70+
"metadata": {},
71+
"source": [
72+
"Checking the method resolution order\n\n"
73+
]
74+
},
75+
{
76+
"cell_type": "code",
77+
"execution_count": null,
78+
"metadata": {
79+
"collapsed": false
80+
},
81+
"outputs": [],
82+
"source": [
83+
"assert spy.call_args_list == [\n mock.call(\"on_enter_state\"),\n mock.call(\"on_enter_initial\"),\n mock.call(\"validation_1\"),\n mock.call(\"validation_2\"),\n mock.call(\"condition_1\"),\n mock.call(\"condition_2\"),\n mock.call(\"unless_1\"),\n mock.call(\"unless_2\"),\n mock.call(\"before_transition\"),\n mock.call(\"before_go_inline_1\"),\n mock.call(\"before_go_inline_2\"),\n mock.call(\"on_execute_1\"),\n mock.call(\"on_execute_2\"),\n mock.call(\"before_go\"),\n mock.call(\"on_go\"),\n mock.call(\"on_exit_state\"),\n mock.call(\"on_exit_initial\"),\n mock.call(\"on_enter_state\"),\n mock.call(\"on_enter_final\"),\n mock.call(\"after_go_inline_1\"),\n mock.call(\"after_go_inline_2\"),\n mock.call(\"after_go\"),\n mock.call(\"after_transition\"),\n]"
84+
]
3185
}
3286
],
3387
"metadata": {

docs/auto_examples/all_actions_machine.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,55 @@ def on_enter_final(self):
115115
def on_exit_final(self):
116116
"hopefully this will not be called"
117117
return self.spy("on_exit_final")
118+
119+
120+
# %%
121+
# Testing
122+
# -------
123+
124+
machine = AllActionsMachine()
125+
spy = machine.spy
126+
127+
128+
# %%
129+
# Only before actions have their result collected.
130+
131+
result = machine.go()
132+
assert result == [
133+
"before_transition",
134+
"before_go_inline_1",
135+
"before_go_inline_2",
136+
"on_execute_1",
137+
"on_execute_2",
138+
"before_go",
139+
"on_go",
140+
]
141+
142+
# %%
143+
# Checking the method resolution order
144+
145+
assert spy.call_args_list == [
146+
mock.call("on_enter_state"),
147+
mock.call("on_enter_initial"),
148+
mock.call("validation_1"),
149+
mock.call("validation_2"),
150+
mock.call("condition_1"),
151+
mock.call("condition_2"),
152+
mock.call("unless_1"),
153+
mock.call("unless_2"),
154+
mock.call("before_transition"),
155+
mock.call("before_go_inline_1"),
156+
mock.call("before_go_inline_2"),
157+
mock.call("on_execute_1"),
158+
mock.call("on_execute_2"),
159+
mock.call("before_go"),
160+
mock.call("on_go"),
161+
mock.call("on_exit_state"),
162+
mock.call("on_exit_initial"),
163+
mock.call("on_enter_state"),
164+
mock.call("on_enter_final"),
165+
mock.call("after_go_inline_1"),
166+
mock.call("after_go_inline_2"),
167+
mock.call("after_go"),
168+
mock.call("after_transition"),
169+
]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2132b4efc59380ceee5e938f475144a6
1+
1c02de6db5c0a074eb68328c9fdef075

docs/auto_examples/all_actions_machine.rst

Lines changed: 103 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,7 @@ All actions machine
2323

2424
A StateMachine that exercices all possible :ref:`Actions` and :ref:`Guards`.
2525

26-
.. GENERATED FROM PYTHON SOURCE LINES 8-118
27-
28-
29-
30-
.. image-sg:: /auto_examples/images/sphx_glr_all_actions_machine_001.svg
31-
:alt: all actions machine
32-
:srcset: /auto_examples/images/sphx_glr_all_actions_machine_001.svg
33-
:class: sphx-glr-single-img
34-
35-
36-
37-
26+
.. GENERATED FROM PYTHON SOURCE LINES 8-120
3827
3928
.. code-block:: default
4029
@@ -150,6 +139,108 @@ A StateMachine that exercices all possible :ref:`Actions` and :ref:`Guards`.
150139
return self.spy("on_exit_final")
151140
152141
142+
143+
144+
145+
.. image-sg:: /auto_examples/images/sphx_glr_all_actions_machine_001.svg
146+
:alt: all actions machine
147+
:srcset: /auto_examples/images/sphx_glr_all_actions_machine_001.svg
148+
:class: sphx-glr-single-img
149+
150+
151+
152+
153+
154+
.. GENERATED FROM PYTHON SOURCE LINES 121-123
155+
156+
Testing
157+
-------
158+
159+
.. GENERATED FROM PYTHON SOURCE LINES 123-128
160+
161+
.. code-block:: default
162+
163+
164+
machine = AllActionsMachine()
165+
spy = machine.spy
166+
167+
168+
169+
170+
171+
172+
173+
174+
175+
.. GENERATED FROM PYTHON SOURCE LINES 129-130
176+
177+
Only before actions have their result collected.
178+
179+
.. GENERATED FROM PYTHON SOURCE LINES 130-142
180+
181+
.. code-block:: default
182+
183+
184+
result = machine.go()
185+
assert result == [
186+
"before_transition",
187+
"before_go_inline_1",
188+
"before_go_inline_2",
189+
"on_execute_1",
190+
"on_execute_2",
191+
"before_go",
192+
"on_go",
193+
]
194+
195+
196+
197+
198+
199+
200+
201+
202+
.. GENERATED FROM PYTHON SOURCE LINES 143-144
203+
204+
Checking the method resolution order
205+
206+
.. GENERATED FROM PYTHON SOURCE LINES 144-170
207+
208+
.. code-block:: default
209+
210+
211+
assert spy.call_args_list == [
212+
mock.call("on_enter_state"),
213+
mock.call("on_enter_initial"),
214+
mock.call("validation_1"),
215+
mock.call("validation_2"),
216+
mock.call("condition_1"),
217+
mock.call("condition_2"),
218+
mock.call("unless_1"),
219+
mock.call("unless_2"),
220+
mock.call("before_transition"),
221+
mock.call("before_go_inline_1"),
222+
mock.call("before_go_inline_2"),
223+
mock.call("on_execute_1"),
224+
mock.call("on_execute_2"),
225+
mock.call("before_go"),
226+
mock.call("on_go"),
227+
mock.call("on_exit_state"),
228+
mock.call("on_exit_initial"),
229+
mock.call("on_enter_state"),
230+
mock.call("on_enter_final"),
231+
mock.call("after_go_inline_1"),
232+
mock.call("after_go_inline_2"),
233+
mock.call("after_go"),
234+
mock.call("after_transition"),
235+
]
236+
237+
238+
239+
240+
241+
242+
243+
153244
.. _sphx_glr_download_auto_examples_all_actions_machine.py:
154245

155246
.. only:: html

0 commit comments

Comments
 (0)