Skip to content

Commit 5821751

Browse files
committed
Merge dev into main
2 parents 273f195 + de813cb commit 5821751

File tree

9 files changed

+98
-18
lines changed

9 files changed

+98
-18
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# OSBot-Utils
22

3-
![Current Release](https://img.shields.io/badge/release-v3.52.0-blue)
3+
![Current Release](https://img.shields.io/badge/release-v3.52.1-blue)
44
![Python](https://img.shields.io/badge/python-3.8+-green)
55
![Type-Safe](https://img.shields.io/badge/Type--Safe-✓-brightgreen)
66
![Caching](https://img.shields.io/badge/Caching-Built--In-orange)

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

osbot_utils/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v3.52.0
1+
v3.52.1

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "osbot_utils"
3-
version = "v3.52.0"
3+
version = "v3.52.1"
44
description = "OWASP Security Bot - Utils"
55
authors = ["Dinis Cruz <[email protected]>"]
66
license = "MIT"

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,3 @@ class Extended_Config(Base_Config):
100100
Extended_Config() # BUG: should auto-assign Extended_Handler
101101

102102

103-

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: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import re
22
import sys
3+
import json
34
import pytest
45
from enum import Enum
56
from decimal import Decimal
67
from typing import Optional, Union, List, Dict, get_origin, Type, ForwardRef, Any, Set, Tuple
78
from unittest import TestCase
89
from unittest.mock import patch
10+
from osbot_utils.type_safe.primitives.domains.identifiers.Safe_Id import Safe_Id
11+
from osbot_utils.type_safe.primitives.domains.identifiers.Edge_Id import Edge_Id
912
from osbot_utils.type_safe.primitives.core.Safe_Float import Safe_Float
1013
from osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id import Safe_Str__Id
1114
from osbot_utils.type_safe.primitives.domains.identifiers.Obj_Id import Obj_Id
@@ -178,8 +181,8 @@ class An_Class(Type_Safe):
178181
assert type(an_class.data.edge_ids) is Type_Safe__Dict # FIXED: correct this should be a dict
179182

180183

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

185188
# error_message = "In Type_Safe__Tuple: Invalid type for item: Expected 'str', but got 'int'"
@@ -2022,4 +2025,34 @@ class An_Class(Type_Safe):
20222025
# assert With_Base ().obj () == __(an_str=None) # BUG
20232026

20242027
assert With_Base ().json() == {'an_str': ''} # FIXED
2025-
assert With_Base ().obj () == __(an_str='') # FIXED
2028+
assert With_Base ().obj () == __(an_str='') # FIXED
2029+
2030+
def test__regression__set__json__serialisation_issue(self):
2031+
2032+
class An_Class(Type_Safe):
2033+
an_dict : Dict[Safe_Id , Set[Edge_Id ]]
2034+
safe_id = Safe_Id('safe-id_jlqsh')
2035+
edge_id = Edge_Id('6106b8e7')
2036+
an_class = An_Class()
2037+
an_class.an_dict[safe_id] = {edge_id}
2038+
2039+
#assert an_class.obj () == __(an_dict=__(safe_id_jlqsh={'6106b8e7'})) # BUG, this should be list, right? i.e. ['6106b8e7']
2040+
#assert an_class.json() == {'an_dict': {'safe-id_jlqsh': {'6106b8e7'}}} # BUG, this should be list, right? i.e. ['6106b8e7']
2041+
2042+
assert an_class.obj () == __(an_dict=__(safe_id_jlqsh=['6106b8e7'])) # FIXED
2043+
assert an_class.json() == {'an_dict': {'safe-id_jlqsh': ['6106b8e7']}} # FIXED
2044+
2045+
# error_message = "Object of type set is not JSON serializable"
2046+
# with pytest.raises(TypeError, match=error_message): # BUG
2047+
# json.dumps(an_class.json())
2048+
assert json.dumps(an_class.json()) == '{"an_dict": {"safe-id_jlqsh": ["6106b8e7"]}}'
2049+
2050+
assert type(an_class.json().get('an_dict') ) is dict
2051+
#assert type(an_class.json().get('an_dict').get('safe-id_jlqsh')) is set # BUG
2052+
assert type(an_class.json().get('an_dict').get('safe-id_jlqsh')) is list # FIXED
2053+
assert json.loads(json.dumps(an_class.json())) == an_class.json()
2054+
error_message = "Type Set cannot be instantiated; use set() instead"
2055+
# with pytest.raises(TypeError, match=re.escape(error_message)):
2056+
# An_Class.from_json(an_class.json()) # BUG
2057+
assert An_Class.from_json(an_class.json()).obj() == an_class.obj() # FIXED
2058+
assert An_Class.from_json(an_class.json()).json() == an_class.json() # FIXED

0 commit comments

Comments
 (0)