State Propagation and Parent Notification
ObjectState.forward_to_parent_state() lets one independently scoped state
notify a parent state that a conceptual field changed. It is a callback bridge,
not a second mutation or persistence path.
Scope hierarchy is not dataclass hierarchy
Nested dataclasses are flattened inside one ObjectState and updated with a
dotted path. They do not need parent forwarding:
from dataclasses import dataclass, field
from objectstate import ObjectState
@dataclass
class Palette:
background: str = "black"
@dataclass
class DisplayConfig:
palette: Palette = field(default_factory=Palette)
state = ObjectState(DisplayConfig(), scope_id="display")
state.update_parameter("palette.background", "white")
By contrast, two objects with distinct lifecycles may have independent states
whose scopes form a parent/child relationship. The child can use its
parent_state for ancestor context and can notify the parent when the parent
view owns a summary or preview of that child.
Explicit parent notification
from dataclasses import dataclass
from objectstate import ObjectState
@dataclass
class Collection:
summary: str = ""
@dataclass
class Entry:
value: int = 0
parent = ObjectState(Collection(), scope_id="collection")
child = ObjectState(
Entry(),
scope_id="collection::entry_0",
parent_state=parent,
)
parent.on_resolved_changed(lambda paths: print(paths))
child.forward_to_parent_state("summary")
# {'summary'}
The method requires a parent state. It guards against reentrant forwarding and
calls the parent’s resolved-change callbacks with the chosen path. It does not
write to the parent’s parameters or advance either saved baseline.
Field-path selection
The notification path is selected in this order:
the explicit
field_pathargument;the child’s internal parent-field binding, when its composing owner set one;
the last
scope_idsegment, with a numeric suffix removed.
Prefer an explicit field path at public composition boundaries. The scope-based
fallback is useful for conventional child scopes such as entry_0, but scope
naming is not structural dataclass ownership.