Skip to content

Commit

Permalink
refactored non-cached methods to Type_Safe__Not_Cached starting with …
Browse files Browse the repository at this point in the history
…the annotations
  • Loading branch information
DinisCruz committed Jan 20, 2025
1 parent 0a83d98 commit ca84dc3
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 85 deletions.
27 changes: 27 additions & 0 deletions osbot_utils/type_safe/shared/Type_Safe__Annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import get_origin
from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache


class Type_Safe__Annotations:

def all_annotations(self, target):
return type_safe_cache.get_annotations(target) # use cache

def all_annotations__in_class(self, cls):
return type_safe_cache.get_class_annotations(cls)

def obj_attribute_annotation(self, target, attr_name):
return self.all_annotations(target).get(attr_name) # use cache

def obj_is_attribute_annotation_of_type(self, target, attr_name, expected_type):
attribute_annotation = self.obj_attribute_annotation(target, attr_name)
if expected_type is attribute_annotation:
return True
if expected_type is type(attribute_annotation):
return True
if expected_type is get_origin(attribute_annotation): # todo: use get_origin cache # handle genericAlias
return True
return False


type_safe_annotations = Type_Safe__Annotations()
6 changes: 3 additions & 3 deletions osbot_utils/type_safe/shared/Type_Safe__Cache.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import inspect
from typing import get_origin
from weakref import WeakKeyDictionary
from osbot_utils.type_safe.shared.Type_Safe__Not_Cached import type_safe_not_cached
from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES
from osbot_utils.utils.Objects import all_annotations__in_class, all_annotations


class Type_Safe__Cache:
Expand Down Expand Up @@ -49,7 +49,7 @@ def get_annotations(self, target):
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())
annotations = dict(type_safe_not_cached.all_annotations(target).items())
self._obj__annotations_cache[annotations_key] = annotations
else:
self.cache_hit__obj__annotations += 1
Expand All @@ -59,7 +59,7 @@ 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
if self.skip_cache or cls not in self._cls__annotations_cache:
annotations = all_annotations__in_class(cls).items()
annotations = type_safe_not_cached.all_annotations__in_class(cls).items()
self._cls__annotations_cache[cls] = annotations
else:
self.cache_hit__cls__annotations += 1
Expand Down
19 changes: 19 additions & 0 deletions osbot_utils/type_safe/shared/Type_Safe__Not_Cached.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Type_Safe__Not_Cached:

def all_annotations(self, target):
annotations = {}
if hasattr(target.__class__, '__mro__'):
for base in reversed(target.__class__.__mro__):
if hasattr(base, '__annotations__'):
annotations.update(base.__annotations__)
return annotations

def all_annotations__in_class(self, target):
annotations = {}
if hasattr(target, '__mro__'):
for base in reversed(target.__mro__):
if hasattr(base, '__annotations__'):
annotations.update(base.__annotations__)
return annotations

type_safe_not_cached = Type_Safe__Not_Cached()
11 changes: 6 additions & 5 deletions osbot_utils/type_safe/shared/Type_Safe__Validation.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import types
import typing
from enum import EnumMeta
from typing import Any, Annotated, Optional, get_args, get_origin, ForwardRef, Type, Dict, List
from typing import Any, Annotated, Optional, get_args, get_origin, ForwardRef, Type, Dict
from osbot_utils.type_safe.shared.Type_Safe__Annotations import type_safe_annotations
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, obj_attribute_annotation, all_annotations
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


