Skip to content

Commit fad06dd

Browse files
committed
Merge dev into main
2 parents 1848f70 + 38c40d5 commit fad06dd

File tree

8 files changed

+52
-19
lines changed

8 files changed

+52
-19
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# OSBot-Utils
22

3-
![Current Release](https://img.shields.io/badge/release-v3.51.0-blue)
3+
![Current Release](https://img.shields.io/badge/release-v3.51.1-blue)
44
![Python](https://img.shields.io/badge/python-3.8+-green)
55
![Type-Safe](https://img.shields.io/badge/Type--Safe-✓-brightgreen)
66
![Caching](https://img.shields.io/badge/Caching-Built--In-orange)

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 ,

osbot_utils/type_safe/type_safe_core/steps/Type_Safe__Step__From_Json.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,9 @@ def deserialize_nested_dict(self, value_class, dict_value):
308308
return dict_value # No type info, use raw dict
309309

310310
def deserialize_type_safe_value(self, value_class, dict_value): # Handle deserialization of Type_Safe subclass values.
311-
if 'node_type' in dict_value:
312-
value_class = type_safe_convert.get_class_from_class_name(dict_value['node_type'])
311+
value = dict_value.get('node_type')
312+
if value:
313+
value_class = type_safe_convert.get_class_from_class_name(value)
313314

314315
return self.deserialize_from_dict(value_class(), dict_value)
315316

osbot_utils/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v3.51.0
1+
v3.51.1

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "osbot_utils"
3-
version = "v3.51.0"
3+
version = "v3.51.1"
44
description = "OWASP Security Bot - Utils"
55
authors = ["Dinis Cruz <[email protected]>"]
66
license = "MIT"

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

tests/unit/type_safe/type_safe_core/steps/test_Type_Safe__Step__From_Json.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from enum import Enum
66
from typing import Dict, List, Set, Any, Optional, ForwardRef
77
from unittest import TestCase
8+
from osbot_utils.utils.Env import in_github_action
89
from osbot_utils.type_safe.Type_Safe import Type_Safe
910
from osbot_utils.type_safe.primitives.domains.cryptography.safe_str.Safe_Str__Hash import Safe_Str__Hash
1011
from osbot_utils.type_safe.primitives.domains.files.safe_str.Safe_Str__File__Path import Safe_Str__File__Path
@@ -521,7 +522,10 @@ class Large_Class(Type_Safe):
521522
elapsed = time.time() - start
522523

523524
assert len(obj.items) == 1000
524-
assert elapsed < 0.01 # Should complete in under 10ms
525+
if in_github_action():
526+
assert elapsed < 0.05 # in GH Actions this is about ~10ms
527+
else:
528+
assert elapsed < 0.01 # on dev laptop this is ~2ms
525529

526530
def test__forward_ref_in_list_works(self): # Forward refs in List work correctly """
527531

0 commit comments

Comments
 (0)