osbot-utils 1.88.0__py3-none-any.whl → 1.89.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osbot_utils/base_classes/Type_Safe.py +24 -8
- osbot_utils/base_classes/Type_Safe__Base.py +117 -0
- osbot_utils/base_classes/Type_Safe__Dict.py +22 -0
- osbot_utils/base_classes/Type_Safe__List.py +2 -113
- osbot_utils/helpers/Xml_To_Dict.py +87 -0
- osbot_utils/utils/Objects.py +7 -4
- osbot_utils/version +1 -1
- {osbot_utils-1.88.0.dist-info → osbot_utils-1.89.0.dist-info}/METADATA +2 -2
- {osbot_utils-1.88.0.dist-info → osbot_utils-1.89.0.dist-info}/RECORD +11 -8
- {osbot_utils-1.88.0.dist-info → osbot_utils-1.89.0.dist-info}/LICENSE +0 -0
- {osbot_utils-1.88.0.dist-info → osbot_utils-1.89.0.dist-info}/WHEEL +0 -0
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
import sys
|
5
5
|
import types
|
6
|
-
from osbot_utils.utils.Objects
|
6
|
+
from osbot_utils.utils.Objects import default_value # todo: remove test mocking requirement for this to be here (instead of on the respective method)
|
7
7
|
|
8
8
|
# Backport implementations of get_origin and get_args for Python 3.7
|
9
9
|
if sys.version_info < (3, 8): # pragma: no cover
|
@@ -23,7 +23,7 @@ if sys.version_info < (3, 8): # pragma
|
|
23
23
|
else:
|
24
24
|
return ()
|
25
25
|
else:
|
26
|
-
from typing import get_origin, get_args
|
26
|
+
from typing import get_origin, get_args, ForwardRef
|
27
27
|
|
28
28
|
if sys.version_info >= (3, 10):
|
29
29
|
NoneType = types.NoneType
|
@@ -148,6 +148,7 @@ class Type_Safe:
|
|
148
148
|
def __default__value__(cls, var_type):
|
149
149
|
import typing
|
150
150
|
from osbot_utils.base_classes.Type_Safe__List import Type_Safe__List
|
151
|
+
from osbot_utils.base_classes.Type_Safe__Dict import Type_Safe__Dict
|
151
152
|
|
152
153
|
if var_type is typing.Set: # todo: refactor the dict, set and list logic, since they are 90% the same
|
153
154
|
return set()
|
@@ -156,13 +157,28 @@ class Type_Safe:
|
|
156
157
|
|
157
158
|
if var_type is typing.Dict:
|
158
159
|
return {}
|
159
|
-
|
160
|
-
|
160
|
+
|
161
|
+
if get_origin(var_type) is dict: # e.g. Dict[key_type, value_type]
|
162
|
+
key_type, value_type = get_args(var_type)
|
163
|
+
if isinstance(key_type, ForwardRef): # Handle forward references on key_type ---
|
164
|
+
forward_name = key_type.__forward_arg__
|
165
|
+
if forward_name == cls.__name__:
|
166
|
+
key_type = cls
|
167
|
+
if isinstance(value_type, ForwardRef): # Handle forward references on value_type ---
|
168
|
+
forward_name = value_type.__forward_arg__
|
169
|
+
if forward_name == cls.__name__:
|
170
|
+
value_type = cls
|
171
|
+
return Type_Safe__Dict(expected_key_type=key_type, expected_value_type=value_type)
|
161
172
|
|
162
173
|
if var_type is typing.List:
|
163
|
-
return []
|
174
|
+
return [] # handle case when List was used with no type information provided
|
175
|
+
|
164
176
|
if get_origin(var_type) is list: # if we have list defined as list[type]
|
165
177
|
item_type = get_args(var_type)[0] # get the type that was defined
|
178
|
+
if isinstance(item_type, ForwardRef): # handle the case when the type is a forward reference
|
179
|
+
forward_name = item_type.__forward_arg__
|
180
|
+
if forward_name == cls.__name__: # if the forward reference is to the current class (simple name check)
|
181
|
+
item_type = cls # set the item_type to the current class
|
166
182
|
return Type_Safe__List(expected_type=item_type) # and used it as expected_type in Type_Safe__List
|
167
183
|
else:
|
168
184
|
return default_value(var_type) # for all other cases call default_value, which will try to create a default instance
|
@@ -258,16 +274,16 @@ class Type_Safe:
|
|
258
274
|
return self
|
259
275
|
|
260
276
|
def deserialize_dict__using_key_value_annotations(self, key, value):
|
277
|
+
from osbot_utils.base_classes.Type_Safe__Dict import Type_Safe__Dict
|
278
|
+
|
261
279
|
dict_annotations_tuple = get_args(self.__annotations__[key])
|
262
280
|
if not dict_annotations_tuple: # happens when the value is a dict/Dict with no annotations
|
263
281
|
return value
|
264
282
|
if not type(value) is dict:
|
265
283
|
return value
|
266
|
-
#key_class = get_args(self.__annotations__[key])[0]
|
267
|
-
#value_class = get_args(self.__annotations__[key])[1]
|
268
284
|
key_class = dict_annotations_tuple[0]
|
269
285
|
value_class = dict_annotations_tuple[1]
|
270
|
-
new_value =
|
286
|
+
new_value = Type_Safe__Dict(expected_key_type=key_class, expected_value_type=value_class)
|
271
287
|
|
272
288
|
for dict_key, dict_value in value.items():
|
273
289
|
if issubclass(key_class, Type_Safe):
|
@@ -0,0 +1,117 @@
|
|
1
|
+
from typing import get_origin, get_args, Union, Optional, Any, ForwardRef
|
2
|
+
|
3
|
+
EXACT_TYPE_MATCH = (int, float, str, bytes, bool, complex)
|
4
|
+
|
5
|
+
class Type_Safe__Base:
|
6
|
+
def is_instance_of_type(self, item, expected_type):
|
7
|
+
if expected_type is Any:
|
8
|
+
return True
|
9
|
+
if isinstance(expected_type, ForwardRef): # todo: add support for ForwardRef
|
10
|
+
return True
|
11
|
+
origin = get_origin(expected_type)
|
12
|
+
args = get_args(expected_type)
|
13
|
+
if origin is None:
|
14
|
+
if expected_type in EXACT_TYPE_MATCH:
|
15
|
+
if type(item) is expected_type:
|
16
|
+
return True
|
17
|
+
else:
|
18
|
+
expected_type_name = type_str(expected_type)
|
19
|
+
actual_type_name = type_str(type(item))
|
20
|
+
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
21
|
+
else:
|
22
|
+
if isinstance(item, expected_type): # Non-parameterized type
|
23
|
+
return True
|
24
|
+
else:
|
25
|
+
expected_type_name = type_str(expected_type)
|
26
|
+
actual_type_name = type_str(type(item))
|
27
|
+
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
28
|
+
|
29
|
+
elif origin is list and args: # Expected type is List[...]
|
30
|
+
(item_type,) = args
|
31
|
+
if not isinstance(item, list):
|
32
|
+
expected_type_name = type_str(expected_type)
|
33
|
+
actual_type_name = type_str(type(item))
|
34
|
+
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
35
|
+
for idx, elem in enumerate(item):
|
36
|
+
try:
|
37
|
+
self.is_instance_of_type(elem, item_type)
|
38
|
+
except TypeError as e:
|
39
|
+
raise TypeError(f"In list at index {idx}: {e}")
|
40
|
+
return True
|
41
|
+
elif origin is dict and args: # Expected type is Dict[...]
|
42
|
+
key_type, value_type = args
|
43
|
+
if not isinstance(item, dict):
|
44
|
+
expected_type_name = type_str(expected_type)
|
45
|
+
actual_type_name = type_str(type(item))
|
46
|
+
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
47
|
+
for k, v in item.items():
|
48
|
+
try:
|
49
|
+
self.is_instance_of_type(k, key_type)
|
50
|
+
except TypeError as e:
|
51
|
+
raise TypeError(f"In dict key '{k}': {e}")
|
52
|
+
try:
|
53
|
+
self.is_instance_of_type(v, value_type)
|
54
|
+
except TypeError as e:
|
55
|
+
raise TypeError(f"In dict value for key '{k}': {e}")
|
56
|
+
return True
|
57
|
+
elif origin is tuple:
|
58
|
+
if not isinstance(item, tuple):
|
59
|
+
expected_type_name = type_str(expected_type)
|
60
|
+
actual_type_name = type_str(type(item))
|
61
|
+
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
62
|
+
if len(args) != len(item):
|
63
|
+
raise TypeError(f"Expected tuple of length {len(args)}, but got {len(item)}")
|
64
|
+
for idx, (elem, elem_type) in enumerate(zip(item, args)):
|
65
|
+
try:
|
66
|
+
self.is_instance_of_type(elem, elem_type)
|
67
|
+
except TypeError as e:
|
68
|
+
raise TypeError(f"In tuple at index {idx}: {e}")
|
69
|
+
return True
|
70
|
+
elif origin is Union or expected_type is Optional: # Expected type is Union[...]
|
71
|
+
for arg in args:
|
72
|
+
try:
|
73
|
+
self.is_instance_of_type(item, arg)
|
74
|
+
return True
|
75
|
+
except TypeError:
|
76
|
+
continue
|
77
|
+
expected_type_name = type_str(expected_type)
|
78
|
+
actual_type_name = type_str(type(item))
|
79
|
+
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
80
|
+
else:
|
81
|
+
if isinstance(item, origin):
|
82
|
+
return True
|
83
|
+
else:
|
84
|
+
expected_type_name = type_str(expected_type)
|
85
|
+
actual_type_name = type_str(type(item))
|
86
|
+
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
87
|
+
|
88
|
+
# todo: see if we should/can move this to the Objects.py file
|
89
|
+
def type_str(tp):
|
90
|
+
origin = get_origin(tp)
|
91
|
+
if origin is None:
|
92
|
+
if hasattr(tp, '__name__'):
|
93
|
+
return tp.__name__
|
94
|
+
else:
|
95
|
+
return str(tp)
|
96
|
+
else:
|
97
|
+
args = get_args(tp)
|
98
|
+
args_str = ', '.join(type_str(arg) for arg in args)
|
99
|
+
return f"{origin.__name__}[{args_str}]"
|
100
|
+
|
101
|
+
def get_object_type_str(obj):
|
102
|
+
if isinstance(obj, dict):
|
103
|
+
if not obj:
|
104
|
+
return "Dict[Empty]"
|
105
|
+
key_types = set(type(k).__name__ for k in obj.keys())
|
106
|
+
value_types = set(type(v).__name__ for v in obj.values())
|
107
|
+
key_type_str = ', '.join(sorted(key_types))
|
108
|
+
value_type_str = ', '.join(sorted(value_types))
|
109
|
+
return f"Dict[{key_type_str}, {value_type_str}]"
|
110
|
+
elif isinstance(obj, list):
|
111
|
+
if not obj:
|
112
|
+
return "List[Empty]"
|
113
|
+
elem_types = set(type(e).__name__ for e in obj)
|
114
|
+
elem_type_str = ', '.join(sorted(elem_types))
|
115
|
+
return f"List[{elem_type_str}]"
|
116
|
+
else:
|
117
|
+
return type(obj).__name__
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from osbot_utils.base_classes.Type_Safe__Base import type_str, Type_Safe__Base
|
2
|
+
|
3
|
+
class Type_Safe__Dict(Type_Safe__Base, dict):
|
4
|
+
def __init__(self, expected_key_type, expected_value_type, *args, **kwargs):
|
5
|
+
super().__init__(*args, **kwargs)
|
6
|
+
|
7
|
+
self.expected_key_type = expected_key_type
|
8
|
+
self.expected_value_type = expected_value_type
|
9
|
+
|
10
|
+
for k, v in self.items(): # check type-safety of ctor arguments
|
11
|
+
self.is_instance_of_type(k, self.expected_key_type )
|
12
|
+
self.is_instance_of_type(v, self.expected_value_type)
|
13
|
+
|
14
|
+
def __setitem__(self, key, value): # Check type-safety before allowing assignment.
|
15
|
+
self.is_instance_of_type(key, self.expected_key_type)
|
16
|
+
self.is_instance_of_type(value, self.expected_value_type)
|
17
|
+
super().__setitem__(key, value)
|
18
|
+
|
19
|
+
def __repr__(self):
|
20
|
+
key_type_name = type_str(self.expected_key_type)
|
21
|
+
value_type_name = type_str(self.expected_value_type)
|
22
|
+
return f"dict[{key_type_name}, {value_type_name}] with {len(self)} entries"
|
@@ -1,8 +1,7 @@
|
|
1
|
-
from
|
1
|
+
from osbot_utils.base_classes.Type_Safe__Base import Type_Safe__Base, type_str
|
2
2
|
|
3
|
-
EXACT_TYPE_MATCH = (int, float, str, bytes, bool, complex)
|
4
3
|
|
5
|
-
class Type_Safe__List(list):
|
4
|
+
class Type_Safe__List(Type_Safe__Base, list):
|
6
5
|
|
7
6
|
def __init__(self, expected_type, *args):
|
8
7
|
super().__init__(*args)
|
@@ -19,115 +18,5 @@ class Type_Safe__List(list):
|
|
19
18
|
raise TypeError(f"In Type_Safe__List: Invalid type for item: {e}")
|
20
19
|
super().append(item)
|
21
20
|
|
22
|
-
def is_instance_of_type(self, item, expected_type):
|
23
|
-
if expected_type is Any:
|
24
|
-
return True
|
25
|
-
if isinstance(expected_type, ForwardRef): # todo: add support for ForwardRef
|
26
|
-
return True
|
27
|
-
origin = get_origin(expected_type)
|
28
|
-
args = get_args(expected_type)
|
29
|
-
if origin is None:
|
30
|
-
if expected_type in EXACT_TYPE_MATCH:
|
31
|
-
if type(item) is expected_type:
|
32
|
-
return True
|
33
|
-
else:
|
34
|
-
expected_type_name = type_str(expected_type)
|
35
|
-
actual_type_name = type_str(type(item))
|
36
|
-
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
37
|
-
else:
|
38
|
-
if isinstance(item, expected_type): # Non-parameterized type
|
39
|
-
return True
|
40
|
-
else:
|
41
|
-
expected_type_name = type_str(expected_type)
|
42
|
-
actual_type_name = type_str(type(item))
|
43
|
-
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
44
21
|
|
45
|
-
elif origin is list and args: # Expected type is List[...]
|
46
|
-
(item_type,) = args
|
47
|
-
if not isinstance(item, list):
|
48
|
-
expected_type_name = type_str(expected_type)
|
49
|
-
actual_type_name = type_str(type(item))
|
50
|
-
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
51
|
-
for idx, elem in enumerate(item):
|
52
|
-
try:
|
53
|
-
self.is_instance_of_type(elem, item_type)
|
54
|
-
except TypeError as e:
|
55
|
-
raise TypeError(f"In list at index {idx}: {e}")
|
56
|
-
return True
|
57
|
-
elif origin is dict and args: # Expected type is Dict[...]
|
58
|
-
key_type, value_type = args
|
59
|
-
if not isinstance(item, dict):
|
60
|
-
expected_type_name = type_str(expected_type)
|
61
|
-
actual_type_name = type_str(type(item))
|
62
|
-
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
63
|
-
for k, v in item.items():
|
64
|
-
try:
|
65
|
-
self.is_instance_of_type(k, key_type)
|
66
|
-
except TypeError as e:
|
67
|
-
raise TypeError(f"In dict key '{k}': {e}")
|
68
|
-
try:
|
69
|
-
self.is_instance_of_type(v, value_type)
|
70
|
-
except TypeError as e:
|
71
|
-
raise TypeError(f"In dict value for key '{k}': {e}")
|
72
|
-
return True
|
73
|
-
elif origin is tuple:
|
74
|
-
if not isinstance(item, tuple):
|
75
|
-
expected_type_name = type_str(expected_type)
|
76
|
-
actual_type_name = type_str(type(item))
|
77
|
-
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
78
|
-
if len(args) != len(item):
|
79
|
-
raise TypeError(f"Expected tuple of length {len(args)}, but got {len(item)}")
|
80
|
-
for idx, (elem, elem_type) in enumerate(zip(item, args)):
|
81
|
-
try:
|
82
|
-
self.is_instance_of_type(elem, elem_type)
|
83
|
-
except TypeError as e:
|
84
|
-
raise TypeError(f"In tuple at index {idx}: {e}")
|
85
|
-
return True
|
86
|
-
elif origin is Union or expected_type is Optional: # Expected type is Union[...]
|
87
|
-
for arg in args:
|
88
|
-
try:
|
89
|
-
self.is_instance_of_type(item, arg)
|
90
|
-
return True
|
91
|
-
except TypeError:
|
92
|
-
continue
|
93
|
-
expected_type_name = type_str(expected_type)
|
94
|
-
actual_type_name = type_str(type(item))
|
95
|
-
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
96
|
-
else:
|
97
|
-
if isinstance(item, origin):
|
98
|
-
return True
|
99
|
-
else:
|
100
|
-
expected_type_name = type_str(expected_type)
|
101
|
-
actual_type_name = type_str(type(item))
|
102
|
-
raise TypeError(f"Expected '{expected_type_name}', but got '{actual_type_name}'")
|
103
22
|
|
104
|
-
# todo: see if we should/can move this to the Objects.py file
|
105
|
-
def type_str(tp):
|
106
|
-
origin = get_origin(tp)
|
107
|
-
if origin is None:
|
108
|
-
if hasattr(tp, '__name__'):
|
109
|
-
return tp.__name__
|
110
|
-
else:
|
111
|
-
return str(tp)
|
112
|
-
else:
|
113
|
-
args = get_args(tp)
|
114
|
-
args_str = ', '.join(type_str(arg) for arg in args)
|
115
|
-
return f"{origin.__name__}[{args_str}]"
|
116
|
-
|
117
|
-
def get_object_type_str(obj):
|
118
|
-
if isinstance(obj, dict):
|
119
|
-
if not obj:
|
120
|
-
return "Dict[Empty]"
|
121
|
-
key_types = set(type(k).__name__ for k in obj.keys())
|
122
|
-
value_types = set(type(v).__name__ for v in obj.values())
|
123
|
-
key_type_str = ', '.join(sorted(key_types))
|
124
|
-
value_type_str = ', '.join(sorted(value_types))
|
125
|
-
return f"Dict[{key_type_str}, {value_type_str}]"
|
126
|
-
elif isinstance(obj, list):
|
127
|
-
if not obj:
|
128
|
-
return "List[Empty]"
|
129
|
-
elem_types = set(type(e).__name__ for e in obj)
|
130
|
-
elem_type_str = ', '.join(sorted(elem_types))
|
131
|
-
return f"List[{elem_type_str}]"
|
132
|
-
else:
|
133
|
-
return type(obj).__name__
|
@@ -0,0 +1,87 @@
|
|
1
|
+
from typing import Dict, Any, Union
|
2
|
+
from xml.etree.ElementTree import Element
|
3
|
+
|
4
|
+
from osbot_utils.base_classes.Type_Safe import Type_Safe
|
5
|
+
|
6
|
+
class XML_Attribute(Type_Safe):
|
7
|
+
name : str
|
8
|
+
value : str
|
9
|
+
namespace: str
|
10
|
+
|
11
|
+
class XML_Element(Type_Safe):
|
12
|
+
attributes: Dict[str, XML_Attribute]
|
13
|
+
children : Dict[str, Union[str, 'XML_Element']]
|
14
|
+
|
15
|
+
|
16
|
+
class Xml_To_Dict(Type_Safe):
|
17
|
+
xml_data : str = None # Input XML string
|
18
|
+
root : Element = None # Root ElementTree.Element
|
19
|
+
namespaces : Dict[str, str] # XML namespaces
|
20
|
+
xml_dict : Dict[str, Any] # Parsed XML as dictionary
|
21
|
+
|
22
|
+
def setup(self) :
|
23
|
+
from xml.etree.ElementTree import ParseError
|
24
|
+
try:
|
25
|
+
self.load_namespaces()
|
26
|
+
self.load_root()
|
27
|
+
|
28
|
+
except ParseError as e:
|
29
|
+
raise ValueError(f"Invalid XML: {str(e)}")
|
30
|
+
return self
|
31
|
+
|
32
|
+
|
33
|
+
def load_namespaces(self):
|
34
|
+
from xml.etree.ElementTree import iterparse
|
35
|
+
from io import StringIO
|
36
|
+
|
37
|
+
for event, elem in iterparse(StringIO(self.xml_data), events=("start-ns",)):
|
38
|
+
self.namespaces[elem[0]] = elem[1]
|
39
|
+
|
40
|
+
def load_root(self):
|
41
|
+
from xml.etree.ElementTree import fromstring
|
42
|
+
|
43
|
+
self.root = fromstring(self.xml_data)
|
44
|
+
|
45
|
+
def element_to_dict(self, element: Element) -> Union[Dict[str, Any], str]:
|
46
|
+
"""Convert an ElementTree.Element to a dictionary"""
|
47
|
+
result: Dict[str, Any] = {}
|
48
|
+
|
49
|
+
|
50
|
+
if element.attrib: # Handle attributes
|
51
|
+
result.update(element.attrib)
|
52
|
+
|
53
|
+
# Handle child elements
|
54
|
+
child_nodes: Dict[str, Any] = {}
|
55
|
+
for child in element:
|
56
|
+
tag = child.tag # Remove namespace prefix if present
|
57
|
+
if '}' in tag:
|
58
|
+
tag = tag.split('}', 1)[1]
|
59
|
+
|
60
|
+
child_data = self.element_to_dict(child)
|
61
|
+
|
62
|
+
if tag in child_nodes:
|
63
|
+
if not isinstance(child_nodes[tag], list):
|
64
|
+
child_nodes[tag] = [child_nodes[tag]]
|
65
|
+
child_nodes[tag].append(child_data)
|
66
|
+
else:
|
67
|
+
child_nodes[tag] = child_data
|
68
|
+
|
69
|
+
# Handle text content
|
70
|
+
text = element.text.strip() if element.text else ''
|
71
|
+
if text:
|
72
|
+
if child_nodes or result:
|
73
|
+
result['_text'] = text
|
74
|
+
else:
|
75
|
+
return text
|
76
|
+
elif not child_nodes and not result: # Make sure we return text content even for empty nodes
|
77
|
+
return text
|
78
|
+
|
79
|
+
# Combine results
|
80
|
+
if child_nodes:
|
81
|
+
result.update(child_nodes)
|
82
|
+
|
83
|
+
return result
|
84
|
+
|
85
|
+
def parse(self) -> Dict[str, Any]: # Convert parsed XML to dictionary
|
86
|
+
self.xml_dict = self.element_to_dict(self.root)
|
87
|
+
return self
|
osbot_utils/utils/Objects.py
CHANGED
@@ -30,14 +30,17 @@ def are_types_compatible_for_assigment(source_type, target_type):
|
|
30
30
|
import types
|
31
31
|
import typing
|
32
32
|
|
33
|
+
if isinstance(target_type, str): # If the "target_type" is a forward reference (string), handle it here.
|
34
|
+
if target_type == source_type.__name__: # Simple check: does the string match the actual class name
|
35
|
+
return True
|
33
36
|
if source_type is target_type:
|
34
37
|
return True
|
35
38
|
if source_type is int and target_type is float:
|
36
39
|
return True
|
37
|
-
if target_type in source_type.__mro__:
|
40
|
+
if target_type in source_type.__mro__: # this means that the source_type has the target_type has of its base types
|
38
41
|
return True
|
39
|
-
if target_type is callable:
|
40
|
-
if source_type is types.MethodType:
|
42
|
+
if target_type is callable: # handle case where callable was used as the target type
|
43
|
+
if source_type is types.MethodType: # and a method or function was used as the source type
|
41
44
|
return True
|
42
45
|
if source_type is types.FunctionType:
|
43
46
|
return True
|
@@ -413,7 +416,7 @@ def value_type_matches_obj_annotation_for_attr(target, attr_name, value):
|
|
413
416
|
origin_attr_type = get_origin(attr_type) # to handle when type definion contains an generic
|
414
417
|
if origin_attr_type is typing.Union:
|
415
418
|
args = get_args(attr_type)
|
416
|
-
if len(args)==2 and args[1] is type(None): # todo: find a better way to do this, since this is handling an edge case when origin_attr_type is Optional (
|
419
|
+
if len(args)==2 and args[1] is type(None): # todo: find a better way to do this, since this is handling an edge case when origin_attr_type is Optional (which is an shorthand for Union[X, None] )
|
417
420
|
attr_type = args[0]
|
418
421
|
origin_attr_type = get_origin(attr_type)
|
419
422
|
|
osbot_utils/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
v1.
|
1
|
+
v1.89.0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: osbot_utils
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.89.0
|
4
4
|
Summary: OWASP Security Bot - Utils
|
5
5
|
Home-page: https://github.com/owasp-sbot/OSBot-Utils
|
6
6
|
License: MIT
|
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
|
|
23
23
|
|
24
24
|
Powerful Python util methods and classes that simplify common apis and tasks.
|
25
25
|
|
26
|
-

