diff --git a/osbot_utils/helpers/trace/Trace_Call.py b/osbot_utils/helpers/trace/Trace_Call.py index e77a1562..7e79f2a6 100644 --- a/osbot_utils/helpers/trace/Trace_Call.py +++ b/osbot_utils/helpers/trace/Trace_Call.py @@ -1,8 +1,7 @@ import linecache import sys import threading -from functools import wraps - +from functools import wraps from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self from osbot_utils.helpers.trace.Trace_Call__Config import Trace_Call__Config, PRINT_MAX_STRING_LENGTH from osbot_utils.helpers.trace.Trace_Call__Handler import Trace_Call__Handler diff --git a/osbot_utils/type_safe/Type_Safe.py b/osbot_utils/type_safe/Type_Safe.py index 4cbbcc1c..dcd01b37 100644 --- a/osbot_utils/type_safe/Type_Safe.py +++ b/osbot_utils/type_safe/Type_Safe.py @@ -4,11 +4,10 @@ import sys import types -from osbot_utils.type_safe.steps.Type_Safe__Step__Default_Kwargs import type_safe_step_default_kwargs +from osbot_utils.type_safe.steps.Type_Safe__Step__Default_Kwargs import type_safe_step_default_kwargs from osbot_utils.type_safe.steps.Type_Safe__Step__Default_Value import type_safe_step_default_value from osbot_utils.type_safe.steps.Type_Safe__Step__Init import type_safe_step_init from osbot_utils.type_safe.steps.Type_Safe__Step__Set_Attr import type_safe_step_set_attr -from osbot_utils.utils.Objects import all_annotations from osbot_utils.type_safe.Cache__Class_Kwargs import cache__class_kwargs # Backport implementations of get_origin and get_args for Python 3.7 @@ -262,29 +261,26 @@ def __default_kwargs__(self): # Return entire (inc # # return kwargs - def __kwargs__(self): - """Return a dictionary of the current instance's attribute values including inherited class defaults.""" - kwargs = {} - # Update with instance-specific values - for key, value in self.__default_kwargs__().items(): - kwargs[key] = self.__getattribute__(key) - # if hasattr(self, key): - # kwargs[key] = self.__getattribute__(key) - # else: - # kwargs[key] = value # todo: see if this is stil a valid scenario - return kwargs + def __kwargs__(self): # Return a dictionary of the current instance's attribute values including inherited class defaults. + return type_safe_step_default_kwargs.kwargs(self) + # kwargs = {} + # # Update with instance-specific values + # for key, value in self.__default_kwargs__().items(): + # kwargs[key] = self.__getattribute__(key) + # return kwargs def __locals__(self): + return type_safe_step_default_kwargs.locals(self) """Return a dictionary of the current instance's attribute values.""" - kwargs = self.__kwargs__() - - if not isinstance(vars(self), types.FunctionType): - for k, v in vars(self).items(): - if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod): - if k.startswith('__') is False: - kwargs[k] = v - return kwargs + # kwargs = self.__kwargs__() + # + # if not isinstance(vars(self), types.FunctionType): + # for k, v in vars(self).items(): + # if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod): + # if k.startswith('__') is False: + # kwargs[k] = v + #return kwargs @classmethod def __schema__(cls): @@ -309,6 +305,7 @@ def json(self): return self.serialize_to_dict() + # todo: see if we still need this. now that Type_Safe handles base types, there should be no need for this def merge_with(self, target): original_attrs = {k: v for k, v in self.__dict__.items() if k not in target.__dict__} # Store the original attributes of self that should be retained. self.__dict__ = target.__dict__ # Set the target's __dict__ to self, now self and target share the same __dict__. @@ -323,6 +320,7 @@ def reset(self): for k,v in self.__cls_kwargs__().items(): setattr(self, k, v) + # todo: see if we still need this here in this class def update_from_kwargs(self, **kwargs): # Update instance attributes with values from provided keyword arguments. from osbot_utils.utils.Objects import value_type_matches_obj_annotation_for_attr for key, value in kwargs.items(): @@ -333,113 +331,7 @@ def update_from_kwargs(self, **kwargs): # Update instanc setattr(self, key, value) return self - def deserialize_type__using_value(self, value): - if value: - try: - module_name, type_name = value.rsplit('.', 1) - if module_name == 'builtins' and type_name == 'NoneType': # Special case for NoneType (which serialises as builtins.* , but it actually in types.* ) - value = types.NoneType - else: - module = __import__(module_name, fromlist=[type_name]) - value = getattr(module, type_name) - except (ValueError, ImportError, AttributeError) as e: - raise ValueError(f"Could not reconstruct type from '{value}': {str(e)}") - return value - - def deserialize_dict__using_key_value_annotations(self, key, value): - from osbot_utils.type_safe.Type_Safe__Dict import Type_Safe__Dict - annotations = all_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 - if not type(value) is dict: - return value - key_class = dict_annotations_tuple[0] - value_class = dict_annotations_tuple[1] - new_value = Type_Safe__Dict(expected_key_type=key_class, expected_value_type=value_class) - - for dict_key, dict_value in value.items(): - if issubclass(key_class, Type_Safe): - new__dict_key = key_class().deserialize_from_dict(dict_key) - else: - new__dict_key = key_class(dict_key) - - if type(dict_value) == value_class: # if the value is already the target, then just use it - new__dict_value = dict_value - elif issubclass(value_class, Type_Safe): - new__dict_value = value_class().deserialize_from_dict(dict_value) - elif value_class is Any: - new__dict_value = dict_value - else: - new__dict_value = value_class(dict_value) - new_value[new__dict_key] = new__dict_value - - return new_value - - # todo: this needs refactoring, since the logic and code is getting quite complex (to be inside methods like this) - def deserialize_from_dict(self, data, raise_on_not_found=False): - from decimal import Decimal - from enum import EnumMeta - 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 - 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 - - if hasattr(data, 'items') is False: - raise ValueError(f"Expected a dictionary, but got '{type(data)}'") - - for key, value in data.items(): - if hasattr(self, key) and isinstance(getattr(self, key), Type_Safe): - getattr(self, key).deserialize_from_dict(value) # if the attribute is a Type_Safe object, then also deserialize it - else: - if hasattr(self, '__annotations__'): # can only do type safety checks if the class does not have annotations - if hasattr(self, key) is False: # make sure we are now adding new attributes to the class - if raise_on_not_found: - raise ValueError(f"Attribute '{key}' not found in '{self.__class__.__name__}'") - else: - continue - if 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 - value = self.deserialize_dict__using_key_value_annotations(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 - 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) - type_safe_list = Type_Safe__List(expected_type) # create a new instance of Type_Safe__List - for item in value: # next we need to convert all items (to make sure they all match the type) - if type(item) is dict: - new_item = expected_type(**item) # create new object - else: - new_item = expected_type(item) - type_safe_list.append(new_item) # and add it to the new type_safe_list obejct - 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 - 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 - value = Decimal(value) - elif 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 - value = Random_Guid(value) - elif 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 - value = Timestamp_Now(value) - setattr(self, key, value) # Direct assignment for primitive types and other structures - return self def obj(self): from osbot_utils.utils.Objects import dict_to_obj @@ -456,13 +348,16 @@ def print(self): @classmethod def from_json(cls, json_data, raise_on_not_found=False): - from osbot_utils.utils.Json import json_parse + from osbot_utils.type_safe.steps.Type_Safe__Step__From_Json import type_safe_step_from_json # circular dependency on Type_Safe + return type_safe_step_from_json.from_json(cls, json_data, raise_on_not_found) - if type(json_data) is str: - json_data = json_parse(json_data) - if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided) - return cls().deserialize_from_dict(json_data,raise_on_not_found=raise_on_not_found) - return cls() + # from osbot_utils.utils.Json import json_parse + # + # if type(json_data) is str: + # json_data = json_parse(json_data) + # if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided) + # return cls().deserialize_from_dict(json_data,raise_on_not_found=raise_on_not_found) + # return cls() # todo: see if it is possible to add recursive protection to this logic def serialize_to_dict(obj): diff --git a/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Kwargs.py b/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Kwargs.py index a5124d44..b29c8478 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Kwargs.py +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__Default_Kwargs.py @@ -21,5 +21,22 @@ def default_kwargs(self, _self): return kwargs + def kwargs(self, _self): + kwargs = {} + for key, value in self.default_kwargs(_self).items(): # Update with instance-specific values + kwargs[key] = _self.__getattribute__(key) + return kwargs + + def locals(self, _self): + """Return a dictionary of the current instance's attribute values.""" + kwargs = self.kwargs(_self) + + if not isinstance(vars(_self), types.FunctionType): + for k, v in vars(_self).items(): + if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod): + if k.startswith('__') is False: + kwargs[k] = v + return kwargs + type_safe_step_default_kwargs = Type_Safe__Step__Default_Kwargs() diff --git a/osbot_utils/type_safe/steps/Type_Safe__Step__From_Json.py b/osbot_utils/type_safe/steps/Type_Safe__Step__From_Json.py new file mode 100644 index 00000000..27ec6d3f --- /dev/null +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__From_Json.py @@ -0,0 +1,137 @@ +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 + +# todo; refactor all this python compatibility into the python_3_8 class +if sys.version_info < (3, 8): # pragma: no cover + + def get_args(tp): + import typing + if isinstance(tp, typing._GenericAlias): + return tp.__args__ + else: + return () +else: + from typing import get_args, Any + + +class Type_Safe__Step__From_Json: + + # todo: this needs refactoring, since the logic and code is getting quite complex (to be inside methods like this) + def deserialize_from_dict(self, _self, data, raise_on_not_found=False): + + if hasattr(data, 'items') is False: + raise ValueError(f"Expected a dictionary, but got '{type(data)}'") + + for key, value in data.items(): + if hasattr(_self, key) and isinstance(getattr(_self, key), Type_Safe): + self.deserialize_from_dict(getattr(_self, key), value) # if the attribute is a Type_Safe object, then also deserialize it + else: + if hasattr(_self, '__annotations__'): # can only do type safety checks if the class does not have annotations + if hasattr(_self, key) is False: # make sure we are now adding new attributes to the class + if raise_on_not_found: + raise ValueError(f"Attribute '{key}' not found in '{_self.__class__.__name__}'") + else: + continue + if 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 + 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 + 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) + type_safe_list = Type_Safe__List(expected_type) # create a new instance of Type_Safe__List + for item in value: # next we need to convert all items (to make sure they all match the type) + if type(item) is dict: + new_item = expected_type(**item) # create new object + else: + new_item = expected_type(item) + type_safe_list.append(new_item) # and add it to the new type_safe_list obejct + 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 + 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 + value = Decimal(value) + elif 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 + value = Random_Guid(value) + elif 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 + value = Timestamp_Now(value) + setattr(_self, key, value) # Direct assignment for primitive types and other structures + + return _self + + def deserialize_type__using_value(self, value): + if value: + try: + module_name, type_name = value.rsplit('.', 1) + if module_name == 'builtins' and type_name == 'NoneType': # Special case for NoneType (which serialises as builtins.* , but it actually in types.* ) + value = types.NoneType + else: + module = __import__(module_name, fromlist=[type_name]) + value = getattr(module, type_name) + except (ValueError, ImportError, AttributeError) as e: + raise ValueError(f"Could not reconstruct type from '{value}': {str(e)}") + return 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) + 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 + if not type(value) is dict: + return value + key_class = dict_annotations_tuple[0] + value_class = dict_annotations_tuple[1] + new_value = Type_Safe__Dict(expected_key_type=key_class, expected_value_type=value_class) + + for dict_key, dict_value in value.items(): + if issubclass(key_class, Type_Safe): + new__dict_key = self.deserialize_from_dict(key_class(), dict_key) + else: + new__dict_key = key_class(dict_key) + + if type(dict_value) == value_class: # if the value is already the target, then just use it + new__dict_value = dict_value + elif issubclass(value_class, Type_Safe): + new__dict_value = self.deserialize_from_dict(value_class(), dict_value) + elif value_class is Any: + new__dict_value = dict_value + else: + new__dict_value = value_class(dict_value) + new_value[new__dict_key] = new__dict_value + + return new_value + + def from_json(self, _cls, json_data, raise_on_not_found=False): + from osbot_utils.utils.Json import json_parse + + if type(json_data) is str: + json_data = json_parse(json_data) + if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided) + return self.deserialize_from_dict(_cls(), json_data,raise_on_not_found=raise_on_not_found) + return _cls() + +type_safe_step_from_json = Type_Safe__Step__From_Json() \ No newline at end of file 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 38cdbbb5..7639bcd5 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 @@ -47,7 +47,10 @@ def setattr(self, _super, _self, name, value): if isinstance(attribute, Type_Safe__Validator): attribute.validate(value=value, field_name=name, target_type=target_type) elif annotation_origin is dict: - value = _self.deserialize_dict__using_key_value_annotations(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 + 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) _super.__setattr__(name, value) diff --git a/tests/unit/helpers/sqlite/test_Sqlite__Field.py b/tests/unit/helpers/sqlite/test_Sqlite__Field.py index c3c5aaaf..134dd4bf 100644 --- a/tests/unit/helpers/sqlite/test_Sqlite__Field.py +++ b/tests/unit/helpers/sqlite/test_Sqlite__Field.py @@ -1,7 +1,8 @@ -from typing import Union, Optional -from unittest import TestCase -from osbot_utils.helpers.sqlite.Sqlite__Field import Sqlite__Field, Sqlite__Field__Type -from osbot_utils.utils.Misc import random_string +from typing import Union, Optional +from unittest import TestCase +from osbot_utils.helpers.sqlite.Sqlite__Field import Sqlite__Field, Sqlite__Field__Type +from osbot_utils.type_safe.steps.Type_Safe__Step__From_Json import type_safe_step_from_json +from osbot_utils.utils.Misc import random_string class test_Sqlite__Field(TestCase): @@ -137,7 +138,7 @@ def test__regression__type_safety_on__union_vars_assigment(self): sqlite_field = Sqlite__Field.from_json(data__name__id) assert Sqlite__Field().json() == data__default - assert sqlite_field.json() == Sqlite__Field().deserialize_from_dict(data__name__id).json() + assert sqlite_field.json() == type_safe_step_from_json.deserialize_from_dict(Sqlite__Field(),data__name__id).json() assert sqlite_field.json() == data__name__id with self.assertRaises(Exception) as context: diff --git a/tests/unit/testing/performance/test_Performance_Measure__Session.py b/tests/unit/testing/performance/test_Performance_Measure__Session.py index 52d971ae..539b9d06 100644 --- a/tests/unit/testing/performance/test_Performance_Measure__Session.py +++ b/tests/unit/testing/performance/test_Performance_Measure__Session.py @@ -36,7 +36,7 @@ class An_Class_6(Type_Safe): Performance_Measure__Session().measure(str ).print().assert_time(self.time_100_ns ) - Performance_Measure__Session().measure(Random_Guid).print().assert_time(self.time_6_kns ) + Performance_Measure__Session().measure(Random_Guid).print().assert_time(self.time_5_kns, self.time_6_kns ) Performance_Measure__Session().measure(An_Class_1 ).print().assert_time(self.time_100_ns ) Performance_Measure__Session().measure(An_Class_2 ).print().assert_time(self.time_5_kns , self.time_6_kns ) Performance_Measure__Session().measure(An_Class_3 ).print().assert_time(self.time_10_kns, self.time_20_kns ) diff --git a/tests/unit/type_safe/test_Type_Safe.py b/tests/unit/type_safe/test_Type_Safe.py index f0ebd402..2fd145bc 100644 --- a/tests/unit/type_safe/test_Type_Safe.py +++ b/tests/unit/type_safe/test_Type_Safe.py @@ -2,19 +2,20 @@ import sys import types import pytest -from enum import Enum, auto -from typing import Union, Optional, Type -from unittest import TestCase -from osbot_utils.helpers.Timestamp_Now import Timestamp_Now -from osbot_utils.helpers.Guid import Guid -from osbot_utils.helpers.Random_Guid import Random_Guid -from osbot_utils.type_safe.Type_Safe import Type_Safe, serialize_to_dict -from osbot_utils.type_safe.Type_Safe__List import Type_Safe__List -from osbot_utils.testing.Catch import Catch -from osbot_utils.testing.Stdout import Stdout -from osbot_utils.utils.Json import json_dumps -from osbot_utils.utils.Misc import random_string, list_set -from osbot_utils.utils.Objects import obj_data, __ , default_value +from enum import Enum, auto +from typing import Union, Optional, Type +from unittest import TestCase +from osbot_utils.helpers.Timestamp_Now import Timestamp_Now +from osbot_utils.helpers.Guid import Guid +from osbot_utils.helpers.Random_Guid import Random_Guid +from osbot_utils.type_safe.Type_Safe import Type_Safe, serialize_to_dict +from osbot_utils.type_safe.Type_Safe__List import Type_Safe__List +from osbot_utils.testing.Catch import Catch +from osbot_utils.testing.Stdout import Stdout +from osbot_utils.type_safe.steps.Type_Safe__Step__From_Json import type_safe_step_from_json +from osbot_utils.utils.Json import json_dumps +from osbot_utils.utils.Misc import random_string, list_set +from osbot_utils.utils.Objects import obj_data, __ , default_value class test_Type_Safe(TestCase): @@ -212,7 +213,7 @@ class An_Class(Type_Safe): assert an_class.json() == an_class.serialize_to_dict() an_class_2 = An_Class() - an_class_2.deserialize_from_dict(an_class_dict) + type_safe_step_from_json.deserialize_from_dict(an_class_2, an_class_dict) assert an_class_2.an_str == an_class.an_str assert an_class_2.an_enum == an_class.an_enum assert an_class_2.json() == an_class_dict @@ -231,7 +232,7 @@ class An_Class(Type_Safe): an_class_dict = {'an_enum': 'value_2', 'an_str': ''} an_class = An_Class() - an_class.deserialize_from_dict(an_class_dict) + type_safe_step_from_json.deserialize_from_dict(an_class, an_class_dict) assert an_class.json() == an_class_dict @@ -244,7 +245,7 @@ class An_Parent_Class(An_Base_Class): an_parent_dict = {'in_base': 'base', 'in_parent': 'parent'} an_parent_class = An_Parent_Class() - an_parent_class.deserialize_from_dict(an_parent_dict) + type_safe_step_from_json.deserialize_from_dict(an_parent_class,an_parent_dict) assert an_parent_class.json() == an_parent_dict # check nested objects @@ -257,7 +258,7 @@ class An_Class_2(Type_Safe): an_class_1_dict = {'an_class_1': {'in_class_1': 'data_1'}, 'in_class_2': 'data_2'} an_class_2 = An_Class_2() - an_class_2.deserialize_from_dict(an_class_1_dict) + type_safe_step_from_json.deserialize_from_dict(an_class_2, an_class_1_dict) assert an_class_2.json() == an_class_1_dict with Stdout() as stdout: