Skip to content

Commit

Permalink
Major improvement and refactoring on Type_Save__Set__Attr, which had …
Browse files Browse the repository at this point in the history
…a massive positive performance impact

Immutable vars cache, now returns the expected type (i.e. a dict instead of a list)
  • Loading branch information
DinisCruz committed Jan 20, 2025
1 parent 09c95c6 commit 95a1592
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 22 deletions.
2 changes: 1 addition & 1 deletion osbot_utils/type_safe/shared/Type_Safe__Cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def get_class_immutable_vars(self, cls):
immutable_vars = self._cls__immutable_vars.get(cls)
if self.skip_cache or not immutable_vars:
annotations = self.get_class_annotations(cls)
immutable_vars = [key for key, value in annotations if value in IMMUTABLE_TYPES]
immutable_vars = {key: value for key, value in annotations if value in IMMUTABLE_TYPES}
self._cls__immutable_vars[cls] = immutable_vars
else:
self.cache_hit__cls__immutable_vars += 1
Expand Down
4 changes: 2 additions & 2 deletions osbot_utils/type_safe/shared/Type_Safe__Raise_Exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

class Type_Safe__Raise_Exception:

def type_mismatch_error(self, var_name: str, expected_type: Any, actual_value: Any) -> None: # Raises formatted error for type validation failures
exception_message = f"variable '{var_name}' is defined as type '{expected_type}' but has value '{actual_value}' of type '{type(actual_value)}'"
def type_mismatch_error(self, var_name: str, expected_type: type, actual_type: type) -> None: # Raises formatted error for type validation failures
exception_message = f"Invalid type for attribute '{var_name}'. Expected '{expected_type}' but got '{actual_type}'"
raise ValueError(exception_message)

def immutable_type_error(self, var_name, var_type):
Expand Down
2 changes: 1 addition & 1 deletion osbot_utils/type_safe/shared/Type_Safe__Validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ def validate_type_immutability(self, var_name: str, var_type: Any) -> None:

def validate_variable_type(self, var_name, var_type, var_value): # Validate type compatibility
if var_type and not isinstance(var_value, var_type):
type_safe_raise_exception.type_mismatch_error(var_name, var_type, var_value)
type_safe_raise_exception.type_mismatch_error(var_name, var_type, type(var_value))

type_safe_validation = Type_Safe__Validation()
28 changes: 20 additions & 8 deletions osbot_utils/type_safe/steps/Type_Safe__Step__Set_Attr.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from typing import get_origin, Annotated, get_args
from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache
from osbot_utils.utils.Objects import all_annotations
from osbot_utils.utils.Objects import convert_dict_to_value_from_obj_annotation
from osbot_utils.utils.Objects import convert_to_value_from_obj_annotation
from osbot_utils.utils.Objects import value_type_matches_obj_annotation_for_attr
from osbot_utils.utils.Objects import value_type_matches_obj_annotation_for_union_and_annotated
from osbot_utils.type_safe.validators.Type_Safe__Validator import Type_Safe__Validator
from typing import get_origin, Annotated, get_args, _SpecialGenericAlias
from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache
from osbot_utils.type_safe.shared.Type_Safe__Raise_Exception import type_safe_raise_exception
from osbot_utils.utils.Objects import all_annotations, are_types_compatible_for_assigment
from osbot_utils.utils.Objects import convert_dict_to_value_from_obj_annotation
from osbot_utils.utils.Objects import convert_to_value_from_obj_annotation
from osbot_utils.utils.Objects import value_type_matches_obj_annotation_for_attr
from osbot_utils.utils.Objects import value_type_matches_obj_annotation_for_union_and_annotated
from osbot_utils.type_safe.validators.Type_Safe__Validator import Type_Safe__Validator


class Type_Safe__Step__Set_Attr:
Expand Down Expand Up @@ -78,6 +79,17 @@ def handle_get_class(self, _self, annotations, name, value):
return value

def setattr(self, _super, _self, name, value):
if value is not None and type(value) is not _SpecialGenericAlias: # todo: refactor this section into a separate method
immutable_vars = type_safe_cache.get_class_immutable_vars(_self.__class__)
if name in immutable_vars:
expected_type = immutable_vars[name]
if value is not type:
current_type = type(value)
else:
current_type = type
if not are_types_compatible_for_assigment(current_type, expected_type):
type_safe_raise_exception.type_mismatch_error(name, expected_type, current_type)
return _super.__setattr__(name, value)

