osbot-utils 1.13.0__py3-none-any.whl → 1.16.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.
@@ -3,10 +3,12 @@
3
3
  # the data is available in IDE's code complete
4
4
  import functools
5
5
  import inspect
6
+ import sys
6
7
  import types
8
+ import typing
7
9
  from decimal import Decimal
8
- from enum import Enum, EnumMeta, EnumType
9
- from typing import get_origin, get_args
10
+ from enum import Enum, EnumMeta
11
+ from typing import List
10
12
 
11
13
  from osbot_utils.base_classes.Type_Safe__List import Type_Safe__List
12
14
  from osbot_utils.utils.Dev import pprint
@@ -16,7 +18,30 @@ from osbot_utils.utils.Objects import default_value, value_type
16
18
  raise_exception_on_obj_type_annotation_mismatch, obj_is_attribute_annotation_of_type, enum_from_value, \
17
19
  obj_is_type_union_compatible, value_type_matches_obj_annotation_for_union_attr
18
20
 
19
- immutable_types = (bool, int, float, complex, str, tuple, frozenset, bytes, types.NoneType, EnumMeta)
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
30
+
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)
20
45
 
21
46
 
22
47
  #todo: see if we can also add type safety to method execution
@@ -115,25 +140,28 @@ class Kwargs_To_Self: # todo: check if the description below is st
115
140
  def __exit__(self, exc_type, exc_val, exc_tb): pass
116
141
 
117
142
  def __setattr__(self, name, value):
118
- # if self.__type_safety__:
143
+ if not hasattr(self, '__annotations__'): # can't do type safety checks if the class does not have annotations
144
+ return super().__setattr__(name, value)
145
+
146
+ # if self.__type_safety__:
119
147
  # if self.__lock_attributes__:
120
148
  # todo: this can't work on all, current hypothesis is that this will work for the values that are explicitly set
121
149
  # if not hasattr(self, name):
122
150
  # 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
123
151
 
124
- if value is not None:
125
- check_1 = value_type_matches_obj_annotation_for_attr(self, name, value)
126
- check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value)
127
- if (check_1 is False and check_2 is None or
128
- check_1 is None and check_2 is False or
129
- check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars
130
- raise Exception(f"Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
131
- else:
132
- if hasattr(self, name) and self.__annotations__.get(name) : # don't allow previously set variables to be set to None
133
- if getattr(self, name) is not None: # unless it is already set to None
134
- 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)}'")
152
+ if value is not None:
153
+ check_1 = value_type_matches_obj_annotation_for_attr(self, name, value)
154
+ check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value)
155
+ if (check_1 is False and check_2 is None or
156
+ check_1 is None and check_2 is False or
157
+ check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars
158
+ raise Exception(f"Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
159
+ else:
160
+ if hasattr(self, name) and self.__annotations__.get(name) : # don't allow previously set variables to be set to None
161
+ if getattr(self, name) is not None: # unless it is already set to None
162
+ 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)}'")
135
163
 
136
- super().__setattr__(name, value)
164
+ super().__setattr__(name, value)
137
165
 
138
166
  def __attr_names__(self):
139
167
  return list_set(self.__locals__())
@@ -156,30 +184,43 @@ class Kwargs_To_Self: # todo: check if the description below is st
156
184
  if (k in kwargs) is False: # do not set the value is it has already been set
157
185
  kwargs[k] = v
158
186
 
159
- for var_name, var_type in base_cls.__annotations__.items():
160
- if hasattr(base_cls, var_name) is False: # only add if it has not already been defined
161
- if var_name in kwargs:
162
- continue
163
- var_value = cls.__default__value__(var_type)
164
- kwargs[var_name] = var_value
165
- else:
166
- var_value = getattr(base_cls, var_name)
167
- if var_value is not None: # allow None assignments on ctor since that is a valid use case
168
- if var_type and not isinstance(var_value, var_type): # check type
169
- exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'"
170
- raise Exception(exception_message)
171
- 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
172
- #todo: fix type safety bug that I believe is caused here
173
- if obj_is_type_union_compatible(var_type, immutable_types) is False: # if var_type is not something like Optional[Union[int, str]]
174
- if type(var_type) not in immutable_types:
175
- 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}'"
176
- raise Exception(exception_message)
187
+ if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
188
+ for var_name, var_type in base_cls.__annotations__.items():
189
+ if hasattr(base_cls, var_name) is False: # only add if it has not already been defined
190
+ if var_name in kwargs:
191
+ continue
192
+ var_value = cls.__default__value__(var_type)
193
+ kwargs[var_name] = var_value
194
+ else:
195
+ var_value = getattr(base_cls, var_name)
196
+ if var_value is not None: # allow None assignments on ctor since that is a valid use case
197
+ if var_type and not isinstance(var_value, var_type): # check type
198
+ exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'"
199
+ raise Exception(exception_message)
200
+ 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
201
+ #todo: fix type safety bug that I believe is caused here
202
+ if obj_is_type_union_compatible(var_type, immutable_types) is False: # if var_type is not something like Optional[Union[int, str]]
203
+ if type(var_type) not in immutable_types:
204
+ 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}'"
205
+ raise Exception(exception_message)
177
206
  if include_base_classes is False:
178
207
  break
179
208
  return kwargs
180
209
 
181
210
  @classmethod
182
211
  def __default__value__(cls, var_type):
