objectstate.context_manager
Generic contextvars-based context management system for lazy configuration.
This module provides explicit context scoping using Python’s contextvars to enable hierarchical configuration resolution without explicit parameter passing.
Key features: 1. Explicit context scoping with config_context() manager 2. Config extraction from functions, dataclasses, and objects 3. Config merging for context hierarchy 4. Clean separation between UI windows and contexts
Key components: - current_temp_global: ContextVar holding current merged global config - config_context(): Context manager for creating context scopes - extract_config_overrides(): Extract config values from any object type - merge_configs(): Merge overrides into base config
Functions
|
Build a complete context stack for placeholder resolution. |
Clear current context (for testing/debugging). |
|
Clear the extract_all_configs cache. |
|
|
Create new context scope with obj's matching fields merged into base config. |
|
Extract all config instances from a context object using type-driven approach. |
Extract all *_config attributes from current context. |
|
Get non-None fields as config overrides. |
|
Get parameter defaults as config overrides. |
|
Extract config attributes from step/pipeline objects. |
|
|
Get all ancestor types for target_type by walking up the known hierarchy. |
|
Get the base global config (fallback when no context set). |
Get information about current context for debugging. |
|
Get the current layer stack for provenance tracking. |
|
Get the current stack of context types (outermost to innermost). |
|
Get current context or None. |
|
Get the context type stack with normalized (base) types, excluding global configs. |
|
|
Extract root (plate path) from scope_key for visibility checks. |
|
Get all non-global types that come before target_type in the hierarchy. |
|
Check if ancestor_type comes before descendant_type in the context hierarchy. |
|
Check if two types are the same when normalized. |
|
Check if target scope is affected by edit at editing scope. |
|
Merge overrides into base config, creating new immutable instance. |
Register that context_obj_type is the parent of object_instance_type in the hierarchy. |
|
|
Set current context (for testing/debugging). |
|
Spawn a thread with current context propagated. |
Remove a type from the hierarchy registry when its editor closes. |
- objectstate.context_manager.config_context(obj, mask_with_none: bool = False, use_live_global: bool = True, scope_id: str | None = None)[source]
Create new context scope with obj’s matching fields merged into base config.
This is the universal context manager for all config context needs. It works by: 1. Finding fields that exist on both obj and the base config type 2. Using matching field values to create a temporary merged config 3. Setting that as the current context
- Parameters:
obj – Object with config fields (pipeline_config, step, etc.)
mask_with_none – If True, None values override/mask base config values. If False (default), None values are ignored (normal inheritance). Use True when editing GlobalPipelineConfig to mask thread-local loaded instance with static class defaults.
use_live_global – If True (default), use LIVE global config (UI sees unsaved edits). If False, use SAVED global config (compiler sees saved values).
scope_id – Optional scope identifier for provenance tracking (e.g., “plate_123::step_0”). When provided, the (scope_id, obj) tuple is pushed to context_layer_stack to enable inheritance source lookup via get_field_provenance().
- Usage:
# UI operations (default - uses LIVE) with config_context(orchestrator.pipeline_config):
# UI sees unsaved global config edits
# Compilation (explicit - uses SAVED) with config_context(orchestrator.pipeline_config, use_live_global=False):
# Compiler uses saved global config only
# With provenance tracking with config_context(step, scope_id=”plate_123::step_0”):
# Layer tracked for inheritance source lookup
- objectstate.context_manager.get_context_type_stack()[source]
Get the current stack of context types (outermost to innermost).
This enables generic hierarchy inference without hardcoding specific types. The stack represents the order in which config_context() calls were nested.
- Returns:
Tuple of types representing the context hierarchy. Empty tuple if no context is active.
Example
- with config_context(global_config): # stack = (GlobalPipelineConfig,)
- with config_context(pipeline_config): # stack = (GlobalPipelineConfig, PipelineConfig)
- with config_context(step): # stack = (GlobalPipelineConfig, PipelineConfig, Step)
types = get_context_type_stack() # types == (GlobalPipelineConfig, PipelineConfig, Step)
- objectstate.context_manager.get_context_layer_stack() Tuple[Tuple[str, Any], ...][source]
Get the current layer stack for provenance tracking.
Returns a tuple of (scope_id, obj) tuples, ordered from outermost to innermost. Only includes layers where scope_id was explicitly provided to config_context().
Used by get_field_provenance() to determine which scope provided a resolved value.
- Returns:
Tuple of (scope_id, obj) tuples representing the context hierarchy. Empty tuple if no layers with scope_ids are active.
Example
- with config_context(plate, scope_id=”plate_123”):
- with config_context(step, scope_id=”plate_123::step_0”):
layers = get_context_layer_stack() # layers == ((“plate_123”, plate), (“plate_123::step_0”, step))
- objectstate.context_manager.get_root_from_scope_key(scope_key: str) str[source]
Extract root (plate path) from scope_key for visibility checks.
scope_key format: - Pipeline-level: just plate path (e.g., “/path/to/plate”) - Step-level: plate_path::step_token (e.g., “/path/to/plate::step_a”) - Global: empty string
Returns the portion before “::” (or the whole string if no “::” present).
- objectstate.context_manager.is_scope_affected(target_scope_id: str | None, editing_scope_id: str | None) bool[source]
Check if target scope is affected by edit at editing scope.
Uses scope_id hierarchy - no type introspection needed: - None/”” (global) → affects all - “plate_path” (pipeline) → affects same plate + all its steps - “plate_path::token” (step) → affects only that step
- Parameters:
target_scope_id – The scope being checked for affectedness (None = global)
editing_scope_id – The scope where the edit occurred (None = global)
- Returns:
True if target should refresh when editing_scope changes
- objectstate.context_manager.register_hierarchy_relationship(context_obj_type, object_instance_type)[source]
Register that context_obj_type is the parent of object_instance_type in the hierarchy.
Called by ParameterFormManager when a root manager registers. This builds up the known hierarchy from actual usage patterns.
- Parameters:
context_obj_type – The parent/context type (e.g., PipelineConfig for Step editor)
object_instance_type – The child type being edited (e.g., Step)
Note
Types are normalized to base types (e.g., LazyPathPlanningConfig → PathPlanningConfig), but lazy-to-global normalization is prevented to preserve distinct hierarchy levels (PipelineConfig stays as PipelineConfig, not collapsed to GlobalPipelineConfig).
- objectstate.context_manager.unregister_hierarchy_relationship(object_instance_type)[source]
Remove a type from the hierarchy registry when its editor closes.
- Parameters:
object_instance_type – The type to remove from the registry
- objectstate.context_manager.get_ancestors_from_hierarchy(target_type)[source]
Get all ancestor types for target_type by walking up the known hierarchy.
Returns ancestors in order from outermost to innermost (excluding target_type itself).
- Parameters:
target_type – The type to find ancestors for
- Returns:
List of ancestor types in hierarchy order (grandparent, parent, …)
- objectstate.context_manager.get_normalized_stack()[source]
Get the context type stack with normalized (base) types, excluding global configs.
- Returns:
List of base types in hierarchy order (outermost to innermost), with global config types filtered out.
- objectstate.context_manager.get_types_before_in_stack(target_type)[source]
Get all non-global types that come before target_type in the hierarchy.
First tries the active context_type_stack (for resolution during config_context), then falls back to the known hierarchy registry (for cross-window updates).
- Parameters:
target_type – The type to find ancestors for (will be normalized)
- Returns:
List of base types that come before target_type in the hierarchy. Empty list if no ancestors found.
- objectstate.context_manager.is_ancestor_in_context(ancestor_type, descendant_type)[source]
Check if ancestor_type comes before descendant_type in the context hierarchy.
This determines whether changes to ancestor_type should affect descendant_type.
- Parameters:
ancestor_type – The potential ancestor type
descendant_type – The potential descendant type
- Returns:
True if ancestor_type is an ancestor of descendant_type, False otherwise.
- objectstate.context_manager.is_same_type_in_context(type_a, type_b)[source]
Check if two types are the same when normalized.
Handles lazy vs base type equivalence.
- Parameters:
type_a – First type to compare
type_b – Second type to compare
- Returns:
True if both types normalize to the same base type.
- objectstate.context_manager.build_context_stack(object_instance: object, ancestor_objects: list[object] | None = None, ancestor_objects_with_scopes: list[tuple[str, object]] | None = None, current_scope_id: str | None = None, use_live: bool = True)[source]
Build a complete context stack for placeholder resolution.
SINGLE SOURCE OF TRUTH: Uses ancestor_objects from ObjectStateRegistry as the sole mechanism for parent hierarchy. No separate context_obj parameter.
Layer order (innermost to outermost when entered): 1. Global context layer (from ancestors or thread-local) 2. Ancestor objects (from ObjectStateRegistry.get_ancestor_objects()) 3. Current object_instance
- Parameters:
object_instance – The object being edited (type used to infer global editing mode)
ancestor_objects – List of ancestor objects from least→most specific (from ObjectStateRegistry). This is the SINGLE SOURCE OF TRUTH for parent hierarchy. DEPRECATED: Use ancestor_objects_with_scopes for provenance tracking.
ancestor_objects_with_scopes – List of (scope_id, object) tuples from least→most specific. Enables provenance tracking via context_layer_stack.
current_scope_id – Scope ID for the current object_instance. Used for provenance tracking.
- Returns:
ExitStack with all context layers entered. Caller must manage the stack lifecycle.
- objectstate.context_manager.extract_from_function_signature(func) Dict[str, Any][source]
Get parameter defaults as config overrides.
This enables functions to provide config context through their parameter defaults. Useful for step functions that want to specify their own config values.
- Parameters:
func – Function to extract parameter defaults from
- Returns:
Dict of parameter_name -> default_value for parameters with defaults
- objectstate.context_manager.extract_from_dataclass_fields(obj) Dict[str, Any][source]
Get non-None fields as config overrides.
This extracts concrete values from dataclass instances, ignoring None values which represent fields that should inherit from context.
- Parameters:
obj – Dataclass instance to extract field values from
- Returns:
Dict of field_name -> value for non-None fields
- objectstate.context_manager.extract_from_object_attributes(obj) Dict[str, Any][source]
Extract config attributes from step/pipeline objects.
This handles orchestrators, steps, and other objects that have *_config attributes. It flattens the config hierarchy into a single dict of field overrides.
- Parameters:
obj – Object to extract config attributes from
- Returns:
Dict of field_name -> value for all non-None config fields
- objectstate.context_manager.merge_configs(base, overrides: Dict[str, Any])[source]
Merge overrides into base config, creating new immutable instance.
This creates a new config instance with override values merged in, preserving immutability of the original base config.
- Parameters:
base – Base config instance (base config type)
overrides – Dict of field_name -> value to override
- Returns:
New config instance with overrides applied
- objectstate.context_manager.get_base_global_config(use_live: bool = True)[source]
Get the base global config (fallback when no context set).
This provides the global config that was set up with ensure_global_config_context(), or a default if none was set. Used as the base for merging operations.
- Parameters:
use_live – If True (default), return LIVE config (UI sees unsaved edits). If False, return SAVED config (compiler sees saved values).
- Returns:
Current global config instance or default instance of base config type
- objectstate.context_manager.get_current_temp_global()[source]
Get current context or None.
This is the primary interface for resolution functions to access the current context. Returns None if no context is active.
- Returns:
Current merged global config or None
- objectstate.context_manager.set_current_temp_global(config)[source]
Set current context (for testing/debugging).
This is primarily for testing purposes. Normal code should use config_context() manager instead.
- Parameters:
config – Global config instance to set as current context
- Returns:
Token for resetting the context
- objectstate.context_manager.clear_current_temp_global()[source]
Clear current context (for testing/debugging).
This removes any active context, causing resolution to fall back to default behavior.
- objectstate.context_manager.get_context_info() Dict[str, Any][source]
Get information about current context for debugging.
- Returns:
Dict with context information including type, field count, etc.
- objectstate.context_manager.extract_all_configs_from_context() Dict[str, Any][source]
Extract all *_config attributes from current context.
This is used by the resolution system to get all available configs for cross-dataclass inheritance resolution.
- Returns:
Dict of config_name -> config_instance for all *_config attributes
- objectstate.context_manager.extract_all_configs(context_obj) Dict[str, Any][source]
Extract all config instances from a context object using type-driven approach.
PERFORMANCE: Results are cached per context object id. Cache is cleared on context changes via clear_extract_all_configs_cache().
This function leverages dataclass field type annotations to efficiently extract config instances, avoiding string matching and runtime attribute scanning.
- Parameters:
context_obj – Object to extract configs from (orchestrator, merged config, etc.)
- Returns:
Dict mapping config type names to config instances
- objectstate.context_manager.clear_extract_all_configs_cache() None[source]
Clear the extract_all_configs cache. Call when context changes.
- objectstate.context_manager.spawn_thread_with_context(target, daemon: bool = True, name: str = None, *args, **kwargs)[source]
Spawn a thread with current context propagated.
This ensures thread-local context (like GlobalPipelineConfig from config framework) is accessible in spawned thread.
Python’s threading module creates isolated thread-local storage, so contextvars don’t automatically propagate to spawned threads. This utility wraps threading.Thread to propagate the current context using contextvars.copy_context().
- Parameters:
target – The function to run in thread
daemon – Whether thread should be a daemon thread (default: True)
name – Optional thread name
*args – Positional arguments to pass to target
**kwargs – Keyword arguments to pass to target
- Returns:
The spawned Thread object
Example
>>> spawn_thread_with_context(my_function, arg1, kwarg=value) >>> # Equivalent to: >>> ctx = contextvars.copy_context() >>> threading.Thread(target=ctx.run, args=(my_function, arg1), kwargs={'kwarg': value}).start()