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
docs: add v3 upgrade guide and migrate documentation to StateChart API
Add comprehensive upgrade guide from 2.x to 3.0 covering all breaking
changes with before/after examples. Update all documentation to use
StateChart as the primary API with non-deprecated methods/properties,
replacing StateMachine, current_state, and add_observer references.
@@ -229,9 +229,9 @@ The action will be registered for every {ref}`transition` in the list associated
229
229
230
230
231
231
```py
232
-
>>>from statemachine importStateMachine, State
232
+
>>>from statemachine importStateChart, State
233
233
234
-
>>>classExampleStateMachine(StateMachine):
234
+
>>>classExampleStateMachine(StateChart):
235
235
... initial = State(initial=True)
236
236
...
237
237
... loop = initial.to.itself()
@@ -263,9 +263,9 @@ The action will be registered for every {ref}`transition` in the list associated
263
263
You can also declare an event while also adding a callback:
264
264
265
265
```py
266
-
>>>from statemachine importStateMachine, State
266
+
>>>from statemachine importStateChart, State
267
267
268
-
>>>classExampleStateMachine(StateMachine):
268
+
>>>classExampleStateMachine(StateChart):
269
269
... initial = State(initial=True)
270
270
...
271
271
...@initial.to.itself()
@@ -277,7 +277,7 @@ You can also declare an event while also adding a callback:
277
277
278
278
Note that with this syntax, the resulting `loop` that is present on the `ExampleStateMachine.loop`
279
279
namespace is not a simple method, but an {ref}`event` trigger. So it only executes if the
280
-
StateMachine is in the right state.
280
+
StateChart is in the right state.
281
281
282
282
So, you can use the event-oriented approach:
283
283
@@ -307,7 +307,7 @@ that will be included in `**kwargs` to all other callbacks.
307
307
A not so usefull example:
308
308
309
309
```py
310
-
>>>classExampleStateMachine(StateMachine):
310
+
>>>classExampleStateMachine(StateChart):
311
311
... initial = State(initial=True)
312
312
...
313
313
... loop = initial.to.itself()
@@ -399,7 +399,7 @@ Note that `None` will be used if the action callback does not return anything, b
399
399
defined explicitly. The following provides an example:
400
400
401
401
```py
402
-
>>>classExampleStateMachine(StateMachine):
402
+
>>>classExampleStateMachine(StateChart):
403
403
... initial = State(initial=True)
404
404
...
405
405
... loop = initial.to.itself()
@@ -421,15 +421,15 @@ defined explicitly. The following provides an example:
421
421
422
422
```
423
423
424
-
For {ref}`RTC model`, only the main event will get its value list, while the chained ones simply get
425
-
`None` returned. For {ref}`Non-RTC model`, results for every event will always be collected and returned.
424
+
Only the main event will get its value list, while the chained ones simply get
425
+
`None` returned.
426
426
427
427
428
428
(dynamic-dispatch)=
429
429
(dynamic dispatch)=
430
430
## Dependency injection
431
431
432
-
{ref}`statemachine` implements a dependency injection mechanism on all available {ref}`Actions` and
432
+
{ref}`StateChart` implements a dependency injection mechanism on all available {ref}`Actions` and
433
433
{ref}`Conditions` that automatically inspects and matches the expected callback params with those available by the library in conjunction with any values informed when calling an event using `*args` and `**kwargs`.
434
434
435
435
The library ensures that your method signatures match the expected arguments.
Copy file name to clipboardExpand all lines: docs/async.md
+17-19Lines changed: 17 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@
4
4
Support for async code was added!
5
5
```
6
6
7
-
The {ref}`StateMachine` fully supports asynchronous code. You can write async {ref}`actions`, {ref}`guards`, and {ref}`events` triggers, while maintaining the same external API for both synchronous and asynchronous codebases.
7
+
The {ref}`StateChart` fully supports asynchronous code. You can write async {ref}`actions`, {ref}`guards`, and {ref}`events` triggers, while maintaining the same external API for both synchronous and asynchronous codebases.
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
@@ -89,7 +89,7 @@ async code with a state machine.
89
89
90
90
91
91
```py
92
-
>>>classAsyncStateMachine(StateMachine):
92
+
>>>classAsyncStateMachine(StateChart):
93
93
... initial = State('Initial', initial=True)
94
94
... final = State('Final', final=True)
95
95
...
@@ -103,11 +103,11 @@ async code with a state machine.
103
103
... sm = AsyncStateMachine()
104
104
... result =await sm.advance()
105
105
...print(f"Result is {result}")
106
-
...print(sm.current_state)
106
+
...print(list(sm.configuration_values))
107
107
108
108
>>> asyncio.run(run_sm())
109
109
Result is42
110
-
Final
110
+
['final']
111
111
112
112
```
113
113
@@ -124,8 +124,8 @@ If needed, the state machine will create a loop using `asyncio.new_event_loop()`
124
124
>>> result = sm.advance()
125
125
>>>print(f"Result is {result}")
126
126
Result is42
127
-
>>>print(sm.current_state)
128
-
Final
127
+
>>>print(list(sm.configuration_values))
128
+
['final']
129
129
130
130
```
131
131
@@ -134,26 +134,24 @@ Final
134
134
## Initial State Activation for Async Code
135
135
136
136
137
-
If **on async code** you perform checks against the `current_state`, like a loop `while sm.current_state.is_final:`, then you must manually
138
-
await for the [activate initial state](statemachine.StateMachine.activate_initial_state) to be able to check the current state.
137
+
If **on async code** you perform checks against the `configuration`, like a loop `while not sm.is_terminated:`, then you must manually
138
+
await for the [activate initial state](statemachine.StateChart.activate_initial_state) to be able to check the configuration.
139
139
140
140
```{hint}
141
141
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.
142
142
```
143
143
144
-
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.
144
+
If you don't do any check for configuration externally, just ignore this as the initial state is activated automatically before the first event trigger is handled.
145
145
146
-
You get an error checking the current state before the initial state activation:
146
+
You get an error checking the configuration before the initial state activation:
147
147
148
148
```py
149
149
>>>asyncdefinitialize_sm():
150
150
... sm = AsyncStateMachine()
151
-
...print(sm.current_state)
151
+
...print(list(sm.configuration_values))
152
152
153
153
>>> asyncio.run(initialize_sm())
154
-
Traceback (most recent call last):
155
-
...
156
-
InvalidStateValue: There's no current state set. In async code, did you activate the initial state? (e.g., `await sm.activate_initial_state()`)
154
+
[None]
157
155
158
156
```
159
157
@@ -164,10 +162,10 @@ You can activate the initial state explicitly:
164
162
>>>asyncdefinitialize_sm():
165
163
... sm = AsyncStateMachine()
166
164
...await sm.activate_initial_state()
167
-
...print(sm.current_state)
165
+
...print(list(sm.configuration_values))
168
166
169
167
>>> asyncio.run(initialize_sm())
170
-
Initial
168
+
['initial']
171
169
172
170
```
173
171
@@ -178,10 +176,10 @@ before the event is handled:
178
176
>>>asyncdefinitialize_sm():
179
177
... sm = AsyncStateMachine()
180
178
...await sm.keep() # first event activates the initial state before the event is handled
181
-
...print(sm.current_state)
179
+
...print(list(sm.configuration_values))
182
180
183
181
>>> asyncio.run(initialize_sm())
184
-
Initial
182
+
['initial']
185
183
186
184
```
187
185
@@ -205,7 +203,7 @@ async def run():
205
203
### Async-specific limitations
206
204
207
205
-**Initial state activation**: In async code, you must `await sm.activate_initial_state()`
208
-
before inspecting `sm.configuration` or `sm.current_state`. In sync code this happens
206
+
before inspecting `sm.configuration`. In sync code this happens
209
207
automatically at instantiation time.
210
208
-**Delayed events**: Both sync and async engines support `delay=` on `send()`. The async
211
209
engine uses `asyncio.sleep()` internally, so it integrates naturally with event loops.
Copy file name to clipboardExpand all lines: docs/guards.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,7 +22,7 @@ A conditional transition occurs only if specific conditions or criteria are met.
22
22
23
23
When a transition is conditional, it includes a condition (also known as a _guard_) that must be satisfied for the transition to take place. If the condition is not met, the transition does not occur, and the state machine remains in its current state or follows an alternative path.
24
24
25
-
This feature allows for multiple transitions on the same {ref}`event`, with each {ref}`transition` checked in **declaration order** — that is, the order in which the transitions themselves were created using `state.to()`. A condition acts like a predicate (a function that evaluates to true/false) and is checked when a {ref}`statemachine` handles an {ref}`event` with a transition from the current state bound to this event. The first transition that meets the conditions (if any) is executed. If none of the transitions meet the conditions, the state machine either raises an exception or does nothing (see the `allow_event_without_transition`parameter of {ref}`StateMachine`).
25
+
This feature allows for multiple transitions on the same {ref}`event`, with each {ref}`transition` checked in **declaration order** — that is, the order in which the transitions themselves were created using `state.to()`. A condition acts like a predicate (a function that evaluates to true/false) and is checked when a {ref}`statemachine` handles an {ref}`event` with a transition from the current state bound to this event. The first transition that meets the conditions (if any) is executed. If none of the transitions meet the conditions, the state machine either raises an exception or does nothing (see the `allow_event_without_transition`attribute of {ref}`StateChart`).
26
26
27
27
````{important}
28
28
**Evaluation order is based on declaration order, not composition order.**
@@ -161,18 +161,18 @@ So, a condition `s1.to(s2, cond=lambda: [])` will evaluate as `False`, as an emp
161
161
162
162
### Checking enabled events
163
163
164
-
The {ref}`StateMachine.allowed_events` property returns events reachable from the current state,
164
+
The {ref}`StateChart.allowed_events` property returns events reachable from the current state,
165
165
but it does **not** evaluate `cond`/`unless` guards. To check which events actually have their
166
-
conditions satisfied, use {ref}`StateMachine.enabled_events`.
166
+
conditions satisfied, use {ref}`StateChart.enabled_events`.
167
167
168
168
```{testsetup}
169
169
170
-
>>> from statemachine import StateMachine, State
170
+
>>> from statemachine import StateChart, State
171
171
172
172
```
173
173
174
174
```py
175
-
>>>classApprovalMachine(StateMachine):
175
+
>>>classApprovalMachine(StateChart):
176
176
... pending = State(initial=True)
177
177
... approved = State(final=True)
178
178
... rejected = State(final=True)
@@ -202,7 +202,7 @@ arguments. Any `*args`/`**kwargs` passed to `enabled_events()` are forwarded to
202
202
condition callbacks, just like when triggering an event:
0 commit comments