Skip to content

Commit

Permalink
important fix (and behaviour change) to Type_Safe handling of Type[*]…
Browse files Browse the repository at this point in the history
… now the type provided in * is the default value

this makes it more compatible with the current behaviour of Type_Safe and makes the Type assignments much more useful
  • Loading branch information
DinisCruz committed Jan 9, 2025
1 parent 4f94637 commit f8a870e
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 14 deletions.
9 changes: 9 additions & 0 deletions osbot_utils/type_safe/Type_Safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ def __default__value__(cls, var_type):
from osbot_utils.type_safe.Type_Safe__List import Type_Safe__List
from osbot_utils.type_safe.Type_Safe__Dict import Type_Safe__Dict

if get_origin(var_type) is type: # Special handling for Type[T] # todo: reuse the get_origin value
type_args = get_args(var_type)
if type_args:
if isinstance(type_args[0], ForwardRef):
forward_name = type_args[0].__forward_arg__
if forward_name == cls.__name__:
return cls
return type_args[0] # Return the actual type as the default value

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:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/type_safe/bugs/test_Type_Safe__bugs.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import re
import sys
import pytest
from typing import Optional, Union, Dict, Type
from unittest import TestCase
from osbot_utils.helpers.Random_Guid import Random_Guid
from osbot_utils.utils.Objects import __
from osbot_utils.type_safe.Type_Safe import Type_Safe
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self

class test_Type_Safe__bugs(TestCase):



def test__bug__in__convert_dict_to_value_from_obj_annotation(self):
class An_Class_2_B(Type_Safe):
an_str: str
Expand Down
66 changes: 59 additions & 7 deletions tests/unit/type_safe/regression/test_Type_Safe__regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
import pytest
import sys
from decimal import Decimal
from typing import Optional, Union, List, Dict, get_origin, Type
from typing import Optional, Union, List, Dict, get_origin, Type, ForwardRef
from unittest import TestCase
from unittest.mock import patch

from osbot_utils.utils.Dev import pprint

from osbot_utils.helpers.Timestamp_Now import Timestamp_Now
from osbot_utils.helpers.Guid import Guid
from osbot_utils.helpers.python_compatibility.python_3_8 import Annotated
Expand All @@ -22,13 +25,62 @@

class test_Type_Safe__regression(TestCase):

def test__bug__forward_refs_in_type(self):
def test__regression__type_annotations_with_forward_ref(self):
class An_Class_1(Type_Safe):
an_type__forward_ref: Type['An_Class_1'] # Forward reference to self
an_type__direct: Type[Type_Safe] # Direct reference for comparison

# with pytest.raises(TypeError, match=re.escape("Invalid type for attribute 'an_type__forward_ref'. Expected 'typing.Type[ForwardRef('An_Class_1')]' but got '<class 'typing.ForwardRef'>'")):
# test_class = An_Class_1() # Fixed BUG should not have raised
test_class = An_Class_1()
assert test_class.an_type__forward_ref is An_Class_1
assert test_class.an_type__direct is Type_Safe

assert test_class.__annotations__['an_type__forward_ref'] == Type[ForwardRef('An_Class_1')] # Confirm forward ref is correct
assert test_class.__annotations__['an_type__direct' ] == Type[Type_Safe] # Confirm direct ref is correct
#
# The bug manifests when trying to set default values for these types
#assert test_class.an_type__forward_ref is None # Fixed BUG: This fails with TypeError
assert test_class.an_type__forward_ref is An_Class_1
assert test_class.an_type__direct is Type_Safe # Direct reference works fine


def test__regression__type_annotations_default_to_none(self):
class Schema__Base(Type_Safe): pass # Define base class

class Schema__Default__Types(Type_Safe):
base_type: Type[Schema__Base] # Type annotation that should default to Schema__Base

defaults = Schema__Default__Types()