Expand Down Expand Up @@ -58,7 +60,7 @@ def check_if__type_matches__obj_annotation__for_union_and_annotated(self, target
from typing import Union, get_origin, get_args

value_type = type(value)
attribute_annotation = obj_attribute_annotation(target, attr_name)
attribute_annotation = type_safe_annotations.obj_attribute_annotation(target, attr_name)
origin = get_origin(attribute_annotation)

if origin is Union:
Expand Down Expand Up @@ -139,8 +141,7 @@ def check_if__type_matches__obj_annotation__for_attr(self, target,
attr_name,
value
) -> Optional[bool]:
import typing
annotations = all_annotations(target)
annotations = type_safe_cache.get_annotations(target)
attr_type = annotations.get(attr_name)
if attr_type:
origin_attr_type = get_origin(attr_type) # to handle when type definition contains a generic
Expand Down
45 changes: 23 additions & 22 deletions osbot_utils/type_safe/steps/Type_Safe__Step__From_Json.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import sys
import types
from decimal import Decimal
from enum import EnumMeta
from osbot_utils.type_safe.Type_Safe import Type_Safe
from osbot_utils.type_safe.Type_Safe__List import Type_Safe__List
from osbot_utils.helpers.Random_Guid import Random_Guid
from osbot_utils.helpers.Random_Guid_Short import Random_Guid_Short
from osbot_utils.utils.Objects import obj_is_attribute_annotation_of_type, all_annotations
from osbot_utils.utils.Objects import obj_attribute_annotation
from osbot_utils.utils.Objects import enum_from_value
from osbot_utils.helpers.Safe_Id import Safe_Id
from osbot_utils.helpers.Timestamp_Now import Timestamp_Now
from decimal import Decimal
from enum import EnumMeta
from osbot_utils.type_safe.Type_Safe import Type_Safe
from osbot_utils.type_safe.Type_Safe__List import Type_Safe__List
from osbot_utils.helpers.Random_Guid import Random_Guid
from osbot_utils.helpers.Random_Guid_Short import Random_Guid_Short
from osbot_utils.type_safe.shared.Type_Safe__Annotations import type_safe_annotations
from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache
from osbot_utils.utils.Objects import enum_from_value
from osbot_utils.helpers.Safe_Id import Safe_Id
from osbot_utils.helpers.Timestamp_Now import Timestamp_Now

# todo; refactor all this python compatibility into the python_3_8 class
if sys.version_info < (3, 8): # pragma: no cover
Expand Down Expand Up @@ -43,12 +43,12 @@ def deserialize_from_dict(self, _self, data, raise_on_not_found=False):
raise ValueError(f"Attribute '{key}' not found in '{_self.__class__.__name__}'")
else:
continue
if obj_attribute_annotation(_self, key) == type: # Handle type objects
if type_safe_annotations.obj_attribute_annotation(_self, key) == type: # Handle type objects
value = self.deserialize_type__using_value(value)
elif obj_is_attribute_annotation_of_type(_self, key, dict): # handle the case when the value is a dict
elif type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, dict): # handle the case when the value is a dict
value = self.deserialize_dict__using_key_value_annotations(_self, key, value)
elif obj_is_attribute_annotation_of_type(_self, key, list): # handle the case when the value is a list
attribute_annotation = obj_attribute_annotation(_self, key) # get the annotation for this variable
elif type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, list): # handle the case when the value is a list
attribute_annotation = type_safe_annotations.obj_attribute_annotation(_self, key) # get the annotation for this variable
attribute_annotation_args = get_args(attribute_annotation)
if attribute_annotation_args:
expected_type = get_args(attribute_annotation)[0] # get the first arg (which is the type)
Expand All @@ -62,21 +62,21 @@ def deserialize_from_dict(self, _self, data, raise_on_not_found=False):
value = type_safe_list # todo: refactor out this create list code, maybe to an deserialize_from_list method
else:
if value is not None:
if obj_is_attribute_annotation_of_type(_self, key, EnumMeta): # Handle the case when the value is an Enum
if type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, EnumMeta): # Handle the case when the value is an Enum
enum_type = getattr(_self, '__annotations__').get(key)
if type(value) is not enum_type: # If the value is not already of the target type
value = enum_from_value(enum_type, value) # Try to resolve the value into the enum

# todo: refactor these special cases into a separate method to class
elif obj_is_attribute_annotation_of_type(_self, key, Decimal): # handle Decimals
elif type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, Decimal): # handle Decimals
value = Decimal(value)
elif obj_is_attribute_annotation_of_type(_self, key, Safe_Id): # handle Safe_Id
elif type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, Safe_Id): # handle Safe_Id
value = Safe_Id(value)
elif obj_is_attribute_annotation_of_type(_self, key, Random_Guid): # handle Random_Guid
elif type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, Random_Guid): # handle Random_Guid
value = Random_Guid(value)
elif obj_is_attribute_annotation_of_type(_self, key, Random_Guid_Short): # handle Random_Guid_Short
elif type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, Random_Guid_Short): # handle Random_Guid_Short
value = Random_Guid_Short(value)
elif obj_is_attribute_annotation_of_type(_self, key, Timestamp_Now): # handle Timestamp_Now
elif type_safe_annotations.obj_is_attribute_annotation_of_type(_self, key, Timestamp_Now): # handle Timestamp_Now
value = Timestamp_Now(value)
setattr(_self, key, value) # Direct assignment for primitive types and other structures

Expand All @@ -97,7 +97,8 @@ def deserialize_type__using_value(self, value):

def deserialize_dict__using_key_value_annotations(self, _self, key, value):
from osbot_utils.type_safe.Type_Safe__Dict import Type_Safe__Dict
annotations = all_annotations(_self)