|
27
27
|
[](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
|
28
28
|
|
29
29
|
|
@@ -2,8 +2,10 @@ osbot_utils/__init__.py,sha256=DdJDmQc9zbQUlPVyTJOww6Ixrn9n4bD3ami5ItQfzJI,16
|
|
2
2
|
osbot_utils/base_classes/Cache_Pickle.py,sha256=kPCwrgUbf_dEdxUz7vW1GuvIPwlNXxuRhb-H3AbSpII,5884
|
3
3
|
osbot_utils/base_classes/Kwargs_To_Disk.py,sha256=HHoy05NC_w35WcT-OnSKoSIV_cLqaU9rdjH0_KNTM0E,1096
|
4
4
|
osbot_utils/base_classes/Kwargs_To_Self.py,sha256=weFNsBfBNV9W_qBkN-IdBD4yYcJV_zgTxBRO-ZlcPS4,141
|
5
|
-
osbot_utils/base_classes/Type_Safe.py,sha256=
|
6
|
-
osbot_utils/base_classes/
|
5
|
+
osbot_utils/base_classes/Type_Safe.py,sha256=oEwxNPa1pcq6ccJUyWPUuuMXK6CRzVIzwQNL2lcc6E4,24663
|
6
|
+
osbot_utils/base_classes/Type_Safe__Base.py,sha256=CFPYe8_i5vvTLyc7s8CXbY4n_dY6sqVfBY8w9Vo77ZA,5468
|
7
|
+
osbot_utils/base_classes/Type_Safe__Dict.py,sha256=sfZcukhXUd9TS0PQpAk-gGLfZUJSC6BtMh6jF4Fn8Jw,1107
|
8
|
+
osbot_utils/base_classes/Type_Safe__List.py,sha256=pXDzJJttpEQQ9oTdsw7BykMB4VIX2rZzi1ZrnCzMZ8M,650
|
7
9
|
osbot_utils/base_classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
10
|
osbot_utils/context_managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
11
|
osbot_utils/context_managers/async_invoke.py,sha256=-ja3K8orLy8Of54CIYSK-zn443pOIDY2hnFBjVELrXc,829
|
@@ -75,6 +77,7 @@ osbot_utils/helpers/Str_ASCII.py,sha256=PRqyu449XnKrLn6b9Miii1Hv-GO5OAa1UhhgvlRc
|
|
75
77
|
osbot_utils/helpers/Timestamp_Now.py,sha256=k3-SUGYx2jLTXvgZYeECqPRJhVxqWPmW7co1l6r12jk,438
|
76
78
|
osbot_utils/helpers/Type_Registry.py,sha256=Ajk3SyMSKDi2g9SJYUtTgg7PZkAgydaHcpbGuEN3S94,311
|
77
79
|
osbot_utils/helpers/Type_Safe_Method.py,sha256=8E88of__An9_ZhJKz6Kp22C1mb9WLED0jWNLOII3fJs,10489
|
80
|
+
osbot_utils/helpers/Xml_To_Dict.py,sha256=EbuiLi1cElakYyueSKi6LKJXUDz4-oi4QTFI1WTu7Rs,2846
|
78
81
|
osbot_utils/helpers/Zip_Bytes.py,sha256=KB5zWfCf6ET4alNfqNrSp5DxZ3Jp9oDHpc6tK2iO_qg,4320
|
79
82
|
osbot_utils/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
80
83
|
osbot_utils/helpers/ast/Ast.py,sha256=lcPQOSxXI6zgmMnIVF9WM6ISqViWX-sq4d_UC0CDG8s,1155
|
@@ -303,7 +306,7 @@ osbot_utils/utils/Json.py,sha256=0DZGlCU7Nqte5n0r7ctPXFybqA5MRfSrTz5zuK_6UFk,709
|
|
303
306
|
osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
|
304
307
|
osbot_utils/utils/Lists.py,sha256=tPz5x5s3sRO97WZ_nsxREBPC5cwaHrhgaYBhsrffTT8,5599
|
305
308
|
osbot_utils/utils/Misc.py,sha256=H_xexJgiTxB3jDeDiW8efGQbO0Zuy8MM0iQ7qXC92JI,17363
|
306
|
-
osbot_utils/utils/Objects.py,sha256=
|
309
|
+
osbot_utils/utils/Objects.py,sha256=qzdwfQZMyI-ySlOhuUM6z2M4cW9eZDXpUeTIzgYwlS4,19977
|
307
310
|
osbot_utils/utils/Png.py,sha256=V1juGp6wkpPigMJ8HcxrPDIP4bSwu51oNkLI8YqP76Y,1172
|
308
311
|
osbot_utils/utils/Process.py,sha256=lr3CTiEkN3EiBx3ZmzYmTKlQoPdkgZBRjPulMxG-zdo,2357
|
309
312
|
osbot_utils/utils/Python_Logger.py,sha256=tx8N6wRKL3RDHboDRKZn8SirSJdSAE9cACyJkxrThZ8,12792
|
@@ -315,8 +318,8 @@ osbot_utils/utils/Toml.py,sha256=Rxl8gx7mni5CvBAK-Ai02EKw-GwtJdd3yeHT2kMloik,166
|
|
315
318
|
osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
|
316
319
|
osbot_utils/utils/Zip.py,sha256=pR6sKliUY0KZXmqNzKY2frfW-YVQEVbLKiyqQX_lc-8,14052
|
317
320
|
osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
318
|
-
osbot_utils/version,sha256=
|
319
|
-
osbot_utils-1.
|
320
|
-
osbot_utils-1.
|
321
|
-
osbot_utils-1.
|
322
|
-
osbot_utils-1.
|
321
|
+
osbot_utils/version,sha256=ty30vvmdD1qw-8a6mVUfcEOT_nStazbsrdXwAHaom5k,8
|
322
|
+
osbot_utils-1.89.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
323
|
+
osbot_utils-1.89.0.dist-info/METADATA,sha256=fqPYnTXYJMDtR982ODsggiUxzZHnnLBFCaBpnOMtpKw,1317
|
324
|
+
osbot_utils-1.89.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
325
|
+
osbot_utils-1.89.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|