assert defaults.__annotations__ == {'base_type': Type[Schema__Base]} # Confirm annotation is correct
#assert defaults.base_type is None # Fixed BUG: This should be Schema__Base instead of None
assert defaults.base_type is Schema__Base
assert type(defaults.__class__.__annotations__['base_type']) == type(Type[Schema__Base])

# Also test in inheritance scenario to be thorough
class Schema__Child(Schema__Default__Types):
child_type: Type[Schema__Base]

child = Schema__Child()
assert all_annotations(child) == {'base_type' : Type[Schema__Base],
'child_type': Type[Schema__Base]} # Confirm both annotations exist
#assert child.base_type is None # Fixed BUG: Should be Schema__Base
#assert child.child_type is None # Fixed BUG: Should be Schema__Base
assert child.base_type is Schema__Base
assert child.child_type is Schema__Base

def test__regression__forward_refs_in_type(self):
class An_Class_1(Type_Safe):
an_type__str : Type[str]
an_type__forward_ref: Type['An_Class_1']

an_class = An_Class_1()
assert an_class.obj() == __(an_type__str=None, an_type__forward_ref=None)
assert an_class.an_type__str is str
assert an_class.an_type__forward_ref is An_Class_1
assert an_class.json() == { 'an_type__forward_ref': 'test_Type_Safe__regression.An_Class_1' ,
'an_type__str' : 'builtins.str' }
assert an_class.obj() == __(an_type__str='builtins.str', an_type__forward_ref='test_Type_Safe__regression.An_Class_1')

an_class.an_type__str = str
an_class.an_type__str = Random_Guid
Expand All @@ -55,8 +107,8 @@ class An_Class(Type_Safe):
# with pytest.raises(TypeError, match="Subscripted generics cannot be used with class and instance checks"):
# An_Class() # FXIED BUG

assert An_Class().obj() == __(an_guid = 'osbot_utils.helpers.Guid.Guid',
an_time_stamp = None )
assert An_Class().obj() == __(an_guid = 'osbot_utils.helpers.Guid.Guid' ,
an_time_stamp = 'osbot_utils.helpers.Timestamp_Now.Timestamp_Now' )

def test__regression__type_from_json(self):
class An_Class(Type_Safe):
Expand Down Expand Up @@ -131,8 +183,8 @@ class An_Class(Type_Safe):
an_type_int: Type[int]

an_class = An_Class()
assert an_class.an_type_str is None
assert an_class.an_type_int is None
assert an_class.an_type_str is str
assert an_class.an_type_int is int
an_class.an_type_str = str
an_class.an_type_int = int

Expand Down
8 changes: 4 additions & 4 deletions tests/unit/type_safe/test_Type_Safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ class An_Typing_Class(Type_Safe):
assert default_value(round_trip.an_list) == []
assert default_value(round_trip.an_set ) == set()

def test__type__with_type__are_enforced(self):
def test__type__with_type__are_enforced__and_default_is_type(self):
class An_Class(Type_Safe):
an_type_str: Type[str]
an_type_int: Type[int]
Expand All @@ -934,8 +934,8 @@ class Timestamp_Now__Extra(Timestamp_Now):
pass

an_class = An_Class()
assert an_class.an_type_str is None
assert an_class.an_type_int is None
assert an_class.an_type_str is str
assert an_class.an_type_int is int
an_class.an_type_str = str
an_class.an_type_int = int
an_class.an_type_str = Guid
Expand All @@ -948,7 +948,7 @@ class An_Class_1(Type_Safe):
an_guid : Type[Guid]
an_time_stamp: Type[Timestamp_Now]

assert An_Class_1().json() == {'an_guid': None, 'an_time_stamp': None}
assert An_Class_1().json() == {'an_guid': 'osbot_utils.helpers.Guid.Guid', 'an_time_stamp': 'osbot_utils.helpers.Timestamp_Now.Timestamp_Now'}



Expand Down

0 comments on commit f8a870e

Please sign in to comment.