212
+ if var_type is typing.Set: # todo: refactor the dict, set and list logic, since they are 90% the same
213
+ return set()
214
+ if get_origin(var_type) is set:
215
+ return set() # todo: add Type_Safe__Set
216
+
217
+ if var_type is typing.Dict:
218
+ return {}
219
+ if get_origin(var_type) is dict:
220
+ return {} # todo: add Type_Safe__Dict
221
+
222
+ if var_type is typing.List:
223
+ return [] # handle case when List was used with no type information provided
183
224
  if get_origin(var_type) is list: # if we have list defined as list[type]
184
225
  item_type = get_args(var_type)[0] # get the type that was defined
185
226
  return Type_Safe__List(expected_type=item_type) # and used it as expected_type in Type_Safe__List
@@ -198,9 +239,10 @@ class Kwargs_To_Self: # todo: check if the description below is st
198
239
  if not isinstance(v, classmethod):
199
240
  kwargs[k] = v
200
241
  # add the vars defined with the annotations
201
- for var_name, var_type in base_cls.__annotations__.items():
202
- var_value = getattr(self, var_name)
203
- kwargs[var_name] = var_value
242
+ if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
243
+ for var_name, var_type in base_cls.__annotations__.items():
244
+ var_value = getattr(self, var_name)
245
+ kwargs[var_name] = var_value
204
246
 
205
247
  return kwargs
206
248
 
@@ -230,7 +272,9 @@ class Kwargs_To_Self: # todo: check if the description below is st
230
272
 
231
273
  @classmethod
232
274
  def __schema__(cls):
233
- return cls.__annotations__
275
+ if hasattr(cls,'__annotations__'): # can only do type safety checks if the class does not have annotations
276
+ return cls.__annotations__
277
+ return {}
234
278
 
235
279
  # global methods added to any class that base classes this
236
280
  # todo: see if there should be a prefix on these methods, to make it easier to spot them
@@ -256,8 +300,9 @@ class Kwargs_To_Self: # todo: check if the description below is st
256
300
  """Update instance attributes with values from provided keyword arguments."""
257
301
  for key, value in kwargs.items():
258
302
  if value is not None:
