You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/actions.md
+44-23Lines changed: 44 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -304,33 +304,54 @@ See {ref}`conditions` and {ref}`validators`.
304
304
305
305
## Ordering
306
306
307
-
Actions and Guards will be executed in the following order:
307
+
There are major groups of callbacks, these groups run sequentially.
308
308
309
-
-`validators()` (attached to the transition)
310
-
311
-
-`conditions()` (attached to the transition)
312
-
313
-
-`unless()` (attached to the transition)
314
-
315
-
-`before_transition()`
316
-
317
-
-`before_<event>()`
318
-
319
-
-`on_exit_state()`
320
-
321
-
-`on_exit_<state.id>()`
322
-
323
-
-`on_transition()`
324
-
325
-
-`on_<event>()`
326
-
327
-
-`on_enter_state()`
309
+
```{warning}
310
+
Actions registered on the same group don't have order guaranties and are executed in parallel when using the {ref}`AsyncEngine`, and may be executed in parallel in future versions of {ref}`SyncEngine`.
311
+
```
328
312
329
-
-`on_enter_<state.id>()`
330
313
331
-
-`after_<event>()`
314
+
```{list-table}
315
+
:header-rows: 1
316
+
317
+
* - Group
318
+
- Action
319
+
- Current state
320
+
- Description
321
+
* - Validators
322
+
- `validators()`
323
+
- `source`
324
+
- Validators raise exceptions.
325
+
* - Conditions
326
+
- `cond()`, `unless()`
327
+
- `source`
328
+
- Conditions are predicates that prevent transitions to occur.
Copy file name to clipboardExpand all lines: docs/async.md
+56-19Lines changed: 56 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,19 +8,15 @@ The {ref}`StateMachine` fully supports asynchronous code. You can write async {r
8
8
9
9
This is achieved through a new concept called "engine," an internal strategy pattern abstraction that manages transitions and callbacks.
10
10
11
-
There are two engines:
11
+
There are two engines, {ref}`SyncEngine` and {ref}`AsyncEngine`.
12
12
13
-
SyncEngine
14
-
: Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.
15
13
16
-
AsyncEngine
17
-
: Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists.
14
+
## Sync vs async engines
18
15
19
-
These engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios:
16
+
Engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios.
20
17
21
18
22
19
```{list-table} Sync vs async engines
23
-
:widths: 15 10 25 10 10
24
20
:header-rows: 1
25
21
26
22
* - Outer scope
@@ -30,32 +26,40 @@ These engines are internal and are activated automatically by inspecting the reg
30
26
- Reuses external loop
31
27
* - Sync
32
28
- No
33
-
- Sync
29
+
- SyncEngine
34
30
- No
35
31
- No
36
32
* - Sync
37
33
- Yes
38
-
- Async
34
+
- AsyncEngine
39
35
- Yes
40
36
- No
41
37
* - Async
42
38
- No
43
-
- Sync
39
+
- SyncEngine
44
40
- No
45
41
- No
46
42
* - Async
47
43
- Yes
48
-
- Async
44
+
- AsyncEngine
49
45
- No
50
46
- Yes
51
47
52
48
```
53
49
54
-
55
50
```{note}
56
51
All handlers will run on the same thread they are called. Therefore, mixing synchronous and asynchronous code is not recommended unless you are confident in your implementation.
57
52
```
58
53
54
+
### SyncEngine
55
+
Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.
56
+
There's no event loop.
57
+
58
+
### AsyncEngine
59
+
Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists.
60
+
61
+
62
+
59
63
## Asynchronous Support
60
64
61
65
We support native coroutine callbacks using asyncio, enabling seamless integration with asynchronous code. There is no change in the public API of the library to work with asynchronous codebases.
@@ -72,6 +76,7 @@ async code with a state machine.
72
76
... initial = State('Initial', initial=True)
73
77
... final = State('Final', final=True)
74
78
...
79
+
... keep = initial.to.itself(internal=True)
75
80
... advance = initial.to(final)
76
81
...
77
82
...asyncdefon_advance(self):
@@ -91,7 +96,10 @@ Final
91
96
92
97
## Sync codebase with async callbacks
93
98
94
-
The same state machine can be executed in a synchronous codebase, even if it contains async callbacks. The callbacks will be awaited using `asyncio.get_event_loop()` if needed.
99
+
The same state machine with async callbacks can be executed in a synchronous codebase,
100
+
even if the calling context don't have an asyncio loop.
101
+
102
+
If needed, the state machine will create a loop using `asyncio.new_event_loop()` and callbacks will be awaited using `loop.run_until_complete()`.
95
103
96
104
97
105
```py
@@ -109,24 +117,53 @@ Final
109
117
## Initial State Activation for Async Code
110
118
111
119
112
-
If you perform checks against the `current_state`, like a loop `while sm.current_state.is_final:`, then on async code you must manually
120
+
If **on async code**you perform checks against the `current_state`, like a loop `while sm.current_state.is_final:`, then you must manually
113
121
await for the [activate initial state](statemachine.StateMachine.activate_initial_state) to be able to check the current state.
114
122
123
+
```{hint}
124
+
This manual initial state activation on async is because Python don't allow awaiting at class initalization time and the initial state activation may contain async callbacks that must be awaited.
125
+
```
126
+
115
127
If you don't do any check for current state externally, just ignore this as the initial state is activated automatically before the first event trigger is handled.
116
128
129
+
You get an error checking the current state before the initial state activation:
130
+
131
+
```py
132
+
>>>asyncdefinitialize_sm():
133
+
... sm = AsyncStateMachine()
134
+
...print(sm.current_state)
135
+
136
+
>>> asyncio.run(initialize_sm())
137
+
Traceback (most recent call last):
138
+
...
139
+
InvalidStateValue: There's no current state set. In async code, did you activate the initial state? (e.g., `await sm.activate_initial_state()`)
140
+
141
+
```
142
+
143
+
You can activate the initial state explicitly:
144
+
117
145
118
146
```py
119
147
>>>asyncdefinitialize_sm():
120
148
... sm = AsyncStateMachine()
121
149
...await sm.activate_initial_state()
122
-
...return sm
150
+
...print(sm.current_state)
123
151
124
-
>>> sm = asyncio.run(initialize_sm())
125
-
>>>print(sm.current_state)
152
+
>>> asyncio.run(initialize_sm())
126
153
Initial
127
154
128
155
```
129
156
130
-
```{hint}
131
-
This manual initial state activation on async is because Python don't allow awaiting at class initalization time and the initial state activation may contain async callbacks that must be awaited.
157
+
Or just by sending an event. The first event activates the initial state automatically
158
+
before the event is handled:
159
+
160
+
```py
161
+
>>>asyncdefinitialize_sm():
162
+
... sm = AsyncStateMachine()
163
+
...await sm.keep() # first event activates the initial state before the event is handled
To install Python State Machine, if you're using [poetry](https://python-poetry.org/):
6
+
To install Python State Machine using [poetry](https://python-poetry.org/):
7
7
8
-
poetry add python-statemachine
8
+
```shell
9
+
poetry add python-statemachine
10
+
```
9
11
12
+
Alternatively, if you prefer using [pip](https://pip.pypa.io):
10
13
11
-
Or to install using [pip](https://pip.pypa.io):
14
+
```shell
15
+
python3 -m pip install python-statemachine
16
+
```
12
17
13
-
pip install python-statemachine
18
+
For those looking to generate diagrams from your state machines, [pydot](https://github.com/pydot/pydot) and [Graphviz](https://graphviz.org/) are required.
19
+
Conveniently, you can install python-statemachine along with the `pydot` dependency using the extras option.
20
+
For more information, please refer to our documentation.
To generate diagrams from your machines, you'll also need `pydot` and `Graphviz`. You can
17
-
install this library already with `pydot` dependency using the `extras` install option. See
18
-
our docs for more details.
19
-
20
-
pip install python-statemachine[diagrams]
21
-
22
-
23
-
If you don't have [pip](https://pip.pypa.io) installed, this [Python installation guide](http://docs.python-guide.org/en/latest/starting/installation/) can guide
24
-
you through the process.
25
26
26
27
27
28
## From sources
@@ -30,12 +31,18 @@ The sources for Python State Machine can be downloaded from the [Github repo](ht
0 commit comments