Subclassing any StateChart whose compound state body contains a callback raises
AttributeError: 'State' object has no attribute '_callbacks' at subclass creation time.
Reproduction
from statemachine import State, StateChart
class Journey(StateChart):
class shire(State.Compound):
bag_end = State(initial=True)
green_dragon = State()
visit_pub = bag_end.to(green_dragon)
def on_enter_green_dragon(self):
...
road = State(final=True)
depart = shire.to(road)
class Child(Journey): # AttributeError
pass
Traceback (most recent call last):
...
File "statemachine/factory.py", line 168, in _unpack_builders_callbacks
if state._callbacks:
^^^^^^^^^^^^^^^^
AttributeError: 'State' object has no attribute '_callbacks'
Cause
NestedStateFactory builds the compound as a State instance carrying _callbacks, and
StateMachineMetaclass._unpack_builders_callbacks consumes it with del state._callbacks:
def _unpack_builders_callbacks(cls):
callbacks = {}
for state in iterate_states(cls.states):
if state._callbacks:
callbacks.update(state._callbacks)
del state._callbacks
add_inherited reuses the same State objects in the subclass, so the second metaclass run
re-reads an attribute the first run deleted. Any callback in the body triggers it, including a
plain method or an on=/cond= decorator.
Notes
Only compound bodies are affected: a flat StateChart never populates _callbacks on a state.
A compound body with no callbacks at all also subclasses fine, since _callbacks stays None
and the attribute is never deleted.
Setting state._callbacks = None instead of deleting it, or guarding with
getattr(state, "_callbacks", None), both fix the crash. The subclass does not need the
setattr again, since it inherits the attributes the base class already received.
Reproduced on the current develop.
Subclassing any
StateChartwhose compound state body contains a callback raisesAttributeError: 'State' object has no attribute '_callbacks'at subclass creation time.Reproduction
Cause
NestedStateFactorybuilds the compound as aStateinstance carrying_callbacks, andStateMachineMetaclass._unpack_builders_callbacksconsumes it withdel state._callbacks:add_inheritedreuses the sameStateobjects in the subclass, so the second metaclass runre-reads an attribute the first run deleted. Any callback in the body triggers it, including a
plain method or an
on=/cond=decorator.Notes
Only compound bodies are affected: a flat
StateChartnever populates_callbackson a state.A compound body with no callbacks at all also subclasses fine, since
_callbacksstaysNoneand the attribute is never deleted.
Setting
state._callbacks = Noneinstead of deleting it, or guarding withgetattr(state, "_callbacks", None), both fix the crash. The subclass does not need thesetattragain, since it inherits the attributes the base class already received.Reproduced on the current
develop.