Skip to content

Commit 4b4a911

Browse files
committed
fixed edge case with base classes null assigments causing side effects
1 parent 1848f70 commit 4b4a911

File tree

3 files changed

+41
-13
lines changed

3 files changed

+41
-13
lines changed

osbot_utils/type_safe/type_safe_core/steps/Type_Safe__Step__Class_Kwargs.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,21 @@ def process_annotation(self, cls : Type ,
103103
var_name : str ,
104104
var_type : Type ):
105105

106-
class_declares_annotation = var_name in getattr(base_cls, '__annotations__', {})
107-
108-
if not hasattr(base_cls, var_name):
109-
self.handle_undefined_var(cls, kwargs, var_name, var_type)
110-
elif class_declares_annotation and base_cls is cls:
111-
origin = type_safe_cache.get_origin(var_type) # Only recalculate default for Type[T] annotations
112-
if origin is type:
113-
self.handle_undefined_var(cls, kwargs, var_name, var_type)
114-
else:
115-
self.handle_defined_var(base_cls, var_name, var_type)
116-
else:
117-
self.handle_defined_var(base_cls, var_name, var_type)
106+
class_declares_annotation = var_name in getattr(base_cls, '__annotations__', {}) # Check if this class has the annotation in its own __annotations__
107+
class_has_own_value = var_name in base_cls.__dict__ # Check if this class defines its own value (not inherited)
108+
109+
if not hasattr(base_cls, var_name): # Case 1: No value exists anywhere in hierarchy
110+
self.handle_undefined_var(cls, kwargs, var_name, var_type) # Create fresh default value for this type
111+
elif class_declares_annotation and base_cls is cls and not class_has_own_value: # Case 2: Target class redeclares annotation without own value
112+
self.handle_undefined_var(cls, kwargs, var_name, var_type) # Create fresh default, don't inherit parent's explicit None
113+
elif class_declares_annotation and base_cls is cls: # Case 3: Target class declares annotation with its own value
114+
origin = type_safe_cache.get_origin(var_type) # Check if it's a Type[T] annotation
115+
if origin is type: # Type[T] annotations need special handling
116+
self.handle_undefined_var(cls, kwargs, var_name, var_type) # Recalculate default for Type[T]
117+
else: # Normal annotation with explicit value
118+
self.handle_defined_var(base_cls, var_name, var_type) # Validate the defined value
119+
else: # Case 4: Inherited value from parent class
120+
self.handle_defined_var(base_cls, var_name, var_type) # Use and validate the inherited value
118121

119122
def process_annotations(self, cls : Type , # Process all annotations
120123
base_cls : Type ,

tests/unit/type_safe/type_safe_core/_bugs/test_Type_Safe__bugs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def __init__(self): # this will make the __annotations__ to
7171
assert an_class.an_str == 'new_value'
7272
assert an_class.an_bool == False
7373

74-
def test__bug__type_annotation__non_none_parent_default(self):
74+
def test__regression__type_annotation__non_none_parent_default(self):
7575
# What happens when parent has a non-None default?
7676
# This combines BOTH bugs:
7777
# 1. Subclass inherits parent's value (Base_Handler) instead of auto-assigning Extended_Handler
@@ -99,3 +99,5 @@ class Extended_Config(Base_Config):
9999
with pytest.raises(ValueError, match=re.escape(error_message)):
100100
Extended_Config() # BUG: should auto-assign Extended_Handler
101101

102+
103+

tests/unit/type_safe/type_safe_core/_regression/test_Type_Safe__regression.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,3 +2000,26 @@ class Types_L2(Types_L1):
20002000
with Types_L2() as _:
20012001
#assert _.node_type is None # BUG: should be Node_L2
20022002
assert _.node_type is Node_L2
2003+
2004+
def test__regression__base_class_none_prevents_object_creation(self):
2005+
from osbot_utils.type_safe.primitives.core.Safe_Str import Safe_Str
2006+
from osbot_utils.testing.__ import __
2007+
2008+
class Base_Class(Type_Safe):
2009+
an_str : Safe_Str = None # BUG: this is preventing creation
2010+
2011+
class With_Base(Base_Class):
2012+
an_str : Safe_Str # BUG: this should have been created
2013+
2014+
class An_Class(Type_Safe):
2015+
an_str : Safe_Str
2016+
2017+
assert An_Class ().json() == {'an_str': ''}
2018+
assert An_Class ().obj () == __(an_str='')
2019+
assert Base_Class().json() == {'an_str': None}
2020+
assert Base_Class().obj () == __(an_str=None)
2021+
# assert With_Base ().json() == {'an_str': None} # BUG
2022+
# assert With_Base ().obj () == __(an_str=None) # BUG
2023+
2024+
assert With_Base ().json() == {'an_str': ''} # FIXED
2025+
assert With_Base ().obj () == __(an_str='') # FIXED

0 commit comments

Comments
 (0)