objectstate.lazy_factory
Generic lazy dataclass factory using flexible resolution.
Functions
|
Decorator that automatically creates: 1. |
Add lazy __getattribute__ to an existing class. |
|
|
Create dataclass for editing with configurable value preservation. |
Create a decorator factory for a specific global config class. |
|
Ensure proper thread-local storage setup for any global config type. |
|
|
Get the base type for a lazy dataclass type. |
|
Look up group abbreviation from GROUP_ABBREVIATIONS_REGISTRY. |
Get names of fields inherited from parent dataclasses (not defined in cls itself). |
|
|
Get the lazy type for a base dataclass type. |
Get all registered lazy types for constructor patching. |
|
|
Check if a config instance is an instance of a global config class. |
|
Check if a config type is a global config (marked by @auto_create_decorator). |
|
Check if an object or type is a lazy dataclass. |
|
Context manager that patches lazy dataclass constructors to preserve None vs concrete distinction. |
Rebuild lazy config to reference new global config while preserving field states. |
|
|
Rebuild a dataclass via make_dataclass with None defaults for specified fields. |
|
Register a lazy dataclass type for constructor patching. |
|
Register mapping between lazy dataclass type and its base type. |
|
Replace dataclass fields while preserving raw None values. |
Recursively resolve lazy dataclass instances to concrete values for serialization. |
Classes
Virtual base class for all global config types. |
|
Metaclass that makes isinstance(obj, GlobalConfigBase) work by checking _is_global_config marker. |
|
Base class for all lazy dataclasses created by LazyDataclassFactory. |
|
Generic factory for creating lazy dataclasses with flexible resolution. |
|
Declarative method bindings for lazy dataclasses. |
|
|
Decorator/marker for abbreviations in config classes. |
- objectstate.lazy_factory.get_inherited_field_names(cls: Type) set[source]
Get names of fields inherited from parent dataclasses (not defined in cls itself).
A field is “inherited” if it exists in a parent’s __dataclass_fields__ but is NOT in this class’s own __annotations__ (i.e., not redefined here).
- objectstate.lazy_factory.rebuild_with_none_defaults(cls: Type, field_names_to_none: set | None = None, new_name: str | None = None) Type[source]
Rebuild a dataclass via make_dataclass with None defaults for specified fields.
This is the UNIFIED approach for both base classes (inherit_as_none) and lazy classes. Instead of patching Field objects after @dataclass, we rebuild with correct defaults.
- Parameters:
cls – The dataclass to rebuild
field_names_to_none – Fields that should have default=None. If None, ALL fields get default=None (for lazy classes).
new_name – Optional new class name (for lazy classes)
- Returns:
A new class with the same fields but modified defaults
- objectstate.lazy_factory.replace_raw(instance, **changes)[source]
Replace dataclass fields while preserving raw None values.
Unlike dataclasses.replace(), this function uses object.__getattribute__ to get field values, preventing lazy resolution from being triggered. This is critical for lazy dataclasses where None means “inherit from parent” and must not be resolved during copy operations.
- Parameters:
instance – The dataclass instance to copy
**changes – Field values to override
- Returns:
A new instance with raw values preserved (not resolved)
- objectstate.lazy_factory.register_lazy_type_mapping(lazy_type: Type, base_type: Type) None[source]
Register mapping between lazy dataclass type and its base type.
- objectstate.lazy_factory.get_base_type_for_lazy(lazy_type: Type) Type | None[source]
Get the base type for a lazy dataclass type.
- objectstate.lazy_factory.register_lazy_type(cls: Type) Type[source]
Register a lazy dataclass type for constructor patching.
This decorator/function marks a lazy dataclass as needing constructor patching when used in code execution contexts (e.g., exec()).
- Parameters:
cls – The lazy dataclass type to register
- Returns:
The class unchanged (allows use as decorator)
Example
register_lazy_type(LazyPipelineConfig) register_lazy_type(LazyZarrConfig)
# Or as decorator: @register_lazy_type class LazyCustomConfig:
…
- objectstate.lazy_factory.get_registered_lazy_types() frozenset[source]
Get all registered lazy types for constructor patching.
- Returns:
Immutable set of registered lazy dataclass types
- objectstate.lazy_factory.is_lazy_dataclass(obj_or_type) bool[source]
Check if an object or type is a lazy dataclass.
ANTI-DUCK-TYPING: Uses isinstance() check against LazyDataclass base class instead of hasattr() attribute sniffing.
Works with both instances and types, and naturally handles Optional types without unwrapping.
- Parameters:
obj_or_type – Either a dataclass instance or a dataclass type
- Returns:
True if the object/type is a lazy dataclass
Examples
>>> is_lazy_dataclass(PipelineConfig) # True (type check) >>> is_lazy_dataclass(GlobalPipelineConfig) # False >>> is_lazy_dataclass(pipeline_config_instance) # True (instance check) >>> is_lazy_dataclass(LazyPathPlanningConfig) # True >>> is_lazy_dataclass(PathPlanningConfig) # False
# Works with Optional without unwrapping! >>> config: Optional[PipelineConfig] = PipelineConfig() >>> is_lazy_dataclass(config) # True - checks the instance, not the type annotation
- class objectstate.lazy_factory.GlobalConfigMeta[source]
Metaclass that makes isinstance(obj, GlobalConfigBase) work by checking _is_global_config marker.
- This enables type-safe isinstance checks without inheritance:
- if isinstance(config, GlobalConfigBase): # Returns True for GlobalPipelineConfig
# Returns False for PipelineConfig (lazy version)
- class objectstate.lazy_factory.GlobalConfigBase[source]
Virtual base class for all global config types.
Uses custom metaclass to check _is_global_config marker instead of actual inheritance. This prevents lazy versions (PipelineConfig) from being considered global configs.
- Usage:
if isinstance(config, GlobalConfigBase): # Generic, works for any global config
- Instead of:
if isinstance(config, GlobalPipelineConfig): # Hardcoded, breaks extensibility
- class objectstate.lazy_factory.LazyDataclass[source]
Base class for all lazy dataclasses created by LazyDataclassFactory.
- This enables isinstance() checks without duck typing or unwrapping:
isinstance(config, LazyDataclass) # Works! isinstance(optional_config, LazyDataclass) # Works even for Optional!
All lazy dataclasses inherit from this, regardless of naming convention: - PipelineConfig (lazy version of GlobalPipelineConfig) - LazyPathPlanningConfig - LazyWellFilterConfig - etc.
ANTI-DUCK-TYPING: Use isinstance(obj, LazyDataclass) instead of hasattr() checks.
- objectstate.lazy_factory.is_global_config_type(config_type: Type) bool[source]
Check if a config type is a global config (marked by @auto_create_decorator).
- GENERIC SCOPE RULE: Use this instead of hardcoding class name checks like:
if config_class == GlobalPipelineConfig:
- Instead use:
if is_global_config_type(config_class):
- Parameters:
config_type – The config class to check
- Returns:
True if the type is marked as a global config, False otherwise
- objectstate.lazy_factory.is_global_config_instance(config_instance: Any) bool[source]
Check if a config instance is an instance of a global config class.
- GENERIC SCOPE RULE: Use this instead of hardcoding isinstance checks like:
if isinstance(config, GlobalPipelineConfig):
- Instead use:
if is_global_config_instance(config):
- Or use the virtual base class:
if isinstance(config, GlobalConfigBase):
- Parameters:
config_instance – The config instance to check
- Returns:
True if the instance is of a global config type, False otherwise
- objectstate.lazy_factory.get_lazy_type_for_base(base_type: Type) Type | None[source]
Get the lazy type for a base dataclass type.
- objectstate.lazy_factory.bind_lazy_resolution_to_class(cls: Type) None[source]
Add lazy __getattribute__ to an existing class.
This enables concrete classes (like WellFilterConfig stored in GlobalPipelineConfig) to resolve None values via MRO without changing their static defaults.
- Parameters:
cls – The class to add lazy resolution to
- class objectstate.lazy_factory.LazyMethodBindings[source]
Declarative method bindings for lazy dataclasses.
- static create_resolver() Callable[[Any, str], Any][source]
Create field resolver method using new pure function interface.
- static create_getattribute() Callable[[Any, str], Any][source]
Create lazy __getattribute__ method using new context system.
- class objectstate.lazy_factory.LazyDataclassFactory[source]
Generic factory for creating lazy dataclasses with flexible resolution.
- static make_lazy_simple(base_class: Type, lazy_class_name: str = None) Type[source]
Create lazy dataclass using new contextvars system.
SIMPLIFIED: No complex hierarchy providers or field path detection needed. Uses new contextvars system for all resolution.
- Parameters:
base_class – Base dataclass to make lazy
lazy_class_name – Optional name for the lazy class
- Returns:
Generated lazy dataclass with contextvars-based resolution
- objectstate.lazy_factory.patch_lazy_constructors(types: List[Type] | None = None)[source]
Context manager that patches lazy dataclass constructors to preserve None vs concrete distinction.
This is critical for code editors that use exec() to create dataclass instances. Without patching, lazy dataclasses would resolve None values to concrete defaults during construction, making it impossible to distinguish between explicitly set values and inherited values.
The patched constructor sets fields provided in kwargs and otherwise uses the dataclass defaults/default_factory (or None if none exist). This preserves the None vs concrete distinction while still instantiating nested lazy configs.
- Parameters:
types – Optional list of lazy types to patch. If None, uses all registered lazy types.
- Usage:
# Register types at module level register_lazy_type(LazyPipelineConfig) register_lazy_type(LazyZarrConfig)
# Patch during code execution with patch_lazy_constructors():
exec(code_string, namespace) # Lazy dataclasses created during exec() will preserve None values
Example
# Without patching: LazyZarrConfig(compression=’gzip’) # All unspecified fields resolve to defaults
# With patching: with patch_lazy_constructors():
LazyZarrConfig(compression=’gzip’) # Only compression is set, rest are None
- objectstate.lazy_factory.ensure_global_config_context(global_config_type: Type, global_config_instance: Any) None[source]
Ensure proper thread-local storage setup for any global config type.
- objectstate.lazy_factory.resolve_lazy_configurations_for_serialization(data: Any) Any[source]
Recursively resolve lazy dataclass instances to concrete values for serialization.
CRITICAL: This function must be called WITHIN a config_context() block! The context provides the hierarchy for lazy resolution.
How it works: 1. For lazy dataclasses: Access fields with getattr() to trigger resolution 2. The lazy __getattribute__ uses the active config_context() to resolve None values 3. Convert resolved values to base config for pickling
- Example (from README.md):
- with config_context(orchestrator.pipeline_config):
# Lazy resolution happens here via context resolved_steps = resolve_lazy_configurations_for_serialization(steps)
- objectstate.lazy_factory.create_dataclass_for_editing(dataclass_type: Type[T], source_config: Any, preserve_values: bool = False, context_provider: Callable[[Any], None] | None = None) T[source]
Create dataclass for editing with configurable value preservation.
- objectstate.lazy_factory.rebuild_lazy_config_with_new_global_reference(existing_lazy_config: Any, new_global_config: Any, global_config_type: Type | None = None) Any[source]
Rebuild lazy config to reference new global config while preserving field states.
This function preserves the exact field state of the existing lazy config: - Fields that are None (using lazy resolution) remain None - Fields that have been explicitly set retain their concrete values - Nested dataclass fields are recursively rebuilt to reference new global config - The underlying global config reference is updated for None field resolution
- Parameters:
existing_lazy_config – Current lazy config instance
new_global_config – New global config to reference for lazy resolution
global_config_type – Type of the global config (defaults to type of new_global_config)
- Returns:
New lazy config instance with preserved field states and updated global reference
- class objectstate.lazy_factory.abbreviation(name: str)[source]
Decorator/marker for abbreviations in config classes.
Can be used as: 1. Class decorator: @abbreviation(‘wfc’) to set class abbreviation 2. In Annotated types: Annotated[Type, abbreviation(‘wf’)] for field abbreviation
Examples
@abbreviation(‘wfc’) @global_pipeline_config @dataclass class WellFilterConfig:
well_filter: Annotated[Optional[int], abbreviation(‘wf’)] = None well_filter_mode: Annotated[WellFilterMode, abbreviation(‘wfm’)] = WellFilterMode.INCLUDE
- Parameters:
name – The abbreviation string
- Returns:
the class with _abbreviation attribute set When used in Annotated: a marker object storing the abbreviation
- Return type:
When used as class decorator
- objectstate.lazy_factory.get_group_abbreviation(config_type: str | type) str[source]
Look up group abbreviation from GROUP_ABBREVIATIONS_REGISTRY.
Handles both type objects and type name strings.
- Parameters:
config_type – Config class or type name to get abbreviation for
- Returns:
Abbreviation string if found, otherwise falls back to class name prefix
- objectstate.lazy_factory.create_global_default_decorator(target_config_class: Type)[source]
Create a decorator factory for a specific global config class.
The decorator accumulates all decorations, then injects all fields at once when the module finishes loading. Also creates lazy versions of all decorated configs.