osbot-utils 1.11.0__py3-none-any.whl → 1.15.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.
Files changed (33) hide show
  1. osbot_utils/base_classes/Kwargs_To_Self.py +92 -45
  2. osbot_utils/graphs/mermaid/Mermaid__Graph.py +5 -3
  3. osbot_utils/graphs/mermaid/Mermaid__Renderer.py +3 -1
  4. osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py +14 -15
  5. osbot_utils/graphs/mgraph/MGraph.py +4 -2
  6. osbot_utils/helpers/ast/Ast.py +6 -3
  7. osbot_utils/helpers/html/Tag__Head.py +4 -2
  8. osbot_utils/helpers/pubsub/PubSub__Client.py +2 -1
  9. osbot_utils/helpers/pubsub/PubSub__Room.py +3 -1
  10. osbot_utils/helpers/pubsub/PubSub__Server.py +4 -3
  11. osbot_utils/helpers/sqlite/Sqlite__Cursor.py +4 -1
  12. osbot_utils/helpers/sqlite/Sqlite__Database.py +4 -1
  13. osbot_utils/helpers/sqlite/Sqlite__Table.py +10 -0
  14. osbot_utils/helpers/sqlite/Sqlite__Table__Create.py +4 -2
  15. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py +24 -2
  16. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +2 -1
  17. osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py +7 -2
  18. osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py +48 -7
  19. osbot_utils/testing/Profiler.py +2 -2
  20. osbot_utils/testing/Temp_File.py +4 -4
  21. osbot_utils/utils/Env.py +89 -0
  22. osbot_utils/utils/Files.py +48 -3
  23. osbot_utils/utils/Int.py +0 -1
  24. osbot_utils/utils/Misc.py +8 -1
  25. osbot_utils/utils/Objects.py +22 -21
  26. osbot_utils/utils/Str.py +8 -2
  27. osbot_utils/utils/Zip.py +44 -1
  28. osbot_utils/utils/__init__.py +0 -16
  29. osbot_utils/version +1 -1
  30. {osbot_utils-1.11.0.dist-info → osbot_utils-1.15.0.dist-info}/METADATA +8 -5
  31. {osbot_utils-1.11.0.dist-info → osbot_utils-1.15.0.dist-info}/RECORD +33 -32
  32. {osbot_utils-1.11.0.dist-info → osbot_utils-1.15.0.dist-info}/LICENSE +0 -0
  33. {osbot_utils-1.11.0.dist-info → osbot_utils-1.15.0.dist-info}/WHEEL +0 -0
@@ -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):
@@ -84,4 +84,7 @@ class Sqlite__Cursor(Kwargs_To_Self):
84
84
  self.execute(sql_query)
85
85
  all_rows = self.cursor().fetchall()
86
86
  all_values = [cell.get(cell_name) for cell in all_rows]
87
- return all_values
87
+ return all_values
88
+
89
+ def vacuum(self):
90
+ return self.execute("VACUUM")
@@ -130,8 +130,11 @@ class Sqlite__Database(Kwargs_To_Self):
130
130
  table_names.append('sqlite_master')
131
131
  return table_names
132
132
 
133
+ def purge_database(self): # this fells like a better name than vacuum :)
134
+ return self.vacuum()
133
135
 
134
-
136
+ def vacuum(self):
137
+ return self.cursor().vacuum()
135
138
 
136
139
 
137
140
 
@@ -130,6 +130,7 @@ class Sqlite__Table(Kwargs_To_Self):
130
130
  rows = table_sqlite_master.cursor().execute__fetch_all(sql_query, params)
131
131
  return table_sqlite_master.list_of_field_name_from_rows(rows, field_name)
132
132
 
133
+
133
134
  def new_row_obj(self, row_data=None):
134
135
  if self.row_schema:
135
136
  new_obj = self.row_schema()
@@ -154,6 +155,7 @@ class Sqlite__Table(Kwargs_To_Self):
154
155
  picked_row_data[field_name] = field_value
155
156
  return picked_row_data
156
157
  return row_data
158
+
157
159
  def parse_row(self, row):
158
160
  if row and self.auto_pickle_blob:
159
161
  fields = self.fields__cached()
@@ -169,6 +171,14 @@ class Sqlite__Table(Kwargs_To_Self):
169
171
  def print(self, **kwargs):
170
172
  return Print_Table(**kwargs).print(self.rows())
171
173
 
174
+ def row(self, where, fields=None):
175
+ if fields is None:
176
+ return self.select_row_where(**where)
177
+
178
+ sql_query, params = self.sql_builder(limit=1).query_select_fields_with_conditions(fields, where)
179
+ row = self.cursor().execute__fetch_one(sql_query, params)
180
+ return self.parse_row(row)
181
+
172
182
  def row_add(self, row_obj=None):
