Skip to content

Commit 54e3971

Browse files
committed
added fix for edge case with roundtrip with set values
1 parent 273f195 commit 54e3971

File tree

6 files changed

+96
-13
lines changed

6 files changed

+96
-13
lines changed

osbot_utils/type_safe/type_safe_core/collections/Type_Safe__Dict.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ def serialize_value(v):
9090
if isinstance(v, list):
9191
return serialized
9292
elif isinstance(v, tuple):
93-
return tuple(serialized)
93+
return serialized
9494
else: # set
95-
return set(serialized)
95+
return serialized
9696
else:
9797
return serialize_to_dict(v) # Use serialize_to_dict for unknown types (so that we don't return a non json object)
9898

osbot_utils/type_safe/type_safe_core/steps/Type_Safe__Step__From_Json.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ def deserialize_dict_value(self, _self, value_class, dict_value):
258258
return self.deserialize_nested_dict(value_class, dict_value)
259259
if value_origin is tuple:
260260
return tuple(dict_value) # typing.Tuple cannot be invoked, so we need to use the tuple
261+
if value_origin is set: # Handle Set[T] type annotations
262+
return self.deserialize_set_in_dict_value(_self, value_class, dict_value)
261263
else: # Default: try to instantiate with the value
262264
return value_class(dict_value)
263265

@@ -292,6 +294,36 @@ def deserialize_list_in_dict_value(self, _self, value_class, dict_value):
292294

293295
return type_safe_list
294296

297+
def deserialize_set_in_dict_value(self, _self, value_class, dict_value): # Handle Set[T] types when they appear as dictionary values.
298+
if not isinstance(dict_value, list): # JSON deserializes sets as lists
299+
return dict_value
300+
301+
args = get_args(value_class)
302+
if not args:
303+
return set(dict_value) # No type info, return as plain set
304+
305+
item_type = args[0]
306+
307+
if isinstance(item_type, ForwardRef): # Handle forward references with _self context
308+
if _self:
309+
forward_name = item_type.__forward_arg__
310+
if forward_name == _self.__class__.__name__:
311+
item_type = _self.__class__
312+
313+
type_safe_set = Type_Safe__Set(item_type)
314+
315+
for item in dict_value: # Handle Type_Safe subclasses
316+
if isinstance(item_type, type) and issubclass(item_type, Type_Safe):
317+
if isinstance(item, dict):
318+
type_safe_set.add(item_type.from_json(item))
319+
else:
320+
type_safe_set.add(item)
321+
elif isinstance(item_type, type) and issubclass(item_type, Type_Safe__Primitive):
322+
type_safe_set.add(item_type(item))
323+
else:
324+
type_safe_set.add(item)
325+
326+
return type_safe_set
295327
def deserialize_nested_dict(self, value_class, dict_value): # Handle deserialization of nested Dict[K, V] types.
296328
if not isinstance(dict_value, dict):
297329
return dict_value

tests/unit/type_safe/type_safe_core/_bugs/test_Type_Safe__bugs.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,38 @@ class Extended_Config(Base_Config):
101101

102102

103103

