StateChart 3.2.1¶
Not released yet
Bug fixes in 3.2.1¶
Symmetric state for on_exit_state across compound boundaries¶
When exiting a compound state directly (a transition like child -> outsider),
the generic on_exit_state() callback reported the transition’s source for
every exited state. Exiting child and its parent parent both arrived
with state and source bound to child, so the parent level was never
observable and the two exit calls were indistinguishable.
This was asymmetric with on_enter_state(), which already binds state (and
target) to each individual state being entered. The exit side now matches:
state (and source) is bound to the individual state being exited.
>>> from statemachine import State, StateChart
>>> class FSM(StateChart):
... orphan = State(initial=True)
...
... class parent(State.Compound):
... child = State()
...
... switch = orphan.to(parent.child) | parent.child.to(orphan)
...
... def on_exit_state(self, source, state):
... print(f"exit {state.id} (source={source.id})")
>>> sm = FSM()
>>> sm.send("switch")
exit orphan (source=orphan)
>>> sm.send("switch")
exit child (source=child)
exit parent (source=parent)
Before this fix, the last line read exit child (source=child), hiding the
parent. State-specific callbacks (on_exit_<state>) were already correctly
keyed per state and are unaffected. Flat (non-compound) machines are also
unaffected, since there the exited state is always the transition’s source.
#634.
Negative indices in OrderedSet.__getitem__¶
OrderedSet.__getitem__ raised ValueError (leaking from itertools.islice)
when called with a negative index, instead of following the sequence protocol.
Negative indices now count from the end like any Python sequence, and an index
that is still out of range after normalisation raises IndexError:
>>> from statemachine.orderedset import OrderedSet
>>> s = OrderedSet([1, 2, 3])
>>> s[-1]
3
>>> s[-3]
1
>>> s[-4]
Traceback (most recent call last):
...
IndexError: index -4 out of range
#633.
Lazy / translation proxy objects as name¶
A State (or Event) name can now be any object castable to str, including
lazy translation proxies (e.g. django.utils.translation.gettext_lazy).
Earlier 3.x releases stored the value as-is and later assumed it was a real
str, so a proxy broke message formatting (notably the TransitionNotAllowed
message) and str(state). The proxy is now kept untouched and only resolved via
str() at the point of display, so the active locale is honored at render time
instead of at class-definition time.
>>> from statemachine import State, StateMachine
>>> class Lazy: # stand-in for a translation proxy (resolved on str())
... def __init__(self, value):
... self.value = value
... def __str__(self):
... return self.value
>>> class SM(StateMachine):
... draft = State(Lazy("Rascunho"), initial=True)
... published = State(Lazy("Publicado"), final=True)
... publish = draft.to(published)
>>> str(SM.draft)
'Rascunho'
#632.