Skip to content

Commit e8fa6de

Browse files
committed
Merge tag '1.0.1' into develop
*January 11, 2022* Welcome to StateMachine 1.0! This version is a huge refactoring adding a lot of new and exiting features. We hope that you enjoy. These release notes cover the [](#whats-new-in-10), as well as some [backwards incompatible changes](#backwards-incompatible-changes-in-10) you'll want to be aware of when upgrading from StateMachine 0.9.0 or earlier. We've [begun the deprecation process for some features](#deprecated-features-in-10). StateMachine 1.0 supports Python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, and 3.11. This is the last release to support Python 2.7, 3.5 and 3.6. Transitions now support `cond` and `unless` parameters, to restrict the execution. ```python class ApprovalMachine(StateMachine): "A workflow machine" requested = State("Requested", initial=True) accepted = State("Accepted") rejected = State("Rejected") completed = State("Completed") validate = requested.to(accepted, cond="is_ok") | requested.to(rejected) ``` You can generate diagrams from your statemachine. Example: ![OrderControl](docs/images/order_control_machine_initial.png) Every single callback, being `actions` or `guards`, is now handled equally by the library. Also, we've improved the internals in a way that you can implement your callbacks with any number of arbritrary positional or keyword arguments (`*args, **kwargs`), and the dispatch will match the available arguments with your method signature. This means that if on your `on_enter_<state>()` or `on_execute_<event>()` method, you also need to know the `source` (`state`), or the `event` (`event`), or access a keyword argument passed with the trigger, you're covered. Just add this parameter to the method and It will be passed by the dispatch mechanics. Example of what's available: ```py def action_or_guard_method_name(self, *args, event_data, event, source, state, model, **kwargs): pass ``` Observers are a way do generically add behaviour to a StateMachine without changing it's internal implementation. The `StateMachine` itself is registered as an observer, so by using `StateMachine.add_observer()` an external object can have the same level of functionalities provided to the built-in class. - Fixed mypy complaining about incorrect type for ``StateMachine`` class. - The initial `state` is now entered when the machine starts. The `actions`, if defined, `on_enter_state` and `on_enter_<state>` are now called. Prior to this release, as we didn't have `validators-and-guards`, there wasn't an elegant way to declare multiples target states starting from the same pair (event, state). But the library allowed a near-hackish way, by declaring a target state as the result of the `on_<event>` callback. So, the previous code (not valid anymore): ```py class ApprovalMachine(StateMachine): "A workflow machine" requested = State('Requested', initial=True) accepted = State('Accepted') rejected = State('Rejected') validate = requested.to(accepted, rejected) def on_validate(self, current_time): if self.model.is_ok(): self.model.accepted_at = current_time return self.accepted else: return self.rejected ``` Should be rewriten to use `guards`, like this: ``` py class ApprovalMachine(StateMachine): "A workflow machine" requested = State("Requested", initial=True) accepted = State("Accepted") rejected = State("Rejected") validate = requested.to(accepted, conditions="is_ok") | requested.to(rejected) def on_validate(self, current_time): self.model.accepted_at = current_time ``` This issue was reported at [#265](#265). Now StateMachine will execute the actions associated with the `on_enter_state` and `on_enter_<state>` when initialized, if they exists. Statemachine integrity checks are now performed at class declaration (import time) instead of on instance creation. This allows early feedback of invalid definitions. This was the previous behaviour, you only got an error when trying to instantiate a StateMachine: ```py class CampaignMachine(StateMachine): "A workflow machine" draft = State('Draft', initial=True) producing = State('Being produced') closed = State('Closed', initial=True) # Should raise an Exception when instantiated add_job = draft.to(draft) | producing.to(producing) produce = draft.to(producing) deliver = producing.to(closed) with pytest.raises(exceptions.InvalidDefinition): CampaignMachine() ``` Not this is performed as the class definition is performed: ```py with pytest.raises(exceptions.InvalidDefinition): class CampaignMachine(StateMachine): "A workflow machine" draft = State("Draft", initial=True) producing = State("Being produced") closed = State( "Closed", initial=True ) # Should raise an Exception right after the class is defined add_job = draft.to(draft) | producing.to(producing) produce = draft.to(producing) deliver = producing.to(closed) ``` - Due to the check validations and setup performed at the machine initialization, it's now harder to perform monkey-patching to add callbacks at runtime (not a bad thing after all). - `TransitionNotAllowed` changed internal attr from `transition` to `event`. - `CombinedTransition` does not exist anymore. `State` now holds a flat `Transition` list called `TransitionList` that implements de `OR` operator. This turns a valid StateMachine traversal much easier: `[transition for state in machine.states for transition in state.transitions]`. - `StateMachine.get_transition` is removed. See `event`. - The previous excetions `MultipleStatesFound` and `MultipleTransitionCallbacksFound` are removed. Since now you can have more than one callback defined to the same transition. - `on_enter_state` and `on_exit_state` now accepts any combination of parameters following the `dynamic-dispatch` rules. Previously it only accepted the `state` param. - `Transition.__init__` param `on_execute` renamed to simply `on`, and now follows the `dynamic-dispatch`. - `Transition.destinations` removed in favor of `Transition.target` (following SCXML convention). Now each transition only points to a unique target. Each `source->target` pair is holded by a single `Transition`. - `StateMachine.run` is deprecated in favor of `StateMachine.send`. - `StateMachine.allowed_transitions` is deprecated in favor of `StateMachine.allowed_events`. - `Statemachine.is_<state>` is deprecated in favor of `StateMachine.<state>.is_active`. - `State.identification` is deprecated in favor of `State.id`.
2 parents 03a7baf + 4f9ce66 commit e8fa6de

12 files changed

Lines changed: 246 additions & 235 deletions

docs/auto_examples/all_actions_machine.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"outputs": [],
1010
"source": [
11-
"%matplotlib inline"
11+
"# import piplite\n# await piplite.install('python-statemachine[diagrams]')"
1212
]
1313
},
1414
{

docs/auto_examples/guess_the_number_machine.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"outputs": [],
1010
"source": [
11-
"%matplotlib inline"
11+
"# import piplite\n# await piplite.install('python-statemachine[diagrams]')"
1212
]
1313
},
1414
{

docs/auto_examples/order_control_machine.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"outputs": [],
1010
"source": [
11-
"%matplotlib inline"
11+
"# import piplite\n# await piplite.install('python-statemachine[diagrams]')"
1212
]
1313
},
1414
{

docs/auto_examples/order_control_rich_model_machine.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"outputs": [],
1010
"source": [
11-
"%matplotlib inline"
11+
"# import piplite\n# await piplite.install('python-statemachine[diagrams]')"
1212
]
1313
},
1414
{

docs/auto_examples/traffic_light_machine.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"outputs": [],
1010
"source": [
11-
"%matplotlib inline"
11+
"# import piplite\n# await piplite.install('python-statemachine[diagrams]')"
1212
]
1313
},
1414
{

docs/conf.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,5 +356,8 @@
356356
"min_reported_time": 9999,
357357
"thumbnail_size": (400, 280),
358358
"image_scrapers": ("matplotlib", MachineScraper()),
359-
# "first_notebook_cell": "",
359+
"first_notebook_cell": (
360+
"# import piplite\n"
361+
"# await piplite.install('python-statemachine[diagrams]')\n"
362+
),
360363
}

docs/releases/1.0.0.md

Lines changed: 2 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -2,229 +2,5 @@
22

33
*January 11, 2022*
44

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-
![OrderControl](../images/order_control_machine_initial.png)
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

Comments
 (0)