objectstate.object_state

ObjectState: Extracted MODEL from ParameterFormManager.

This class holds configuration state independently of UI widgets. Lifecycle: Created when object added to pipeline, persists until removed. PFM attaches to ObjectState when editor opens, detaches when closed.

FieldProxy: Type-safe proxy for accessing ObjectState fields via dotted attribute syntax.

Classes

FieldProxy(state, path, field_type)

Type-safe proxy for accessing ObjectState fields via dotted attribute syntax.

ObjectState(object_instance[, scope_id, ...])

Extracted MODEL from ParameterFormManager - pure Python state without PyQt dependencies.

class objectstate.object_state.FieldProxy(state: ObjectState, path: str, field_type: type)[source]

Type-safe proxy for accessing ObjectState fields via dotted attribute syntax.

Provides IDE autocomplete while using flat internal storage: - External API: state.fields.well_filter_config.well_filter (type-safe) - Internal: state.parameters[‘well_filter_config.well_filter’] (flat dict)

__init__(state: ObjectState, path: str, field_type: type)[source]

Initialize FieldProxy.

Parameters:
  • state – The ObjectState this proxy accesses

  • path – Current dotted path (empty for root)

  • field_type – Type of the object at this path

__getattr__(name: str) Any[source]

Get field value or nested FieldProxy.

Parameters:

name – Field name to access

Returns:

FieldProxy for nested dataclass fields, or resolved value for leaf fields

__setattr__(name: str, value: Any) None[source]

Prevent attribute setting - use state.update_parameter() instead.

class objectstate.object_state.ObjectState(object_instance: Any, scope_id: str | None = None, parent_state: ObjectState | None = None, exclude_params: List[str] | None = None, initial_values: Dict[str, Any] | None = None)[source]

Extracted MODEL from ParameterFormManager - pure Python state without PyQt dependencies.

Lifecycle: - Created when object added to pipeline (before any UI) - Persists until object removed from pipeline - PFM attaches to ObjectState when editor opens, detaches when closed

Core Attributes (8 total): - object_instance: Backing object (updated on Save) - parameters: Mutable working copy (None = unset, value = user-set) - _saved_resolved: Resolved snapshot at save time - _live_resolved: Resolved snapshot using live hierarchy (None = needs compute) - _invalid_fields: Fields needing partial recompute - nested_states: Recursive containment - _parent_state: Parent for context derivation - scope_id: Scope for registry lookup

Everything else is derived: - context_obj → _parent_state.object_instance - dirty_fields → _live_resolved != _saved_resolved - signature_diff_fields → parameters != signature defaults - user_set_fields → {k for k,v in parameters.items() if v is not None}

__init__(object_instance: Any, scope_id: str | None = None, parent_state: ObjectState | None = None, exclude_params: List[str] | None = None, initial_values: Dict[str, Any] | None = None)[source]

Initialize ObjectState with minimal attributes.

Parameters:
  • object_instance – The object being edited (dataclass, callable, etc.) If the object declares __objectstate_delegate__, parameters are extracted from that attribute instead (delegation pattern).

  • scope_id – Scope identifier for filtering (e.g., “/path::step_0”)

  • parent_state – Parent ObjectState for nested forms

  • exclude_params – Parameters to exclude from extraction (e.g., [‘func’] for FunctionStep)

  • initial_values – Initial values to override extracted defaults (e.g., saved kwargs)

property context_obj: Any | None

Derive context_obj from parent_state (no separate attribute needed).

property saved_object: Any

Get the saved baseline object with the correct type.

For delegation: returns _extraction_target (the delegate/config) For non-delegation: returns object_instance

This is the object that should be used for context resolution when use_saved=True. It represents the “saved” state of the editable object.

property fields: FieldProxy

Type-safe field access via FieldProxy.

Returns:

state.fields.well_filter_config.well_filter → resolved value

Return type:

FieldProxy for accessing fields with IDE autocomplete

property parameter_descriptions: Dict[str, str | None]

Get parameter descriptions for all parameters.

Returns:

Dictionary mapping dotted parameter paths to their descriptions (value may be None). E.g., {‘well_filter_config.well_filter’: ‘Filter wells by…’}