173
183
  invalid_reason = self.sql_builder().validate_row_obj(row_obj)
174
184
  if invalid_reason:
@@ -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,3 +1,4 @@
1
+ from osbot_utils.decorators.lists.index_by import index_by
1
2
  from osbot_utils.decorators.methods.cache_on_self import cache_on_self
2
3
  from osbot_utils.helpers.sqlite.domains.Sqlite__DB__Local import Sqlite__DB__Local
3
4
  from osbot_utils.helpers.sqlite.tables.Sqlite__Table__Files import Sqlite__Table__Files
@@ -11,12 +12,33 @@ class Sqlite__DB__Files(Sqlite__DB__Local):
11
12
  def add_file(self, path, contents=None, metadata=None):
12
13
  return self.table_files().add_file(path, contents, metadata)
13
14
 
15
+ def clear_table(self):
16
+ self.table_files().clear()
17
+
18
+ def delete_file(self, path):
19
+ return self.table_files().delete_file(path)
20
+
21
+ def file(self, path, include_contents=False):
22
+ return self.table_files().file(path, include_contents=include_contents)
23
+
24
+ def file_exists(self, path):
25
+ return self.table_files().file_exists(path)
26
+
27
+ def file_names(self):
28
+ return self.table_files().select_field_values('path')
14
29
  @cache_on_self
15
30
  def table_files(self):
16
31
  return Sqlite__Table__Files(database=self).setup()
17
32
 
18
- def files(self):
19
- return self.table_files().files()
33
+ @index_by
34
+ def files(self,include_contents=False):
35
+ return self.table_files().files(include_contents=include_contents)
36
+
37
+ def files__with_content(self):
38
+ return self.files(include_contents=True)
39
+
40
+ def files__by_path(self):
41
+ return self.files(index_by='path')
20
42
 
21
43
  def setup(self):
22
44
  self.table_files()
@@ -10,7 +10,8 @@ class Sqlite__DB__Local(Sqlite__Database):
10
10
  db_name: str
11
11
 
12
12
  def __init__(self, db_path=None, db_name=None):
13
- self.db_name = db_name or random_text('db_local') + '.sqlite'
13
+ if hasattr(self, 'db_name') is False:
14
+ self.db_name = db_name or random_text('db_local') + '.sqlite'
14
15
  super().__init__(db_path=db_path or self.path_local_db())
15
16
 
16
17
  def path_db_folder(self):
@@ -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
@@ -1,6 +1,7 @@
1
1
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
2
  from osbot_utils.helpers.sqlite.Sqlite__Table import Sqlite__Table
3
- from osbot_utils.utils.Misc import timestamp_utc_now
3
+ from osbot_utils.utils.Misc import timestamp_utc_now, bytes_sha256, str_sha256
4
+ from osbot_utils.utils.Status import status_warning, status_ok
4
5
 
5
6
  SQLITE__TABLE_NAME__FILES = 'files'
6
7
 
@@ -21,10 +22,32 @@ class Sqlite__Table__Files(Sqlite__Table):
21
22
  super().__init__(**kwargs)
22
23
 
23
24
  def add_file(self, path, contents=None, metadata= None):
24
- if self.contains(path=path): # don't allow multiple entries for the same file path (until we add versioning support)
25
- return None
26
- row_data = self.create_node_data(path, contents, metadata)
27
- return self.add_row_and_commit(**row_data)
25
+ if self.contains(path=path): # don't allow multiple entries for the same file path (until we add versioning support)
26
+ return status_warning(f"File not added, since file with path '{path}' already exists in the database")
27
+ if metadata is None:
28
+ metadata = {}
29
+ metadata.update(self.create_contents_metadata(contents))
30
+ row_data = self.create_node_data(path, contents, metadata)
31
+ new_row_obj = self.add_row_and_commit(**row_data)
32
+ return status_ok(message='file added', data= new_row_obj)
33
+
34
+ def create_contents_metadata(self, contents):
35
+ file_size = len(contents)
36
+ file_is_binary = type(contents) is bytes
37
+ if file_is_binary:
38
+ file_hash = bytes_sha256(contents)
39
+ else:
40
+ file_hash = str_sha256(str(contents))
41
+ return dict(file_contents=dict(hash = file_hash ,
42
+ is_binary = file_is_binary ,
43
+ size = file_size ))
44
+
45
+ def delete_file(self, path):
46
+ if self.not_contains(path=path): # don't allow multiple entries for the same file path (until we add versioning support)
47
+ return status_warning(f"File not deleted, since file with path '{path}' did not exist in the database")
48
+
49
+ self.rows_delete_where(path=path)
50
+ return status_ok(message='file deleted')
28
51
 
29
52
  def create_node_data(self, path, contents=None, metadata= None):
