osbot-utils 1.76.0__py3-none-any.whl → 1.77.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.
@@ -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
- return f"list[{self.expected_type.__name__}] with {len(self)} elements"
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
- if not isinstance(item, self.expected_type):
12
- raise TypeError(f"In Type_Safe__List: Invalid type for item: Expected '{self.expected_type.__name__}', but got '{type(item).__name__}'")
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__
@@ -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.76.0
1
+ v1.77.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osbot_utils
3
- Version: 1.76.0
3
+ Version: 1.77.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
- ![Current Release](https://img.shields.io/badge/release-v1.76.0-blue)
26
+ ![Current Release](https://img.shields.io/badge/release-v1.77.0-blue)
27
27
  [![codecov](https://codecov.io/gh/owasp-sbot/OSBot-Utils/graph/badge.svg?token=GNVW0COX1N)](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
28
28
 
29
29
 
@@ -3,7 +3,7 @@ osbot_utils/base_classes/Cache_Pickle.py,sha256=kPCwrgUbf_dEdxUz7vW1GuvIPwlNXxuR
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
5
  osbot_utils/base_classes/Type_Safe.py,sha256=HFCSsZzPaLWXsdHwbZnlSXiSQkgUz7-zlSKsZdz4rb8,20723
6
- osbot_utils/base_classes/Type_Safe__List.py,sha256=-80C9OhsK6iDR2dAG8yNLAZV0qg5x3faqvSUigFCMJw,517
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=fqDy8AFimxkUxGAMQBkqYCvQnT_TDcR8EwHOMWPxorc,18992
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=Sh4I_vevwaIEdaWg7696h0OuGLhRGXbvbES7xCszDMg,8
310
- osbot_utils-1.76.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
311
- osbot_utils-1.76.0.dist-info/METADATA,sha256=e5XMnrg0uo8xBZX2txBVODepc-_JQX3oVD9srvPmSAc,1317
312
- osbot_utils-1.76.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
313
- osbot_utils-1.76.0.dist-info/RECORD,,
309
+ osbot_utils/version,sha256=WF5rBBvc5bDfeYdDp3zj4uegAHZtcSRyRBknn9Uv_w0,8
310
+ osbot_utils-1.77.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
311
+ osbot_utils-1.77.0.dist-info/METADATA,sha256=rSXu7wvgm-mtJUOczQ64QcXQ6Zwzm_7MAwleL6x3-I4,1317
312
+ osbot_utils-1.77.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
313
+ osbot_utils-1.77.0.dist-info/RECORD,,