on_resolved_changed(callback: Callable[[Set[str]], None]) None[source]

Subscribe to resolved value change notifications.

The callback is called when resolved values actually change (not just when cache is invalidated). This enables UI components to flash/highlight specific fields when their resolved values change.

Parameters:

callback – Function that takes a Set[str] of changed dotted paths. E.g., {‘processing_config.group_by’, ‘well_filter_config.well_filter’}

off_resolved_changed(callback: Callable[[Set[str]], None]) None[source]

Unsubscribe from resolved value change notifications.

on_state_changed(callback: Callable[[], None]) None[source]

Subscribe to materialized state change notifications (dirty/signature diffs).

off_state_changed(callback: Callable[[], None]) None[source]

Unsubscribe from materialized state change notifications.

forward_to_parent_state(field_path: str | None = None) None[source]

Forward child state changes to parent state.

Notifies the parent state that a field has conceptually changed, causing the parent’s on_resolved_changed callbacks to fire (e.g., for UI flash).

Parameters:

field_path – Dotted path of the field that changed (e.g., ‘func’, ‘config.value’). If None, uses _parent_field_name if set, otherwise defaults to scope suffix.

Example

# In child ObjectState callback def on_child_changed(changed_paths):

child_state.forward_to_parent_state(‘func’) # Notify parent its ‘func’ changed

Raises:

RuntimeError – If called on a state without a parent state.

reset_all_parameters() None[source]

Reset all parameters to defaults.

update_parameter(param_name: str, value: Any) None[source]

Update parameter value in state.

Enforces invariants: 1. State mutation → scope+type+field aware cache invalidation 2. State mutation → global token increment (for live context cache)

PERFORMANCE: Three-tier filtering for minimal invalidation: - SCOPE: Only descendants of this scope (they inherit from us) - TYPE: Only states with this type in their tree - FIELD: Only the specific field that changed

Parameters:
  • param_name – Name of parameter to update

  • value – New value

get_resolved_value(param_name: str) Any[source]

Get resolved value for a field from the bulk snapshot.

Parameters:

param_name – Field name to resolve (can be dotted path like ‘path_planning_config.well_filter’)

Returns:

Resolved value from _live_resolved snapshot. For dataclass container fields, returns a reconstructed dataclass instance with all sub-fields populated from live resolved values.

get_saved_resolved_value(param_name: str) Any[source]

Get saved resolved value for a field from the saved snapshot.

Unlike get_resolved_value() which returns live values (including unsaved edits), this returns the saved baseline with inheritance applied. This is useful for compilation and other operations that should only consider saved state.

For container fields (dataclasses), this reconstructs the entire nested dataclass with all sub-fields populated from saved resolved values.

Parameters:

param_name – Field name to resolve (can be dotted path like ‘path_planning_config.well_filter’)

Returns:

Saved resolved value from _saved_resolved snapshot. For dataclass fields, returns a reconstructed dataclass instance.

get_provenance(param_name: str) Tuple[str, type] | None[source]

Get the source scope_id and type for an inherited field value.

For fields where the local value is None (inherited), returns the scope_id of the ancestor that provided the value AND the type that has it. Used for click-to-source navigation in the UI.

The source_type may differ from the local container type due to MRO inheritance. For example, WellFilterConfig.well_filter might inherit from PathPlanningConfig.

NOTE: Returns provenance even when the resolved value is None (signature default). A “concrete None” just means the class default is None and nothing overrode it.

Parameters:

param_name – Field name (can be dotted path like ‘path_planning_config.well_filter’)

Returns:

The scope and type that provided the value, or None if the value is local (not inherited).

Return type:

(source_scope_id, source_type)

find_path_for_type(container_type: type) str | None[source]

Find the path prefix for a container type in this ObjectState.

With flat storage, nested configs are identified by their path prefix. Given a container type (e.g., PathPlanningConfig), returns the path prefix (e.g., ‘path_planning_config’).

Handles type normalization: LazyPathPlanningConfig matches PathPlanningConfig.

Parameters:

container_type – The type to find the path for

Returns:

Path prefix for the type, or None if not found. Returns “” (empty string) if type is the root object type.