104+
def test__bug__set__json__serialisation_issue(self):
105+
import json
106+
from typing import Dict, Set
107+
from osbot_utils.testing.__ import __
108+
from osbot_utils.type_safe.primitives.domains.identifiers.Safe_Id import Safe_Id
109+
from osbot_utils.type_safe.primitives.domains.identifiers.Edge_Id import Edge_Id
110+
111+
class An_Class(Type_Safe):
112+
an_dict : Dict[Safe_Id , Set[Edge_Id ]]
113+
safe_id = Safe_Id('safe-id_jlqsh')
114+
edge_id = Edge_Id('6106b8e7')
115+
an_class = An_Class()
116+
an_class.an_dict[safe_id] = {edge_id}
117+
118+
#assert an_class.obj () == __(an_dict=__(safe_id_jlqsh={'6106b8e7'})) # BUG, this should be list, right? i.e. ['6106b8e7']
119+
#assert an_class.json() == {'an_dict': {'safe-id_jlqsh': {'6106b8e7'}}} # BUG, this should be list, right? i.e. ['6106b8e7']
120+
121+
assert an_class.obj () == __(an_dict=__(safe_id_jlqsh=['6106b8e7'])) # FIXED
122+
assert an_class.json() == {'an_dict': {'safe-id_jlqsh': ['6106b8e7']}} # FIXED
123+
124+
# error_message = "Object of type set is not JSON serializable"
125+
# with pytest.raises(TypeError, match=error_message): # BUG
126+
# json.dumps(an_class.json())
127+
assert json.dumps(an_class.json()) == '{"an_dict": {"safe-id_jlqsh": ["6106b8e7"]}}'
128+
129+
assert type(an_class.json().get('an_dict') ) is dict
130+
#assert type(an_class.json().get('an_dict').get('safe-id_jlqsh')) is set # BUG
131+
assert type(an_class.json().get('an_dict').get('safe-id_jlqsh')) is list # FIXED
132+
assert json.loads(json.dumps(an_class.json())) == an_class.json()
133+
error_message = "Type Set cannot be instantiated; use set() instead"
134+
# with pytest.raises(TypeError, match=re.escape(error_message)):
135+
# An_Class.from_json(an_class.json()) # BUG
136+
assert An_Class.from_json(an_class.json()).obj() == an_class.obj() # FIXED
137+
assert An_Class.from_json(an_class.json()).json() == an_class.json() # FIXED
138+

tests/unit/type_safe/type_safe_core/_regression/test_Type_Safe__Dict__regression.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,10 @@ class Another_Type: # Ano
238238

239239
# Get JSON representation
240240
json_data = type_dict.json()
241-
assert json_data == { 'test_Type_Safe__Dict__regression.Another_Type' : {'value4', 'value3'},
242-
'test_Type_Safe__Dict__regression.Bug_Type_Keys': {'value2', 'value1'}} # FIXED
241+
json_data['test_Type_Safe__Dict__regression.Another_Type' ].sort() # because the original was a set, we need to sort it so that the comparison below works
242+
json_data['test_Type_Safe__Dict__regression.Bug_Type_Keys' ].sort()
243+
assert json_data == { 'test_Type_Safe__Dict__regression.Another_Type' : ['value3', 'value4'],
244+
'test_Type_Safe__Dict__regression.Bug_Type_Keys': ['value1', 'value2']} # FIXED
243245

244246
# # The bug is that the keys in json_data are still Type objects
245247
# # This assertion passes when the bug is present
@@ -268,7 +270,7 @@ class Schema_With_Type_Dict(Type_Safe): # Sch
268270

269271
#assert json_data == { 'values': { test_Type_Safe__Dict__bugs.Bug_Type_Keys: ['test1', 'test2']}} # BUG should not be using type
270272
bug_type_keys = json_data.get('values').get('test_Type_Safe__Dict__regression.Bug_Type_Keys')
271-
assert type(bug_type_keys) is set
273+
assert type(bug_type_keys) is list
272274
assert 'test1' in bug_type_keys
273275
assert 'test2' in bug_type_keys
274276
#assert type(json_data.values['test_Type_Safe__Dict__bugs.Bug_Type_Keys']) is set

tests/unit/type_safe/type_safe_core/_regression/test_Type_Safe__Tuple__regression.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import re
23
import pytest
34
from typing import Dict, Tuple, Type
@@ -30,11 +31,11 @@ class An_Class(Type_Safe):
3031
# assert An_Class(an_dict_tuple={'an-id': (Safe_Str__Id, Safe_Str__Id)}).json() == { 'an_dict_tuple': { 'an-id': ( Safe_Str__Id, Safe_Str__Id)}, # BUG
3132
# 'an_tuple' : []}
3233

