osbot-utils 1.30.0__py3-none-any.whl → 1.32.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/Kwargs_To_Self.py +3 -303
- osbot_utils/base_classes/Type_Safe.py +299 -4
- osbot_utils/testing/Catch.py +1 -1
- osbot_utils/utils/Env.py +25 -7
- osbot_utils/utils/Files.py +16 -0
- osbot_utils/utils/Http.py +7 -0
- osbot_utils/utils/Objects.py +14 -0
- osbot_utils/version +1 -1
- {osbot_utils-1.30.0.dist-info → osbot_utils-1.32.0.dist-info}/METADATA +2 -2
- {osbot_utils-1.30.0.dist-info → osbot_utils-1.32.0.dist-info}/RECORD +12 -12
- {osbot_utils-1.30.0.dist-info → osbot_utils-1.32.0.dist-info}/LICENSE +0 -0
- {osbot_utils-1.30.0.dist-info → osbot_utils-1.32.0.dist-info}/WHEEL +0 -0
@@ -1,304 +1,4 @@
|
|
1
|
-
|
2
|
-
# the code is not polluted with them (like in the example below)
|
3
|
-
# the data is available in IDE's code complete
|
4
|
-
import functools
|
5
|
-
import inspect
|
6
|
-
import sys
|
7
|
-
import types
|
8
|
-
import typing
|
9
|
-
from decimal import Decimal
|
10
|
-
from enum import Enum, EnumMeta
|
11
|
-
from typing import List
|
12
|
-
from osbot_utils.base_classes.Type_Safe__List import Type_Safe__List
|
13
|
-
from osbot_utils.utils.Dev import pprint
|
14
|
-
from osbot_utils.utils.Json import json_parse
|
15
|
-
from osbot_utils.utils.Misc import list_set
|
16
|
-
from osbot_utils.utils.Objects import default_value, value_type_matches_obj_annotation_for_attr, \
|
17
|
-
raise_exception_on_obj_type_annotation_mismatch, obj_is_attribute_annotation_of_type, enum_from_value, \
|
18
|
-
obj_is_type_union_compatible, value_type_matches_obj_annotation_for_union_attr
|
1
|
+
from osbot_utils.base_classes.Type_Safe import Type_Safe
|
19
2
|
|
20
|
-
#
|
21
|
-
|
22
|
-
def get_origin(tp):
|
23
|
-
if isinstance(tp, typing._GenericAlias):
|
24
|
-
return tp.__origin__
|
25
|
-
elif tp is typing.Generic:
|
26
|
-
return typing.Generic
|
27
|
-
else:
|
28
|
-
return None
|
29
|
-
|
30
|
-
def get_args(tp):
|
31
|
-
if isinstance(tp, typing._GenericAlias):
|
32
|
-
return tp.__args__
|
33
|
-
else:
|
34
|
-
return ()
|
35
|
-
else:
|
36
|
-
from typing import get_origin, get_args
|
37
|
-
|
38
|
-
if sys.version_info >= (3, 10):
|
39
|
-
NoneType = types.NoneType
|
40
|
-
else:
|
41
|
-
NoneType = type(None)
|
42
|
-
|
43
|
-
immutable_types = (bool, int, float, complex, str, tuple, frozenset, bytes, NoneType, EnumMeta)
|
44
|
-
|
45
|
-
|
46
|
-
#todo: see if we can also add type safety to method execution
|
47
|
-
# for example if we have an method like def add_node(self, title: str, call_index: int):
|
48
|
-
# throw an exception if the type of the value passed in is not the same as the one defined in the method
|
49
|
-
|
50
|
-
class Kwargs_To_Self:
|
51
|
-
|
52
|
-
def __init__(self, **kwargs):
|
53
|
-
"""
|
54
|
-
Initialize an instance of the derived class, strictly assigning provided keyword
|
55
|
-
arguments to corresponding instance attributes.
|
56
|
-
|
57
|
-
Parameters:
|
58
|
-
**kwargs: Variable length keyword arguments.
|
59
|
-
|
60
|
-
Raises:
|
61
|
-
Exception: If a key from kwargs does not correspond to any attribute
|
62
|
-
pre-defined in the class, an exception is raised to prevent
|
63
|
-
setting an undefined attribute.
|
64
|
-
|
65
|
-
"""
|
66
|
-
# if 'disable_type_safety' in kwargs: # special case
|
67
|
-
# self.__type_safety__ = kwargs['disable_type_safety'] is False
|
68
|
-
# del kwargs['disable_type_safety']
|
69
|
-
|
70
|
-
for (key, value) in self.__cls_kwargs__().items(): # assign all default values to self
|
71
|
-
if value is not None: # when the value is explicitly set to None on the class static vars, we can't check for type safety
|
72
|
-
raise_exception_on_obj_type_annotation_mismatch(self, key, value)
|
73
|
-
if hasattr(self, key):
|
74
|
-
existing_value = getattr(self, key)
|
75
|
-
if existing_value is not None:
|
76
|
-
setattr(self, key, existing_value)
|
77
|
-
continue
|
78
|
-
setattr(self, key, value)
|
79
|
-
|
80
|
-
for (key, value) in kwargs.items(): # overwrite with values provided in ctor
|
81
|
-
if hasattr(self, key):
|
82
|
-
if value is not None: # prevent None values from overwriting existing values, which is quite common in default constructors
|
83
|
-
setattr(self, key, value)
|
84
|
-
else:
|
85
|
-
raise Exception(f"{self.__class__.__name__} has no attribute '{key}' and cannot be assigned the value '{value}'. "
|
86
|
-
f"Use {self.__class__.__name__}.__default_kwargs__() see what attributes are available")
|
87
|
-
|
88
|
-
def __enter__(self): return self
|
89
|
-
def __exit__(self, exc_type, exc_val, exc_tb): pass
|
90
|
-
|
91
|
-
def __setattr__(self, name, value):
|
92
|
-
if not hasattr(self, '__annotations__'): # can't do type safety checks if the class does not have annotations
|
93
|
-
return super().__setattr__(name, value)
|
94
|
-
|
95
|
-
# if self.__type_safety__:
|
96
|
-
# if self.__lock_attributes__:
|
97
|
-
# todo: this can't work on all, current hypothesis is that this will work for the values that are explicitly set
|
98
|
-
# if not hasattr(self, name):
|
99
|
-
# raise AttributeError(f"'[Object Locked] Current object is locked (with __lock_attributes__=True) which prevents new attributes allocations (i.e. setattr calls). In this case {type(self).__name__}' object has no attribute '{name}'") from None
|
100
|
-
|
101
|
-
if value is not None:
|
102
|
-
check_1 = value_type_matches_obj_annotation_for_attr (self, name, value)
|
103
|
-
check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value)
|
104
|
-
if (check_1 is False and check_2 is None or
|
105
|
-
check_1 is None and check_2 is False or
|
106
|
-
check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars
|
107
|
-
raise Exception(f"Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
|
108
|
-
else:
|
109
|
-
if hasattr(self, name) and self.__annotations__.get(name) : # don't allow previously set variables to be set to None
|
110
|
-
if getattr(self, name) is not None: # unless it is already set to None
|
111
|
-
raise Exception(f"Can't set None, to a variable that is already set. Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
|
112
|
-
|
113
|
-
super().__setattr__(name, value)
|
114
|
-
|
115
|
-
def __attr_names__(self):
|
116
|
-
return list_set(self.__locals__())
|
117
|
-
|
118
|
-
@classmethod
|
119
|
-
def __cls_kwargs__(cls, include_base_classes=True):
|
120
|
-
"""Return current class dictionary of class level variables and their values."""
|
121
|
-
kwargs = {}
|
122
|
-
|
123
|
-
for base_cls in inspect.getmro(cls):
|
124
|
-
if base_cls is object: # Skip the base 'object' class
|
125
|
-
continue
|
126
|
-
for k, v in vars(base_cls).items():
|
127
|
-
# todo: refactor this logic since it is weird to start with a if not..., and then if ... continue (all these should be if ... continue )
|
128
|
-
if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
|
129
|
-
if isinstance(v, classmethod): # also remove class methods
|
130
|
-
continue
|
131
|
-
if type(v) is functools._lru_cache_wrapper: # todo, find better way to handle edge cases like this one (which happens when the @cache decorator is used in a instance method that uses Kwargs_To_Self)
|
132
|
-
continue
|
133
|
-
if (k in kwargs) is False: # do not set the value is it has already been set
|
134
|
-
kwargs[k] = v
|
135
|
-
|
136
|
-
if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
137
|
-
for var_name, var_type in base_cls.__annotations__.items():
|
138
|
-
if hasattr(base_cls, var_name) is False: # only add if it has not already been defined
|
139
|
-
if var_name in kwargs:
|
140
|
-
continue
|
141
|
-
var_value = cls.__default__value__(var_type)
|
142
|
-
kwargs[var_name] = var_value
|
143
|
-
else:
|
144
|
-
var_value = getattr(base_cls, var_name)
|
145
|
-
if var_value is not None: # allow None assignments on ctor since that is a valid use case
|
146
|
-
if var_type and not isinstance(var_value, var_type): # check type
|
147
|
-
exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'"
|
148
|
-
raise Exception(exception_message)
|
149
|
-
if var_type not in immutable_types and var_name.startswith('__') is False: # if var_type is not one of the immutable_types or is an __ internal
|
150
|
-
#todo: fix type safety bug that I believe is caused here
|
151
|
-
if obj_is_type_union_compatible(var_type, immutable_types) is False: # if var_type is not something like Optional[Union[int, str]]
|
152
|
-
if type(var_type) not in immutable_types:
|
153
|
-
exception_message = f"variable '{var_name}' is defined as type '{var_type}' which is not supported by Kwargs_To_Self, with only the following immutable types being supported: '{immutable_types}'"
|
154
|
-
raise Exception(exception_message)
|
155
|
-
if include_base_classes is False:
|
156
|
-
break
|
157
|
-
return kwargs
|
158
|
-
|
159
|
-
@classmethod
|
160
|
-
def __default__value__(cls, var_type):
|
161
|
-
if var_type is typing.Set: # todo: refactor the dict, set and list logic, since they are 90% the same
|
162
|
-
return set()
|
163
|
-
if get_origin(var_type) is set:
|
164
|
-
return set() # todo: add Type_Safe__Set
|
165
|
-
|
166
|
-
if var_type is typing.Dict:
|
167
|
-
return {}
|
168
|
-
if get_origin(var_type) is dict:
|
169
|
-
return {} # todo: add Type_Safe__Dict
|
170
|
-
|
171
|
-
if var_type is typing.List:
|
172
|
-
return [] # handle case when List was used with no type information provided
|
173
|
-
if get_origin(var_type) is list: # if we have list defined as list[type]
|
174
|
-
item_type = get_args(var_type)[0] # get the type that was defined
|
175
|
-
return Type_Safe__List(expected_type=item_type) # and used it as expected_type in Type_Safe__List
|
176
|
-
else:
|
177
|
-
return default_value(var_type) # for all other cases call default_value, which will try to create a default instance
|
178
|
-
|
179
|
-
def __default_kwargs__(self):
|
180
|
-
"""Return entire (including base classes) dictionary of class level variables and their values."""
|
181
|
-
kwargs = {}
|
182
|
-
cls = type(self)
|
183
|
-
for base_cls in inspect.getmro(cls): # Traverse the inheritance hierarchy and collect class-level attributes
|
184
|
-
if base_cls is object: # Skip the base 'object' class
|
185
|
-
continue
|
186
|
-
for k, v in vars(base_cls).items():
|
187
|
-
if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
|
188
|
-
if not isinstance(v, classmethod):
|
189
|
-
kwargs[k] = v
|
190
|
-
# add the vars defined with the annotations
|
191
|
-
if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
192
|
-
for var_name, var_type in base_cls.__annotations__.items():
|
193
|
-
var_value = getattr(self, var_name)
|
194
|
-
kwargs[var_name] = var_value
|
195
|
-
|
196
|
-
return kwargs
|
197
|
-
|
198
|
-
def __kwargs__(self):
|
199
|
-
"""Return a dictionary of the current instance's attribute values including inherited class defaults."""
|
200
|
-
kwargs = {}
|
201
|
-
# Update with instance-specific values
|
202
|
-
for key, value in self.__default_kwargs__().items():
|
203
|
-
kwargs[key] = self.__getattribute__(key)
|
204
|
-
# if hasattr(self, key):
|
205
|
-
# kwargs[key] = self.__getattribute__(key)
|
206
|
-
# else:
|
207
|
-
# kwargs[key] = value # todo: see if this is stil a valid scenario
|
208
|
-
return kwargs
|
209
|
-
|
210
|
-
|
211
|
-
def __locals__(self):
|
212
|
-
"""Return a dictionary of the current instance's attribute values."""
|
213
|
-
kwargs = self.__kwargs__()
|
214
|
-
|
215
|
-
if not isinstance(vars(self), types.FunctionType):
|
216
|
-
for k, v in vars(self).items():
|
217
|
-
if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod):
|
218
|
-
if k.startswith('__') is False:
|
219
|
-
kwargs[k] = v
|
220
|
-
return kwargs
|
221
|
-
|
222
|
-
@classmethod
|
223
|
-
def __schema__(cls):
|
224
|
-
if hasattr(cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
225
|
-
return cls.__annotations__
|
226
|
-
return {}
|
227
|
-
|
228
|
-
# global methods added to any class that base classes this
|
229
|
-
# todo: see if there should be a prefix on these methods, to make it easier to spot them
|
230
|
-
# of if these are actually that useful that they should be added like this
|
231
|
-
def json(self):
|
232
|
-
return self.serialize_to_dict()
|
233
|
-
|
234
|
-
def merge_with(self, target):
|
235
|
-
original_attrs = {k: v for k, v in self.__dict__.items() if k not in target.__dict__} # Store the original attributes of self that should be retained.
|
236
|
-
self.__dict__ = target.__dict__ # Set the target's __dict__ to self, now self and target share the same __dict__.
|
237
|
-
self.__dict__.update(original_attrs) # Reassign the original attributes back to self.
|
238
|
-
return self
|
239
|
-
|
240
|
-
# def locked(self, value=True): # todo: figure out best way to do this (maybe???)
|
241
|
-
# self.__lock_attributes__ = value # : update, with the latest changes were we don't show internals on __locals__() this might be a good way to do this
|
242
|
-
# return self
|
243
|
-
|
244
|
-
def reset(self):
|
245
|
-
for k,v in self.__cls_kwargs__().items():
|
246
|
-
setattr(self, k, v)
|
247
|
-
|
248
|
-
def update_from_kwargs(self, **kwargs):
|
249
|
-
"""Update instance attributes with values from provided keyword arguments."""
|
250
|
-
for key, value in kwargs.items():
|
251
|
-
if value is not None:
|
252
|
-
if hasattr(self,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
253
|
-
if value_type_matches_obj_annotation_for_attr(self, key, value) is False:
|
254
|
-
raise Exception(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'")
|
255
|
-
setattr(self, key, value)
|
256
|
-
return self
|
257
|
-
|
258
|
-
|
259
|
-
def deserialize_from_dict(self, data):
|
260
|
-
for key, value in data.items():
|
261
|
-
if hasattr(self, key) and isinstance(getattr(self, key), Kwargs_To_Self):
|
262
|
-
getattr(self, key).deserialize_from_dict(value) # Recursive call for complex nested objects
|
263
|
-
else:
|
264
|
-
if hasattr(self, '__annotations__'): # can only do type safety checks if the class does not have annotations
|
265
|
-
if obj_is_attribute_annotation_of_type(self, key, EnumMeta): # Handle the case when the value is an Enum
|
266
|
-
enum_type = getattr(self, '__annotations__').get(key)
|
267
|
-
if type(value) is not enum_type: # If the value is not already of the target type
|
268
|
-
value = enum_from_value(enum_type, value) # Try to resolve the value into the enum
|
269
|
-
|
270
|
-
setattr(self, key, value) # Direct assignment for primitive types and other structures
|
271
|
-
|
272
|
-
return self
|
273
|
-
|
274
|
-
def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough
|
275
|
-
return serialize_to_dict(self)
|
276
|
-
|
277
|
-
def print(self):
|
278
|
-
pprint(serialize_to_dict(self))
|
279
|
-
|
280
|
-
@classmethod
|
281
|
-
def from_json(cls, json_data):
|
282
|
-
if type(json_data) is str:
|
283
|
-
json_data = json_parse(json_data)
|
284
|
-
if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided)
|
285
|
-
return cls().deserialize_from_dict(json_data)
|
286
|
-
return None
|
287
|
-
|
288
|
-
# todo: see if it is possible to add recursive protection to this logic
|
289
|
-
def serialize_to_dict(obj):
|
290
|
-
if isinstance(obj, (str, int, float, bool, bytes, Decimal)) or obj is None:
|
291
|
-
return obj
|
292
|
-
elif isinstance(obj, Enum):
|
293
|
-
return obj.name
|
294
|
-
elif isinstance(obj, list) or isinstance(obj, List):
|
295
|
-
return [serialize_to_dict(item) for item in obj]
|
296
|
-
elif isinstance(obj, dict):
|
297
|
-
return {key: serialize_to_dict(value) for key, value in obj.items()}
|
298
|
-
elif hasattr(obj, "__dict__"):
|
299
|
-
data = {} # todo: look at a more advanced version which saved the type of the object, for example with {'__type__': type(obj).__name__}
|
300
|
-
for key, value in obj.__dict__.items():
|
301
|
-
data[key] = serialize_to_dict(value) # Recursive call for complex types
|
302
|
-
return data
|
303
|
-
else:
|
304
|
-
raise TypeError(f"Type {type(obj)} not serializable")
|
3
|
+
# todo: refactor all of Kwargs_To_Self to use Type_Safe
|
4
|
+
Kwargs_To_Self = Type_Safe
|
@@ -1,6 +1,301 @@
|
|
1
|
-
|
1
|
+
# todo: find a way to add these documentations strings to a separate location so that
|
2
|
+
# the code is not polluted with them (like in the example below)
|
3
|
+
# the data is available in IDE's code complete
|
4
|
+
import functools
|
5
|
+
import inspect
|
6
|
+
import sys
|
7
|
+
import types
|
8
|
+
import typing
|
9
|
+
from decimal import Decimal
|
10
|
+
from enum import Enum, EnumMeta
|
11
|
+
from typing import List
|
12
|
+
from osbot_utils.base_classes.Type_Safe__List import Type_Safe__List
|
13
|
+
from osbot_utils.utils.Dev import pprint
|
14
|
+
from osbot_utils.utils.Json import json_parse
|
15
|
+
from osbot_utils.utils.Misc import list_set
|
16
|
+
from osbot_utils.utils.Objects import default_value, value_type_matches_obj_annotation_for_attr, \
|
17
|
+
raise_exception_on_obj_type_annotation_mismatch, obj_is_attribute_annotation_of_type, enum_from_value, \
|
18
|
+
obj_is_type_union_compatible, value_type_matches_obj_annotation_for_union_attr, \
|
19
|
+
convert_dict_to_value_from_obj_annotation
|
2
20
|
|
3
|
-
#
|
4
|
-
|
21
|
+
# Backport implementations of get_origin and get_args for Python 3.7
|
22
|
+
if sys.version_info < (3, 8):
|
23
|
+
def get_origin(tp):
|
24
|
+
if isinstance(tp, typing._GenericAlias):
|
25
|
+
return tp.__origin__
|
26
|
+
elif tp is typing.Generic:
|
27
|
+
return typing.Generic
|
28
|
+
else:
|
29
|
+
return None
|
5
30
|
|
6
|
-
|
31
|
+
def get_args(tp):
|
32
|
+
if isinstance(tp, typing._GenericAlias):
|
33
|
+
return tp.__args__
|
34
|
+
else:
|
35
|
+
return ()
|
36
|
+
else:
|
37
|
+
from typing import get_origin, get_args
|
38
|
+
|
39
|
+
if sys.version_info >= (3, 10):
|
40
|
+
NoneType = types.NoneType
|
41
|
+
else:
|
42
|
+
NoneType = type(None)
|
43
|
+
|
44
|
+
immutable_types = (bool, int, float, complex, str, tuple, frozenset, bytes, NoneType, EnumMeta)
|
45
|
+
|
46
|
+
|
47
|
+
#todo: see if we can also add type safety to method execution
|
48
|
+
# for example if we have an method like def add_node(self, title: str, call_index: int):
|
49
|
+
# throw an exception if the type of the value passed in is not the same as the one defined in the method
|
50
|
+
|
51
|
+
class Type_Safe:
|
52
|
+
|
53
|
+
def __init__(self, **kwargs):
|
54
|
+
"""
|
55
|
+
Initialize an instance of the derived class, strictly assigning provided keyword
|
56
|
+
arguments to corresponding instance attributes.
|
57
|
+
|
58
|
+
Parameters:
|
59
|
+
**kwargs: Variable length keyword arguments.
|
60
|
+
|
61
|
+
Raises:
|
62
|
+
Exception: If a key from kwargs does not correspond to any attribute
|
63
|
+
pre-defined in the class, an exception is raised to prevent
|
64
|
+
setting an undefined attribute.
|
65
|
+
|
66
|
+
"""
|
67
|
+
# if 'disable_type_safety' in kwargs: # special case
|
68
|
+
# self.__type_safety__ = kwargs['disable_type_safety'] is False
|
69
|
+
# del kwargs['disable_type_safety']
|
70
|
+
|
71
|
+
for (key, value) in self.__cls_kwargs__().items(): # assign all default values to self
|
72
|
+
if value is not None: # when the value is explicitly set to None on the class static vars, we can't check for type safety
|
73
|
+
raise_exception_on_obj_type_annotation_mismatch(self, key, value)
|
74
|
+
if hasattr(self, key):
|
75
|
+
existing_value = getattr(self, key)
|
76
|
+
if existing_value is not None:
|
77
|
+
setattr(self, key, existing_value)
|
78
|
+
continue
|
79
|
+
setattr(self, key, value)
|
80
|
+
|
81
|
+
for (key, value) in kwargs.items(): # overwrite with values provided in ctor
|
82
|
+
if hasattr(self, key):
|
83
|
+
if value is not None: # prevent None values from overwriting existing values, which is quite common in default constructors
|
84
|
+
setattr(self, key, value)
|
85
|
+
else:
|
86
|
+
raise ValueError(f"{self.__class__.__name__} has no attribute '{key}' and cannot be assigned the value '{value}'. "
|
87
|
+
f"Use {self.__class__.__name__}.__default_kwargs__() see what attributes are available")
|
88
|
+
|
89
|
+
def __enter__(self): return self
|
90
|
+
def __exit__(self, exc_type, exc_val, exc_tb): pass
|
91
|
+
|
92
|
+
def __setattr__(self, name, value):
|
93
|
+
if not hasattr(self, '__annotations__'): # can't do type safety checks if the class does not have annotations
|
94
|
+
return super().__setattr__(name, value)
|
95
|
+
|
96
|
+
if value is not None:
|
97
|
+
if type(value) is dict:
|
98
|
+
value = convert_dict_to_value_from_obj_annotation(self, name, value)
|
99
|
+
check_1 = value_type_matches_obj_annotation_for_attr (self, name, value)
|
100
|
+
check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value)
|
101
|
+
if (check_1 is False and check_2 is None or
|
102
|
+
check_1 is None and check_2 is False or
|
103
|
+
check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars
|
104
|
+
raise ValueError(f"Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
|
105
|
+
else:
|
106
|
+
if hasattr(self, name) and self.__annotations__.get(name) : # don't allow previously set variables to be set to None
|
107
|
+
if getattr(self, name) is not None: # unless it is already set to None
|
108
|
+
raise ValueError(f"Can't set None, to a variable that is already set. Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
|
109
|
+
|
110
|
+
super().__setattr__(name, value)
|
111
|
+
|
112
|
+
def __attr_names__(self):
|
113
|
+
return list_set(self.__locals__())
|
114
|
+
|
115
|
+
@classmethod
|
116
|
+
def __cls_kwargs__(cls, include_base_classes=True):
|
117
|
+
"""Return current class dictionary of class level variables and their values."""
|
118
|
+
kwargs = {}
|
119
|
+
|
120
|
+
for base_cls in inspect.getmro(cls):
|
121
|
+
if base_cls is object: # Skip the base 'object' class
|
122
|
+
continue
|
123
|
+
for k, v in vars(base_cls).items():
|
124
|
+
# todo: refactor this logic since it is weird to start with a if not..., and then if ... continue (all these should be if ... continue )
|
125
|
+
if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
|
126
|
+
if isinstance(v, classmethod): # also remove class methods
|
127
|
+
continue
|
128
|
+
if type(v) is functools._lru_cache_wrapper: # todo, find better way to handle edge cases like this one (which happens when the @cache decorator is used in a instance method that uses Kwargs_To_Self)
|
129
|
+
continue
|
130
|
+
if (k in kwargs) is False: # do not set the value is it has already been set
|
131
|
+
kwargs[k] = v
|
132
|
+
|
133
|
+
if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
134
|
+
for var_name, var_type in base_cls.__annotations__.items():
|
135
|
+
if hasattr(base_cls, var_name) is False: # only add if it has not already been defined
|
136
|
+
if var_name in kwargs:
|
137
|
+
continue
|
138
|
+
var_value = cls.__default__value__(var_type)
|
139
|
+
kwargs[var_name] = var_value
|
140
|
+
else:
|
141
|
+
var_value = getattr(base_cls, var_name)
|
142
|
+
if var_value is not None: # allow None assignments on ctor since that is a valid use case
|
143
|
+
if var_type and not isinstance(var_value, var_type): # check type
|
144
|
+
exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'"
|
145
|
+
raise ValueError(exception_message)
|
146
|
+
if var_type not in immutable_types and var_name.startswith('__') is False: # if var_type is not one of the immutable_types or is an __ internal
|
147
|
+
#todo: fix type safety bug that I believe is caused here
|
148
|
+
if obj_is_type_union_compatible(var_type, immutable_types) is False: # if var_type is not something like Optional[Union[int, str]]
|
149
|
+
if type(var_type) not in immutable_types:
|
150
|
+
exception_message = f"variable '{var_name}' is defined as type '{var_type}' which is not supported by Kwargs_To_Self, with only the following immutable types being supported: '{immutable_types}'"
|
151
|
+
raise ValueError(exception_message)
|
152
|
+
if include_base_classes is False:
|
153
|
+
break
|
154
|
+
return kwargs
|
155
|
+
|
156
|
+
@classmethod
|
157
|
+
def __default__value__(cls, var_type):
|
158
|
+
if var_type is typing.Set: # todo: refactor the dict, set and list logic, since they are 90% the same
|
159
|
+
return set()
|
160
|
+
if get_origin(var_type) is set:
|
161
|
+
return set() # todo: add Type_Safe__Set
|
162
|
+
|
163
|
+
if var_type is typing.Dict:
|
164
|
+
return {}
|
165
|
+
if get_origin(var_type) is dict:
|
166
|
+
return {} # todo: add Type_Safe__Dict
|
167
|
+
|
168
|
+
if var_type is typing.List:
|
169
|
+
return [] # handle case when List was used with no type information provided
|
170
|
+
if get_origin(var_type) is list: # if we have list defined as list[type]
|
171
|
+
item_type = get_args(var_type)[0] # get the type that was defined
|
172
|
+
return Type_Safe__List(expected_type=item_type) # and used it as expected_type in Type_Safe__List
|
173
|
+
else:
|
174
|
+
return default_value(var_type) # for all other cases call default_value, which will try to create a default instance
|
175
|
+
|
176
|
+
def __default_kwargs__(self):
|
177
|
+
"""Return entire (including base classes) dictionary of class level variables and their values."""
|
178
|
+
kwargs = {}
|
179
|
+
cls = type(self)
|
180
|
+
for base_cls in inspect.getmro(cls): # Traverse the inheritance hierarchy and collect class-level attributes
|
181
|
+
if base_cls is object: # Skip the base 'object' class
|
182
|
+
continue
|
183
|
+
for k, v in vars(base_cls).items():
|
184
|
+
if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
|
185
|
+
if not isinstance(v, classmethod):
|
186
|
+
kwargs[k] = v
|
187
|
+
# add the vars defined with the annotations
|
188
|
+
if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
189
|
+
for var_name, var_type in base_cls.__annotations__.items():
|
190
|
+
var_value = getattr(self, var_name)
|
191
|
+
kwargs[var_name] = var_value
|
192
|
+
|
193
|
+
return kwargs
|
194
|
+
|
195
|
+
def __kwargs__(self):
|
196
|
+
"""Return a dictionary of the current instance's attribute values including inherited class defaults."""
|
197
|
+
kwargs = {}
|
198
|
+
# Update with instance-specific values
|
199
|
+
for key, value in self.__default_kwargs__().items():
|
200
|
+
kwargs[key] = self.__getattribute__(key)
|
201
|
+
# if hasattr(self, key):
|
202
|
+
# kwargs[key] = self.__getattribute__(key)
|
203
|
+
# else:
|
204
|
+
# kwargs[key] = value # todo: see if this is stil a valid scenario
|
205
|
+
return kwargs
|
206
|
+
|
207
|
+
|
208
|
+
def __locals__(self):
|
209
|
+
"""Return a dictionary of the current instance's attribute values."""
|
210
|
+
kwargs = self.__kwargs__()
|
211
|
+
|
212
|
+
if not isinstance(vars(self), types.FunctionType):
|
213
|
+
for k, v in vars(self).items():
|
214
|
+
if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod):
|
215
|
+
if k.startswith('__') is False:
|
216
|
+
kwargs[k] = v
|
217
|
+
return kwargs
|
218
|
+
|
219
|
+
@classmethod
|
220
|
+
def __schema__(cls):
|
221
|
+
if hasattr(cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
222
|
+
return cls.__annotations__
|
223
|
+
return {}
|
224
|
+
|
225
|
+
# global methods added to any class that base classes this
|
226
|
+
# todo: see if there should be a prefix on these methods, to make it easier to spot them
|
227
|
+
# of if these are actually that useful that they should be added like this
|
228
|
+
def json(self):
|
229
|
+
return self.serialize_to_dict()
|
230
|
+
|
231
|
+
def merge_with(self, target):
|
232
|
+
original_attrs = {k: v for k, v in self.__dict__.items() if k not in target.__dict__} # Store the original attributes of self that should be retained.
|
233
|
+
self.__dict__ = target.__dict__ # Set the target's __dict__ to self, now self and target share the same __dict__.
|
234
|
+
self.__dict__.update(original_attrs) # Reassign the original attributes back to self.
|
235
|
+
return self
|
236
|
+
|
237
|
+
# def locked(self, value=True): # todo: figure out best way to do this (maybe???)
|
238
|
+
# self.__lock_attributes__ = value # : update, with the latest changes were we don't show internals on __locals__() this might be a good way to do this
|
239
|
+
# return self
|
240
|
+
|
241
|
+
def reset(self):
|
242
|
+
for k,v in self.__cls_kwargs__().items():
|
243
|
+
setattr(self, k, v)
|
244
|
+
|
245
|
+
def update_from_kwargs(self, **kwargs):
|
246
|
+
"""Update instance attributes with values from provided keyword arguments."""
|
247
|
+
for key, value in kwargs.items():
|
248
|
+
if value is not None:
|
249
|
+
if hasattr(self,'__annotations__'): # can only do type safety checks if the class does not have annotations
|
250
|
+
if value_type_matches_obj_annotation_for_attr(self, key, value) is False:
|
251
|
+
raise ValueError(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'")
|
252
|
+
setattr(self, key, value)
|
253
|
+
return self
|
254
|
+
|
255
|
+
|
256
|
+
def deserialize_from_dict(self, data):
|
257
|
+
for key, value in data.items():
|
258
|
+
if hasattr(self, key) and isinstance(getattr(self, key), Type_Safe):
|
259
|
+
getattr(self, key).deserialize_from_dict(value) # Recursive call for complex nested objects
|
260
|
+
else:
|
261
|
+
if hasattr(self, '__annotations__'): # can only do type safety checks if the class does not have annotations
|
262
|
+
if obj_is_attribute_annotation_of_type(self, key, EnumMeta): # Handle the case when the value is an Enum
|
263
|
+
enum_type = getattr(self, '__annotations__').get(key)
|
264
|
+
if type(value) is not enum_type: # If the value is not already of the target type
|
265
|
+
value = enum_from_value(enum_type, value) # Try to resolve the value into the enum
|
266
|
+
|
267
|
+
setattr(self, key, value) # Direct assignment for primitive types and other structures
|
268
|
+
|
269
|
+
return self
|
270
|
+
|
271
|
+
def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough
|
272
|
+
return serialize_to_dict(self)
|
273
|
+
|
274
|
+
def print(self):
|
275
|
+
pprint(serialize_to_dict(self))
|
276
|
+
|
277
|
+
@classmethod
|
278
|
+
def from_json(cls, json_data):
|
279
|
+
if type(json_data) is str:
|
280
|
+
json_data = json_parse(json_data)
|
281
|
+
if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided)
|
282
|
+
return cls().deserialize_from_dict(json_data)
|
283
|
+
return None
|
284
|
+
|
285
|
+
# todo: see if it is possible to add recursive protection to this logic
|
286
|
+
def serialize_to_dict(obj):
|
287
|
+
if isinstance(obj, (str, int, float, bool, bytes, Decimal)) or obj is None:
|
288
|
+
return obj
|
289
|
+
elif isinstance(obj, Enum):
|
290
|
+
return obj.name
|
291
|
+
elif isinstance(obj, list) or isinstance(obj, List):
|
292
|
+
return [serialize_to_dict(item) for item in obj]
|
293
|
+
elif isinstance(obj, dict):
|
294
|
+
return {key: serialize_to_dict(value) for key, value in obj.items()}
|
295
|
+
elif hasattr(obj, "__dict__"):
|
296
|
+
data = {} # todo: look at a more advanced version which saved the type of the object, for example with {'__type__': type(obj).__name__}
|
297
|
+
for key, value in obj.__dict__.items():
|
298
|
+
data[key] = serialize_to_dict(value) # Recursive call for complex types
|
299
|
+
return data
|
300
|
+
else:
|
301
|
+
raise TypeError(f"Type {type(obj)} not serializable")
|
osbot_utils/testing/Catch.py
CHANGED
@@ -42,7 +42,7 @@ class Catch:
|
|
42
42
|
if self.expected_error:
|
43
43
|
self.assert_error_is(self.expected_error)
|
44
44
|
if self.expect_exception and exception_type is None:
|
45
|
-
raise
|
45
|
+
raise ValueError(f'Expected exception: {self.expected_error} but no exception was raised')
|
46
46
|
if self.catch_exception:
|
47
47
|
return True # returning true here will prevent the exception to be propagated (which is the objective of this class :) )
|
48
48
|
return False
|
osbot_utils/utils/Env.py
CHANGED
@@ -6,14 +6,28 @@ from osbot_utils.utils.Files import all_parent_folders, file_exists
|
|
6
6
|
from osbot_utils.utils.Misc import list_set
|
7
7
|
from osbot_utils.utils.Str import strip_quotes
|
8
8
|
|
9
|
-
|
9
|
+
|
10
|
+
def env__home():
|
11
|
+
return get_env('HOME', '')
|
12
|
+
|
13
|
+
def env__home__is__root():
|
10
14
|
return os.getenv('HOME') == '/root'
|
11
15
|
|
12
|
-
def
|
16
|
+
def env__old_pwd():
|
17
|
+
return get_env('OLDPWD', '')
|
18
|
+
|
19
|
+
def env__pwd():
|
20
|
+
return get_env('PWD', '')
|
21
|
+
|
22
|
+
def env__old_pwd__remove(value):
|
23
|
+
return value.replace(env__old_pwd(), '')
|
24
|
+
|
25
|
+
def env__terminal__is__xterm():
|
13
26
|
return os.getenv('TERM') == 'xterm'
|
14
27
|
|
15
|
-
def
|
16
|
-
return not
|
28
|
+
def env__terminal__is_not__xterm():
|
29
|
+
return not env__terminal__is__xterm()
|
30
|
+
|
17
31
|
|
18
32
|
def platform_darwin():
|
19
33
|
return platform == 'darwin'
|
@@ -21,6 +35,10 @@ def platform_darwin():
|
|
21
35
|
def env_value(var_name):
|
22
36
|
return env_vars().get(var_name, None)
|
23
37
|
|
38
|
+
def env_var_set(var_name):
|
39
|
+
value = os.getenv(var_name)
|
40
|
+
return value is not None and value != ''
|
41
|
+
|
24
42
|
def env_vars_list():
|
25
43
|
return list_set(env_vars())
|
26
44
|
|
@@ -109,6 +127,6 @@ def unload_dotenv(dotenv_path=None):
|
|
109
127
|
env_unload_from_file(env_path) # Process it
|
110
128
|
break # Stop after unloading the first .env file
|
111
129
|
|
112
|
-
|
113
|
-
env_load
|
114
|
-
get_env
|
130
|
+
is_env_var_set = env_var_set
|
131
|
+
env_load = load_dotenv
|
132
|
+
get_env = os.getenv
|
osbot_utils/utils/Files.py
CHANGED
@@ -340,6 +340,8 @@ class Files:
|
|
340
340
|
def parent_folder_combine(file, path):
|
341
341
|
return Files.path_combine(os.path.dirname(file),path)
|
342
342
|
|
343
|
+
|
344
|
+
|
343
345
|
@staticmethod
|
344
346
|
def pickle_save_to_file(object_to_save, path=None):
|
345
347
|
path = path or temp_file(extension=".pickle")
|
@@ -491,12 +493,26 @@ def folders_names_in_folder(target):
|
|
491
493
|
folders = folders_in_folder(target)
|
492
494
|
return folders_names(folders)
|
493
495
|
|
496
|
+
def path_combine_safe(base_path, file_location, raise_exception=False): # handle possible directory transversal attacks
|
497
|
+
full_path = os.path.join(base_path, file_location) # Combine and normalize paths
|
498
|
+
normalised_base_path = os.path.normpath(base_path)
|
499
|
+
normalised_full_path = os.path.normpath(full_path)
|
500
|
+
|
501
|
+
if os.path.commonpath([normalised_base_path, normalised_full_path]) == normalised_base_path: # Check for directory traversal
|
502
|
+
return normalised_full_path
|
503
|
+
if raise_exception:
|
504
|
+
raise ValueError("Invalid file location: directory traversal attempt detected.")
|
505
|
+
return None
|
506
|
+
|
494
507
|
def parent_folder_create(target):
|
495
508
|
return folder_create(parent_folder(target))
|
496
509
|
|
497
510
|
def parent_folder_exists(target):
|
498
511
|
return folder_exists(parent_folder(target))
|
499
512
|
|
513
|
+
def parent_folder_name(target):
|
514
|
+
return folder_name(parent_folder(target))
|
515
|
+
|
500
516
|
def parent_folder_not_exists(target):
|
501
517
|
return parent_folder_exists(target) is False
|
502
518
|
|
osbot_utils/utils/Http.py
CHANGED
@@ -22,6 +22,13 @@ def current_host_online(url_to_use=URL_CHECK_HOST_ONLINE):
|
|
22
22
|
def dns_ip_address(host):
|
23
23
|
return socket.gethostbyname(host)
|
24
24
|
|
25
|
+
def is_url_online(target):
|
26
|
+
try:
|
27
|
+
http_request(target, method='HEAD')
|
28
|
+
return True
|
29
|
+
except:
|
30
|
+
return False
|
31
|
+
|
25
32
|
def is_port_open(host, port, timeout=0.5):
|
26
33
|
return port_is_open(host=host, port=port, timeout=timeout)
|
27
34
|
|
osbot_utils/utils/Objects.py
CHANGED
@@ -69,6 +69,9 @@ def base_classes(cls):
|
|
69
69
|
target = type(cls)
|
70
70
|
return type_base_classes(target)
|
71
71
|
|
72
|
+
def base_classes_names(cls):
|
73
|
+
return [cls.__name__ for cls in base_classes(cls)]
|
74
|
+
|
72
75
|
def class_functions_names(target):
|
73
76
|
return list_set(class_functions(target))
|
74
77
|
|
@@ -89,6 +92,17 @@ def class_full_name(target):
|
|
89
92
|
type_name = type_target.__name__
|
90
93
|
return f'{type_module}.{type_name}'
|
91
94
|
|
95
|
+
def convert_dict_to_value_from_obj_annotation(target, attr_name, value):
|
96
|
+
if target is not None and attr_name is not None:
|
97
|
+
if hasattr(target, '__annotations__'):
|
98
|
+
obj_annotations = target.__annotations__
|
99
|
+
if hasattr(obj_annotations,'get'):
|
100
|
+
attribute_annotation = obj_annotations.get(attr_name)
|
101
|
+
if 'Type_Safe' in base_classes_names(attribute_annotation):
|
102
|
+
return attribute_annotation(**value)
|
103
|
+
return value
|
104
|
+
|
105
|
+
|
92
106
|
def default_value(target : type):
|
93
107
|
try:
|
94
108
|
return target() # try to create the object using the default constructor
|
osbot_utils/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
v1.
|
1
|
+
v1.32.0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: osbot_utils
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.32.0
|
4
4
|
Summary: OWASP Security Bot - Utils
|
5
5
|
Home-page: https://github.com/owasp-sbot/OSBot-Utils
|
6
6
|
License: MIT
|
@@ -22,7 +22,7 @@ Description-Content-Type: text/markdown
|
|
22
22
|
|
23
23
|
Powerful Python util methods and classes that simplify common apis and tasks.
|
24
24
|
|
25
|
-

|
26
26
|
[](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
|
27
27
|
|
28
28
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
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
|
-
osbot_utils/base_classes/Kwargs_To_Self.py,sha256=
|
5
|
-
osbot_utils/base_classes/Type_Safe.py,sha256=
|
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=62eV2AnIAN-7VshhOpvWw0v_DZHJucLq10VQHjgCfX8,17089
|
6
6
|
osbot_utils/base_classes/Type_Safe__List.py,sha256=-80C9OhsK6iDR2dAG8yNLAZV0qg5x3faqvSUigFCMJw,517
|
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
|
@@ -240,7 +240,7 @@ osbot_utils/helpers/trace/Trace_Call__Stats.py,sha256=gmiotIrOXe2ssxodzQQ56t8eGT
|
|
240
240
|
osbot_utils/helpers/trace/Trace_Call__View_Model.py,sha256=a40nn6agCEMd2ecsJ93n8vXij0omh0D69QilqwmN_ao,4545
|
241
241
|
osbot_utils/helpers/trace/Trace_Files.py,sha256=SNpAmuBlSUS9NyVocgZ5vevzqVaIqoh622yZge3a53A,978
|
242
242
|
osbot_utils/helpers/trace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
243
|
-
osbot_utils/testing/Catch.py,sha256=
|
243
|
+
osbot_utils/testing/Catch.py,sha256=HdNoKnrPBjvVj87XYN-Wa1zpo5z3oByURT6TKbd5QpQ,2229
|
244
244
|
osbot_utils/testing/Duration.py,sha256=iBrczAuw6j3jXtG7ZPraT0PXbCILEcCplJbqei96deA,2217
|
245
245
|
osbot_utils/testing/Hook_Method.py,sha256=A1t6WQzeR9hv5ddaz7ILtvKyJpRrRBRnftB-zgcIQpA,4126
|
246
246
|
osbot_utils/testing/Log_To_Queue.py,sha256=pZQ7I1ne-H365a4WLS60oAD-B16pxIZO4suvCdaTW8U,1703
|
@@ -265,17 +265,17 @@ osbot_utils/utils/Assert.py,sha256=u9XLgYn91QvNWZGyPi29SjPJSXRHlm9andIn3NJEVog,1
|
|
265
265
|
osbot_utils/utils/Call_Stack.py,sha256=MAq_0vMxnbeLfCe9qQz7GwJYaOuXpt3qtQwN6wiXsU0,6595
|
266
266
|
osbot_utils/utils/Csv.py,sha256=oHLVpjRJqrLMz9lubMCNEoThXWju5rNTprcwHc1zq2c,1012
|
267
267
|
osbot_utils/utils/Dev.py,sha256=HibpQutYy_iG8gGV8g1GztxNN4l29E4Bi7UZaVL6-L8,1203
|
268
|
-
osbot_utils/utils/Env.py,sha256=
|
268
|
+
osbot_utils/utils/Env.py,sha256=243O6ENzaRjHXp8DIHUuv4Wfk-zHTE18KZr0cU8uWyo,5474
|
269
269
|
osbot_utils/utils/Exceptions.py,sha256=KyOUHkXQ_6jDTq04Xm261dbEZuRidtsM4dgzNwSG8-8,389
|
270
|
-
osbot_utils/utils/Files.py,sha256=
|
270
|
+
osbot_utils/utils/Files.py,sha256=RHbxq8AVdGT9S-OrxEWhNEOhbIrHDRU1UbGB1O05Ga8,21615
|
271
271
|
osbot_utils/utils/Functions.py,sha256=0E6alPJ0fJpBiJgFOWooCOi265wSRyxxXAJ5CELBnso,3498
|
272
|
-
osbot_utils/utils/Http.py,sha256=
|
272
|
+
osbot_utils/utils/Http.py,sha256=WlXEfgT_NaiDVD7vCDUxy_nOm5Qf8x_L0A3zd8B5tX8,4706
|
273
273
|
osbot_utils/utils/Int.py,sha256=PmlUdU4lSwf4gJdmTVdqclulkEp7KPCVUDO6AcISMF4,116
|
274
274
|
osbot_utils/utils/Json.py,sha256=UNaBazuH1R40fsHjpjuK8kmAANmUHoK9Q0PUeYmgPeY,6254
|
275
275
|
osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
|
276
276
|
osbot_utils/utils/Lists.py,sha256=CLEjgZwAixJAFlubWEKjnUUhUN85oqvR7UqExVW7rdY,5502
|
277
277
|
osbot_utils/utils/Misc.py,sha256=ljscBemI5wOhfkl1BVpsqshacTOCKkOisV4er9xPCWM,16640
|
278
|
-
osbot_utils/utils/Objects.py,sha256=
|
278
|
+
osbot_utils/utils/Objects.py,sha256=qAWNLISL-gYTl1Ihj4fBSZ9I6n-p-YPUhRZu9YQwqWQ,15235
|
279
279
|
osbot_utils/utils/Png.py,sha256=V1juGp6wkpPigMJ8HcxrPDIP4bSwu51oNkLI8YqP76Y,1172
|
280
280
|
osbot_utils/utils/Process.py,sha256=lr3CTiEkN3EiBx3ZmzYmTKlQoPdkgZBRjPulMxG-zdo,2357
|
281
281
|
osbot_utils/utils/Python_Logger.py,sha256=tx8N6wRKL3RDHboDRKZn8SirSJdSAE9cACyJkxrThZ8,12792
|
@@ -286,8 +286,8 @@ osbot_utils/utils/Toml.py,sha256=dqiegndCJF7V1YT1Tc-b0-Bl6QWyL5q30urmQwMXfMQ,140
|
|
286
286
|
osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
|
287
287
|
osbot_utils/utils/Zip.py,sha256=t9txUxJzLBEHot6WJwF0iTTUQ1Gf_V2pVwsWzAqw_NU,12163
|
288
288
|
osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
289
|
-
osbot_utils/version,sha256=
|
290
|
-
osbot_utils-1.
|
291
|
-
osbot_utils-1.
|
292
|
-
osbot_utils-1.
|
293
|
-
osbot_utils-1.
|
289
|
+
osbot_utils/version,sha256=bXbruCHeH-hb0hIQlj3n7tV2_vXn3ypQsk6TaP1y0Zk,8
|
290
|
+
osbot_utils-1.32.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
291
|
+
osbot_utils-1.32.0.dist-info/METADATA,sha256=B4r-4DcyCbnaGO-x7uJIrie537cYmE8Ux4hGFen0Wtc,1266
|
292
|
+
osbot_utils-1.32.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
293
|
+
osbot_utils-1.32.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|