-
-
Notifications
You must be signed in to change notification settings - Fork 111
Subclassing a StateChart with callbacks in a State.Compound body raises AttributeError #646
Description
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.