StateMachine 1.0.1¶
January 11, 2023
Welcome to StateMachine 1.0.1!
This version is a huge refactoring adding a lot of new and exiting features. We hope that you enjoy.
These release notes cover the What's new in 1.0, as well as some backwards incompatible changes 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.
Python compatibility in 1.0¶
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.
What’s new in 1.0¶
Validators and Guards¶
Transitions now support cond
and unless
parameters, to restrict
the execution.
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)
See also
See Validators and guards for more details.
Support for diagrams¶
You can generate diagrams from your statemachine.
Example:
See also
See Diagrams for more details.
Unified dispatch mecanism for callbacks (actions and guards)¶
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:
def action_or_guard_method_name(self, *args, event_data, event, source, state, model, **kwargs):
pass
See also
See Dynamic dispatch for more details.
Add observers to a running StateMachine¶
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.
See also
See Observers for more details.
Minor features in 1.0¶
Backwards incompatible changes in 1.0¶
Multiple targets from the same origin state¶
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):
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:
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
See also
See Validators and guards of more details.
StateMachine now enters the initial state¶
This issue was reported at #265.
Now StateMachine will execute the actions associated with the on_enter_state
and
on_enter_<state>
when initialized, if they exists.
See also
See State actions for more details.
Integrity is checked at class definition¶
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:
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:
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)
Other backwards incompatible changes in 1.0¶
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 fromtransition
toevent
.CombinedTransition
does not exist anymore. State now holds a flat Transition list calledTransitionList
that implements deOR
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
andMultipleTransitionCallbacksFound
are removed. Since now you can have more than one callback defined to the same transition.on_enter_state
andon_exit_state
now accepts any combination of parameters following the Dynamic dispatch rules. Previously it only accepted thestate
param.Transition.__init__
paramon_execute
renamed to simplyon
, and now follows the Dynamic dispatch.Transition.destinations
removed in favor ofTransition.target
(following SCXML convention). Now each transition only points to a unique target. Eachsource->target
pair is holded by a singleTransition
.
Deprecated features in 1.0¶
Statemachine class¶
StateMachine.run
is deprecated in favor ofStateMachine.send
.StateMachine.allowed_transitions
is deprecated in favor ofStateMachine.allowed_events
.Statemachine.is_<state>
is deprecated in favor ofStateMachine.<state>.is_active
.
State class¶
State.identification
is deprecated in favor ofState.id
.