30
53
  node_data = {'path' : path ,
@@ -34,9 +57,27 @@ class Sqlite__Table__Files(Sqlite__Table):
34
57
  node_data['timestamp'] = timestamp_utc_now()
35
58
  return node_data
36
59
 
60
+ def field_names_without_content(self): # todo: refactor to get this directly from the schema
61
+ return ['id', 'path', 'metadata', 'timestamp'] # and so that these values are not hard-coded here
62
+
63
+ def file(self, path, include_contents=True):
64
+ if include_contents:
65
+ fields = ['*']
66
+ else:
67
+ fields = self.field_names_without_content()
68
+ return self.row(where=dict(path=path), fields = fields)
69
+
70
+ def file_without_contents(self, path):
71
+ return self.file(path, include_contents=False)
72
+
73
+ def file_exists(self, path):
74
+ return self.contains(path=path)
37
75
 
38
- def files(self):
39
- return self.rows()
76
+ def files(self, include_contents=False):
77
+ if include_contents:
78
+ return self.rows()
79
+ fields_names = self.field_names_without_content()
80
+ return self.rows(fields_names)
40
81
 
41
82
  def setup(self):
42
83
  if self.exists() is False:
@@ -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
@@ -1,11 +1,11 @@
1
- from osbot_utils.utils.Files import Files, file_delete, folder_delete_all, files_list, file_create, file_name, \
1
+ from osbot_utils.utils.Files import Files, file_delete, folder_delete_all, files_list, file_create, \
2
2
  parent_folder, file_exists, file_contents
3
3
  from osbot_utils.utils.Misc import random_filename
4
4
 
5
5
 
6
6
  class Temp_File:
7
- def __init__(self, contents='...', extension='tmp'):
8
- self.tmp_file = random_filename(extension)
7
+ def __init__(self, contents='...', extension='tmp',file_name=None, ):
8
+ self.tmp_file = file_name or random_filename(extension)
9
9
  self.tmp_folder = None
10
10
  self.file_path = None
11
11
  self.original_contents = contents
@@ -30,7 +30,7 @@ class Temp_File:
30
30
  return file_exists(self.file_path)
31
31
 
32
32
  def file_name(self):
33
- return file_name(self.path())
33
+ return Files.file_name(self.path())
34
34
 
35
35
  def files_in_folder(self):
36
36
  return files_list(self.tmp_folder)
@@ -0,0 +1,89 @@
1
+ # In Misc.py
2
+ import os
3
+ from sys import platform
4
+
5
+ from osbot_utils.utils.Dev import pprint
6
+ from osbot_utils.utils.Files import all_parent_folders
7
+ from osbot_utils.utils.Misc import list_set
8
+ from osbot_utils.utils.Str import strip_quotes
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'
21
+
22
+ def env_value(var_name):
23
+ return env_vars().get(var_name, None)
24
+
25
+ def env_vars_list():
26
+ return list_set(env_vars())
27
+
28
+ def env_vars(reload_vars=False):
29
+ """
30
+ if reload_vars reload data from .env file
31
+ then return dictionary with current environment variables
32
+ """
33
+ if reload_vars:
34
+ load_dotenv()
35
+ vars = os.environ
36
+ data = {}
37
+ for key in vars:
38
+ data[key] = vars[key]
39
+ return data
40
+
41
+ def env_load_from_file(path, override=False):
42
+ if os.path.exists(path):
43
+ with open(path) as f:
44
+ for line in f:
45
+ line = line.strip()
46
+ if not line or line.startswith('#'): # Strip whitespace and ignore comments
47
+ continue
48
+ key, value = line.split(sep='=', maxsplit=1) # Split the line into key and value
49
+ value = strip_quotes(value.strip()) # Handle case when the value is in quotes
50
+ if override or key.strip() not in os.environ: # Set the environment variable
51
+ os.environ[key.strip()] = value.strip()
52
+
53
+ def env_unload_from_file(path):
54
+ if os.path.exists(path):
55
+ with open(path) as f:
56
+ for line in f:
57
+ line = line.strip()
58
+ if not line or line.startswith('#'): # Strip whitespace and ignore comments
59
+ continue
60
+ key, _ = line.split(sep='=', maxsplit=1) # Split the line into key and value
61
+ key = key.strip()
62
+ if key in os.environ: # Remove the environment variable if it exists
63
+ del os.environ[key]
64
+
65
+ def load_dotenv(dotenv_path=None, override=False):
66
+ if dotenv_path: # If a specific dotenv path is provided, load from it
67
+ env_load_from_file(dotenv_path, override)
68
+ else:
69
+ directories = all_parent_folders(include_path=True) # Define the possible directories to search for the .env file (which is this and all parent folders)
70
+ for directory in directories: # Iterate through the directories and load the .env file if found
71
+ env_path = os.path.join(directory, '.env') # Define the path to the .env file
72
+ if os.path.exists(env_path): # If we found one
73
+ env_load_from_file(env_path, override) # Process it
74
+ break # Stop after loading the first .env file # Stop after loading the first .env file
75
+
76
+
77
+ def unload_dotenv(dotenv_path=None):
78
+ if dotenv_path: # If a specific dotenv path is provided, unload from it
79
+ env_unload_from_file(dotenv_path)
80
+ else:
81
+ directories = all_parent_folders(include_path=True) # Define the possible directories to search for the .env file (which is this and all parent folders)
82
+ for directory in directories: # Iterate through the directories and unload the .env file if found
83
+ env_path = os.path.join(directory, '.env') # Define the path to the .env file
84
+ if os.path.exists(env_path): # If we found one
85
+ env_unload_from_file(env_path) # Process it
86
+ break # Stop after unloading the first .env file
87
+
88
+
89
+ env_load = load_dotenv
@@ -21,7 +21,7 @@ class Files:
21
21
  def copy(source:str, destination:str) -> str:
22
22
  if file_exists(source): # make sure source file exists
23
23
  destination_parent_folder = parent_folder(destination) # get target parent folder
24
- folder_create(destination_parent_folder) # ensure targer folder exists # todo: check if this is still needed (we should be using a copy method that creates the required fodlers)
24
+ folder_create(destination_parent_folder) # ensure target folder exists # todo: check if this is still needed (we should be using a copy method that creates the required fodlers)
25
25
  return shutil.copy(source, destination) # copy file and returns file destination
26
26
 
27
27
  @staticmethod
@@ -308,7 +308,11 @@ class Files:
308
308
  @staticmethod
309
309
  def path_combine(path1, path2):
310
310
  if type(path1) in [str, Path] and type(path2) in [str, Path]:
311
- return abspath(join(str(path1), str(path2)))
311
+ parent_path = str(path1)
312
+ sub_path = str(path2)
313
+ if sub_path.startswith('/'):
314
+ sub_path = sub_path[1:]
315
+ return abspath(join(parent_path,sub_path))
312
316
 
313
317
  @staticmethod
314
318
  def parent_folder(path):
@@ -424,13 +428,53 @@ class Files:
424
428
  file.write(contents)
425
429
  return path
426
430
 
427
- # todo: refactor the methods above into static methods
431
+ # todo: refactor the methods above into static methods (as bellow)
428
432
 
433
+ def all_parent_folders(path=None, include_path=False):
434
+ if path is None:
435
+ path = os.getcwd()
436
+ parent_directories = []
429
437
 
438
+ # Optionally include the starting path
439
+ if include_path:
440
+ parent_directories.append(path)
441
+
442
+ while True: # Split the path into parts
443
+ path, tail = os.path.split(path)
444
+ if tail:
445
+ parent_directories.append(path)
446
+ else:
447
+ if path and path not in parent_directories: # to handle the root path case
448
+ parent_directories.append(path)
449
+ break
450
+
451
+ return parent_directories
452
+ def file_move(source_file, target_file):
453
+ if file_exists(source_file):
454
+ file_copy(source_file, target_file)
455
+ if file_exists(target_file):
456
+ if file_delete(source_file):
457
+ return True
458
+ return False
459
+
460
+ def folders_names_in_folder(target):
461
+ folders = folders_in_folder(target)
462
+ return folders_names(folders)
463
+
464
+ def stream_to_bytes(stream):
465
+ return stream.read()
466
+
467
+ def stream_to_file(stream, path=None):
468
+ if path is None: # if path is not defined
469
+ path = Files.temp_file() # save it to a temp file
470
+ with open(path, 'wb') as file: # Write the content to the file
471
+ file.write(stream.read())
472
+ return path
430
473
 
431
474
  # helper methods
432
475
  # todo: all all methods above (including the duplicated mappings at the top)
433
476
 
477
+ bytes_to_file = Files.write_bytes
434
478
  create_folder = Files.folder_create
435
479
  create_folder_in_parent = Files.folder_create_in_parent
436
480
  create_temp_file = Files.write
@@ -465,6 +509,7 @@ file_open_gz = Files.open_gz
465
509
  file_open_bytes = Files.open_bytes
466
510
  file_to_base64 = Files.file_to_base64
467
511
  file_from_base64 = Files.file_from_base64
512
+ file_from_bytes = Files.write_bytes
468
513
  file_save = Files.save
469
514
  file_sha256 = Files.contents_sha256
470
515
  file_size = Files.file_size
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,14 +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
-
10
- from dotenv import load_dotenv
9
+ import typing
10
+ from typing import Union
11
11
 
12
12
  from osbot_utils.utils.Misc import list_set
13
13
  from osbot_utils.utils.Str import str_unicode_escape, str_max_width
14
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
+
15
34
  def are_types_compatible_for_assigment(source_type, target_type):
16
35
  if source_type is target_type:
17
36
  return True
@@ -88,24 +107,6 @@ def enum_from_value(enum_type, value):
88
107
  except KeyError:
89
108
  raise ValueError(f"Value '{value}' is not a valid member of {enum_type.__name__}.") # Handle the case where the value does not match any Enum member
90
109
 
91
- def env_value(var_name):
92
- return env_vars().get(var_name, None)
93
-
94
- def env_vars_list():
95
- return list_set(env_vars())
96
-
97
- def env_vars(reload_vars=False):
98
- """
99
- if reload_vars reload data from .env file
100
- then return dictionary with current environment variables
101
- """
102
- if reload_vars:
103
- load_dotenv()
104
- vars = os.environ
105
- data = {}
106
- for key in vars:
107
- data[key] = vars[key]
108
- return data
109
110
 
110
111
  def get_field(target, field, default=None):
111
112
  if target is not None:
osbot_utils/utils/Str.py CHANGED
@@ -3,13 +3,19 @@ from html import escape, unescape
3
3
 
4
4
  from osbot_utils.utils.Files import safe_file_name
5
5
 
6
+ # todo: refactor this this class all str related methods (mainly from the Misc class)
6
7
 
7
- def html_escape(value):
8
+ def html_escape(value: str):
8
9
  return escape(value)
9
10
 
10
- def html_unescape(value):
11
+ def html_unescape(value: str):
11
12
  return unescape(value)
12
13
 
14
+ def strip_quotes(value: str): # Remove surrounding quotes (single or double)
15
+ if (value.startswith("'") and value.endswith("'")) or (value.startswith('"') and value.endswith('"')):
16
+ return value[1:-1]
17
+ return value
18
+
13
19
  def str_dedent(value, strip=True):
14
20
  result = textwrap.dedent(value)
15
21
  if strip:
osbot_utils/utils/Zip.py CHANGED
@@ -1,11 +1,41 @@
1
+ import gzip
1
2
  import io
2
3
  import os
3
4
  import shutil
5
+ import tarfile
4
6
  import zipfile
5
7
  from os.path import abspath
6
8
 
7
- from osbot_utils.utils.Files import temp_folder, folder_files, temp_file, is_file
9
+ from osbot_utils.utils.Files import temp_folder, folder_files, temp_file, is_file, file_copy, file_move
10
+
11
+
12
+ def gz_tar_bytes_file_list(gz_bytes):
13
+ gz_buffer_from_bytes = io.BytesIO(gz_bytes)
14
+ with gzip.GzipFile(fileobj=gz_buffer_from_bytes, mode='rb') as gz:
15
+ decompressed_data = gz.read()
16
+ tar_buffer_from_bytes = io.BytesIO(decompressed_data) # Assuming the decompressed data is a tag file, process it
17
+ with tarfile.open(fileobj=tar_buffer_from_bytes, mode='r:') as tar:
18
+ return sorted(tar.getnames())
19
+
20
+ def gz_tar_bytes_get_file(gz_bytes, tar_file_path):
21
+ gz_buffer_from_bytes = io.BytesIO(gz_bytes)
22
+ with gzip.GzipFile(fileobj=gz_buffer_from_bytes, mode='rb') as gz:
23
+ decompressed_data = gz.read()
24
+ tar_buffer_from_bytes = io.BytesIO(decompressed_data)
25
+ with tarfile.open(fileobj=tar_buffer_from_bytes, mode='r:') as tar:
26
+ extracted_file = tar.extractfile(tar_file_path)
27
+ if extracted_file:
28
+ return extracted_file.read()
29
+ else:
30
+ raise FileNotFoundError(f"The file {tar_file_path} was not found in the tar archive.")
8
31
 
32
+ def gz_zip_bytes_file_list(gz_bytes):
33
+ gz_buffer_from_bytes = io.BytesIO(gz_bytes)
34
+ with gzip.GzipFile(fileobj=gz_buffer_from_bytes, mode='rb') as gz:
35
+ decompressed_data = gz.read()
36
+ zip_buffer_from_bytes = io.BytesIO(decompressed_data) # Assuming the decompressed data is a zip file, process it
37
+ with zipfile.ZipFile(zip_buffer_from_bytes, 'r') as zf:
38
+ return sorted(zf.namelist())
9
39
 
10
40
  def unzip_file(zip_file, target_folder=None, format='zip'):
11
41
  target_folder = target_folder or temp_folder()
@@ -28,6 +58,14 @@ def zip_bytes_get_file(zip_bytes, zip_file_path):
28
58
  with zipfile.ZipFile(zip_buffer, 'r') as zf:
29
59
  return zf.read(zip_file_path)
30
60
 
61
+ def zip_bytes_extract_to_folder(zip_bytes, target_folder=None):
62
+ target_folder = target_folder or temp_folder() # Use the provided target folder or create a temporary one
63
+ zip_buffer = io.BytesIO(zip_bytes) # Create a BytesIO buffer from the zip bytes
64
+ with zipfile.ZipFile(zip_buffer, 'r') as zf: # Open the zip file from the buffer
65
+ zf.extractall(target_folder) # Extract all files to the target folder
66
+ return target_folder # Return the path of the target folder
67
+
68
+
31
69
  def zip_bytes_file_list(zip_bytes):
32
70
  zip_buffer_from_bytes = io.BytesIO(zip_bytes)
33
71
  with zipfile.ZipFile(zip_buffer_from_bytes, 'r') as zf:
@@ -61,6 +99,11 @@ def zip_files_to_bytes(target_files, root_folder=None):
61
99
  def zip_folder(root_dir, format='zip'):
62
100
  return shutil.make_archive(base_name=root_dir, format=format, root_dir=root_dir)
63
101
 
102
+ def zip_folder_to_file (root_dir, target_file):
103
+ zip_file = zip_folder(root_dir)
104
+ return file_move(zip_file, target_file)
105
+
106
+
64
107
  def zip_folder_to_bytes(root_dir): # todo add unit test
65
108
  zip_buffer = io.BytesIO() # Create a BytesIO buffer to hold the zipped file
66
109
  with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf: # Create a ZipFile object with the buffer as the target
@@ -1,16 +0,0 @@
1
- """
2
- In this class you will find the following helper classes:
3
-
4
- - Assert
5
- - Dev
6
- - Files
7
- - Http
8
- - Json
9
- - Lists
10
- - Misc
11
- - Png
12
- - Process
13
- - Temp_File
14
- - Unzip_File
15
- - Zip_Folder
16
- """
osbot_utils/version CHANGED
@@ -1 +1 @@
1
- v1.11.0
1
+ v1.15.0
@@ -1,17 +1,20 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osbot_utils
3
- Version: 1.11.0
3
+ Version: 1.15.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
- Requires-Dist: python-dotenv
15
18
  Project-URL: Repository, https://github.com/owasp-sbot/OSBot-Utils
16
19
  Description-Content-Type: text/markdown
17
20
 
@@ -19,14 +22,14 @@ Description-Content-Type: text/markdown
19
22
 
20
23
  Powerful Python util methods and classes that simplify common apis and tasks.
21
24
 
22
- ![Current Release](https://img.shields.io/badge/release-v1.11.0-blue)
25
+ ![Current Release](https://img.shields.io/badge/release-v1.15.0-blue)
23
26
  [![codecov](https://codecov.io/gh/owasp-sbot/OSBot-Utils/graph/badge.svg?token=GNVW0COX1N)](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
24
27
 
25
28
 
26
29
 
27
30
  ## Install - Release 1.x
28
31
 
29
- **for main branch**: add to requirements.txt
32
+ **for main branch**: just get it from pypi
30
33
 
31
34
  ```
32
35
  pip install osbot-utils
@@ -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
@@ -169,26 +169,26 @@ osbot_utils/helpers/pubsub/schemas/Schema__Event__Message.py,sha256=rt8W-DGitmR-
169
169
  osbot_utils/helpers/pubsub/schemas/Schema__PubSub__Client.py,sha256=yOQSn4o1bIsEoyhnQJYen372so89Ben1wMWUO12G4h8,239
170
170
  osbot_utils/helpers/pubsub/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
171
  osbot_utils/helpers/sqlite/Capture_Sqlite_Error.py,sha256=GSuRYgs1yKQjxMszPoaI7fsfMfuUqhb64AaIysRE6Cs,1747
172
- osbot_utils/helpers/sqlite/Sqlite__Cursor.py,sha256=0O7xEVNP4rk9GFo95AlL8F--Q3qb5FG2pIWHm5FzqjU,3303
173
- osbot_utils/helpers/sqlite/Sqlite__Database.py,sha256=wovss-inRP2ioLSyEgnqtJJWmHIp6zGZNeiB9f5t2So,5261
172
+ osbot_utils/helpers/sqlite/Sqlite__Cursor.py,sha256=k5G9Tkk3nx6nHoSanLmpuJG_TceAmN7uRBCt0bo6sIc,3364
173
+ osbot_utils/helpers/sqlite/Sqlite__Database.py,sha256=XcyZ7sqXQeV0dAyERiE3kYFeMubfhKidLbrlrZXLzQI,5432
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
- osbot_utils/helpers/sqlite/Sqlite__Table.py,sha256=LTRCn9X2ciF29HEM9aKMHcZ0ZO4xTP2de9L2PvcUCOA,14805
177
- osbot_utils/helpers/sqlite/Sqlite__Table__Create.py,sha256=3Z6erCsfuVWU9AjeaXsIZQbM-a0_dKrzEOksp1H3ZHo,3825
176
+ osbot_utils/helpers/sqlite/Sqlite__Table.py,sha256=FsFSolFN2N5V8DfZPp4gZL9xmCXaOhmG38wQmgRrvp8,15145
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
181
181
  osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py,sha256=w5HmzkXqikGggS93obZWsqPRp6d2g7Vs74U9WI_u6Vc,10362
182
182
  osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests__Patch.py,sha256=XiqSPnMUP7VIUBy2QZc_ql-pfG2kueTWNsjqt9pdhoY,2322
183
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py,sha256=ofk9e0iUXhoLE8MdYWzmjgcCRhlK_OgEyY2NtBu4iQU,768
183
+ osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py,sha256=tirQs4VjnjzGagaT17NigqprMuQiELHm5FwAE5PJT1U,1510
184
184
  osbot_utils/helpers/sqlite/domains/Sqlite__DB__Graph.py,sha256=aGykK4Qll5Rn0xJR4RvF5bN6llNEjx-Vst52XTVSNio,1751
185
185
  osbot_utils/helpers/sqlite/domains/Sqlite__DB__Json.py,sha256=ILx1wOsmwmvV2yb_fMzLzo18uGNub8cxhBrRfYxCCqA,3808
186
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py,sha256=NV0NkwjWwhU7Dq-l_sL1RqsQVxjYtkZJPqH5G96TYFc,882
186
+ osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py,sha256=n5kGCVkVC16JBEBfwh8sXmCJMSAtrkAUtSgXggHXVkc,932
187
187
  osbot_utils/helpers/sqlite/domains/Sqlite__DB__Requests.py,sha256=8Txl9kX1kHMTEpL-bIjJbzcCfMFb4nHeLrhdIiBtgAc,1541
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
@@ -197,7 +197,7 @@ osbot_utils/helpers/sqlite/sql_builder/SQL_Builder__Select.py,sha256=_5fszoWTknE
197
197
  osbot_utils/helpers/sqlite/sql_builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
198
198
  osbot_utils/helpers/sqlite/tables/Sqlite__Table__Config.py,sha256=M-h6fYQgmX7Yaq3cSmpePutajLVeVy_IDUrDk97EH6U,2061
199
199
  osbot_utils/helpers/sqlite/tables/Sqlite__Table__Edges.py,sha256=YwlWj9GYBDeotqDFdjMOb6MyuHhPZKasndAtuHiLrkQ,1635
200
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py,sha256=vtu9g78bw36mfdgeGe2BoGIC-kNStFRcNOWOsjZz6kc,1510
200
+ osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py,sha256=ZlhTqroHd9T8vqNwYpRv5cYHw7Fi-F6fxANh_Fr0nt4,3613
201
201
  osbot_utils/helpers/sqlite/tables/Sqlite__Table__Nodes.py,sha256=GT8h3wD4hGvEtqQuBs0sBbcu2ydktRHTi95PEL2ffHQ,1721
202
202
  osbot_utils/helpers/sqlite/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
203
203
  osbot_utils/helpers/trace/Trace_Call.py,sha256=xTmR5U2u0vKnIgMSu-4NAibWKIV4-Eql3vACwOr1lkw,6329
@@ -219,10 +219,10 @@ 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
- osbot_utils/testing/Temp_File.py,sha256=U7lP3ucqqKIADBF3M-4rPiYqT-i7loDn-cWNFsN5_ow,1410
225
+ osbot_utils/testing/Temp_File.py,sha256=gL1BJ6aRNg2o4LeUJ4f3bxKc75iw1qp0WKP9T3rS_WU,1435
226
226
  osbot_utils/testing/Temp_Folder.py,sha256=wZQhCi5Cy0dQQAXuu3_jArDz5T94Q_JX68GENU6nTMo,4793
227
227
  osbot_utils/testing/Temp_Sys_Path.py,sha256=gOMD-7dQYQlejoDYUqsrmuZQ9DLC07ymPZB3zYuNmG4,256
228
228
  osbot_utils/testing/Temp_Web_Server.py,sha256=0A-gZsd0_3wRj2YuBEOWyV2rhT6dcS2BlArngPXGTtk,3186
@@ -235,26 +235,27 @@ 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=BF9whkLIIBEQpx5Ww0bhRau55UhwV3SJT_z9DUoT3UY,4060
238
239
  osbot_utils/utils/Exceptions.py,sha256=KyOUHkXQ_6jDTq04Xm261dbEZuRidtsM4dgzNwSG8-8,389
239
- osbot_utils/utils/Files.py,sha256=zJ9-B99AkRKcZCd-xiAuqt9YSlNOHogQOciBmOMbwf4,17807
240
+ osbot_utils/utils/Files.py,sha256=HRMdDq1ZctDfCkIfuZGgQ3fG6O03qJEFEIkb8HTYzfU,19407
240
241
  osbot_utils/utils/Functions.py,sha256=0E6alPJ0fJpBiJgFOWooCOi265wSRyxxXAJ5CELBnso,3498
241
242
  osbot_utils/utils/Http.py,sha256=Z8V149M2HDrKBoXkDD5EXgqTGx6vQoUqXugXK__wcuw,4572
242
- osbot_utils/utils/Int.py,sha256=oDWQmop18IFfYVa2drz4qA5LxlL8T5Isjjo3fFN_vBw,130
243
+ osbot_utils/utils/Int.py,sha256=PmlUdU4lSwf4gJdmTVdqclulkEp7KPCVUDO6AcISMF4,116
243
244
  osbot_utils/utils/Json.py,sha256=AfOXYBxAGzqwaNeHZ-aC-RpurNmHzPxGoym7MMY8UN4,6366
244
245
  osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
245
246
  osbot_utils/utils/Lists.py,sha256=CLEjgZwAixJAFlubWEKjnUUhUN85oqvR7UqExVW7rdY,5502
246
- osbot_utils/utils/Misc.py,sha256=R7BMg4XcQ_83PntNP4mNylkpLjoIwMnsswllaDJVbbY,16486
247
- osbot_utils/utils/Objects.py,sha256=6MBSqQH4XedEyWmHDnf2OizRx38NTsCHUoaWj16V9_Q,13817
247
+ osbot_utils/utils/Misc.py,sha256=OFbDe5NrUNNo8bneRnIwlfWSo-gwaYMP5L1eBTDro2s,16716
248
+ osbot_utils/utils/Objects.py,sha256=qI6EVvx7kzXQrVGzsDk8txJkIXjsy7m2EEPZnYGy5R0,13867
248
249
  osbot_utils/utils/Png.py,sha256=V1juGp6wkpPigMJ8HcxrPDIP4bSwu51oNkLI8YqP76Y,1172
249
250
  osbot_utils/utils/Process.py,sha256=lr3CTiEkN3EiBx3ZmzYmTKlQoPdkgZBRjPulMxG-zdo,2357
250
251
  osbot_utils/utils/Python_Logger.py,sha256=z_20FlFdaLoUgJ0cmqXnyA7icgFIpBz8Ej1_xVKWsWM,11245
251
252
  osbot_utils/utils/Status.py,sha256=Yq4s0TelXgn0i2QjCP9V8mP30GabXp_UL-jjM6Iwiw4,4305
252
- osbot_utils/utils/Str.py,sha256=nc7cVdQ3ugcgxqpHIjuuf6WRXCiBLy1oDPLnsk5lVAw,1398
253
+ osbot_utils/utils/Str.py,sha256=gcStFbB57ol74ASIr00pWImpd6ObBr5BLpSB0VUKNTE,1749
253
254
  osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
254
- osbot_utils/utils/Zip.py,sha256=ogn_vkTD2rJ6Yq1MQ5_9UQnYIscby1bx25cMN4QsnX8,4503
255
- osbot_utils/utils/__init__.py,sha256=AtewTFWWgEjSqHBn8uP6H9ZgY0U3dMtS-1grgneSKdI,172
256
- osbot_utils/version,sha256=prx7MCno_OAZwhbe9147hDB8kAgdP_7KZU_T1QeHj_I,8
257
- osbot_utils-1.11.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
258
- osbot_utils-1.11.0.dist-info/METADATA,sha256=NnsjD88LY1EU8RNg3Og0kfSUzJL0e4QEX-ctzsRWxns,1097
259
- osbot_utils-1.11.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
260
- osbot_utils-1.11.0.dist-info/RECORD,,
255
+ osbot_utils/utils/Zip.py,sha256=YFahdBguVK71mLdYy4m7mqVAQ5al-60QnTmYK-txCfY,6784
256
+ osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
257
+ osbot_utils/version,sha256=6N-ed9f4qxkyGvpgFOr2Jl7OZbURQ8I_IfaWgnkeIPw,8
258
+ osbot_utils-1.15.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
259
+ osbot_utils-1.15.0.dist-info/METADATA,sha256=HijF1q8V9YDln_BgjxtAW1Rfesu8LtmOibEaKFcx-UY,1266
260
+ osbot_utils-1.15.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
261
+ osbot_utils-1.15.0.dist-info/RECORD,,