diff --git a/osbot_utils/type_safe/shared/Type_Safe__Cache.py b/osbot_utils/type_safe/shared/Type_Safe__Cache.py index f435f5a2..326f4462 100644 --- a/osbot_utils/type_safe/shared/Type_Safe__Cache.py +++ b/osbot_utils/type_safe/shared/Type_Safe__Cache.py @@ -1,45 +1,62 @@ import inspect -from typing import get_origin -from weakref import WeakKeyDictionary +from typing import get_origin +from weakref import WeakKeyDictionary +from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES class Type_Safe__Cache: - _annotations_cache : WeakKeyDictionary - _cls_kwargs_cache : WeakKeyDictionary - _get_origin_cache : WeakKeyDictionary - _mro_cache : WeakKeyDictionary - _valid_vars_cache : WeakKeyDictionary + _cls__annotations_cache : WeakKeyDictionary + _cls__immutable_vars : WeakKeyDictionary + _cls__kwargs_cache : WeakKeyDictionary + _get_origin_cache : WeakKeyDictionary + _mro_cache : WeakKeyDictionary + _valid_vars_cache : WeakKeyDictionary - cache_hit__annotations : int = 0 - cache_hit__cls_kwargs : int = 0 - cache_hit__get_origin : int = 0 - cache_hit__mro : int = 0 - cache_hit__valid_vars : int = 0 - skip_cache : bool = False + cache_hit__cls__annotations : int = 0 + cache_hit__cls__kwargs : int = 0 + cache_hit__cls__immutable_vars: int = 0 + cache_hit__get_origin : int = 0 + cache_hit__mro : int = 0 + cache_hit__valid_vars : int = 0 + skip_cache : bool = False # Caching system for Type_Safe methods def __init__(self): - self._annotations_cache = WeakKeyDictionary() # Cache for class annotations - self._cls_kwargs_cache = WeakKeyDictionary() # Cache for class kwargs - self._get_origin_cache = WeakKeyDictionary() # Cache for get_origin results - self._mro_cache = WeakKeyDictionary() # Cache for Method Resolution Order - self._valid_vars_cache = WeakKeyDictionary() + self._cls__annotations_cache = WeakKeyDictionary() # Cache for class annotations + self._cls__immutable_vars = WeakKeyDictionary() # Cache for class immutable vars + self._cls__kwargs_cache = WeakKeyDictionary() # Cache for class kwargs + self._get_origin_cache = WeakKeyDictionary() # Cache for get_origin results + self._mro_cache = WeakKeyDictionary() # Cache for Method Resolution Order + self._valid_vars_cache = WeakKeyDictionary() def get_cls_kwargs(self, cls): - if self.skip_cache or cls not in self._cls_kwargs_cache: + if self.skip_cache or cls not in self._cls__kwargs_cache: return None else: - self.cache_hit__cls_kwargs += 1 - return self._cls_kwargs_cache.get(cls) + self.cache_hit__cls__kwargs += 1 + return self._cls__kwargs_cache.get(cls) def get_class_annotations(self, cls): - if self.skip_cache or cls not in self._annotations_cache: - self._annotations_cache[cls] = cls.__annotations__.items() + annotations = self._cls__annotations_cache.get(cls) # this is a more efficient cache retrieval pattern (we only get the data from the dict once) + if not annotations: # todo: apply this to the other cache getters + if self.skip_cache or cls not in self._cls__annotations_cache: + annotations = cls.__annotations__.items() + self._cls__annotations_cache[cls] = annotations else: - self.cache_hit__annotations += 1 - return self._annotations_cache[cls] + self.cache_hit__cls__annotations += 1 + return annotations + + 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] + self._cls__immutable_vars[cls] = immutable_vars + else: + self.cache_hit__cls__immutable_vars += 1 + return immutable_vars def get_class_mro(self, cls): if self.skip_cache or cls not in self._mro_cache: @@ -69,17 +86,18 @@ def get_valid_class_variables(self, cls, validator): return self._valid_vars_cache[cls] def set_cache__cls_kwargs(self, cls, kwargs): - self._cls_kwargs_cache[cls] = kwargs + self._cls__kwargs_cache[cls] = kwargs return kwargs def print_cache_hits(self): print() print("###### Type_Safe_Cache Hits ########") print() - print(f" annotations : {self.cache_hit__annotations}") - print(f" cls_kwargs : {self.cache_hit__cls_kwargs }") - print(f" get_origin : {self.cache_hit__get_origin }") - print(f" mro : {self.cache_hit__mro }") - print(f" valid_vars : {self.cache_hit__valid_vars }") + print(f" annotations : {self.cache_hit__cls__annotations }") + print(f" cls__kwargs : {self.cache_hit__cls__kwargs }") + print(f" cls__immutable_vars: {self.cache_hit__cls__immutable_vars }") + print(f" get_origin : {self.cache_hit__get_origin }") + print(f" mro : {self.cache_hit__mro }") + print(f" valid_vars : {self.cache_hit__valid_vars }") type_safe_cache = Type_Safe__Cache() \ No newline at end of file diff --git a/osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py b/osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py index 11aaa1a8..18a5ae42 100644 --- a/osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py +++ b/osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py @@ -1,6 +1,4 @@ import types from enum import EnumMeta - -#IMMUTABLE_TYPES = (bool, int, float, complex, str, tuple, frozenset, bytes, types.NoneType, EnumMeta, type) IMMUTABLE_TYPES = (bool, int, float, complex, str, bytes, types.NoneType, EnumMeta, type) \ No newline at end of file diff --git a/osbot_utils/type_safe/shared/Type_Safe__Validation.py b/osbot_utils/type_safe/shared/Type_Safe__Validation.py index c5d16991..b364899d 100644 --- a/osbot_utils/type_safe/shared/Type_Safe__Validation.py +++ b/osbot_utils/type_safe/shared/Type_Safe__Validation.py @@ -1,11 +1,10 @@ import types -from enum import EnumMeta -from typing import Any, Annotated - -from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache -from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES -from osbot_utils.utils.Objects import obj_is_type_union_compatible -from osbot_utils.type_safe.shared.Type_Safe__Raise_Exception import type_safe_raise_exception +from enum import EnumMeta +from typing import Any, Annotated +from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache +from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES +from osbot_utils.utils.Objects import obj_is_type_union_compatible +from osbot_utils.type_safe.shared.Type_Safe__Raise_Exception import type_safe_raise_exception class Type_Safe__Validation: @@ -27,6 +26,7 @@ def should_skip_var(self, var_name: str, var_value: Any) -> bool: return True return False + # todo: see if need to add cache support to this method (it looks like this method is not called very often) def validate_type_immutability(self, var_name: str, var_type: Any) -> None: # Validates that type is immutable or in supported format if var_type not in IMMUTABLE_TYPES and var_name.startswith('__') is False: # if var_type is not one of the IMMUTABLE_TYPES or is an __ internal if obj_is_type_union_compatible(var_type, IMMUTABLE_TYPES) is False: # if var_type is not something like Optional[Union[int, str]] diff --git a/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py b/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py index d8f9abfa..502ce7d9 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py @@ -97,14 +97,13 @@ def process_annotations(self, cls : Type , def process_mro_class(self, base_cls : Type , # Process class in MRO chain kwargs : Dict[str, Any] )\ -> None: - if base_cls is object: # Skip object class + if base_cls is object: # Skip object class return - class_variables = type_safe_cache.get_valid_class_variables( # Get valid class variables - base_cls, - type_safe_validation.should_skip_var) + class_variables = type_safe_cache.get_valid_class_variables(base_cls , + type_safe_validation.should_skip_var) # Get valid class variables - for name, value in class_variables.items(): # Add non-existing variables + for name, value in class_variables.items(): # Add non-existing variables if name not in kwargs: kwargs[name] = value diff --git a/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Value.py b/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Value.py index aead7d4c..d1954b99 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Value.py +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Value.py @@ -44,6 +44,7 @@ def default_value(self, _cls, var_type): if var_type is typing.Set: # todo: refactor the dict, set and list logic, since they are 90% the same return set() + if get_origin(var_type) is set: return set() # todo: add Type_Safe__Set diff --git a/osbot_utils/type_safe/steps/Type_Safe__Step__Init.py b/osbot_utils/type_safe/steps/Type_Safe__Step__Init.py index d921037c..00d4a114 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Step__Init.py +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__Init.py @@ -4,8 +4,6 @@ class Type_Safe__Step__Init: def init(self, __self, __class_kwargs, **kwargs): - #__class_kwargs = type_safe_step_class_kwargs.get_cls_kwargs(cls) # todo: figure out why this doesn't work here on 1% of the tests (like the ones in CPrint) - for (key, value) in __class_kwargs.items(): # assign all default values to target if value is not None: # when the value is explicitly set to None on the class static vars, we can't check for type safety raise_exception_on_obj_type_annotation_mismatch(__self, key, value) diff --git a/osbot_utils/type_safe/steps/Type_Safe__Step__Set_Attr.py b/osbot_utils/type_safe/steps/Type_Safe__Step__Set_Attr.py index 7639bcd5..bf31c141 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Step__Set_Attr.py +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__Set_Attr.py @@ -1,4 +1,5 @@ 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 @@ -9,48 +10,84 @@ class Type_Safe__Step__Set_Attr: - def setattr(self, _super, _self, name, value): + def verify_value(self, _self, annotations, name, value): # refactor the logic of this method since it is confusing + check_1 = value_type_matches_obj_annotation_for_attr (_self, name, value) + check_2 = value_type_matches_obj_annotation_for_union_and_annotated(_self, name, value) + if (check_1 is False and check_2 is None or + check_1 is None and check_2 is False or + check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars + raise ValueError(f"Invalid type for attribute '{name}'. Expected '{annotations.get(name)}' but got '{type(value)}'") - annotations = all_annotations(_self) - if not annotations: # can't do type safety checks if the class does not have annotations - return _super.__setattr__(name, value) - - if value is not None: - if type(value) is dict: - value = convert_dict_to_value_from_obj_annotation(_self, name, value) - elif type(value) in [int, str]: # for now only a small number of str and int classes are supported (until we understand the full implications of this) - value = convert_to_value_from_obj_annotation (_self, name, value) - else: - origin = get_origin(value) - if origin is not None: - value = origin - check_1 = value_type_matches_obj_annotation_for_attr (_self, name, value) - check_2 = value_type_matches_obj_annotation_for_union_and_annotated(_self, name, value) - if (check_1 is False and check_2 is None or - check_1 is None and check_2 is False or - check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars - raise ValueError(f"Invalid type for attribute '{name}'. Expected '{annotations.get(name)}' but got '{type(value)}'") + def resolve_value(self, _self, annotations, name, value): + if type(value) is dict: + value = self.resolve_value__dict(_self, name, value) + elif type(value) in [int, str]: # for now only a small number of str and int classes are supported (until we understand the full implications of this) + value = self.resolve_value__int_str(_self, name, value) else: - if hasattr(_self, name) and annotations.get(name) : # don't allow previously set variables to be set to None - if getattr(_self, name) is not None: # unless it is already set to None - raise ValueError(f"Can't set None, to a variable that is already set. Invalid type for attribute '{name}'. Expected '{_self.__annotations__.get(name)}' but got '{type(value)}'") + value = self.resolve_value__from_origin(value) + + self.verify_value(_self, annotations, name, value) + return value + + def resolve_value__dict(self, _self, name, value): + return convert_dict_to_value_from_obj_annotation(_self, name, value) + + def resolve_value__int_str(self, _self, name, value): + immutable_vars = type_safe_cache.get_class_immutable_vars(_self.__class__) # get the cached value of immutable vars for this class + + if name in immutable_vars: # we only need to do the conversion if the variable is immutable + return value + + return convert_to_value_from_obj_annotation(_self, name, value) + + - # todo: refactor this to separate method + def resolve_value__from_origin(self, value): + origin = get_origin(value) + if origin is not None: + value = origin + return value + + def validate_if_value_has_been_set(self, _self, annotations, name, value): + if hasattr(_self, name) and annotations.get(name) : # don't allow previously set variables to be set to None + if getattr(_self, name) is not None: # unless it is already set to None + raise ValueError(f"Can't set None, to a variable that is already set. Invalid type for attribute '{name}'. Expected '{_self.__annotations__.get(name)}' but got '{type(value)}'") + + def handle_get_class__annotated(self, annotation, name, value): + annotation_args = get_args(annotation) + target_type = annotation_args[0] + for attribute in annotation_args[1:]: + if isinstance(attribute, Type_Safe__Validator): + attribute.validate(value=value, field_name=name, target_type=target_type) + + def handle_get_class__dict(self, _self, name, value): + # todo: refactor how this actually works since it is not good to having to use the deserialize_dict__using_key_value_annotations from here + from osbot_utils.type_safe.steps.Type_Safe__Step__From_Json import Type_Safe__Step__From_Json # here because of circular dependencies + value = Type_Safe__Step__From_Json().deserialize_dict__using_key_value_annotations(_self, name, value) + return value + + def handle_get_class(self, _self, annotations, name, value): if hasattr(annotations, 'get'): annotation = annotations.get(name) if annotation: annotation_origin = get_origin(annotation) if annotation_origin is Annotated: - annotation_args = get_args(annotation) - target_type = annotation_args[0] - for attribute in annotation_args[1:]: - if isinstance(attribute, Type_Safe__Validator): - attribute.validate(value=value, field_name=name, target_type=target_type) + self.handle_get_class__annotated(annotation, name, value) elif annotation_origin is dict: - # todo: refactor how this actually works since it is not good to having to use the deserialize_dict__using_key_value_annotations from here - from osbot_utils.type_safe.steps.Type_Safe__Step__From_Json import Type_Safe__Step__From_Json - value = Type_Safe__Step__From_Json().deserialize_dict__using_key_value_annotations(_self, name, value) - #value = _self.deserialize_dict__using_key_value_annotations(name, value) + value = self.handle_get_class__dict(_self, name, value) + return value + + def setattr(self, _super, _self, name, value): + + annotations = all_annotations(_self) + if not annotations: # can't do type safety checks if the class does not have annotations + return _super.__setattr__(name, value) + + if value is not None: + value = self.resolve_value (_self, annotations, name, value) + value = self.handle_get_class(_self, annotations, name, value) + else: + self.validate_if_value_has_been_set(_self, annotations, name, value) _super.__setattr__(name, value) diff --git a/tests/unit/testing/performance/test_Performance_Measure__Session.py b/tests/unit/testing/performance/test_Performance_Measure__Session.py index c570cc24..55b99942 100644 --- a/tests/unit/testing/performance/test_Performance_Measure__Session.py +++ b/tests/unit/testing/performance/test_Performance_Measure__Session.py @@ -15,6 +15,7 @@ def setUpClass(cls): pytest.skip("Skipping tests in Github Actions") cls.time_0_ns = 0 cls.time_100_ns = 100 + cls.time_200_ns = 200 cls.time_1_kns = 1_000 cls.time_2_kns = 2_000 cls.time_3_kns = 3_000 @@ -52,7 +53,7 @@ class An_Class_6(Type_Safe): with Performance_Measure__Session(assert_enabled=True) as _: _.measure(str ).print().assert_time(self.time_100_ns, self.time_0_ns ) _.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 ) + _.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 ) diff --git a/tests/unit/type_safe/steps/test_Type_Safe__Step__Class_Kwargs.py b/tests/unit/type_safe/steps/test_Type_Safe__Step__Class_Kwargs.py index 1ceab842..42540648 100644 --- a/tests/unit/type_safe/steps/test_Type_Safe__Step__Class_Kwargs.py +++ b/tests/unit/type_safe/steps/test_Type_Safe__Step__Class_Kwargs.py @@ -1,8 +1,6 @@ from unittest import TestCase -from osbot_utils.context_managers.capture_duration import capture_duration -from osbot_utils.helpers.trace.Trace_Call import trace_calls -from osbot_utils.utils.Dev import pprint from osbot_utils.type_safe.steps.Type_Safe__Step__Class_Kwargs import Type_Safe__Step__Class_Kwargs +from osbot_utils.helpers.trace.Trace_Call import trace_calls class test_Type_Safe__Step__Class_Kwargs(TestCase): @@ -12,9 +10,15 @@ def setUpClass(cls): cls.step_class_kwargs = Type_Safe__Step__Class_Kwargs() #@trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=60) - def test_empty_class(self): - class EmptyClass: pass - with capture_duration() as duration: - assert self.step_class_kwargs.get_cls_kwargs(EmptyClass) == {} + def test_class__empty(self): + class Class__Empty: pass + assert self.step_class_kwargs.get_cls_kwargs(Class__Empty) == {} - #pprint(duration.json()) \ No newline at end of file + #@trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=60, show_class=True) + def test_class__with_one_int(self): + class Class__One_int: + an_int : int + assert self.step_class_kwargs.get_cls_kwargs(Class__One_int) == {'an_int': 0} + #Class__One_int() + #Class__One_int() + #Class__One_int() diff --git a/tests/unit/type_safe/steps/test_Type_Safe__Step__Init.py b/tests/unit/type_safe/steps/test_Type_Safe__Step__Init.py new file mode 100644 index 00000000..949c7d21 --- /dev/null +++ b/tests/unit/type_safe/steps/test_Type_Safe__Step__Init.py @@ -0,0 +1,83 @@ +from unittest import TestCase +from osbot_utils.helpers.trace.Trace_Call import trace_calls +from osbot_utils.type_safe.Type_Safe import Type_Safe +from osbot_utils.type_safe.steps.Type_Safe__Step__Class_Kwargs import Type_Safe__Step__Class_Kwargs +from osbot_utils.type_safe.steps.Type_Safe__Step__Init import Type_Safe__Step__Init + + +class test_Type_Safe__Step__Class_Kwargs(TestCase): + + @classmethod + def setUpClass(cls): + cls.step_class_kwargs = Type_Safe__Step__Class_Kwargs() + cls.step_init = Type_Safe__Step__Init () + + #@trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=60) + def test_init__class__empty(self): + class Class__Empty: pass + empty_class = Class__Empty() + class_kwargs = {} + kwargs = {} + self.step_init.init(empty_class, class_kwargs, **kwargs) + assert empty_class.__dict__ == {} + + #@trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=80) + def test_init__class_one_int__no_kwargs(self): + class Class__One_int: + an_int: int + + one_int = Class__One_int() + class_kwargs = {'an_int': 0} + kwargs = {} + self.step_init.init(one_int, class_kwargs, **kwargs) + assert one_int.__dict__ == {'an_int': 0} + assert one_int.an_int == 0 + + #@trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=80) + def test_init__class_one_int__with_value__no_kwargs(self): + class Class__One_int: + an_int: int + + one_int = Class__One_int() + one_int.an_int = 42 + class_kwargs = {'an_int': 0} + kwargs = {} + self.step_init.init(one_int, class_kwargs, **kwargs) + assert one_int.__dict__ == {'an_int': 42} + assert one_int.an_int == 42 + + #@trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=80) + def test_init__class_one_int__no_value__no_kwargs(self): + class Class__One_int: + pass + + one_int = Class__One_int() + + class_kwargs = {'an_int': 0} + kwargs = {} + self.step_init.init(one_int, class_kwargs, **kwargs) + assert one_int.__dict__ == {'an_int': 0} + assert one_int.an_int == 0 + + @trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=80) + def test_init__class_one_int__with_kwargs(self): + class Class__One_int: + an_int: int + one_int = Class__One_int() + class_kwargs = {'an_int': 0 } + kwargs = {'an_int': 42} + self.step_init.init(one_int, class_kwargs, **kwargs) + assert one_int.__dict__ == {'an_int': 42} + assert one_int.an_int == 42 + + + #@trace_calls(include=['*'], show_internals=True, show_duration=True, duration_padding=80) + def test_init__type_safe__class_one_int__no_kwargs(self): + class Class__One_int(Type_Safe): + an_int: int + + one_int = Class__One_int() + + # assert one_int.__dict__ == {'an_int': 0} + # assert one_int.an_int == 0 + diff --git a/tests/unit/type_safe/steps/test_Type_Safe__Step__Set_Attr.py b/tests/unit/type_safe/steps/test_Type_Safe__Step__Set_Attr.py new file mode 100644 index 00000000..706197fc --- /dev/null +++ b/tests/unit/type_safe/steps/test_Type_Safe__Step__Set_Attr.py @@ -0,0 +1,59 @@ +from unittest import TestCase +from osbot_utils.utils.Misc import random_guid +from osbot_utils.helpers.Random_Guid import Random_Guid +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 + + +class test_Type_Safe__Step__Set_Attr(TestCase): + + @classmethod + def setUpClass(cls): + cls.step_set_attr = Type_Safe__Step__Set_Attr() + + # @trace_calls(include = ['*'], + # show_internals = True , + # show_duration = True , + # duration_padding = 130 , + # show_class = True ) + def test_class__one_int(self): + class Class__One_int: + an_int: int + one_int = Class__One_int() + one_int.an_int = 0 + assert one_int.__class__.__mro__ == (Class__One_int, object) + 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 + + assert self.step_set_attr.setattr(one_int, one_int, 'an_int', 42) is None + assert self.step_set_attr.setattr(one_int, one_int, 'an_int', 42) is None + + def test_class__random_guid(self): + print() + class Class__Random_Guid: + an_str : str + an_guid: Random_Guid + with_random_guid = Class__Random_Guid() + + value_1 = random_guid() + assert self.step_set_attr.setattr(with_random_guid, with_random_guid, 'an_guid', value_1) is None + assert with_random_guid.an_guid == value_1 + assert type(with_random_guid.an_guid) is Random_Guid + + value_2 = f'{random_guid()}' + assert self.step_set_attr.setattr(with_random_guid, with_random_guid, 'an_guid', value_2) is None + assert with_random_guid.an_guid == value_2 + assert type(with_random_guid.an_guid) is Random_Guid + + value_3 = f'{random_guid()}' + assert self.step_set_attr.setattr(with_random_guid, with_random_guid, 'an_str', value_3) is None + assert with_random_guid.an_str == value_3 + assert type(with_random_guid.an_str) is str + + value_4 = random_guid() + assert self.step_set_attr.setattr(with_random_guid, with_random_guid, 'an_str', value_4) is None + assert with_random_guid.an_str == value_4 + assert type(with_random_guid.an_str) is str \ No newline at end of file diff --git a/tests/unit/type_safe/steps/test_perf__Type_Safe__Step__Class_Kwargs.py b/tests/unit/type_safe/steps/test_perf__Type_Safe__Step__Class_Kwargs.py index 9fd99265..04c941c0 100644 --- a/tests/unit/type_safe/steps/test_perf__Type_Safe__Step__Class_Kwargs.py +++ b/tests/unit/type_safe/steps/test_perf__Type_Safe__Step__Class_Kwargs.py @@ -102,8 +102,6 @@ class DefaultsClass: int_val : int = 42 float_val : float = 3.14 bool_val : bool = True - #tuple_val : tuple = (1, 2, 3) - #frozenset_val: frozenset = frozenset([1, 2, 3]) bytes_val : bytes = b"bytes" enum_val : SimpleEnum = SimpleEnum.A