annotations = type_safe_cache.get_annotations(_self)
dict_annotations_tuple = get_args(annotations.get(key))
if not dict_annotations_tuple: # happens when the value is a dict/Dict with no annotations
return value
Expand Down
34 changes: 0 additions & 34 deletions osbot_utils/utils/Objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,24 +276,6 @@ def obj_get_value(target=None, key=None, default=None):
def obj_values(target=None):
return list(obj_dict(target).values())

def obj_attribute_annotation(target, attr_name):
if target is not None and attr_name is not None:
if hasattr(target, '__annotations__'):
obj_annotations = target.__annotations__
if hasattr(obj_annotations,'get'):
attribute_annotation = obj_annotations.get(attr_name)
return attribute_annotation
return None

def obj_is_attribute_annotation_of_type(target, attr_name, expected_type):
attribute_annotation = obj_attribute_annotation(target, attr_name)
if expected_type is attribute_annotation:
return True
if expected_type is type(attribute_annotation):
return True
if expected_type is get_origin(attribute_annotation): # handle genericAlias
return True
return False

def obj_is_type_union_compatible(var_type, compatible_types):
from typing import Union
Expand Down Expand Up @@ -347,22 +329,6 @@ def serialize_to_dict(obj):
return data
else:
raise TypeError(f"Type {type(obj)} not serializable")

def all_annotations(target):
annotations = {}
if hasattr(target.__class__, '__mro__'):
for base in reversed(target.__class__.__mro__):
if hasattr(base, '__annotations__'):
annotations.update(base.__annotations__)
return annotations

def all_annotations__in_class(target):
annotations = {}
if hasattr(target, '__mro__'):
for base in reversed(target.__mro__):
if hasattr(base, '__annotations__'):
annotations.update(base.__annotations__)
return annotations



Expand Down
8 changes: 3 additions & 5 deletions tests/unit/context_managers/test_capture_duration.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from unittest import TestCase

from osbot_utils.testing.Stdout import Stdout

from osbot_utils.context_managers.capture_duration import capture_duration
from unittest import TestCase
from osbot_utils.testing.Stdout import Stdout
from osbot_utils.context_managers.capture_duration import capture_duration

class test_capture_duration(TestCase):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import pytest
from unittest import TestCase
from typing import get_args, get_origin, List, Dict, Any, Union, Optional
from osbot_utils.type_safe.shared.Type_Safe__Annotations import type_safe_annotations
from osbot_utils.type_safe.steps.Type_Safe__Step__Init import type_safe_step_init
from osbot_utils.testing.performance.Performance_Measure__Session import Performance_Measure__Session
from osbot_utils.utils.Objects import (obj_data, default_value, all_annotations,
obj_is_type_union_compatible,
obj_is_attribute_annotation_of_type)
from osbot_utils.utils.Objects import (obj_data, default_value ,
obj_is_type_union_compatible)
from osbot_utils.utils.Json import json_dumps, json_parse

class An_Class: # Simple test class with annotations
Expand Down Expand Up @@ -152,7 +152,7 @@ def do_default_value(): #
return default_value(str)

def do_all_annotations(): # Performance of all_annotations()
return all_annotations(obj)
return type_safe_annotations.all_annotations(obj)

with Performance_Measure__Session() as session:
session.measure(do_obj_data ).assert_time(self.time_8_kns, self.time_9_kns )
Expand All @@ -166,7 +166,7 @@ def check_type_union(): # P
return obj_is_type_union_compatible(str, (str, int))

def check_annotation_type(): # Performance of obj_is_attribute_annotation_of_type()
return obj_is_attribute_annotation_of_type(obj, 'an_str', str)
return type_safe_annotations.obj_is_attribute_annotation_of_type(obj, 'an_str', str)

def check_value_matches(): # Performance of value_type_matches_obj_annotation_for_attr()
return type_safe_step_init.check_if__type_matches__obj_annotation__for_attr(obj, 'an_str', 'test')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, List, Dict, Union
from typing import Optional, List, Dict, Union
from unittest import TestCase
from osbot_utils.helpers.trace.Trace_Call import trace_calls
from osbot_utils.type_safe.Type_Safe import Type_Safe
Expand Down Expand Up @@ -37,16 +37,17 @@ class test__perf__Type_Safe__tracing(TestCase):
show_types = False ,
show_class = True ,
show_duration = True ,
duration_padding = 150 ,
duration_padding = 140 ,
#duration_bigger_than = 0.001
)
def test_complex_types(self):

class ComplexTypes(Type_Safe): # Multiple complex types
optional_str : Optional [str]
str_list : List [str]
#an_int : int
#optional_str : Optional [str]
#str_list : List [str]
int_dict : Dict [str, int]
union_field : Union [str, int]
#union_field : Union [str, int]

ComplexTypes()

Expand Down
Loading

0 comments on commit ca84dc3

Please sign in to comment.