osbot-utils 1.76.0__py3-none-any.whl → 1.78.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 +10 -1
- osbot_utils/base_classes/Type_Safe__List.py +122 -3
- osbot_utils/utils/Objects.py +6 -0
- osbot_utils/version +1 -1
- {osbot_utils-1.76.0.dist-info → osbot_utils-1.78.0.dist-info}/METADATA +2 -2
- {osbot_utils-1.76.0.dist-info → osbot_utils-1.78.0.dist-info}/RECORD +8 -8
- {osbot_utils-1.76.0.dist-info → osbot_utils-1.78.0.dist-info}/LICENSE +0 -0
- {osbot_utils-1.76.0.dist-info → osbot_utils-1.78.0.dist-info}/WHEEL +0 -0
@@ -20,7 +20,8 @@ from osbot_utils.utils.Misc import list_set
|
|
20
20
|
from osbot_utils.utils.Objects import default_value, value_type_matches_obj_annotation_for_attr, \
|
21
21
|
raise_exception_on_obj_type_annotation_mismatch, obj_is_attribute_annotation_of_type, enum_from_value, \
|
22
22
|
obj_is_type_union_compatible, value_type_matches_obj_annotation_for_union_attr, \
|
23
|
-
convert_dict_to_value_from_obj_annotation, dict_to_obj, convert_to_value_from_obj_annotation
|
23
|
+
convert_dict_to_value_from_obj_annotation, dict_to_obj, convert_to_value_from_obj_annotation, \
|
24
|
+
obj_attribute_annotation
|
24
25
|
|
25
26
|
# Backport implementations of get_origin and get_args for Python 3.7
|
26
27
|
if sys.version_info < (3, 8): # pragma: no cover
|
@@ -290,6 +291,14 @@ class Type_Safe:
|
|
290
291
|
continue
|
291
292
|
if obj_is_attribute_annotation_of_type(self, key, dict): # handle the case when the value is a dict
|
292
293
|
value = self.deserialize_dict__using_key_value_annotations(key, value)
|
294
|
+
elif obj_is_attribute_annotation_of_type(self, key, list): # handle the case when the value is a list
|
295
|
+
attribute_annotation = obj_attribute_annotation(self, key) # get the annotation for this variable
|
296
|
+
expected_type = get_args(attribute_annotation)[0] # get the first arg (which is the type)
|
297
|
+
type_safe_list = Type_Safe__List(expected_type) # create a new instance of Type_Safe__List
|
298
|
+
for item in value: # next we need to convert all items (to make sure they all match the type)
|
299
|
+
new_item = expected_type(**item) # create new object
|
300
|
+
type_safe_list.append(new_item) # and add it to the new type_safe_list obejct
|
301
|
+
value = type_safe_list # todo: refactor out this create list code, maybe to an deserialize_from_list method
|
293
302
|
else:
|
294
303
|
if value is not None:
|
295
304
|
if obj_is_attribute_annotation_of_type(self, key, EnumMeta): # Handle the case when the value is an Enum
|
@@ -1,3 +1,7 @@
|
|
1
|
+
from typing import get_origin, get_args, Union, Optional, Any, ForwardRef
|
2
|
+
|
3
|
+
EXACT_TYPE_MATCH = (int, float, str, bytes, bool, complex)
|
4
|
+
|
1
5
|
class Type_Safe__List(list):
|
2
6
|
|
3
7
|
def __init__(self, expected_type, *args):
|
@@ -5,10 +9,125 @@ class Type_Safe__List(list):
|
|
5
9
|
self.expected_type = expected_type
|
6
10
|
|
7
11
|
def __repr__(self):
|
8
|
-
|
12
|
+
expected_type_name = type_str(self.expected_type)
|
13
|
+
return f"list[{expected_type_name}] with {len(self)} elements"
|
9
14
|
|
10
15
|
def append(self, item):
|
11
|
-
|
12
|
-
|
16
|
+
try:
|
17
|
+
self.is_instance_of_type(item, self.expected_type)
|
18
|
+
except TypeError as e:
|
19
|
+
raise TypeError(f"In Type_Safe__List: Invalid type for item: {e}")
|
13
20
|
super().append(item)
|
14
21
|
|
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
|
+
|
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
|
+
|
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__
|
osbot_utils/utils/Objects.py
CHANGED
@@ -394,6 +394,12 @@ def value_type_matches_obj_annotation_for_attr(target, attr_name, value):
|
|
394
394
|
attr_type = obj_annotations.get(attr_name)
|
395
395
|
if attr_type:
|
396
396
|
origin_attr_type = get_origin(attr_type) # to handle when type definion contains an generic
|
397
|
+
if origin_attr_type is typing.Union:
|
398
|
+
args = get_args(attr_type)
|
399
|
+
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 (whcih is an shorthand for Union[X, None] )
|
400
|
+
attr_type = args[0]
|
401
|
+
origin_attr_type = get_origin(attr_type)
|
402
|
+
|
397
403
|
if origin_attr_type:
|
398
404
|
attr_type = origin_attr_type
|
399
405
|
value_type = type(value)
|
osbot_utils/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
v1.
|
1
|
+
v1.78.0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: osbot_utils
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.78.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,8 @@ 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/Type_Safe__List.py,sha256
|
5
|
+
osbot_utils/base_classes/Type_Safe.py,sha256=3RnP8MRlwVXdMd0hZBUPM0xQULUo5s4BoWDc4s5VKQ4,22010
|
6
|
+
osbot_utils/base_classes/Type_Safe__List.py,sha256=iWyoc2xjHkTJrZTVnPse9Rljte2tF67oNq8yA7jnAhY,5996
|
7
7
|
osbot_utils/base_classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
osbot_utils/context_managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
osbot_utils/context_managers/async_invoke.py,sha256=-ja3K8orLy8Of54CIYSK-zn443pOIDY2hnFBjVELrXc,829
|
@@ -294,7 +294,7 @@ osbot_utils/utils/Json.py,sha256=8fpYFXzNPSrwYfXMt3eGKpe-lhxh1kEaCRae1X1943A,666
|
|
294
294
|
osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
|
295
295
|
osbot_utils/utils/Lists.py,sha256=tPz5x5s3sRO97WZ_nsxREBPC5cwaHrhgaYBhsrffTT8,5599
|
296
296
|
osbot_utils/utils/Misc.py,sha256=A8AzI1M912NDxNpPRrW1lPrwIIjVOoGUQHzyyRCEMFU,17102
|
297
|
-
osbot_utils/utils/Objects.py,sha256=
|
297
|
+
osbot_utils/utils/Objects.py,sha256=RiViT25vuh9zk_gveV2VzSKYg6vUaF2wwyAPPsj1qyw,19426
|
298
298
|
osbot_utils/utils/Png.py,sha256=V1juGp6wkpPigMJ8HcxrPDIP4bSwu51oNkLI8YqP76Y,1172
|
299
299
|
osbot_utils/utils/Process.py,sha256=lr3CTiEkN3EiBx3ZmzYmTKlQoPdkgZBRjPulMxG-zdo,2357
|
300
300
|
osbot_utils/utils/Python_Logger.py,sha256=tx8N6wRKL3RDHboDRKZn8SirSJdSAE9cACyJkxrThZ8,12792
|
@@ -306,8 +306,8 @@ osbot_utils/utils/Toml.py,sha256=-_Yv5T8ZhGGoDSSoNEdFhSsXiK_JPjGkPijm4JoeHSk,166
|
|
306
306
|
osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
|
307
307
|
osbot_utils/utils/Zip.py,sha256=pR6sKliUY0KZXmqNzKY2frfW-YVQEVbLKiyqQX_lc-8,14052
|
308
308
|
osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
309
|
-
osbot_utils/version,sha256=
|
310
|
-
osbot_utils-1.
|
311
|
-
osbot_utils-1.
|
312
|
-
osbot_utils-1.
|
313
|
-
osbot_utils-1.
|
309
|
+
osbot_utils/version,sha256=iNZGWI4Q60T4H2HfKcEvHaXbPuwoOyLQAdfo28Caebg,8
|
310
|
+
osbot_utils-1.78.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
311
|
+
osbot_utils-1.78.0.dist-info/METADATA,sha256=Z9xFeZDq27qy4sfwSNZxHuVCf8YRb6bk8DvrL1s2lCA,1317
|
312
|
+
osbot_utils-1.78.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
313
|
+
osbot_utils-1.78.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|