|
2 | 2 |
|
3 | 3 | *January 11, 2022* |
4 | 4 |
|
5 | | -Welcome to StateMachine 1.0! |
6 | | - |
7 | | -This version is a huge refactoring adding a lot of new and exiting features. We hope that |
8 | | -you enjoy. |
9 | | - |
10 | | -These release notes cover the [](#whats-new-in-10), as well as |
11 | | -some [backwards incompatible changes](#backwards-incompatible-changes-in-10) you'll |
12 | | -want to be aware of when upgrading from StateMachine 0.9.0 or earlier. We've |
13 | | -[begun the deprecation process for some features](#deprecated-features-in-10). |
14 | | - |
15 | | - |
16 | | -## Python compatibility in 1.0 |
17 | | - |
18 | | -StateMachine 1.0 supports Python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, and 3.11. |
19 | | - |
20 | | -This is the last release to support Python 2.7, 3.5 and 3.6. |
21 | | - |
22 | | -## What's new in 1.0 |
23 | | - |
24 | | - |
25 | | -### Validators and Guards |
26 | | - |
27 | | -Transitions now support `cond` and `unless` parameters, to restrict |
28 | | -the execution. |
29 | | - |
30 | | -```python |
31 | | - class ApprovalMachine(StateMachine): |
32 | | - "A workflow machine" |
33 | | - requested = State("Requested", initial=True) |
34 | | - accepted = State("Accepted") |
35 | | - rejected = State("Rejected") |
36 | | - completed = State("Completed") |
37 | | - |
38 | | - validate = requested.to(accepted, cond="is_ok") | requested.to(rejected) |
39 | | -``` |
40 | | - |
41 | | -```{seealso} |
42 | | -See {ref}`validators-and-guards` for more details. |
43 | | -``` |
44 | | - |
45 | | -### Support for diagrams |
46 | | - |
47 | | -You can generate diagrams from your statemachine. |
48 | | - |
49 | | -Example: |
50 | | - |
51 | | - |
52 | | - |
53 | | - |
54 | | -```{seealso} |
55 | | -See {ref}`diagrams` for more details. |
56 | | -``` |
57 | | - |
58 | | -### Unified dispatch mecanism for callbacks (actions and guards) |
59 | | - |
60 | | -Every single callback, being {ref}`actions` or {ref}`guards`, is now handled equally by the library. |
61 | | - |
62 | | -Also, we've improved the internals in a way that you can implement your callbacks with any |
63 | | -number of arbritrary positional or keyword arguments (`*args, **kwargs`), and the dispatch will |
64 | | -match the available arguments with your method signature. |
65 | | - |
66 | | -This means that if on your `on_enter_<state>()` or `on_execute_<event>()` method, you also |
67 | | -need to know the `source` ({ref}`state`), or the `event` ({ref}`event`), or access a keyword |
68 | | -argument passed with the trigger, you're covered. Just add this parameter to the method and It |
69 | | - will be passed by the dispatch mechanics. |
70 | | - |
71 | | -Example of what's available: |
72 | | - |
73 | | -```py |
74 | | -def action_or_guard_method_name(self, *args, event_data, event, source, state, model, **kwargs): |
75 | | - pass |
76 | | -``` |
77 | | - |
78 | | -```{seealso} |
79 | | -See {ref}`dynamic-dispatch` for more details. |
80 | | -``` |
81 | | - |
82 | | -### Add observers to a running StateMachine |
83 | | - |
84 | | -Observers are a way do generically add behaviour to a StateMachine without |
85 | | -changing it's internal implementation. |
86 | | - |
87 | | -The `StateMachine` itself is registered as an observer, so by using `StateMachine.add_observer()` |
88 | | -an external object can have the same level of functionalities provided to the built-in class. |
89 | | - |
90 | | -```{seealso} |
91 | | -See {ref}`observers` for more details. |
92 | | -``` |
93 | | - |
94 | | -## Minor features in 1.0 |
95 | | - |
96 | | -- Fixed mypy complaining about incorrect type for ``StateMachine`` class. |
97 | | -- The initial {ref}`state` is now entered when the machine starts. The {ref}`actions`, if defined, |
98 | | - `on_enter_state` and `on_enter_<state>` are now called. |
99 | | - |
100 | | - |
101 | | - |
102 | | -## Backwards incompatible changes in 1.0 |
103 | | - |
104 | | - |
105 | | -### Multiple targets from the same origin state |
106 | | - |
107 | | -Prior to this release, as we didn't have {ref}`validators-and-guards`, there wasn't an elegant way |
108 | | -to declare multiples target states starting from the same pair (event, state). But the library |
109 | | -allowed a near-hackish way, by declaring a target state as the result of the `on_<event>` callback. |
110 | | - |
111 | | -So, the previous code (not valid anymore): |
112 | | - |
113 | | -```py |
114 | | -class ApprovalMachine(StateMachine): |
115 | | - "A workflow machine" |
116 | | - requested = State('Requested', initial=True) |
117 | | - accepted = State('Accepted') |
118 | | - rejected = State('Rejected') |
119 | | - |
120 | | - validate = requested.to(accepted, rejected) |
121 | | - |
122 | | - def on_validate(self, current_time): |
123 | | - if self.model.is_ok(): |
124 | | - self.model.accepted_at = current_time |
125 | | - return self.accepted |
126 | | - else: |
127 | | - return self.rejected |
128 | | -``` |
129 | | - |
130 | | -Should be rewriten to use {ref}`guards`, like this: |
131 | | - |
132 | | -``` py |
133 | | -class ApprovalMachine(StateMachine): |
134 | | - "A workflow machine" |
135 | | - requested = State("Requested", initial=True) |
136 | | - accepted = State("Accepted") |
137 | | - rejected = State("Rejected") |
138 | | - |
139 | | - validate = requested.to(accepted, conditions="is_ok") | requested.to(rejected) |
140 | | - |
141 | | - def on_validate(self, current_time): |
142 | | - self.model.accepted_at = current_time |
143 | | -``` |
144 | | - |
145 | | -```{seealso} |
146 | | -See {ref}`validators-and-guards` of more details. |
147 | | -``` |
148 | | - |
149 | | -### StateMachine now enters the initial state |
150 | | - |
151 | | -This issue was reported at [#265](https://github.com/fgmacedo/python-statemachine/issues/265). |
152 | | - |
153 | | -Now StateMachine will execute the actions associated with the `on_enter_state` and |
154 | | -`on_enter_<state>` when initialized, if they exists. |
155 | | - |
156 | | -```{seealso} |
157 | | -See {ref}`State actions` for more details. |
158 | | -``` |
159 | | - |
160 | | -### Integrity is checked at class definition |
161 | | - |
162 | | -Statemachine integrity checks are now performed at class declaration (import time) instead of on |
163 | | -instance creation. This allows early feedback of invalid definitions. |
164 | | - |
165 | | -This was the previous behaviour, you only got an error when trying to instantiate a StateMachine: |
166 | | - |
167 | | -```py |
168 | | -class CampaignMachine(StateMachine): |
169 | | - "A workflow machine" |
170 | | - draft = State('Draft', initial=True) |
171 | | - producing = State('Being produced') |
172 | | - closed = State('Closed', initial=True) # Should raise an Exception when instantiated |
173 | | - |
174 | | - add_job = draft.to(draft) | producing.to(producing) |
175 | | - produce = draft.to(producing) |
176 | | - deliver = producing.to(closed) |
177 | | - |
178 | | -with pytest.raises(exceptions.InvalidDefinition): |
179 | | - CampaignMachine() |
180 | | -``` |
181 | | - |
182 | | -Not this is performed as the class definition is performed: |
183 | | - |
184 | | -```py |
185 | | -with pytest.raises(exceptions.InvalidDefinition): |
186 | | - |
187 | | - class CampaignMachine(StateMachine): |
188 | | - "A workflow machine" |
189 | | - draft = State("Draft", initial=True) |
190 | | - producing = State("Being produced") |
191 | | - closed = State( |
192 | | - "Closed", initial=True |
193 | | - ) # Should raise an Exception right after the class is defined |
194 | | - |
195 | | - add_job = draft.to(draft) | producing.to(producing) |
196 | | - produce = draft.to(producing) |
197 | | - deliver = producing.to(closed) |
198 | | -``` |
199 | | - |
200 | | -### Other backwards incompatible changes in 1.0 |
201 | | - |
202 | | -- Due to the check validations and setup performed at the machine initialization, it's now harder |
203 | | - to perform monkey-patching to add callbacks at runtime (not a bad thing after all). |
204 | | -- `TransitionNotAllowed` changed internal attr from `transition` to `event`. |
205 | | -- `CombinedTransition` does not exist anymore. {ref}`State` now holds a flat {ref}`Transition` list |
206 | | - called `TransitionList` that implements de `OR` operator. This turns a valid StateMachine |
207 | | - traversal much easier: `[transition for state in machine.states for transition in state.transitions]`. |
208 | | -- `StateMachine.get_transition` is removed. See {ref}`event`. |
209 | | -- The previous excetions `MultipleStatesFound` and `MultipleTransitionCallbacksFound` are removed. |
210 | | - Since now you can have more than one callback defined to the same transition. |
211 | | -- `on_enter_state` and `on_exit_state` now accepts any combination of parameters following the |
212 | | - {ref}`dynamic-dispatch` rules. Previously it only accepted the `state` param. |
213 | | -- `Transition.__init__` param `on_execute` renamed to simply `on`, and now follows the |
214 | | -{ref}`dynamic-dispatch`. |
215 | | -- `Transition.destinations` removed in favor of `Transition.target` (following SCXML convention). |
216 | | -Now each transition only points to a unique target. Each `source->target` pair is holded by a |
217 | | -single `Transition`. |
218 | | - |
219 | | -## Deprecated features in 1.0 |
220 | | - |
221 | | -### Statemachine class |
222 | | - |
223 | | -- `StateMachine.run` is deprecated in favor of `StateMachine.send`. |
224 | | -- `StateMachine.allowed_transitions` is deprecated in favor of `StateMachine.allowed_events`. |
225 | | -- `Statemachine.is_<state>` is deprecated in favor of `StateMachine.<state>.is_active`. |
226 | | - |
227 | | - |
228 | | -### State class |
229 | | - |
230 | | -- `State.identification` is deprecated in favor of `State.id`. |
| 5 | +This release tag was replaced by [](1.0.1.md) due to an error on the metadata when uploading to |
| 6 | +pypi. |
0 commit comments