diff --git a/osbot_utils/type_safe/shared/Type_Safe__Cache.py b/osbot_utils/type_safe/shared/Type_Safe__Cache.py index ded9b86f..69b38eef 100644 --- a/osbot_utils/type_safe/shared/Type_Safe__Cache.py +++ b/osbot_utils/type_safe/shared/Type_Safe__Cache.py @@ -2,7 +2,7 @@ from typing import get_origin from weakref import WeakKeyDictionary from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES -from osbot_utils.utils.Objects import all_annotations__in_class +from osbot_utils.utils.Objects import all_annotations__in_class, all_annotations class Type_Safe__Cache: @@ -10,6 +10,7 @@ class Type_Safe__Cache: _cls__annotations_cache : WeakKeyDictionary _cls__immutable_vars : WeakKeyDictionary _cls__kwargs_cache : WeakKeyDictionary + _obj__annotations_cache : WeakKeyDictionary _get_origin_cache : WeakKeyDictionary _mro_cache : WeakKeyDictionary _valid_vars_cache : WeakKeyDictionary @@ -17,6 +18,7 @@ class Type_Safe__Cache: cache_hit__cls__annotations : int = 0 cache_hit__cls__kwargs : int = 0 cache_hit__cls__immutable_vars: int = 0 + cache_hit__obj__annotations : int = 0 cache_hit__get_origin : int = 0 cache_hit__mro : int = 0 cache_hit__valid_vars : int = 0 @@ -28,6 +30,7 @@ def __init__(self): 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._obj__annotations_cache = WeakKeyDictionary() # Cache for object annotations self._get_origin_cache = WeakKeyDictionary() # Cache for get_origin results self._mro_cache = WeakKeyDictionary() # Cache for Method Resolution Order self._valid_vars_cache = WeakKeyDictionary() @@ -39,6 +42,19 @@ def get_cls_kwargs(self, cls): self.cache_hit__cls__kwargs += 1 return self._cls__kwargs_cache.get(cls) + def get_annotations(self, target): + if target is None: + return {} + annotations_key = target.__class__ + annotations = self._obj__annotations_cache.get(annotations_key) # 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 annotations_key not in self._obj__annotations_cache: + annotations = dict(all_annotations(target).items()) + self._obj__annotations_cache[annotations_key] = annotations + else: + self.cache_hit__obj__annotations += 1 + return annotations + def get_class_annotations(self, cls): 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 @@ -97,6 +113,7 @@ def print_cache_hits(self): 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" obj__annotations : {self.cache_hit__obj__annotations }") 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 }") 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 d7e2f76f..90f073b1 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Step__Init.py +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__Init.py @@ -1,26 +1,11 @@ -from typing import get_origin, get_args, ForwardRef, Annotated, List, Tuple, Dict, Any, Optional, Type -from osbot_utils.type_safe.shared.Type_Safe__Validation import type_safe_validation -from osbot_utils.utils.Objects import all_annotations, obj_attribute_annotation - - class Type_Safe__Step__Init: - # def check_obj_type_annotation_mismatch(self, target, attr_name, value): - # if self.value_type_matches_obj_annotation_for_attr(target, attr_name, value) is False: # handle case with normal types - # if self.value_type_matches_obj_annotation_for_union_and_annotated(target, attr_name, value) is False: # this is done like this because value_type_matches_obj_annotation_for_union_attr will return None when there is no Union objects - # # todo: check if this is still needed since there is no code coverage here - # type_safe_raise_exception.type_mismatch_error(attr_name, target.__annotations__.get(attr_name), type(value)) - # #raise TypeError(f"Invalid type for attribute '{attr_name}'. Expected '{target.__annotations__.get(attr_name)}' but got '{type(value)}'") - - def init(self, __self , __class_kwargs , **kwargs ) -> None: 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 - # self.check_obj_type_annotation_mismatch(__self, key, value) if hasattr(__self, key): existing_value = getattr(__self, key) if existing_value is not None: @@ -28,9 +13,9 @@ def init(self, __self , continue setattr(__self, key, value) - for (key, value) in kwargs.items(): # overwrite with values provided in ctor + for (key, value) in kwargs.items(): # overwrite with values provided in ctor if hasattr(__self, key): - if value is not None: # prevent None values from overwriting existing values, which is quite common in default constructors + if value is not None: # prevent None values from overwriting existing values, which is quite common in default constructors setattr(__self, key, value) else: raise ValueError(f"{__self.__class__.__name__} has no attribute '{key}' and cannot be assigned the value '{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 35c03aca..d856bf15 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 @@ -2,7 +2,6 @@ from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache from osbot_utils.type_safe.shared.Type_Safe__Convert import type_safe_convert from osbot_utils.type_safe.shared.Type_Safe__Validation import type_safe_validation -from osbot_utils.utils.Objects import all_annotations from osbot_utils.type_safe.validators.Type_Safe__Validator import Type_Safe__Validator class Type_Safe__Step__Set_Attr: @@ -74,7 +73,8 @@ def setattr(self, _super, _self, name, value): if self.handle_special_generic_alias(_super, _self, name, value): return - annotations = all_annotations(_self) + annotations = dict(type_safe_cache.get_annotations(_self)) + if not annotations: # can't do type safety checks if the class does not have annotations return _super.__setattr__(name, value) diff --git a/osbot_utils/utils/Status.py b/osbot_utils/utils/Status.py index 8a6028dc..0ade318a 100644 --- a/osbot_utils/utils/Status.py +++ b/osbot_utils/utils/Status.py @@ -1,6 +1,4 @@ # todo refactor into Status class -import traceback - from osbot_utils.utils.Python_Logger import Python_Logger class Status: diff --git a/tests/unit/helpers/ssh/test_SSH__Cache__Requests.py b/tests/unit/helpers/ssh/test_SSH__Cache__Requests.py index a885122c..c810ba40 100644 --- a/tests/unit/helpers/ssh/test_SSH__Cache__Requests.py +++ b/tests/unit/helpers/ssh/test_SSH__Cache__Requests.py @@ -2,8 +2,7 @@ from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self from osbot_utils.helpers.sqlite.cache.Sqlite__Cache__Requests import Sqlite__Cache__Requests from osbot_utils.helpers.sqlite.cache.Sqlite__Cache__Requests__Patch import Sqlite__Cache__Requests__Patch -from osbot_utils.helpers.ssh.SSH__Cache__Requests import SSH__Cache__Requests, SQLITE_DB_NAME__SSH_REQUESTS_CACHE, \ - SQLITE_TABLE_NAME__SSH_REQUESTS +from osbot_utils.helpers.ssh.SSH__Cache__Requests import SSH__Cache__Requests, SQLITE_DB_NAME__SSH_REQUESTS_CACHE, SQLITE_TABLE_NAME__SSH_REQUESTS from osbot_utils.helpers.ssh.SSH__Execute import ENV_VAR__SSH__HOST, SSH__Execute from osbot_utils.utils.Env import get_env from osbot_utils.utils.Files import temp_file, current_temp_folder, parent_folder, file_extension, file_name