259
- if value_type_matches_obj_annotation_for_attr(self, key, value) is False:
260
- raise Exception(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'")
303
+ if hasattr(self,'__annotations__'): # can only do type safety checks if the class does not have annotations
304
+ if value_type_matches_obj_annotation_for_attr(self, key, value) is False:
305
+ raise Exception(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'")
261
306
  setattr(self, key, value)
262
307
  return self
263
308
 
@@ -267,12 +312,14 @@ class Kwargs_To_Self: # todo: check if the description below is st
267
312
  if hasattr(self, key) and isinstance(getattr(self, key), Kwargs_To_Self):
268
313
  getattr(self, key).deserialize_from_dict(value) # Recursive call for complex nested objects
269
314
  else:
270
- if obj_is_attribute_annotation_of_type(self, key, EnumType): # handle the case when the value is an Enum
271
- enum_type = getattr(self, '__annotations__').get(key)
272
- if type(value) is not enum_type: # if the value is not already of the target type
273
- value = enum_from_value(enum_type, value) # try to resolve the value into the enum
315
+ if hasattr(self, '__annotations__'): # can only do type safety checks if the class does not have annotations
316
+ if obj_is_attribute_annotation_of_type(self, key, EnumMeta): # Handle the case when the value is an Enum
317
+ enum_type = getattr(self, '__annotations__').get(key)
318
+ if type(value) is not enum_type: # If the value is not already of the target type
319
+ value = enum_from_value(enum_type, value) # Try to resolve the value into the enum
274
320
 
275
321
  setattr(self, key, value) # Direct assignment for primitive types and other structures
322
+
276
323
  return self
277
324
 
278
325
  def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough
@@ -295,7 +342,7 @@ def serialize_to_dict(obj):
295
342
  return obj
296
343
  elif isinstance(obj, Enum):
297
344
  return obj.name
298
- elif isinstance(obj, list):
345
+ elif isinstance(obj, list) or isinstance(obj, List):
299
346
  return [serialize_to_dict(item) for item in obj]
300
347
  elif isinstance(obj, dict):
301
348
  return {key: serialize_to_dict(value) for key, value in obj.items()}
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from osbot_utils.graphs.mgraph.MGraph__Edge import MGraph__Edge
2
4
  from osbot_utils.graphs.mgraph.MGraph__Node import MGraph__Node
3
5
  from osbot_utils.graphs.mermaid.Mermaid__Edge import Mermaid__Edge
@@ -6,9 +8,9 @@ from osbot_utils.graphs.mgraph.MGraph import MGraph
6
8
 
7
9
 
8
10
  class Mermaid__Graph(MGraph):
9
- edges : list[Mermaid__Edge]
10
- mermaid_code : list
11
- nodes : list[Mermaid__Node]
11
+ edges : List[Mermaid__Edge]
12
+ mermaid_code : List
13
+ nodes : List[Mermaid__Node]
12
14
 
13
15
  # def __init__(self, mgraph=None):
14
16
  # super().__init__()
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
4
  from osbot_utils.graphs.mermaid.configs.Mermaid__Render__Config import Mermaid__Render__Config
3
5
  from osbot_utils.graphs.mermaid.models.Mermaid__Diagram_Direction import Diagram__Direction
@@ -6,7 +8,7 @@ from osbot_utils.graphs.mermaid.models.Mermaid__Diagram__Type import Diagr
6
8
 
7
9
  class Mermaid__Renderer(Kwargs_To_Self):
8
10
  config : Mermaid__Render__Config
9
- mermaid_code : list
11
+ mermaid_code : List
10
12
  diagram_direction : Diagram__Direction = Diagram__Direction.LR
11
13
  diagram_type : Diagram__Type = Diagram__Type.graph
12
14
 
@@ -1,17 +1,16 @@
1
- from enum import auto, Enum
2
-
1
+ from enum import Enum
3
2
 
4
3
  class Diagram__Type(Enum):
5
- class_diagram = auto()
6
- entity_relationship_diagram = auto()
7
- flowchart = auto()
8
- gantt = auto()
9
- git_graph = auto()
10
- graph = auto()
11
- mermaid_map = auto()
12
- mindmap = auto()
13
- pie_chart = auto()
14
- requirement_diagram = auto()
15
- sequence_diagram = "sequenceDiagram"
16
- state_diagram = 'stateDiagram-v2'
17
- user_journey = auto()
4
+ class_diagram = "class_diagram"
5
+ entity_relationship_diagram = "entity_relationship_diagram"
6
+ flowchart = "flowchart"
7
+ gantt = "gantt"
8
+ git_graph = "git_graph"
9
+ graph = "graph"
10
+ mermaid_map = "mermaid_map"
11
+ mindmap = "mindmap"
12
+ pie_chart = "pie_chart"
13
+ requirement_diagram = "requirement_diagram"
14
+ sequence_diagram = "sequenceDiagram" # these are different from the others
15
+ state_diagram = "stateDiagram-v2" # these are different from the others
16
+ user_journey = "user_journey"
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from osbot_utils.utils.Misc import random_text, lower
2
4
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
3
5
  from osbot_utils.graphs.mgraph.MGraph__Config import MGraph__Config
@@ -8,9 +10,9 @@ from osbot_utils.graphs.mgraph.MGraph__Node import MGraph__Node
8
10
  # todo add support for storing the data in sqlite so that we get the ability to search nodes and edges
9
11
  class MGraph(Kwargs_To_Self):
10
12
  config : MGraph__Config
11
- edges : list[MGraph__Edge]
13
+ edges : List[MGraph__Edge]
12
14
  key : str
13
- nodes : list[MGraph__Node]
15
+ nodes : List[MGraph__Node]
14
16
 
15
17
 
16
18
  def __init__(self, **kwargs):
@@ -11,9 +11,12 @@ class Ast:
11
11
  pass
12
12
 
13
13
  def source_code__from(self, target):
14
- source_raw = inspect.getsource(target)
15
- source = str_dedent(source_raw) # remove any training spaces or it won't compile
16
- return source
14
+ try:
15
+ source_raw = inspect.getsource(target)
16
+ source = str_dedent(source_raw) # remove any training spaces or it won't compile
17
+ return source
18
+ except:
19
+ return None
17
20
 
18
21
  def ast_module__from(self, target):
19
22
  source_code = self.source_code__from(target)
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from osbot_utils.helpers.html.Tag__Base import Tag__Base
2
4
  from osbot_utils.helpers.html.Tag__Link import Tag__Link
3
5
  from osbot_utils.helpers.html.Tag__Style import Tag__Style
@@ -6,8 +8,8 @@ from osbot_utils.utils.Dev import pprint
6
8
 
7
9
  class Tag__Head(Tag__Base):
8
10
  title : str
9
- links : list[Tag__Link]
10
- meta : list[Tag__Base]
11
+ links : List[Tag__Link]
12
+ meta : List[Tag__Base]
11
13
  style : Tag__Style
12
14
 
13
15
  def __init__(self, **kwargs):
@@ -1,4 +1,5 @@
1
1
  from queue import Queue
2
+ from typing import List
2
3
 
3
4
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
4
5
  from osbot_utils.helpers.pubsub.Event__Queue import Event__Queue
@@ -13,7 +14,7 @@ from osbot_utils.utils.Misc import random_guid
13
14
  class PubSub__Client(Kwargs_To_Self):
14
15
  event_queue : Event__Queue
15
16
  client_id : str
16
- received_messages : list[str] # todo: fix this to be Events/Messages received via event_queue
17
+ received_messages : List[str] # todo: fix this to be Events/Messages received via event_queue
17
18
 
18
19
  def __init__(self, **kwargs):
19
20
  self.client_id = kwargs.get('client_id') or random_guid()
@@ -1,3 +1,5 @@
1
+ from typing import Set
2
+
1
3
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
4
  from osbot_utils.helpers.pubsub.Event__Queue import Event__Queue
3
5
  from osbot_utils.helpers.pubsub.PubSub__Client import PubSub__Client
@@ -6,7 +8,7 @@ from osbot_utils.helpers.pubsub.PubSub__Client import PubSub__Client
6
8
  class PubSub__Room(Kwargs_To_Self):
7
9
  event_queue: Event__Queue
8
10
  room_name : str
9
- clients : set[PubSub__Client]
11
+ clients : Set[PubSub__Client]
10
12
 
11
13
  def send_to_clients__message(self, message):
12
14
  for client in self.clients:
@@ -1,6 +1,7 @@
1
1
  import queue
2
2
  from threading import Thread
3
3
  from queue import Queue
4
+ from typing import Set, Dict
4
5
 
5
6
  from osbot_utils.helpers.pubsub.Event__Queue import Event__Queue
6
7
  from osbot_utils.helpers.pubsub.PubSub__Client import PubSub__Client
@@ -17,9 +18,9 @@ from osbot_utils.utils.Dev import pprin
17
18
 
18
19
  class PubSub__Server(Event__Queue):
19
20
  #pubsub_db: PubSub__Sqlite
20
- clients : dict
21
- clients_connected: set[PubSub__Client]
22
- rooms : dict[str, PubSub__Room]
21
+ clients : Dict
22
+ clients_connected: Set[PubSub__Client]
23
+ rooms : Dict[str, PubSub__Room]
23
24
  logging : Logging
24
25
 
25
26
  def __init__ (self):
@@ -1,4 +1,6 @@
1
1
  import inspect
2
+ from typing import List
3
+
2
4
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
3
5
  from osbot_utils.decorators.lists.filter_list import filter_list
4
6
  from osbot_utils.helpers.sqlite.Sqlite__Field import Sqlite__Field
@@ -6,7 +8,7 @@ from osbot_utils.helpers.sqlite.Sqlite__Table import Sqlite__Table
6
8
  from osbot_utils.helpers.sqlite.models.Sqlite__Field__Type import Sqlite__Field__Type
7
9
 
8
10
  class Sqlite__Table__Create(Kwargs_To_Self):
9
- fields : list[Sqlite__Field]
11
+ fields : List[Sqlite__Field]
10
12
  table : Sqlite__Table
11
13
 
12
14
  def __init__(self, table_name):
@@ -21,7 +23,7 @@ class Sqlite__Table__Create(Kwargs_To_Self):
21
23
  return True
22
24
  return False
23
25
 
24
- def add_fields(self, fields_data:list[dict]):
26
+ def add_fields(self, fields_data:List[dict]):
25
27
  results = []
26
28
  if fields_data:
27
29
  for field_data in fields_data:
@@ -1,9 +1,14 @@
1
+ import sys
2
+ import types
1
3
  from decimal import Decimal
2
4
  from enum import Enum, auto
3
- from types import NoneType
4
-
5
5
  from osbot_utils.decorators.methods.cache import cache
6
6
 
7
+ if sys.version_info >= (3, 10):
8
+ NoneType = types.NoneType
9
+ else:
10
+ NoneType = type(None)
11
+
7
12
 
8
13
  class Sqlite__Field__Type(Enum):
9
14
  BOOLEAN = bool
@@ -26,8 +26,8 @@ class Profiler:
26
26
  item[arg_name] = self.add_values(option, value)
27
27
  else:
28
28
  if profile_options.get(arg_name):
29
- if arg_name == 'f_locals':
30
- item[arg_name] = value.copy() # create a copy of the var
29
+ if arg_name == 'f_locals' and sys.version_info < (3, 13): # todo: figure out why in 3.13 the value.copy doesn't work
30
+ item[arg_name] = value.copy() # create a copy of the var
31
31
  else:
32
32
  item[arg_name] = value
33
33
  return item
osbot_utils/utils/Env.py CHANGED
@@ -1,10 +1,23 @@
1
1
  # In Misc.py
2
2
  import os
3
+ from sys import platform
3
4
 
5
+ from osbot_utils.utils.Dev import pprint
4
6
  from osbot_utils.utils.Files import all_parent_folders
5
7
  from osbot_utils.utils.Misc import list_set
6
8
  from osbot_utils.utils.Str import strip_quotes
7
9
 
10
+ def env__home_root():
11
+ return os.getenv('HOME') == '/root'
12
+
13
+ def env__terminal_xterm():
14
+ return os.getenv('TERM') == 'xterm'
15
+
16
+ def env__not_terminal_xterm():
17
+ return not env__terminal_xterm()
18
+
19
+ def platform_darwin():
20
+ return platform == 'darwin'
8
21
 
9
22
  def env_value(var_name):
10
23
  return env_vars().get(var_name, None)
osbot_utils/utils/Int.py CHANGED
@@ -1,4 +1,3 @@
1
- @staticmethod
2
1
  def int_is_even(number):
3
2
  return number % 2 == 0
4
3
 
osbot_utils/utils/Misc.py CHANGED
@@ -10,12 +10,19 @@ import textwrap
10
10
  import re
11
11
  import uuid
12
12
  import warnings
13
- from datetime import datetime, timedelta, UTC
13
+ from datetime import datetime, timedelta
14
14
  from secrets import token_bytes
15
15
  from time import sleep
16
16
  from typing import Iterable
17
17
  from urllib.parse import quote_plus, unquote_plus
18
18
 
19
+
20
+ if sys.version_info >= (3, 11):
21
+ from datetime import UTC
22
+ else:
23
+ from datetime import timezone # For versions before 3.11, we need to use a different method or library to handle UTC
24
+ UTC = timezone.utc
25
+
19
26
  def ansi_text_visible_length(text):
20
27
  ansi_escape = re.compile(r'\x1b\[[0-9;]*m') # This regex matches the escape sequences used for text formatting
21
28
  visible_text = ansi_escape.sub('', text) # Remove the escape sequences
@@ -4,12 +4,33 @@ import io
4
4
  import json
5
5
  import os
6
6
  import pickle
7
+ import sys
7
8
  import types
8
- from typing import get_origin, Union, get_args
9
+ import typing
10
+ from typing import Union
9
11
 
10
12
  from osbot_utils.utils.Misc import list_set
11
13
  from osbot_utils.utils.Str import str_unicode_escape, str_max_width
12
14
 
15
+ # Backport implementations of get_origin and get_args for Python 3.7
16
+ if sys.version_info < (3, 8):
17
+ def get_origin(tp):
18
+ if isinstance(tp, typing._GenericAlias):
19
+ return tp.__origin__
20
+ elif tp is typing.Generic:
21
+ return typing.Generic
22
+ else:
23
+ return None
24
+
25
+ def get_args(tp):
26
+ if isinstance(tp, typing._GenericAlias):
27
+ return tp.__args__
28
+ else:
29
+ return ()
30
+ else:
31
+ from typing import get_origin, get_args
32
+
33
+
13
34
  def are_types_compatible_for_assigment(source_type, target_type):
14
35
  if source_type is target_type:
15
36
  return True
osbot_utils/version CHANGED
@@ -1 +1 @@
1
- v1.13.0
1
+ v1.16.0
@@ -1,14 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osbot_utils
3
- Version: 1.13.0
3
+ Version: 1.16.0
4
4
  Summary: OWASP Security Bot - Utils
5
5
  Home-page: https://github.com/owasp-sbot/OSBot-Utils
6
6
  License: MIT
7
7
  Author: Dinis Cruz
8
8
  Author-email: dinis.cruz@owasp.org
9
- Requires-Python: >=3.11,<4.0
9
+ Requires-Python: >=3.7,<4.0
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
12
16
  Classifier: Programming Language :: Python :: 3.11
13
17
  Classifier: Programming Language :: Python :: 3.12
14
18
  Project-URL: Repository, https://github.com/owasp-sbot/OSBot-Utils
@@ -18,7 +22,7 @@ Description-Content-Type: text/markdown
18
22
 
19
23
  Powerful Python util methods and classes that simplify common apis and tasks.
20
24
 
21
- ![Current Release](https://img.shields.io/badge/release-v1.13.0-blue)
25
+ ![Current Release](https://img.shields.io/badge/release-v1.16.0-blue)
22
26
  [![codecov](https://codecov.io/gh/owasp-sbot/OSBot-Utils/graph/badge.svg?token=GNVW0COX1N)](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
23
27
 
24
28
 
@@ -1,7 +1,7 @@
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=7L-DT7BPw_hCNnIypmoPw6PEwoLXkTycYNuOe1YMUBU,17741
4
+ osbot_utils/base_classes/Kwargs_To_Self.py,sha256=mkfnvlKVpNW1fI8pIIkrpP9w7eA7J6giRSAyW4HXWFY,19864
5
5
  osbot_utils/base_classes/Type_Safe__List.py,sha256=-80C9OhsK6iDR2dAG8yNLAZV0qg5x3faqvSUigFCMJw,517
6
6
  osbot_utils/base_classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  osbot_utils/context_managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -33,17 +33,17 @@ osbot_utils/fluent/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVG
33
33
  osbot_utils/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  osbot_utils/graphs/mermaid/Mermaid.py,sha256=iUFZdLAMRC4DjvqdV5RTmoi1eb2SQlGRAa4dIn3URCo,3068
35
35
  osbot_utils/graphs/mermaid/Mermaid__Edge.py,sha256=jwHxHJEAA49aO28T8nnJFxOfpWAZZaWKNT_krG1fwkQ,1893
36
- osbot_utils/graphs/mermaid/Mermaid__Graph.py,sha256=VoplH93NuM3npYy1zQBBWsjFFdUeu3A7q2uU_DipFIs,2829
36
+ osbot_utils/graphs/mermaid/Mermaid__Graph.py,sha256=FRw17efZrdcKyXDKsyb1C8nswIAmljiUAyiF0FHIL4M,2854
37
37
  osbot_utils/graphs/mermaid/Mermaid__Node.py,sha256=j_AVfR3hnKAJH2Z3d17djvU7MfQP8B70Lh7Jv6y0tTs,3322
38
- osbot_utils/graphs/mermaid/Mermaid__Renderer.py,sha256=b1UB2aMb-gZAiiF7FrgO0V-1OxGN5Sq3vVUpupDhWiA,2264
38
+ osbot_utils/graphs/mermaid/Mermaid__Renderer.py,sha256=-5h_Xkaq3boKVWzPYPG_kUoe0SOlR0Tm4zVEXJ70wUk,2289
39
39
  osbot_utils/graphs/mermaid/configs/Mermaid__Edge__Config.py,sha256=PaypnkaDQQhNBIxZqnAB5bxuJAZWh3SQtmHUPfIcDCQ,212
40
40
  osbot_utils/graphs/mermaid/configs/Mermaid__Node__Config.py,sha256=KZWd_uiIm-QIz_wPSDfXfWAWU_A2yxzMeq-h5wldVjQ,465
41
41
  osbot_utils/graphs/mermaid/configs/Mermaid__Render__Config.py,sha256=JGQR6kdiTQh45zFZYvqdEDMghFIWLnVYMZsdukYtGyw,216
42
42
  osbot_utils/graphs/mermaid/examples/Mermaid_Examples__FlowChart.py,sha256=BFlQ1C-DkmVxSWJKUQC8lFVxu26znHvgaXy50QkYVZY,2220
43
43
  osbot_utils/graphs/mermaid/models/Mermaid__Diagram_Direction.py,sha256=m52zJ3bkHuF-Fq29CK3yAlX9RFi5_VQwqQGKaT1XRR4,141
44
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py,sha256=2oMKPZmOfXZ-ZlqR8dayI0f3O0L1XzEwgsqf2EMfV6s,624
44
+ osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py,sha256=QWPjpZ8Y77K3l8v481TK_UzbkxmSkIHDfOxc-P405TM,799
45
45
  osbot_utils/graphs/mermaid/models/Mermaid__Node__Shape.py,sha256=_su5S8nYqg5qpXmWvKFZ05ZT5zLjTZP3sVIhc2hNpgg,1813
46
- osbot_utils/graphs/mgraph/MGraph.py,sha256=GP5Ml_3b0It3SEGodWXQHi4kFCpUhpqV8qgHN0-x6OE,2193
46
+ osbot_utils/graphs/mgraph/MGraph.py,sha256=1Iu2CM9IHxoJxjmABsBqDvi8l09LINLDE9_b53Dz4kM,2218
47
47
  osbot_utils/graphs/mgraph/MGraph__Config.py,sha256=oFTj9eP92HolaOSyk-7tpsPVFZLMIcTH-O-mx93nTiA,204
48
48
  osbot_utils/graphs/mgraph/MGraph__Data.py,sha256=oLaKmxUOXoQbhdUxa_VW_QZA0tAETgRMzP3pvslppHw,5746
49
49
  osbot_utils/graphs/mgraph/MGraph__Edge.py,sha256=TnYUk6-YFpB3_6zY-CY6jJFVSLciGbCxE-fuABw9LX4,916
@@ -63,7 +63,7 @@ osbot_utils/helpers/SCP.py,sha256=fK1xu5mk_axsI7MU2-WIXEjdYetWkUBDb33p6oALgaI,26
63
63
  osbot_utils/helpers/SSH.py,sha256=ou0ZzDXDDVHrDOag-7sD4ZgN00VuO7QwCwinMdOq8lg,6151
64
64
  osbot_utils/helpers/Type_Registry.py,sha256=Ajk3SyMSKDi2g9SJYUtTgg7PZkAgydaHcpbGuEN3S94,311
65
65
  osbot_utils/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
- osbot_utils/helpers/ast/Ast.py,sha256=QbY7HSGzRx7FxHJoo5rDQwEWBRDt6Xsg9gwSeloeaas,1090
66
+ osbot_utils/helpers/ast/Ast.py,sha256=lcPQOSxXI6zgmMnIVF9WM6ISqViWX-sq4d_UC0CDG8s,1155
67
67
  osbot_utils/helpers/ast/Ast_Base.py,sha256=5rHMupBlN_n6lOC31UnSW_lWqxqxaE31v0gn-t32OgQ,3708
68
68
  osbot_utils/helpers/ast/Ast_Data.py,sha256=EL-6lkNDMe_BKBwFP1sJW-Y8t6TXYYv0uGdlxix4Tyg,697
69
69
  osbot_utils/helpers/ast/Ast_Load.py,sha256=ZO2QjhQ13j1m4MwAVbbdAoQ4UoUfzuU6E_RobkhZVpI,2100
@@ -149,15 +149,15 @@ osbot_utils/helpers/html/Tag__Body.py,sha256=U3UNVs1mNBsJ_kG_-OtwYPi-YzFb4Y2w4vI
149
149
  osbot_utils/helpers/html/Tag__Div.py,sha256=so47ldnc8nDGZAlZWp2BtfDxdhwzmKjCW8ca2MUbPDE,109
150
150
  osbot_utils/helpers/html/Tag__H.py,sha256=Z6PD5Uf5wlXmY7-7Xi5bv3Z_EykqxH4wt1Y_U2Sg5HY,295
151
151
  osbot_utils/helpers/html/Tag__HR.py,sha256=Z9YkBbKxas1Q4Tuw_xdpn3FQmkwAPCippilqCgXFhxA,107
152
- osbot_utils/helpers/html/Tag__Head.py,sha256=TIw8Vw2LxJ8PC1HB1No7usNLNFfuhcx1aSwER5dLTvE,1102
152
+ osbot_utils/helpers/html/Tag__Head.py,sha256=uMuxtfkLkxgTMFVUPIWxvu1VwMyGkE7AXylDZJJHElg,1127
153
153
  osbot_utils/helpers/html/Tag__Html.py,sha256=W_QFUF27vpR109g9rpca0zoQis1wkMjGuAty2mwyhH0,1131
154
154
  osbot_utils/helpers/html/Tag__Link.py,sha256=rQ-gZN8EkSv5x1S-smdjvFflwMQHACHQXiOdx0MFke8,437
155
155
  osbot_utils/helpers/html/Tag__Style.py,sha256=LPPlIN7GyMvfCUlbs2eXVMUr9jS0PX5M94A5Ig_jXIs,846
156
156
  osbot_utils/helpers/html/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
157
157
  osbot_utils/helpers/pubsub/Event__Queue.py,sha256=51QO-3bnyrzYa7cMOk5rZP1Pwk1Wne_HtqeQ0oFMHl0,3187
158
- osbot_utils/helpers/pubsub/PubSub__Client.py,sha256=xl_72L7Y7asCHgOaoV4jJBaQyRzCwKLuZlz_VBxvHKU,2292
159
- osbot_utils/helpers/pubsub/PubSub__Room.py,sha256=g--Up1d0LQx65umWlvEOJ57LqugTzO3C5V_VUALiY_Q,457
160
- osbot_utils/helpers/pubsub/PubSub__Server.py,sha256=h-xQmZnhJryyJtTG8JIUxZPuRMuQFBl2agVKorXHg4U,3519
158
+ osbot_utils/helpers/pubsub/PubSub__Client.py,sha256=6K3l4H-Tc0DhktrxpYzLVur1uZ532pQsHWprLNRXFJE,2316
159
+ osbot_utils/helpers/pubsub/PubSub__Room.py,sha256=3drJIAVSAzXB_0Q9dXxwhYWxNaEUaMiWiXLY9Mh02DM,481
160
+ osbot_utils/helpers/pubsub/PubSub__Server.py,sha256=DjQ6PsNGmULdFodspdNktADcoIN3FbdvAitE52m89-w,3548
161
161
  osbot_utils/helpers/pubsub/PubSub__Sqlite.py,sha256=3u1Gbpq22JDgDEexdyMzGvkQAHe97l8_Kuhr_SHFAR8,868
162
162
  osbot_utils/helpers/pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
163
163
  osbot_utils/helpers/pubsub/schemas/Schema__Event.py,sha256=si12mqRRROI3HYGvjQvzPZ_NGw3TpmVZxGhzuWq5cus,402
@@ -174,7 +174,7 @@ osbot_utils/helpers/sqlite/Sqlite__Database.py,sha256=XcyZ7sqXQeV0dAyERiE3kYFeMu
174
174
  osbot_utils/helpers/sqlite/Sqlite__Field.py,sha256=oBWglAOKN0EWVtaRwiQFxmR1FQ61lQ35yKkWXjSiihs,2911
175
175
  osbot_utils/helpers/sqlite/Sqlite__Globals.py,sha256=aP6uIy_y4xzl2soTUCFIJRjsb8JyfxfL6qIEZKIWy_4,230
176
176
  osbot_utils/helpers/sqlite/Sqlite__Table.py,sha256=FsFSolFN2N5V8DfZPp4gZL9xmCXaOhmG38wQmgRrvp8,15145
177
- osbot_utils/helpers/sqlite/Sqlite__Table__Create.py,sha256=3Z6erCsfuVWU9AjeaXsIZQbM-a0_dKrzEOksp1H3ZHo,3825
177
+ osbot_utils/helpers/sqlite/Sqlite__Table__Create.py,sha256=dkzv-H4WCu2yt9Hx7ANuK0dhzWcpbs-Sb4GNKhRTTEo,3850
178
178
  osbot_utils/helpers/sqlite/Temp_Sqlite__Database__Disk.py,sha256=-BhK0_3lSJPWDt-QS8IQDfrw2K_drjtn-yXyf9GbER4,520
179
179
  osbot_utils/helpers/sqlite/Temp_Sqlite__Table.py,sha256=Na2dOHlYSVUKK7STHwdzBYbQgvW2a6cEhUnlC0-T8wk,729
180
180
  osbot_utils/helpers/sqlite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -188,7 +188,7 @@ osbot_utils/helpers/sqlite/domains/Sqlite__DB__Requests.py,sha256=8Txl9kX1kHMTEp
188
188
  osbot_utils/helpers/sqlite/domains/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
189
  osbot_utils/helpers/sqlite/domains/schemas/Schema__Table__Requests.py,sha256=dewvm_GZhGT5gfX6pVK55wVF_7mtSik2IUHus6bNS6E,443
190
190
  osbot_utils/helpers/sqlite/domains/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
191
- osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py,sha256=jFdiwpOPe0Du4SXhDvklpm0iGj9Ieu4H2sVw7lRdbRM,1023
191
+ osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py,sha256=4xsnRLrU13ZT1GisVQZYFn3m7sw6DbY6pg3cg3Ug2cI,1114
192
192
  osbot_utils/helpers/sqlite/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
193
193
  osbot_utils/helpers/sqlite/sample_data/Sqlite__Sample_Data__Chinook.py,sha256=Y_2tZuW0il_iOktmztszg6T6HlOnFCY5xJR8xub77cQ,5415
194
194
  osbot_utils/helpers/sqlite/sample_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -219,7 +219,7 @@ osbot_utils/testing/Log_To_Queue.py,sha256=pZQ7I1ne-H365a4WLS60oAD-B16pxIZO4suvC
219
219
  osbot_utils/testing/Log_To_String.py,sha256=hkjWsJfV68uqgX9nvVqUN3mVPxZQDb-6UBwsSEbQnkA,1216
220
220
  osbot_utils/testing/Logging.py,sha256=aOCQGaoKTPAJt3MX2DJoSs-a7UK0KOLqnzn4GRkPFCY,3010
221
221
  osbot_utils/testing/Patch_Print.py,sha256=RfY4HGIM06dF6JTKibnN1Uco8YL05xPWe1jS4MUiWV0,1566
222
- osbot_utils/testing/Profiler.py,sha256=K2KjIQVmrKW1bp6vwm90PO2rzFbjWaAQ_m1kDcUXo4M,3255
222
+ osbot_utils/testing/Profiler.py,sha256=4em6Lpp0ONRDoDDCZsc_CdAOi_QolKOp4eA7KHN96e4,3365
223
223
  osbot_utils/testing/Stderr.py,sha256=wi1gfjpsxnBK3aOl2jzCTWI-0En1HtPgEin97148_MQ,459
224
224
  osbot_utils/testing/Stdout.py,sha256=XQ9OlOW1aHXY1TiNu8O5b75RoDnoaX5RyMHml3OjlKw,459
225
225
  osbot_utils/testing/Temp_File.py,sha256=gL1BJ6aRNg2o4LeUJ4f3bxKc75iw1qp0WKP9T3rS_WU,1435
@@ -235,17 +235,17 @@ osbot_utils/utils/Assert.py,sha256=u9XLgYn91QvNWZGyPi29SjPJSXRHlm9andIn3NJEVog,1
235
235
  osbot_utils/utils/Call_Stack.py,sha256=MAq_0vMxnbeLfCe9qQz7GwJYaOuXpt3qtQwN6wiXsU0,6595
236
236
  osbot_utils/utils/Csv.py,sha256=oHLVpjRJqrLMz9lubMCNEoThXWju5rNTprcwHc1zq2c,1012
237
237
  osbot_utils/utils/Dev.py,sha256=HibpQutYy_iG8gGV8g1GztxNN4l29E4Bi7UZaVL6-L8,1203
238
- osbot_utils/utils/Env.py,sha256=J_Viw_Lt7a845WUK9u7WD1_YAgtjR31EIG9OEljHrgg,3739
238
+ osbot_utils/utils/Env.py,sha256=BF9whkLIIBEQpx5Ww0bhRau55UhwV3SJT_z9DUoT3UY,4060
239
239
  osbot_utils/utils/Exceptions.py,sha256=KyOUHkXQ_6jDTq04Xm261dbEZuRidtsM4dgzNwSG8-8,389
240
240
  osbot_utils/utils/Files.py,sha256=HRMdDq1ZctDfCkIfuZGgQ3fG6O03qJEFEIkb8HTYzfU,19407
241
241
  osbot_utils/utils/Functions.py,sha256=0E6alPJ0fJpBiJgFOWooCOi265wSRyxxXAJ5CELBnso,3498
242
242
  osbot_utils/utils/Http.py,sha256=Z8V149M2HDrKBoXkDD5EXgqTGx6vQoUqXugXK__wcuw,4572
243
- osbot_utils/utils/Int.py,sha256=oDWQmop18IFfYVa2drz4qA5LxlL8T5Isjjo3fFN_vBw,130
243
+ osbot_utils/utils/Int.py,sha256=PmlUdU4lSwf4gJdmTVdqclulkEp7KPCVUDO6AcISMF4,116
244
244
  osbot_utils/utils/Json.py,sha256=AfOXYBxAGzqwaNeHZ-aC-RpurNmHzPxGoym7MMY8UN4,6366
245
245
  osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
246
246
  osbot_utils/utils/Lists.py,sha256=CLEjgZwAixJAFlubWEKjnUUhUN85oqvR7UqExVW7rdY,5502
247
- osbot_utils/utils/Misc.py,sha256=R7BMg4XcQ_83PntNP4mNylkpLjoIwMnsswllaDJVbbY,16486
248
- osbot_utils/utils/Objects.py,sha256=bsbuYniXM5ztEEY2xBcZfIpiM1qT5Rlsd0ZSeikx9Uw,13361
247
+ osbot_utils/utils/Misc.py,sha256=OFbDe5NrUNNo8bneRnIwlfWSo-gwaYMP5L1eBTDro2s,16716
248
+ osbot_utils/utils/Objects.py,sha256=qI6EVvx7kzXQrVGzsDk8txJkIXjsy7m2EEPZnYGy5R0,13867
249
249
  osbot_utils/utils/Png.py,sha256=V1juGp6wkpPigMJ8HcxrPDIP4bSwu51oNkLI8YqP76Y,1172
250
250
  osbot_utils/utils/Process.py,sha256=lr3CTiEkN3EiBx3ZmzYmTKlQoPdkgZBRjPulMxG-zdo,2357
251
251
  osbot_utils/utils/Python_Logger.py,sha256=z_20FlFdaLoUgJ0cmqXnyA7icgFIpBz8Ej1_xVKWsWM,11245
@@ -254,8 +254,8 @@ osbot_utils/utils/Str.py,sha256=gcStFbB57ol74ASIr00pWImpd6ObBr5BLpSB0VUKNTE,1749
254
254
  osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
255
255
  osbot_utils/utils/Zip.py,sha256=YFahdBguVK71mLdYy4m7mqVAQ5al-60QnTmYK-txCfY,6784
256
256
  osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
257
- osbot_utils/version,sha256=rUUdM3JyA9c1QRTbuda9dilaTWxvsSHNvG8GnX2tEQw,8
258
- osbot_utils-1.13.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
259
- osbot_utils-1.13.0.dist-info/METADATA,sha256=mnNYG7ohU7BXICZbanuZR2R8oFllk1lm3P_Df3mygt0,1066
260
- osbot_utils-1.13.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
261
- osbot_utils-1.13.0.dist-info/RECORD,,
257
+ osbot_utils/version,sha256=jvbddWjj2MWVhPslt752obpXw4GowDhQ0JZVafGNCGw,8
258
+ osbot_utils-1.16.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
259
+ osbot_utils-1.16.0.dist-info/METADATA,sha256=qtnmw5q9rRrRbYFVVWW4D1IcD0162dKKsQp_vO5YQaM,1266
260
+ osbot_utils-1.16.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
261
+ osbot_utils-1.16.0.dist-info/RECORD,,