osbot-utils 1.90.0__py3-none-any.whl → 1.92.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 +38 -7
- osbot_utils/graphs/mermaid/Mermaid.py +7 -7
- osbot_utils/graphs/mgraph/MGraph__Edge.py +1 -4
- osbot_utils/graphs/mgraph/MGraph__Node.py +2 -3
- osbot_utils/graphs/mgraph/MGraph__Serializer.py +4 -5
- osbot_utils/graphs/mgraph/MGraphs.py +0 -5
- osbot_utils/helpers/Zip_Bytes.py +2 -2
- osbot_utils/helpers/python_compatibility/__init__.py +0 -0
- osbot_utils/helpers/python_compatibility/python_3_8.py +8 -0
- osbot_utils/helpers/trace/Trace_Call__Handler.py +21 -21
- osbot_utils/helpers/type_safe/Type_Safe__Validator.py +14 -0
- osbot_utils/helpers/type_safe/__init__.py +0 -0
- osbot_utils/helpers/type_safe/validators/Validator__Max.py +28 -0
- osbot_utils/helpers/type_safe/validators/Validator__Min.py +38 -0
- osbot_utils/helpers/type_safe/validators/Validator__One_Of.py +18 -0
- osbot_utils/helpers/type_safe/validators/Validator__Regex.py +26 -0
- osbot_utils/helpers/type_safe/validators/__init__.py +0 -0
- osbot_utils/helpers/xml/rss/RSS__Feed__Parser.py +11 -1
- osbot_utils/utils/Objects.py +46 -10
- osbot_utils/utils/Regex.py +1 -1
- osbot_utils/version +1 -1
- {osbot_utils-1.90.0.dist-info → osbot_utils-1.92.0.dist-info}/METADATA +2 -2
- {osbot_utils-1.90.0.dist-info → osbot_utils-1.92.0.dist-info}/RECORD +25 -16
- {osbot_utils-1.90.0.dist-info → osbot_utils-1.92.0.dist-info}/LICENSE +0 -0
- {osbot_utils-1.90.0.dist-info → osbot_utils-1.92.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
|
@@ -24,6 +24,7 @@ if sys.version_info < (3, 8): # pragma
|
|
24
24
|
return ()
|
25
25
|
else:
|
26
26
|
from typing import get_origin, get_args, ForwardRef
|
27
|
+
from osbot_utils.helpers.python_compatibility.python_3_8 import Annotated
|
27
28
|
|
28
29
|
if sys.version_info >= (3, 10):
|
29
30
|
NoneType = types.NoneType
|
@@ -44,7 +45,6 @@ class Type_Safe:
|
|
44
45
|
|
45
46
|
for (key, value) in self.__cls_kwargs__().items(): # assign all default values to self
|
46
47
|
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
|
47
|
-
|
48
48
|
raise_exception_on_obj_type_annotation_mismatch(self, key, value)
|
49
49
|
if hasattr(self, key):
|
50
50
|
existing_value = getattr(self, key)
|
@@ -64,11 +64,30 @@ class Type_Safe:
|
|
64
64
|
def __enter__(self): return self
|
65
65
|
def __exit__(self, exc_type, exc_val, exc_tb): pass
|
66
66
|
|
67
|
+
def __getattr__(self, name): # Called when an attribute is not found through normal attribute access
|
68
|
+
if name.startswith(("set_", "get_")): # Check if the requested attribute is a getter or setter method
|
69
|
+
prefix = name[:4] # Extract "set_" or "get_" from the method name
|
70
|
+
attr_name = name[4:] # Get the actual attribute name by removing the prefix
|
71
|
+
|
72
|
+
if hasattr(self, attr_name): # Verify that the target attribute actually exists on the object
|
73
|
+
if prefix == "set_": # Handle setter method creation
|
74
|
+
def setter(value): # Create a dynamic setter function that takes a value parameter
|
75
|
+
setattr(self, attr_name, value) # Set the attribute value using type-safe setattr from Type_Safe
|
76
|
+
return self # Return self for method chaining
|
77
|
+
return setter # Return the setter function
|
78
|
+
else: # get_ # Handle getter method creation
|
79
|
+
def getter(): # Create a dynamic getter function with no parameters
|
80
|
+
return getattr(self, attr_name) # Return the attribute value using Python's built-in getattr
|
81
|
+
return getter # Return the getter function
|
82
|
+
|
83
|
+
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") # Raise error if attribute is not a valid getter/setter
|
84
|
+
|
67
85
|
def __setattr__(self, name, value):
|
68
|
-
from osbot_utils.utils.Objects
|
69
|
-
from osbot_utils.utils.Objects
|
70
|
-
from osbot_utils.utils.Objects
|
71
|
-
from osbot_utils.utils.Objects
|
86
|
+
from osbot_utils.utils.Objects import convert_dict_to_value_from_obj_annotation
|
87
|
+
from osbot_utils.utils.Objects import convert_to_value_from_obj_annotation
|
88
|
+
from osbot_utils.utils.Objects import value_type_matches_obj_annotation_for_attr
|
89
|
+
from osbot_utils.utils.Objects import value_type_matches_obj_annotation_for_union_and_annotated
|
90
|
+
from osbot_utils.helpers.type_safe.Type_Safe__Validator import Type_Safe__Validator
|
72
91
|
|
73
92
|
if not hasattr(self, '__annotations__'): # can't do type safety checks if the class does not have annotations
|
74
93
|
return super().__setattr__(name, value)
|
@@ -79,7 +98,7 @@ class Type_Safe:
|
|
79
98
|
if type(value) in [int, str]: # for now only a small number of str and int classes are supported (until we understand the full implications of this)
|
80
99
|
value = convert_to_value_from_obj_annotation (self, name, value)
|
81
100
|
check_1 = value_type_matches_obj_annotation_for_attr (self, name, value)
|
82
|
-
check_2 =
|
101
|
+
check_2 = value_type_matches_obj_annotation_for_union_and_annotated(self, name, value)
|
83
102
|
if (check_1 is False and check_2 is None or
|
84
103
|
check_1 is None and check_2 is False or
|
85
104
|
check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars
|
@@ -89,6 +108,16 @@ class Type_Safe:
|
|
89
108
|
if getattr(self, name) is not None: # unless it is already set to None
|
90
109
|
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)}'")
|
91
110
|
|
111
|
+
# todo: refactor this to separate method
|
112
|
+
if hasattr(self.__annotations__, 'get'):
|
113
|
+
annotation = self.__annotations__.get(name)
|
114
|
+
if annotation and get_origin(annotation) is Annotated:
|
115
|
+
annotation_args = get_args(annotation)
|
116
|
+
target_type = annotation_args[0]
|
117
|
+
for attribute in annotation_args[1:]:
|
118
|
+
if isinstance(attribute, Type_Safe__Validator):
|
119
|
+
attribute.validate(value=value, field_name=name, target_type=target_type)
|
120
|
+
|
92
121
|
super().__setattr__(name, value)
|
93
122
|
|
94
123
|
def __attr_names__(self):
|
@@ -131,6 +160,8 @@ class Type_Safe:
|
|
131
160
|
else:
|
132
161
|
var_value = getattr(base_cls, var_name)
|
133
162
|
if var_value is not None: # allow None assignments on ctor since that is a valid use case
|
163
|
+
if get_origin(var_type) is Annotated:
|
164
|
+
continue
|
134
165
|
if var_type and not isinstance(var_value, var_type): # check type
|
135
166
|
exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'"
|
136
167
|
raise ValueError(exception_message)
|
@@ -1,10 +1,10 @@
|
|
1
|
-
from osbot_utils.graphs.mermaid.Mermaid__Renderer
|
2
|
-
from osbot_utils.graphs.mermaid.Mermaid__Edge
|
3
|
-
from osbot_utils.graphs.mermaid.Mermaid__Graph
|
4
|
-
from osbot_utils.graphs.mermaid.models.Mermaid__Diagram_Direction
|
5
|
-
from osbot_utils.graphs.mermaid.models.Mermaid__Diagram__Type
|
6
|
-
from osbot_utils.utils.Python_Logger
|
7
|
-
from osbot_utils.base_classes.Kwargs_To_Self
|
1
|
+
from osbot_utils.graphs.mermaid.Mermaid__Renderer import Mermaid__Renderer
|
2
|
+
from osbot_utils.graphs.mermaid.Mermaid__Edge import Mermaid__Edge
|
3
|
+
from osbot_utils.graphs.mermaid.Mermaid__Graph import Mermaid__Graph
|
4
|
+
from osbot_utils.graphs.mermaid.models.Mermaid__Diagram_Direction import Diagram__Direction
|
5
|
+
from osbot_utils.graphs.mermaid.models.Mermaid__Diagram__Type import Diagram__Type
|
6
|
+
from osbot_utils.utils.Python_Logger import Python_Logger
|
7
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
8
8
|
|
9
9
|
class Mermaid(Kwargs_To_Self):
|
10
10
|
graph : Mermaid__Graph
|
@@ -1,8 +1,5 @@
|
|
1
|
-
from osbot_utils.base_classes.Kwargs_To_Self
|
2
|
-
from osbot_utils.graphs.mermaid.Mermaid__Node import LINE_PADDING, Mermaid__Node
|
1
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
3
2
|
from osbot_utils.graphs.mgraph.MGraph__Node import MGraph__Node
|
4
|
-
from osbot_utils.utils.Str import safe_str
|
5
|
-
|
6
3
|
|
7
4
|
class MGraph__Edge(Kwargs_To_Self):
|
8
5
|
attributes : dict
|
@@ -1,6 +1,5 @@
|
|
1
|
-
from osbot_utils.utils.Misc
|
2
|
-
|
3
|
-
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
1
|
+
from osbot_utils.utils.Misc import random_id
|
2
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
4
3
|
|
5
4
|
|
6
5
|
class MGraph__Node(Kwargs_To_Self):
|
@@ -1,9 +1,8 @@
|
|
1
|
-
from enum
|
2
|
-
from osbot_utils.utils.Str
|
3
|
-
from osbot_utils.helpers.Local_Cache
|
4
|
-
|
1
|
+
from enum import Enum, auto
|
2
|
+
from osbot_utils.utils.Str import safe_str
|
3
|
+
from osbot_utils.helpers.Local_Cache import Local_Cache
|
5
4
|
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
6
|
-
from osbot_utils.graphs.mgraph.MGraph
|
5
|
+
from osbot_utils.graphs.mgraph.MGraph import MGraph
|
7
6
|
|
8
7
|
|
9
8
|
class Serialization_Mode(Enum):
|
@@ -1,10 +1,5 @@
|
|
1
|
-
import random
|
2
|
-
|
3
|
-
from osbot_utils.utils.Files import file_exists, file_extension, pickle_load_from_file
|
4
|
-
|
5
1
|
from osbot_utils.graphs.mgraph.MGraph__Random_Graphs import MGraph__Random_Graphs
|
6
2
|
|
7
|
-
|
8
3
|
class MGraphs:
|
9
4
|
|
10
5
|
def new__random(self, config=None, graph_key=None, x_nodes=10, y_edges=20):
|
osbot_utils/helpers/Zip_Bytes.py
CHANGED
@@ -2,10 +2,10 @@ from osbot_utils.base_classes.Type_Safe import Type_Safe
|
|
2
2
|
from osbot_utils.utils.Dev import pprint
|
3
3
|
from osbot_utils.utils.Files import files_list, file_create_from_bytes, temp_file, parent_folder, parent_folder_create
|
4
4
|
from osbot_utils.utils.Misc import random_text
|
5
|
-
from osbot_utils.utils.Regex import
|
5
|
+
from osbot_utils.utils.Regex import list__match_regexes
|
6
6
|
from osbot_utils.utils.Zip import zip_bytes_empty, zip_bytes__files, zip_bytes__add_file, zip_bytes__add_files, \
|
7
7
|
zip_bytes__replace_files, zip_bytes__replace_file, zip_bytes__file_list, zip_bytes__file, \
|
8
|
-
zip_bytes__add_file__from_disk, zip_bytes__add_files__from_disk,
|
8
|
+
zip_bytes__add_file__from_disk, zip_bytes__add_files__from_disk, zip_file__files, zip_bytes__remove_files
|
9
9
|
|
10
10
|
|
11
11
|
class Zip_Bytes(Type_Safe):
|
File without changes
|
@@ -8,28 +8,28 @@ from osbot_utils.helpers.trace.Trace_Call__Stack_Node import Trace_Call__Stack
|
|
8
8
|
from osbot_utils.helpers.trace.Trace_Call__Stats import Trace_Call__Stats
|
9
9
|
|
10
10
|
DEFAULT_ROOT_NODE_NODE_TITLE = 'Trace Session'
|
11
|
-
GLOBAL_FUNCTIONS_TO_IGNORE = ['value_type_matches_obj_annotation_for_attr'
|
12
|
-
'
|
13
|
-
'are_types_compatible_for_assigment'
|
14
|
-
'obj_attribute_annotation'
|
15
|
-
'get_origin'
|
16
|
-
'getmro'
|
17
|
-
'default_value'
|
18
|
-
'raise_exception_on_obj_type_annotation_mismatch'
|
19
|
-
'__cls_kwargs__'
|
20
|
-
'__default__value__'
|
21
|
-
'__setattr__'
|
11
|
+
GLOBAL_FUNCTIONS_TO_IGNORE = ['value_type_matches_obj_annotation_for_attr' , # these are type safety functions which introduce quite a lot of noise in the traces (and unless one is debugging type safety, they will not be needed)
|
12
|
+
'value_type_matches_obj_annotation_for_union_and_annotated' , # todo: map out and document why exactly these methods are ignore (and what is the side effect)
|
13
|
+
'are_types_compatible_for_assigment' ,
|
14
|
+
'obj_attribute_annotation' ,
|
15
|
+
'get_origin' ,
|
16
|
+
'getmro' ,
|
17
|
+
'default_value' ,
|
18
|
+
'raise_exception_on_obj_type_annotation_mismatch' ,
|
19
|
+
'__cls_kwargs__' ,
|
20
|
+
'__default__value__' ,
|
21
|
+
'__setattr__' ,
|
22
22
|
'<module>']
|
23
|
-
GLOBAL_MODULES_TO_IGNORE = ['osbot_utils.helpers.trace.Trace_Call'
|
24
|
-
'osbot_utils.helpers.trace.Trace_Call__Config'
|
25
|
-
'osbot_utils.helpers.trace.Trace_Call__View_Model'
|
26
|
-
'osbot_utils.helpers.trace.Trace_Call__Print_Traces'
|
27
|
-
'osbot_utils.helpers.trace.Trace_Call__Stack'
|
28
|
-
'osbot_utils.base_classes.Type_Safe'
|
29
|
-
'osbot_utils.helpers.CPrint'
|
30
|
-
'osbot_utils.helpers.Print_Table'
|
31
|
-
'osbot_utils.decorators.methods.cache_on_self'
|
32
|
-
'codecs'
|
23
|
+
GLOBAL_MODULES_TO_IGNORE = ['osbot_utils.helpers.trace.Trace_Call' , # todo: map out and document why exactly these modules are ignore (and what is the side effect)
|
24
|
+
'osbot_utils.helpers.trace.Trace_Call__Config' ,
|
25
|
+
'osbot_utils.helpers.trace.Trace_Call__View_Model' ,
|
26
|
+
'osbot_utils.helpers.trace.Trace_Call__Print_Traces' ,
|
27
|
+
'osbot_utils.helpers.trace.Trace_Call__Stack' ,
|
28
|
+
'osbot_utils.base_classes.Type_Safe' ,
|
29
|
+
'osbot_utils.helpers.CPrint' , # also see if this should be done here or at the print/view stage
|
30
|
+
'osbot_utils.helpers.Print_Table' ,
|
31
|
+
'osbot_utils.decorators.methods.cache_on_self' ,
|
32
|
+
'codecs' ]
|
33
33
|
|
34
34
|
#GLOBAL_MODULES_TO_IGNORE = []
|
35
35
|
#GLOBAL_FUNCTIONS_TO_IGNORE = []
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from osbot_utils.helpers.python_compatibility.python_3_8 import Annotated
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
class Type_Safe__Validator: # Base class for all Type_Safe validators.
|
5
|
+
|
6
|
+
def validate(self, value: Any, field_name: str, target_type: type) -> None: # Validate a value against this validator's rules.
|
7
|
+
if value and type(value) != target_type:
|
8
|
+
raise ValueError(f"{field_name} must be of type {target_type}, got {type(value)}")
|
9
|
+
|
10
|
+
def describe(self) -> str: # Return a human-readable description of this validator's rules.
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
Validate = Annotated
|
File without changes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from osbot_utils.helpers.type_safe.Type_Safe__Validator import Type_Safe__Validator
|
3
|
+
|
4
|
+
|
5
|
+
class Validator__Max(Type_Safe__Validator): # Validates that a numeric value is at most the specified maximum."""
|
6
|
+
max_value: float
|
7
|
+
|
8
|
+
def __init__(self, max_value):
|
9
|
+
self.max_value = max_value
|
10
|
+
|
11
|
+
def validate(self, value: Any, field_name: str, target_type: type) -> None:
|
12
|
+
super().validate(value=value, field_name=field_name, target_type=target_type)
|
13
|
+
if value is None:
|
14
|
+
return
|
15
|
+
compare_value = value if isinstance(value, (int, float)) else len(value)
|
16
|
+
if compare_value > self.max_value:
|
17
|
+
if not isinstance(value, (int, float)):
|
18
|
+
msg = f"{field_name} must be at most {self.max_value}, got {compare_value}"
|
19
|
+
elif isinstance(value, (list, tuple)):
|
20
|
+
msg = f"{field_name} must be at most {self.max_value}, got length {compare_value}"
|
21
|
+
else:
|
22
|
+
msg = f"{field_name} must be at most {self.max_value}, got size {compare_value}"
|
23
|
+
raise ValueError(msg)
|
24
|
+
|
25
|
+
def describe(self) -> str:
|
26
|
+
return f"maximum value: {self.max_value}"
|
27
|
+
|
28
|
+
Max = Validator__Max
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from osbot_utils.helpers.type_safe.Type_Safe__Validator import Type_Safe__Validator
|
3
|
+
|
4
|
+
class Validator__Min(Type_Safe__Validator): # Validates that a value is at least the specified minimum. Works with any type that supports the < operator (numbers, strings, lists, etc.)
|
5
|
+
min_value: Any
|
6
|
+
|
7
|
+
def __init__(self, min_value):
|
8
|
+
super().__init__()
|
9
|
+
self.min_value = min_value
|
10
|
+
|
11
|
+
def validate(self, value: Any, field_name: str, target_type: type) -> None:
|
12
|
+
super().validate(value=value, field_name=field_name, target_type=target_type)
|
13
|
+
if value is None: # can't compare if value has been set to None
|
14
|
+
return
|
15
|
+
|
16
|
+
try:
|
17
|
+
compare_value = value if isinstance(value, (int, float)) else len(value)
|
18
|
+
|
19
|
+
if compare_value < self.min_value:
|
20
|
+
if isinstance(value, (int, float)):
|
21
|
+
msg = f"{field_name} must be at least {self.min_value}, got {compare_value}"
|
22
|
+
elif isinstance(value, str):
|
23
|
+
msg = f"{field_name} must have length at least {self.min_value}, got length {compare_value}"
|
24
|
+
elif isinstance(value, (list, tuple)):
|
25
|
+
msg = f"{field_name} must have length at least {self.min_value}, got length {compare_value}"
|
26
|
+
else:
|
27
|
+
msg = f"{field_name} must have size at least {self.min_value}, got size {compare_value}"
|
28
|
+
raise ValueError(msg)
|
29
|
+
except TypeError:
|
30
|
+
raise ValueError(f"Cannot compare {field_name} of type {type(value)} with minimum value of type {type(self.min_value)}")
|
31
|
+
|
32
|
+
def describe(self) -> str:
|
33
|
+
if isinstance(self.min_value, (int, float)):
|
34
|
+
return f"minimum value: {self.min_value}"
|
35
|
+
else:
|
36
|
+
return f"minimum length: {len(self.min_value)}"
|
37
|
+
|
38
|
+
Min = Validator__Min
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from osbot_utils.helpers.type_safe.Type_Safe__Validator import Type_Safe__Validator
|
3
|
+
|
4
|
+
|
5
|
+
class Validator__One_Of(Type_Safe__Validator): # Validates that a value is one of a set of allowed values."""
|
6
|
+
allowed: list
|
7
|
+
|
8
|
+
def __init__(self, allowed):
|
9
|
+
self.allowed = allowed
|
10
|
+
|
11
|
+
def validate(self, value: Any, field_name: str, target_type:type) -> None:
|
12
|
+
if value not in self.allowed:
|
13
|
+
raise ValueError(f"{field_name} must be one of {self.allowed}, got {value}")
|
14
|
+
|
15
|
+
def describe(self) -> str:
|
16
|
+
return f"must be one of: {self.allowed}"
|
17
|
+
|
18
|
+
One_Of = Validator__One_Of
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from osbot_utils.helpers.type_safe.Type_Safe__Validator import Type_Safe__Validator
|
3
|
+
|
4
|
+
class Validator__Regex(Type_Safe__Validator): # Validates that a string matches the specified regex pattern.
|
5
|
+
pattern : str
|
6
|
+
description: str
|
7
|
+
|
8
|
+
def __init__(self, pattern, description=None):
|
9
|
+
self.pattern = pattern
|
10
|
+
self.description = description
|
11
|
+
|
12
|
+
def validate(self, value: Any, field_name: str, target_type:type) -> None:
|
13
|
+
import re
|
14
|
+
if value is None:
|
15
|
+
return
|
16
|
+
if not isinstance(value, str):
|
17
|
+
raise ValueError(f"{field_name} must be a string, got {type(value)}")
|
18
|
+
if not re.match(self.pattern, value):
|
19
|
+
raise ValueError(f"{field_name} must match pattern {self.pattern}")
|
20
|
+
|
21
|
+
def describe(self) -> str:
|
22
|
+
if self.description:
|
23
|
+
return self.description
|
24
|
+
return f"must match pattern: {self.pattern}"
|
25
|
+
|
26
|
+
Regex = Validator__Regex
|
File without changes
|
@@ -25,13 +25,14 @@ class RSS__Feed__Parser:
|
|
25
25
|
guid = self.extract_guid(item_data.get('guid' ))
|
26
26
|
pubDate = self.element_text(item_data.get('pubDate' ))
|
27
27
|
creator = self.element_text(item_data.get('creator' ))
|
28
|
+
categories = self.ensure_is_list(item_data.get('category'))
|
28
29
|
rss_item = RSS__Item(title = title ,
|
29
30
|
link = link ,
|
30
31
|
description = description ,
|
31
32
|
guid = guid ,
|
32
33
|
pubDate = pubDate ,
|
33
34
|
creator = creator ,
|
34
|
-
categories =
|
35
|
+
categories = categories ,
|
35
36
|
content = item_data.get('content' , {}),
|
36
37
|
thumbnail = item_data.get('thumbnail' , {}))
|
37
38
|
|
@@ -74,6 +75,15 @@ class RSS__Feed__Parser:
|
|
74
75
|
def extract_guid(self, target):
|
75
76
|
return Guid(self.extract_text(target))
|
76
77
|
|
78
|
+
def ensure_is_list(self, target):
|
79
|
+
if type(target) is list:
|
80
|
+
return target
|
81
|
+
if type(target) is str:
|
82
|
+
return [target]
|
83
|
+
if target:
|
84
|
+
return [f'{target}']
|
85
|
+
return []
|
86
|
+
|
77
87
|
def element_text(self, target):
|
78
88
|
if isinstance(target, list):
|
79
89
|
for item in target:
|
osbot_utils/utils/Objects.py
CHANGED
@@ -23,7 +23,7 @@ if sys.version_info < (3, 8):
|
|
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, List, Tuple, Dict
|
27
27
|
|
28
28
|
|
29
29
|
def are_types_compatible_for_assigment(source_type, target_type):
|
@@ -343,11 +343,10 @@ def obj_values(target=None):
|
|
343
343
|
return list(obj_dict(target).values())
|
344
344
|
|
345
345
|
def raise_exception_on_obj_type_annotation_mismatch(target, attr_name, value):
|
346
|
-
# todo : check if this is is not causing the type safety issues
|
347
346
|
if value_type_matches_obj_annotation_for_attr(target, attr_name, value) is False: # handle case with normal types
|
348
|
-
if
|
347
|
+
if value_type_matches_obj_annotation_for_union_and_annotated(target, attr_name, value) is True: # handle union cases
|
349
348
|
return # this is done like this because value_type_matches_obj_annotation_for_union_attr will return None when there is no Union objects
|
350
|
-
raise
|
349
|
+
raise TypeError(f"Invalid type for attribute '{attr_name}'. Expected '{target.__annotations__.get(attr_name)}' but got '{type(value)}'")
|
351
350
|
|
352
351
|
def obj_attribute_annotation(target, attr_name):
|
353
352
|
if target is not None and attr_name is not None:
|
@@ -382,15 +381,52 @@ def obj_is_type_union_compatible(var_type, compatible_types):
|
|
382
381
|
return True # If all args are compatible, return True
|
383
382
|
return var_type in compatible_types or var_type is type(None) # Check for direct compatibility or type(None) for non-Union types
|
384
383
|
|
385
|
-
|
386
|
-
|
384
|
+
|
385
|
+
def value_type_matches_obj_annotation_for_union_and_annotated(target, attr_name, value):
|
386
|
+
from osbot_utils.helpers.python_compatibility.python_3_8 import Annotated
|
387
|
+
from typing import Union, get_origin, get_args
|
388
|
+
|
387
389
|
value_type = type(value)
|
388
|
-
attribute_annotation = obj_attribute_annotation(target,attr_name)
|
390
|
+
attribute_annotation = obj_attribute_annotation(target, attr_name)
|
389
391
|
origin = get_origin(attribute_annotation)
|
390
|
-
|
391
|
-
|
392
|
+
|
393
|
+
if origin is Union: # Handle Union types (including Optional)
|
394
|
+
args = get_args(attribute_annotation)
|
392
395
|
return value_type in args
|
393
|
-
|
396
|
+
|
397
|
+
# todo: refactor the logic below to a separate method (and check for duplicate code with other get_origin usage)
|
398
|
+
if origin is Annotated: # Handle Annotated types
|
399
|
+
args = get_args(attribute_annotation)
|
400
|
+
base_type = args[0] # First argument is the base type
|
401
|
+
base_origin = get_origin(base_type)
|
402
|
+
|
403
|
+
if base_origin is None: # Non-container types
|
404
|
+
return isinstance(value, base_type)
|
405
|
+
|
406
|
+
if base_origin in (list, List): # Handle List types
|
407
|
+
if not isinstance(value, list):
|
408
|
+
return False
|
409
|
+
item_type = get_args(base_type)[0]
|
410
|
+
return all(isinstance(item, item_type) for item in value)
|
411
|
+
|
412
|
+
if base_origin in (tuple, Tuple): # Handle Tuple types
|
413
|
+
if not isinstance(value, tuple):
|
414
|
+
return False
|
415
|
+
item_types = get_args(base_type)
|
416
|
+
return len(value) == len(item_types) and all(
|
417
|
+
isinstance(item, item_type)
|
418
|
+
for item, item_type in zip(value, item_types)
|
419
|
+
)
|
420
|
+
|
421
|
+
if base_origin in (dict, Dict): # Handle Dict types
|
422
|
+
if not isinstance(value, dict):
|
423
|
+
return False
|
424
|
+
key_type, value_type = get_args(base_type)
|
425
|
+
return all(isinstance(k, key_type) and isinstance(v, value_type)
|
426
|
+
for k, v in value.items())
|
427
|
+
|
428
|
+
# todo: add support for for other typing constructs
|
429
|
+
return None # if it is not a Union or Annotated types just return None (to give an indication to the caller that the comparison was not made)
|
394
430
|
|
395
431
|
|
396
432
|
def pickle_save_to_bytes(target: object) -> bytes:
|
osbot_utils/utils/Regex.py
CHANGED
osbot_utils/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
v1.
|
1
|
+
v1.92.0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: osbot_utils
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.92.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,7 +2,7 @@ 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=
|
5
|
+
osbot_utils/base_classes/Type_Safe.py,sha256=D1SzaB3Km-UrlucCE3boL5_dbTQBDn1-s1URbehRl8w,27754
|
6
6
|
osbot_utils/base_classes/Type_Safe__Base.py,sha256=CFPYe8_i5vvTLyc7s8CXbY4n_dY6sqVfBY8w9Vo77ZA,5468
|
7
7
|
osbot_utils/base_classes/Type_Safe__Dict.py,sha256=sfZcukhXUd9TS0PQpAk-gGLfZUJSC6BtMh6jF4Fn8Jw,1107
|
8
8
|
osbot_utils/base_classes/Type_Safe__List.py,sha256=pXDzJJttpEQQ9oTdsw7BykMB4VIX2rZzi1ZrnCzMZ8M,650
|
@@ -38,7 +38,7 @@ osbot_utils/fluent/Fluent_Dict.py,sha256=nZ2z91s39sU2a-TLYpBirRoWgDXHrs0tQ9Bi_Zd
|
|
38
38
|
osbot_utils/fluent/Fluent_List.py,sha256=PfDDC9sm16CFnNQ8gkhCEsmKcZp8iyQ0YBpSHvYssG8,1089
|
39
39
|
osbot_utils/fluent/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
40
40
|
osbot_utils/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
|
-
osbot_utils/graphs/mermaid/Mermaid.py,sha256=
|
41
|
+
osbot_utils/graphs/mermaid/Mermaid.py,sha256=G7--iIKm2C1z-tEB1qLNopwoW3_w4oR7Oq7-yA460mM,3164
|
42
42
|
osbot_utils/graphs/mermaid/Mermaid__Edge.py,sha256=jwHxHJEAA49aO28T8nnJFxOfpWAZZaWKNT_krG1fwkQ,1893
|
43
43
|
osbot_utils/graphs/mermaid/Mermaid__Graph.py,sha256=FRw17efZrdcKyXDKsyb1C8nswIAmljiUAyiF0FHIL4M,2854
|
44
44
|
osbot_utils/graphs/mermaid/Mermaid__Node.py,sha256=j_AVfR3hnKAJH2Z3d17djvU7MfQP8B70Lh7Jv6y0tTs,3322
|
@@ -53,11 +53,11 @@ osbot_utils/graphs/mermaid/models/Mermaid__Node__Shape.py,sha256=_su5S8nYqg5qpXm
|
|
53
53
|
osbot_utils/graphs/mgraph/MGraph.py,sha256=1Iu2CM9IHxoJxjmABsBqDvi8l09LINLDE9_b53Dz4kM,2218
|
54
54
|
osbot_utils/graphs/mgraph/MGraph__Config.py,sha256=oFTj9eP92HolaOSyk-7tpsPVFZLMIcTH-O-mx93nTiA,204
|
55
55
|
osbot_utils/graphs/mgraph/MGraph__Data.py,sha256=oLaKmxUOXoQbhdUxa_VW_QZA0tAETgRMzP3pvslppHw,5746
|
56
|
-
osbot_utils/graphs/mgraph/MGraph__Edge.py,sha256=
|
57
|
-
osbot_utils/graphs/mgraph/MGraph__Node.py,sha256=
|
56
|
+
osbot_utils/graphs/mgraph/MGraph__Edge.py,sha256=sS9LojdWZ8AISD0d8gdY6HgcyPM956roHmEkzhM_TQg,785
|
57
|
+
osbot_utils/graphs/mgraph/MGraph__Node.py,sha256=KnUnD918hu5ifjbW_v7AVAAzxjHi5iuA4rluvDGOaPg,893
|
58
58
|
osbot_utils/graphs/mgraph/MGraph__Random_Graphs.py,sha256=MDCmnrv7QcGXJeZDYc4y5Wth6ysB0hHzn1DqfocxtIs,964
|
59
|
-
osbot_utils/graphs/mgraph/MGraph__Serializer.py,sha256=
|
60
|
-
osbot_utils/graphs/mgraph/MGraphs.py,sha256=
|
59
|
+
osbot_utils/graphs/mgraph/MGraph__Serializer.py,sha256=pLgKsJuo1x8S22m6TCLd3bvFLrBo6sTrq1AkknkOA18,1475
|
60
|
+
osbot_utils/graphs/mgraph/MGraphs.py,sha256=TIv1CEsUiPIVxll3AuGLhEVBY4YRzZu_rpEz3Tzrph8,532
|
61
61
|
osbot_utils/graphs/mgraph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
62
|
osbot_utils/helpers/CFormat.py,sha256=1_XvqGwgU6qC97MbzcKF0o7s9mCXpU5Kq9Yf-1ixUwY,6808
|
63
63
|
osbot_utils/helpers/CPrint.py,sha256=ztKPNmT8BGxeyPXSQKRs63PqqbgxKDz_BiZmzFMup9g,1413
|
@@ -77,7 +77,7 @@ osbot_utils/helpers/Str_ASCII.py,sha256=PRqyu449XnKrLn6b9Miii1Hv-GO5OAa1UhhgvlRc
|
|
77
77
|
osbot_utils/helpers/Timestamp_Now.py,sha256=k3-SUGYx2jLTXvgZYeECqPRJhVxqWPmW7co1l6r12jk,438
|
78
78
|
osbot_utils/helpers/Type_Registry.py,sha256=Ajk3SyMSKDi2g9SJYUtTgg7PZkAgydaHcpbGuEN3S94,311
|
79
79
|
osbot_utils/helpers/Type_Safe_Method.py,sha256=8E88of__An9_ZhJKz6Kp22C1mb9WLED0jWNLOII3fJs,10489
|
80
|
-
osbot_utils/helpers/Zip_Bytes.py,sha256=
|
80
|
+
osbot_utils/helpers/Zip_Bytes.py,sha256=6i6uD1TIcMZZRM8BYMseSnihRxUI2vJiai3UnPHrGrY,4290
|
81
81
|
osbot_utils/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
82
82
|
osbot_utils/helpers/ast/Ast.py,sha256=lcPQOSxXI6zgmMnIVF9WM6ISqViWX-sq4d_UC0CDG8s,1155
|
83
83
|
osbot_utils/helpers/ast/Ast_Base.py,sha256=5rHMupBlN_n6lOC31UnSW_lWqxqxaE31v0gn-t32OgQ,3708
|
@@ -210,6 +210,8 @@ osbot_utils/helpers/pubsub/schemas/Schema__Event__Leave_Room.py,sha256=cNugRz-k_
|
|
210
210
|
osbot_utils/helpers/pubsub/schemas/Schema__Event__Message.py,sha256=rt8W-DGitmR-SvmunSG8kbTH_mubE2PKMh2cJ3eJOyo,244
|
211
211
|
osbot_utils/helpers/pubsub/schemas/Schema__PubSub__Client.py,sha256=yOQSn4o1bIsEoyhnQJYen372so89Ben1wMWUO12G4h8,239
|
212
212
|
osbot_utils/helpers/pubsub/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
213
|
+
osbot_utils/helpers/python_compatibility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
214
|
+
osbot_utils/helpers/python_compatibility/python_3_8.py,sha256=kh846vs3ir8xD0RSARJBOL0xufnt3L_Td3K45lDfqng,161
|
213
215
|
osbot_utils/helpers/sqlite/Capture_Sqlite_Error.py,sha256=GSuRYgs1yKQjxMszPoaI7fsfMfuUqhb64AaIysRE6Cs,1747
|
214
216
|
osbot_utils/helpers/sqlite/Sqlite__Cursor.py,sha256=k5G9Tkk3nx6nHoSanLmpuJG_TceAmN7uRBCt0bo6sIc,3364
|
215
217
|
osbot_utils/helpers/sqlite/Sqlite__Database.py,sha256=YSuppFFiR-RcGiAXxhS-Oygw5PvdbV94wm4ybQ7cOF8,5616
|
@@ -260,7 +262,7 @@ osbot_utils/helpers/ssh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
260
262
|
osbot_utils/helpers/trace/Trace_Call.py,sha256=O_y6cncgneYrj3ARDMz-o9Yi1LjsESibUqkGFAg0Jk0,8886
|
261
263
|
osbot_utils/helpers/trace/Trace_Call__Config.py,sha256=UAjdsDEqsPBBsu-h4_QKYL4UMukJmQYBBWGYTmSKS40,3361
|
262
264
|
osbot_utils/helpers/trace/Trace_Call__Graph.py,sha256=HCrXRKQI42DIQxxyFLcaosWiOcUyoITbeV17ICdXcXM,1156
|
263
|
-
osbot_utils/helpers/trace/Trace_Call__Handler.py,sha256=
|
265
|
+
osbot_utils/helpers/trace/Trace_Call__Handler.py,sha256=9EQyIiGB6r3UURyNgEXHPOCmZNOejM0UJNW9c_MjpNU,12650
|
264
266
|
osbot_utils/helpers/trace/Trace_Call__Print_Lines.py,sha256=cy7zLv0_JNxdOIQPfZk6J9bv6AkIW6O643w0ykClXbw,4820
|
265
267
|
osbot_utils/helpers/trace/Trace_Call__Print_Traces.py,sha256=2LGeWMGP1uhSojGMmJmL3bH2B5LFIlfYEqEPNqoyKJw,8628
|
266
268
|
osbot_utils/helpers/trace/Trace_Call__Stack.py,sha256=pIvZ2yP4tymOQraUR2N5R-qlmg5QijyLxt85zmMajUs,7462
|
@@ -269,6 +271,13 @@ osbot_utils/helpers/trace/Trace_Call__Stats.py,sha256=gmiotIrOXe2ssxodzQQ56t8eGT
|
|
269
271
|
osbot_utils/helpers/trace/Trace_Call__View_Model.py,sha256=a40nn6agCEMd2ecsJ93n8vXij0omh0D69QilqwmN_ao,4545
|
270
272
|
osbot_utils/helpers/trace/Trace_Files.py,sha256=SNpAmuBlSUS9NyVocgZ5vevzqVaIqoh622yZge3a53A,978
|
271
273
|
osbot_utils/helpers/trace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
274
|
+
osbot_utils/helpers/type_safe/Type_Safe__Validator.py,sha256=cJIPSBarjV716SZUOLvz7Mthjk-aUYKUQtRDtKUBmN4,779
|
275
|
+
osbot_utils/helpers/type_safe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
276
|
+
osbot_utils/helpers/type_safe/validators/Validator__Max.py,sha256=UxrS-tDS5RqfV5aCSbgoGcKHC9VPGqeS_ACB1dqNpQw,1272
|
277
|
+
osbot_utils/helpers/type_safe/validators/Validator__Min.py,sha256=krNnQjt8XCS5smESvGsFHw2dJWmS9VKn6WXcY1xatkY,1978
|
278
|
+
osbot_utils/helpers/type_safe/validators/Validator__One_Of.py,sha256=AEknqmurAilHMOvbL6EBxAho2pz4llKPNeX0FHBPru8,678
|
279
|
+
osbot_utils/helpers/type_safe/validators/Validator__Regex.py,sha256=k6mZSDM1lISwCK-Atnuw5TTe1qx6DIf-DbzT4hL1Pu8,1005
|
280
|
+
osbot_utils/helpers/type_safe/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
272
281
|
osbot_utils/helpers/xml/Xml__Attribute.py,sha256=r29x8ehRug27KuHQKQEGl6Thz_MNaNHy6X3cPRgH4ho,148
|
273
282
|
osbot_utils/helpers/xml/Xml__Element.py,sha256=J0hMaJPIBNmZf4wo0xrtZBZd5vWie5FoX9XqtAXQToM,871
|
274
283
|
osbot_utils/helpers/xml/Xml__File.py,sha256=c3axXx07rIoK5tGzpLwH-X1B7nusSe1-SZAxJNZJ0KI,420
|
@@ -279,7 +288,7 @@ osbot_utils/helpers/xml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
279
288
|
osbot_utils/helpers/xml/rss/RSS__Channel.py,sha256=HPLsGRNIaPh0_8GYE2a53bSV5Bb4E6j6tKGuy4bxg4Y,605
|
280
289
|
osbot_utils/helpers/xml/rss/RSS__Enclosure.py,sha256=f75U7nXXJ7hLs2zPDui0WsFAmMJaeaverjlxD4M-Otg,142
|
281
290
|
osbot_utils/helpers/xml/rss/RSS__Feed.py,sha256=lhFoeBMWdH1Dp8QnagCGj9bfZHKmB_HkE56hPVZNaM0,425
|
282
|
-
osbot_utils/helpers/xml/rss/RSS__Feed__Parser.py,sha256=
|
291
|
+
osbot_utils/helpers/xml/rss/RSS__Feed__Parser.py,sha256=7sub6zcWqGz8FORXFYRyopTswboUkCU5uGEXAZ6BZDw,5380
|
283
292
|
osbot_utils/helpers/xml/rss/RSS__Image.py,sha256=4uI0jd17pqb8FJ8HQcERXvn3WjGbiOVI8u1tv-IN59U,171
|
284
293
|
osbot_utils/helpers/xml/rss/RSS__Item.py,sha256=y-QI2WBfd9FEsVWc_eNirvZUUslpb2z27hxcm-RVHJQ,611
|
285
294
|
osbot_utils/testing/Catch.py,sha256=HdNoKnrPBjvVj87XYN-Wa1zpo5z3oByURT6TKbd5QpQ,2229
|
@@ -318,11 +327,11 @@ osbot_utils/utils/Json.py,sha256=0DZGlCU7Nqte5n0r7ctPXFybqA5MRfSrTz5zuK_6UFk,709
|
|
318
327
|
osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
|
319
328
|
osbot_utils/utils/Lists.py,sha256=tPz5x5s3sRO97WZ_nsxREBPC5cwaHrhgaYBhsrffTT8,5599
|
320
329
|
osbot_utils/utils/Misc.py,sha256=H_xexJgiTxB3jDeDiW8efGQbO0Zuy8MM0iQ7qXC92JI,17363
|
321
|
-
osbot_utils/utils/Objects.py,sha256=
|
330
|
+
osbot_utils/utils/Objects.py,sha256=FVX0CyVY2g1iXIL4UA1dFzgbsA-GYrJ_cYlxosGxTdo,21713
|
322
331
|
osbot_utils/utils/Png.py,sha256=V1juGp6wkpPigMJ8HcxrPDIP4bSwu51oNkLI8YqP76Y,1172
|
323
332
|
osbot_utils/utils/Process.py,sha256=lr3CTiEkN3EiBx3ZmzYmTKlQoPdkgZBRjPulMxG-zdo,2357
|
324
333
|
osbot_utils/utils/Python_Logger.py,sha256=tx8N6wRKL3RDHboDRKZn8SirSJdSAE9cACyJkxrThZ8,12792
|
325
|
-
osbot_utils/utils/Regex.py,sha256=
|
334
|
+
osbot_utils/utils/Regex.py,sha256=MtHhk69ax7Nwu4CQZK7y4KXHZ6VREwEpIchuioB168c,960
|
326
335
|
osbot_utils/utils/Status.py,sha256=Yq4s0TelXgn0i2QjCP9V8mP30GabXp_UL-jjM6Iwiw4,4305
|
327
336
|
osbot_utils/utils/Str.py,sha256=Y05F46m6s3_H7KoPdeasc1LRaU7R4YifIbsHNQYDEeg,3275
|
328
337
|
osbot_utils/utils/Threads.py,sha256=lnh4doZWYUIoWBZRU_780QPeAIKGDh7INuqmU8Fzmdc,3042
|
@@ -330,8 +339,8 @@ osbot_utils/utils/Toml.py,sha256=Rxl8gx7mni5CvBAK-Ai02EKw-GwtJdd3yeHT2kMloik,166
|
|
330
339
|
osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
|
331
340
|
osbot_utils/utils/Zip.py,sha256=pR6sKliUY0KZXmqNzKY2frfW-YVQEVbLKiyqQX_lc-8,14052
|
332
341
|
osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
333
|
-
osbot_utils/version,sha256=
|
334
|
-
osbot_utils-1.
|
335
|
-
osbot_utils-1.
|
336
|
-
osbot_utils-1.
|
337
|
-
osbot_utils-1.
|
342
|
+
osbot_utils/version,sha256=HcQJfW9qetpTTCAJA7zp8wAdYNIGH7zJcl-GVVPylC4,8
|
343
|
+
osbot_utils-1.92.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
344
|
+
osbot_utils-1.92.0.dist-info/METADATA,sha256=BD8zNKY7lu6bJuUASVxE_R_j2ywMvZj6t32oFGOn9Sw,1317
|
345
|
+
osbot_utils-1.92.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
346
|
+
osbot_utils-1.92.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|