Validators and guards¶
Validations and Guards are checked before a transition is started. They are meant to stop a transition to occur.
The main difference is that Validators raise exceptions to stop the flow, and Guards
act like predicates that shall resolve to a boolean
value.
See also
Please see Dynamic dispatch to know more about how this lib supports multiple signatures for all the available callbacks, being validators and guards or Actions.
Guards¶
Also known as Conditional transition.
A guard is a condition that may be checked when a StateMachine wants to handle
an Event. A guard is declared on the Transition, and when that Transition
would trigger, then the guard (if any) is checked. If the guard is True
then the transition does happen. If the guard is False
, the transition
is ignored.
When Transitions have guards, then it’s possible to define two or more transitions for the same Event from the same State. When the Event happens, then the guarded transitions are checked, one by one, and the first transition whose guard is true will be used, and the others will be ignored.
A guard is generally a boolean function or boolean variable and must not have any side effects. Side effects are reserved for Actions.
There are two variations of Guard clauses available:
- cond
A list of conditions, acting like predicates. A transition is only allowed to occur if all conditions evaluate to
True
.
Single condition:
cond="condition"
Multiple conditions:
cond=["condition1", "condition2"]
- unless
Same as
cond
, but the transition is only allowed if all conditions evaluate toFalse
.
Single condition:
unless="condition"
Multiple conditions:
unless=["condition1", "condition2"]
Hint
In Python, a boolean value is either True
or False
. However, there are also specific values that
are considered “falsy” and will evaluate as False
when used in a boolean context.
These include:
The special value
None
.Numeric values of
0
or0.0
.Empty strings, lists, tuples, sets, and dictionaries.
Instances of certain classes that define a
__bool__()
or__len__()
method that returnsFalse
or0
, respectively.
On the other hand, any value that is not considered “falsy” is considered “truthy” and will evaluate to True
when used in a boolean context.
So, a condition s1.to(s2, cond=lambda: [])
will evaluate as False
, as an empty list is a
falsy value.
Validators¶
Are like Guards, but instead of evaluating to boolean, they are expected to raise an exception to stop the flow. It may be useful for imperative-style programming when you don’t want to continue evaluating other possible transitions and exit immediately.
Single validator:
validators="validator"
Multiple validator:
validators=["validator1", "validator2"]
Both conditions and validators can also be combined for a single event.
<event> = <state1>.to(<state2>, cond="condition1", unless="condition2", validators="validator")
Consider this example:
class InvoiceStateMachine(StateMachine):
unpaid = State(initial=True)
paid = State()
failed = State()
paused = False
offer_valid = True
pay = (
unpaid.to(paid, cond="payment_success") |
unpaid.to(failed, validators="validator", unless="paused") |
failed.to(paid, cond=["payment_success", "offer_valid"])
)
def payment_success(self, event_data):
return <condition logic goes here>
def validator(self):
return <validator logic goes here>
See also
See the example All actions machine for a complete example of validators and guards.
Reference: Statecharts.