resolve_for_type(container_type: type, field_name: str) Any[source]

Resolve a field value given the container type and field name.

Convenience method for callers who have a config object but don’t know its path in the flat storage. Finds the path prefix for the container type and constructs the full dotted path.

Parameters:
  • container_type – Type of the containing config (e.g., PathPlanningConfig)

  • field_name – Field name within that config (e.g., ‘well_filter’)

Returns:

Resolved value, or None if not found

invalidate_cache() None[source]

Invalidate resolved cache - forces full recompute on next access.

invalidate_self_and_nested() None[source]

Invalidate this state’s cache.

With flat storage, no nested states to invalidate.

invalidate_field(field_name: str) None[source]

Mark a specific field as needing recomputation.

PERFORMANCE: Field-level invalidation - only the changed field needs recomputation, not all 20+ fields in the config.

update_object_instance(new_instance: Any) None[source]

Replace object_instance with a new instance and re-extract parameters.

This is used when the object being edited is replaced externally (e.g., from code mode execution). The ObjectState is updated to point to the new instance and parameters are re-extracted to match the new object’s state.

For delegation cases, this updates _extraction_target. For non-delegation cases, it updates object_instance directly.

Parameters:

new_instance – The new object instance to extract parameters from

property last_changed_field: str | None

Field that most recently changed value (not just dirty status).

This tracks any value change regardless of saved/unsaved state, useful for time-travel navigation to show what changed in a transition.

reset_parameter(param_name: str) None[source]

Reset parameter to signature default (None for lazy dataclasses).

Delegates to update_parameter() to ensure consistent invalidation behavior.

get_current_values() Dict[str, Any][source]

Get current parameter values from state.

With flat storage, this returns the flat dict with dotted paths. Callers needing nested structure should use to_object() instead.

For ObjectState, this reads directly from self.parameters. PFM overrides this to also read from widgets.

property dirty_fields: Set[str]

Fields where resolved_live != resolved_saved.

property signature_diff_fields: Set[str]

Fields where raw != signature_default.

property is_raw_dirty: bool

Check if raw parameters differ from saved parameters (not resolved values).

mark_saved() None[source]

Mark current state as saved baseline.

UNIFIED: Works for any object_instance type.

CRITICAL: Invalidates descendant caches for any parameters that changed. This ensures that when saving, other windows that inherited from the old saved values get their caches invalidated so they pick up new values. This mirrors what restore_saved() does but in the opposite direction.

Invalidation is based on comparing the OLD object_instance (about to be replaced) with the NEW self.parameters (live values used for reconstruction).

restore_saved(*, propagate_descendants: bool = True) None[source]

Restore parameters to the last saved baseline (from object_instance).

UNIFIED: Works for any object_instance type.

CRITICAL: Invalidates descendant caches for any parameters that changed. This ensures that when closing a window without saving, other windows that inherited from the unsaved values get their caches invalidated.

Also emits on_resolved_changed for THIS state so same-level observers (like list items subscribed to this ObjectState) flash when values revert.

should_skip_updates() bool[source]

Check if updates should be skipped due to batch operations.

to_object(*, update_delegate: bool = False, sync_delegate: bool = True) Any[source]

Reconstruct object from flat parameters with updated nested configs.

BOUNDARY METHOD - EXPENSIVE - only call at system boundaries: - Save operation - Execute operation - Serialization

UNIFIED: Works for ANY object_instance type. - Python functions: can’t copy, return original - Everything else: shallow copy + reconstruct nested dataclass fields

DELEGATION: If __objectstate_delegate__ was used: - Reconstructs the delegate (e.g., pipeline_config) - If update_delegate=True, updates the delegate attribute on object_instance - Returns the reconstructed delegate (NOT object_instance) - Callers needing the lifecycle object (orchestrator) should use state.object_instance

Parameters:
  • update_delegate – If True, apply reconstructed delegate to object_instance

  • sync_delegate – If True (default), check for delegate changes before reconstruction. Set to False during cache recomputation to avoid re-entrant invalidation.

Returns:

The reconstructed object that matches the stored parameters. For delegation, this is the delegate type (config), not the lifecycle object.