88"""
99
1010import logging
11+ import functools
1112from typing import Any , Dict , Optional , Tuple
1213from dataclasses import is_dataclass
1314
@@ -66,11 +67,16 @@ def invalidate_mro_cache_for_field(changed_type: type, field_name: str) -> None:
6667 del _mro_resolution_cache [key ]
6768
6869
70+ @functools .lru_cache (maxsize = None )
6971def _normalize_to_base (t : type ) -> type :
7072 """Normalize lazy type to its base type for comparison.
7173
7274 LazyWellFilterConfig -> WellFilterConfig
7375 WellFilterConfig -> WellFilterConfig
76+
77+ PERFORMANCE: Results are cached with lru_cache since type->base_type
78+ mappings are immutable after class creation. Import is hoisted to
79+ avoid per-call import overhead.
7480 """
7581 from objectstate .lazy_factory import get_base_type_for_lazy
7682 return get_base_type_for_lazy (t ) or t
@@ -129,6 +135,16 @@ def resolve_field_inheritance(
129135 # This works for both LazyDataclass types AND concrete dataclasses with None fields.
130136 obj_base = _normalize_to_base (obj_type )
131137
138+ # PERFORMANCE: Check MRO resolution cache before doing the full walk.
139+ # Cache key uses (obj_type, field_name, config_identity) where config_identity
140+ # captures which config instances are active. Cache is cleared on context push/pop.
141+ _cache_key = (obj_type , field_name , tuple ((k , id (v )) for k , v in available_configs .items ()))
142+ _cached = _mro_resolution_cache .get (_cache_key , _CACHE_SENTINEL )
143+ if _cached is not _CACHE_SENTINEL :
144+ return _cached
145+
146+ _debug = logger .isEnabledFor (logging .DEBUG )
147+
132148 if needs_resolution :
133149 for config_key , config_instance in available_configs .items ():
134150 # Normalize both sides: LazyWellFilterConfig matches WellFilterConfig
@@ -137,18 +153,20 @@ def resolve_field_inheritance(
137153 try :
138154 field_value = object .__getattribute__ (config_instance , field_name )
139155 if field_value is not None :
140- if field_name == 'well_filter' :
141- logger .debug (f"🔍 CONCRETE VALUE: { obj_type .__name__ } .{ field_name } = { field_value } " )
142- if field_name == 'num_workers' :
143- logger .debug (f"🔍 SAME-TYPE MATCH: { obj_type .__name__ } .{ field_name } = { field_value !r} (type={ type (field_value ).__name__ } ) FROM config_key={ config_key } , config_type={ type (config_instance ).__name__ } " )
156+ if _debug :
157+ if field_name == 'well_filter' :
158+ logger .debug (f"🔍 CONCRETE VALUE: { obj_type .__name__ } .{ field_name } = { field_value } " )
159+ if field_name == 'num_workers' :
160+ logger .debug (f"🔍 SAME-TYPE MATCH: { obj_type .__name__ } .{ field_name } = { field_value !r} (type={ type (field_value ).__name__ } ) FROM config_key={ config_key } , config_type={ type (config_instance ).__name__ } " )
161+ _mro_resolution_cache [_cache_key ] = field_value
144162 return field_value
145163 except AttributeError :
146164 continue
147165
148166 # Step 2: MRO-based inheritance - traverse MRO from most to least specific
149167 # Skip the first entry (self type) since we already checked it above (for lazy) or want to skip it (for concrete)
150168 # This finds PARENT class configs with concrete values (sibling inheritance)
151- if field_name in [ 'output_dir_suffix' , 'sub_dir' , 'well_filter' , 'well_filter_mode' ] :
169+ if _debug and field_name in ( 'output_dir_suffix' , 'sub_dir' , 'well_filter' , 'well_filter_mode' ) :
152170 logger .debug (f"🔍 MRO-INHERITANCE: Resolving { obj_type .__name__ } .{ field_name } " )
153171 logger .debug (f"🔍 MRO-INHERITANCE: MRO = { [cls .__name__ for cls in obj_type .__mro__ ]} " )
154172
@@ -164,24 +182,28 @@ def resolve_field_inheritance(
164182 if instance_base == mro_base :
165183 try :
166184 value = object .__getattribute__ (config_instance , field_name )
167- if field_name in ['output_dir_suffix' , 'sub_dir' , 'well_filter' , 'well_filter_mode' ]:
168- logger .debug (f"🔍 MRO-INHERITANCE: { mro_class .__name__ } .{ field_name } = { value } " )
169- if field_name == 'num_workers' :
170- logger .debug (f"🔍 MRO-INHERITANCE: { mro_class .__name__ } .{ field_name } = { value !r} (type={ type (value ).__name__ } )" )
171- if value is not None :
172- if field_name in ['output_dir_suffix' , 'sub_dir' , 'well_filter' , 'well_filter_mode' ]:
173- logger .debug (f"🔍 MRO-INHERITANCE: FOUND { mro_class .__name__ } .{ field_name } : { value } (returning)" )
185+ if _debug :
186+ if field_name in ('output_dir_suffix' , 'sub_dir' , 'well_filter' , 'well_filter_mode' ):
187+ logger .debug (f"🔍 MRO-INHERITANCE: { mro_class .__name__ } .{ field_name } = { value } " )
174188 if field_name == 'num_workers' :
175- logger .debug (f"🔍 MRO-INHERITANCE: RETURNING { mro_class .__name__ } .{ field_name } = { value !r} " )
189+ logger .debug (f"🔍 MRO-INHERITANCE: { mro_class .__name__ } .{ field_name } = { value !r} (type={ type (value ).__name__ } )" )
190+ if value is not None :
191+ if _debug :
192+ if field_name in ('output_dir_suffix' , 'sub_dir' , 'well_filter' , 'well_filter_mode' ):
193+ logger .debug (f"🔍 MRO-INHERITANCE: FOUND { mro_class .__name__ } .{ field_name } : { value } (returning)" )
194+ if field_name == 'num_workers' :
195+ logger .debug (f"🔍 MRO-INHERITANCE: RETURNING { mro_class .__name__ } .{ field_name } = { value !r} " )
196+ _mro_resolution_cache [_cache_key ] = value
176197 return value
177198 except AttributeError :
178199 continue
179200
180201 # No Step 3: If MRO walk finds nothing, return None.
181202 # "If we wanted static class defaults, it wouldn't have been overridden to None"
182203 # For LazyDataclass, class defaults are all None anyway (via rebuild_with_none_defaults).
183- if field_name in [ 'output_dir_suffix' , 'sub_dir' , 'well_filter' ] :
204+ if _debug and field_name in ( 'output_dir_suffix' , 'sub_dir' , 'well_filter' ) :
184205 logger .debug (f"🔍 NO-RESOLUTION: { obj_type .__name__ } .{ field_name } = None" )
206+ _mro_resolution_cache [_cache_key ] = None
185207 return None
186208
187209
@@ -248,7 +270,9 @@ def resolve_with_provenance(container_type: type, field_name: str) -> Tuple[Any,
248270 # - MRO inheritance only applies when NO concrete value exists in the hierarchy
249271 # for the specific config type being resolved
250272
251- if field_name == 'well_filter' :
273+ _debug = logger .isEnabledFor (logging .DEBUG )
274+
275+ if _debug and field_name == 'well_filter' :
252276 logger .debug (f"🔍 resolve_with_provenance: container={ container_base .__name__ } , field={ field_name } , layers={ len (layers )} " )
253277 logger .debug (f"🔍 resolve_with_provenance: mro_types={ [t .__name__ for t in mro_types ]} " )
254278
@@ -271,21 +295,21 @@ def resolve_with_provenance(container_type: type, field_name: str) -> Tuple[Any,
271295 # This gives hierarchy precedence: inner/child scopes override outer/parent scopes
272296 # REVERSED ORDER: Walk from inner to outer so more specific scopes override general scopes
273297 for scope_id , layer_configs in reversed (all_layer_configs ):
274- if field_name == 'well_filter' :
298+ if _debug and field_name == 'well_filter' :
275299 logger .debug (f"🔍 Phase 1 - Layer scope={ scope_id !r} , checking same-type only (inner to outer)" )
276300
277301 for config_instance in layer_configs .values ():
278302 instance_base = _normalize_to_base (type (config_instance ))
279- if field_name in ('well_filter' , 'enabled' ) and instance_base == container_base :
303+ if _debug and field_name in ('well_filter' , 'enabled' ) and instance_base == container_base :
280304 logger .debug (f"🔍 FOUND same-type config: { instance_base .__name__ } @ scope={ scope_id } " )
281305 if instance_base == container_base : # Same-type only, no MRO
282306 try :
283307 value = object .__getattribute__ (config_instance , field_name )
284- if field_name in ('well_filter' , 'enabled' ):
308+ if _debug and field_name in ('well_filter' , 'enabled' ):
285309 logger .debug (f"🔍 { container_base .__name__ } .{ field_name } @ scope={ scope_id } = { value !r} (from object.__getattribute__)" )
286310 if value is not None :
287311 # Found concrete value in hierarchy - return immediately
288- if field_name in ('well_filter' , 'enabled' ):
312+ if _debug and field_name in ('well_filter' , 'enabled' ):
289313 logger .debug (f"🔍 FOUND concrete value in hierarchy at scope={ scope_id !r} , returning { value !r} " )
290314 return value , scope_id , container_base
291315 # Don't set fallback here - let Phase 2 walk MRO to find
@@ -304,7 +328,7 @@ def resolve_with_provenance(container_type: type, field_name: str) -> Tuple[Any,
304328 #
305329 # Therefore: for each MRO type (most→least specific), scan scopes (inner→outer)
306330 # looking for the first non-None value.
307- if field_name == 'well_filter' :
331+ if _debug and field_name == 'well_filter' :
308332 logger .debug (f"🔍 Phase 2 - MRO fallback, walking layers inner to outer" )
309333
310334 # Track where the "highest-precedence" field exists even if None, so callers
@@ -330,7 +354,7 @@ def resolve_with_provenance(container_type: type, field_name: str) -> Tuple[Any,
330354 except AttributeError :
331355 continue
332356
333- if field_name == 'well_filter' :
357+ if _debug and field_name == 'well_filter' :
334358 logger .debug (f"🔍 MRO: { mro_type .__name__ } .{ field_name } @ { scope_id !r} = { value !r} " )
335359
336360 if value is not None :
0 commit comments