objectstate.dual_axis_resolver

Generic dual-axis resolver for lazy configuration inheritance.

This module provides the core inheritance resolution logic as a pure function, supporting both context hierarchy (X-axis) and sibling inheritance (Y-axis).

The resolver is completely generic and has no application-specific dependencies.

Functions

clear_mro_resolution_cache()

Clear the MRO resolution cache.

get_field_provenance(container_type, field_name)

Find which scope AND type provided the concrete value for a field.

invalidate_mro_cache_for_field(changed_type, ...)

Invalidate cache entries for a specific field that could be affected by a type change.

resolve_field_inheritance(obj, field_name, ...)

MRO-based inheritance resolution.

resolve_with_provenance(container_type, ...)

Resolve a field value AND find its provenance source in ONE walk.

objectstate.dual_axis_resolver.clear_mro_resolution_cache() None[source]

Clear the MRO resolution cache. Call when context fundamentally changes.

objectstate.dual_axis_resolver.invalidate_mro_cache_for_field(changed_type: type, field_name: str) None[source]

Invalidate cache entries for a specific field that could be affected by a type change.

PERFORMANCE: Only clears cache entries where: 1. The field_name matches 2. The obj_type has changed_type in its MRO (could inherit from it)

This is much more targeted than clear_mro_resolution_cache().

objectstate.dual_axis_resolver.resolve_field_inheritance(obj, field_name: str, available_configs: Dict[str, Any]) Any[source]

MRO-based inheritance resolution.

ALGORITHM: For LazyDataclass types:

  1. Check if same-type config exists in context with concrete value

  2. Walk MRO to find parent class configs with concrete value

  3. Fall back to static class defaults if nothing found

For concrete classes with lazy resolution:
  1. SKIP same-type lookup (if you created ProcessingConfig(group_by=None), you want None)

  2. Walk MRO to find PARENT class configs with concrete value (sibling inheritance)

  3. Return None if nothing found (no static default fallback)

Parameters:
  • obj – The object requesting field resolution

  • field_name – Name of the field to resolve

  • available_configs – Dict mapping config type names to config instances

Returns:

Resolved field value or None if not found

objectstate.dual_axis_resolver.resolve_with_provenance(container_type: type, field_name: str) Tuple[Any, str | None, type | None][source]

Resolve a field value AND find its provenance source in ONE walk.

TWO-PHASE DUAL-AXIS RESOLUTION:

Phase 1 - Hierarchy (same-type only, outer to inner):

Walk scopes from global → pipeline → step, checking ONLY the same-type config. If a concrete value is found, return it immediately. This gives hierarchy precedence for directly-set values.

Phase 2 - MRO fallback (only if Phase 1 found nothing):

Walk scopes from inner to outer (step → pipeline → global), doing MRO walk. This allows sibling inheritance when no concrete value exists in the hierarchy.

This ensures: - GlobalPipelineConfig.well_filter_config.well_filter overrides the same field

at PipelineConfig level (hierarchy precedence for same-type)

  • MRO inheritance only applies when NO concrete value exists in the hierarchy for the specific config type being resolved

PERFORMANCE: Single walk instead of separate resolve + provenance calls.

IMPORTANT: Must be called within a config_context() that has scope_ids set up. The layer stack is built by build_context_stack() with ancestor_objects_with_scopes.

Parameters:
  • container_type – The type containing the field (e.g., LazyPathPlanningConfig)

  • field_name – Name of the field to find provenance for (e.g., “well_filter”)

Returns:

The resolved value, scope that provided it, and the TYPE that has the concrete value (may differ from container_type due to MRO inheritance, e.g., PathPlanningConfig instead of WellFilterConfig). If no concrete value found, returns (None, None, None).

Return type:

(resolved_value, source_scope_id, source_type)

objectstate.dual_axis_resolver.get_field_provenance(container_type: type, field_name: str) Tuple[str | None, type | None][source]

Find which scope AND type provided the concrete value for a field.

CONVENIENCE WRAPPER: Calls resolve_with_provenance() and returns scope + type. Use resolve_with_provenance() directly when you also need the value.

Parameters:
  • container_type – The type containing the field (e.g., LazyPathPlanningConfig)

  • field_name – Name of the field to find provenance for (e.g., “well_filter”)

Returns:

The scope_id and type that provided the value. source_type may differ from container_type due to MRO inheritance. Returns (None, None) if no layer has a concrete value.

Return type:

(source_scope_id, source_type)