33-
assert An_Class(an_dict_tuple={'an-id': (str, int)}).json() == { 'an_dict_tuple': {'an-id': ('builtins.str', 'builtins.int')}, # BUG , it should be ('builtins.str', 'builtins.int')
34+
assert An_Class(an_dict_tuple={'an-id': (str, int)}).json() == { 'an_dict_tuple': {'an-id': ['builtins.str', 'builtins.int']}, # BUG , it should be ('builtins.str', 'builtins.int')
3435
'an_tuple' : [] }
3536

36-
assert An_Class(an_dict_tuple={'an-id': (Safe_Str__Id, Safe_Str__Id)}).json() == { 'an_dict_tuple': { 'an-id': ( 'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id',
37-
'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id')}, # BUG
37+
assert An_Class(an_dict_tuple={'an-id': (Safe_Str__Id, Safe_Str__Id)}).json() == { 'an_dict_tuple': { 'an-id': [ 'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id',
38+
'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id']}, # BUG
3839
'an_tuple' : []}
3940

4041
# assert An_Class(an_dict_tuple={'an_id': (Safe_Str__Id, Safe_Str__Id)}).obj() == __(an_tuple = [],
@@ -60,12 +61,25 @@ class An_Class(Type_Safe):
6061
assert An_Class.from_json(An_Class( ).json()).json() == {'an_dict_tuple': {}, 'an_tuple': []}
6162
assert An_Class.from_json(An_Class(an_tuple=(str, int) ).json()).json() == {'an_dict_tuple': {}, 'an_tuple': ['builtins.str', 'builtins.int']}
6263

63-
assert An_Class.from_json(An_Class(an_dict_tuple={'an-id': (str, int) }).json()).json() == { 'an_dict_tuple': {'an-id': ('builtins.str', 'builtins.int')}, # BUG , it should be ('builtins.str', 'builtins.int')
64+
assert An_Class.from_json(An_Class(an_dict_tuple={'an-id': (str, int) }).json()).json() == { 'an_dict_tuple': {'an-id': ['builtins.str', 'builtins.int']}, # FIXED: BUG , it should be ['builtins.str', 'builtins.int']
6465
'an_tuple' : [] }
6566
assert An_Class.from_json(An_Class(an_dict_tuple={'an-id': (Safe_Str__Id,
66-
Safe_Str__Id)}).json()).json() == { 'an_dict_tuple': { 'an-id': ( 'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id',
67-
'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id')}, # BUG
67+
Safe_Str__Id)}).json()).json() == { 'an_dict_tuple': { 'an-id': [ 'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id',
68+
'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id']}, # FIXED: BUG
6869
'an_tuple' : []}
70+
an_class = An_Class(an_dict_tuple = {'an-id': (Safe_Str__Id, Safe_Str__Id)},
71+
an_tuple = (str, str))
72+
an_class_json = an_class.json()
73+
assert an_class_json == {'an_dict_tuple': {'an-id': ['osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id',
74+
'osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id']},
75+
'an_tuple': ['builtins.str', 'builtins.str']}
76+
assert json.dumps(an_class_json) == ('{"an_tuple": ["builtins.str", "builtins.str"], '
77+
'"an_dict_tuple": {"an-id": '
78+
'["osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id", '
79+
'"osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id.Safe_Str__Id"]}}')
80+
assert An_Class.from_json(an_class_json).json() == an_class_json
81+
assert json.loads(json.dumps(an_class_json)) == an_class_json
82+
6983

7084

7185
def test__regression__type_safe_tuple__bypasses_on_add_mul(self):

tests/unit/type_safe/type_safe_core/_regression/test_Type_Safe__regression.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ class An_Class(Type_Safe):
178178
assert type(an_class.data.edge_ids) is Type_Safe__Dict # FIXED: correct this should be a dict
179179

180180

181-
assert an_class.json() == {'data': {'edge_ids': {'b': {'id3'}},
182-
'node_ids': {'a': ('id1', 'id2')},
181+
assert an_class.json() == {'data': {'edge_ids': {'b': ['id3']},
182+
'node_ids': {'a': ['id1', 'id2']},
183183
'tuple_1': ['id1', 'id2']}}
184184

185185
# error_message = "In Type_Safe__Tuple: Invalid type for item: Expected 'str', but got 'int'"

0 commit comments

Comments
 (0)