annotations = all_annotations(_self)
if not annotations: # can't do type safety checks if the class does not have annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ class An_Class_6(Type_Safe):
_.measure(Random_Guid).print().assert_time(self.time_3_kns , self.time_5_kns, self.time_6_kns , self.time_7_kns )
_.measure(An_Class_1 ).print().assert_time(self.time_100_ns, self.time_200_ns )
_.measure(An_Class_2 ).print().assert_time(self.time_1_kns , self.time_2_kns , self.time_3_kns , self.time_4_kns , self.time_5_kns , self.time_6_kns, self.time_7_kns )
_.measure(An_Class_3 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns )
_.measure(An_Class_4 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns )
_.measure(An_Class_5 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns )
_.measure(An_Class_6 ).print().assert_time(self.time_7_kns , self.time_8_kns ,self.time_9_kns , self.time_10_kns, self.time_20_kns )
_.measure(An_Class_3 ).print().assert_time(self.time_4_kns , self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns )
_.measure(An_Class_4 ).print().assert_time(self.time_4_kns , self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns )
_.measure(An_Class_5 ).print().assert_time(self.time_4_kns , self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns )
_.measure(An_Class_6 ).print().assert_time(self.time_4_kns , self.time_7_kns , self.time_8_kns ,self.time_9_kns , self.time_10_kns, self.time_20_kns )

type_safe_cache.print_cache_hits()

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/type_safe/_bugs/test_Type_Safe__bugs.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class An_Class__With_Bad_Values(Kwargs_To_Self):
an_class = An_Class__With_Correct_Values() # should create ok and values should match the type
assert an_class.__locals__() == {'an_bool': an_bool_value, 'an_int': an_int_value, 'an_str': an_str_value}

expected_message = "variable 'an_str' is defined as type '<class 'str'>' but has value 'True' of type '<class 'bool'>'"
expected_message = "Invalid type for attribute 'an_str'. Expected '<class 'str'>' but got '<class 'bool'>'"
with self.assertRaises(Exception) as context:
An_Class__With_Bad_Values()
assert context.exception.args[0] == expected_message
Expand Down Expand Up @@ -174,7 +174,7 @@ class An_Class__With_Bad_Values(Kwargs_To_Self):
an_int : int = an_bool_value # BUG: should have thrown exception here (bool should be allowed on int)
an_str : str = an_bool_value # will throw exception here

expected_message = "variable 'an_str' is defined as type '<class 'str'>' but has value 'True' of type '<class 'bool'>'"
expected_message = "Invalid type for attribute 'an_str'. Expected '<class 'str'>' but got '<class 'bool'>'"
with self.assertRaises(Exception) as context:
An_Class__With_Bad_Values()
assert context.exception.args[0] == expected_message
Expand Down
9 changes: 7 additions & 2 deletions tests/unit/type_safe/steps/test_Type_Safe__Step__Set_Attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from osbot_utils.utils.Objects import all_annotations
from osbot_utils.type_safe.steps.Type_Safe__Step__Set_Attr import Type_Safe__Step__Set_Attr

from osbot_utils.helpers.trace.Trace_Call import trace_calls
from osbot_utils.helpers.trace.Trace_Call import trace_calls


class test_Type_Safe__Step__Set_Attr(TestCase):
Expand All @@ -13,6 +13,10 @@ class test_Type_Safe__Step__Set_Attr(TestCase):
def setUpClass(cls):
cls.step_set_attr = Type_Safe__Step__Set_Attr()

# def setUp(self):
# print()


# @trace_calls(include = ['*'],
# show_internals = True ,
# show_duration = True ,
Expand All @@ -27,8 +31,9 @@ class Class__One_int:
assert all_annotations(one_int) == {'an_int': int}
assert self.step_set_attr.setattr(one_int, one_int, 'an_int', 42) is None
assert one_int.an_int == 42

#pprint()
assert self.step_set_attr.setattr(one_int, one_int, 'an_int', 42) is None
#pprint()
assert self.step_set_attr.setattr(one_int, one_int, 'an_int', 42) is None

def test_class__random_guid(self):
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/type_safe/test_Type_Safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,12 +551,12 @@ def test__default_kwargs__picks_up_bad_types(self):
class An_Bad_Type(Type_Safe):
not_an_int: int = "an str"

expected_error= "variable 'not_an_int' is defined as type '<class 'int'>' but has value 'an str' of type '<class 'str'>'"
expected_error= "Invalid type for attribute 'not_an_int'. Expected '<class 'int'>' but got '<class 'str'>'"
#with Catch(expect_exception=True, expected_error=expected_error):
with pytest.raises(ValueError, match=expected_error ):
An_Bad_Type().__default_kwargs__()

expected_error = "variable 'not_an_int' is defined as type '<class 'int'>' but has value 'an str' of type '<class 'str'>'"
expected_error = "Invalid type for attribute 'not_an_int'. Expected '<class 'int'>' but got '<class 'str'>'"
with pytest.raises(ValueError, match=expected_error ):
An_Bad_Type().__default_kwargs__()

Expand Down

0 comments on commit 95a1592

Please sign in to comment.