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.
- osbot_utils/base_classes/Kwargs_To_Self.py +92 -45
- osbot_utils/graphs/mermaid/Mermaid__Graph.py +5 -3
- osbot_utils/graphs/mermaid/Mermaid__Renderer.py +3 -1
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py +14 -15
- osbot_utils/graphs/mgraph/MGraph.py +4 -2
- osbot_utils/helpers/ast/Ast.py +6 -3
- osbot_utils/helpers/html/Tag__Head.py +4 -2
- osbot_utils/helpers/pubsub/PubSub__Client.py +2 -1
- osbot_utils/helpers/pubsub/PubSub__Room.py +3 -1
- osbot_utils/helpers/pubsub/PubSub__Server.py +4 -3
- osbot_utils/helpers/sqlite/Sqlite__Cursor.py +4 -1
- osbot_utils/helpers/sqlite/Sqlite__Database.py +4 -1
- osbot_utils/helpers/sqlite/Sqlite__Table.py +10 -0
- osbot_utils/helpers/sqlite/Sqlite__Table__Create.py +4 -2
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py +24 -2
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +2 -1
- osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py +7 -2
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py +48 -7
- osbot_utils/testing/Profiler.py +2 -2
- osbot_utils/testing/Temp_File.py +4 -4
- osbot_utils/utils/Env.py +89 -0
- osbot_utils/utils/Files.py +48 -3
- osbot_utils/utils/Int.py +0 -1
- osbot_utils/utils/Misc.py +8 -1
- osbot_utils/utils/Objects.py +22 -21
- osbot_utils/utils/Str.py +8 -2
- osbot_utils/utils/Zip.py +44 -1
- osbot_utils/utils/__init__.py +0 -16
- osbot_utils/version +1 -1
- {osbot_utils-1.11.0.dist-info → osbot_utils-1.15.0.dist-info}/METADATA +8 -5
- {osbot_utils-1.11.0.dist-info → osbot_utils-1.15.0.dist-info}/RECORD +33 -32
- {osbot_utils-1.11.0.dist-info → osbot_utils-1.15.0.dist-info}/LICENSE +0 -0
- {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
|
9
|
-
from typing import
|
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
|
-
|
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
|
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
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
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
|
-
|
160
|
-
|
161
|
-
if var_name
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
if
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
#
|
173
|
-
|
174
|
-
if
|
175
|
-
|
176
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
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
|
260
|
-
|
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
|
271
|
-
|
272
|
-
|
273
|
-
|
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 :
|
10
|
-
mermaid_code :
|
11
|
-
nodes :
|
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 :
|
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
|
2
|
-
|
1
|
+
from enum import Enum
|
3
2
|
|
4
3
|
class Diagram__Type(Enum):
|
5
|
-
class_diagram =
|
6
|
-
entity_relationship_diagram =
|
7
|
-
flowchart =
|
8
|
-
gantt =
|
9
|
-
git_graph =
|
10
|
-
graph =
|
11
|
-
mermaid_map =
|
12
|
-
mindmap =
|
13
|
-
pie_chart =
|
14
|
-
requirement_diagram =
|
15
|
-
sequence_diagram = "sequenceDiagram"
|
16
|
-
state_diagram =
|
17
|
-
user_journey =
|
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 :
|
13
|
+
edges : List[MGraph__Edge]
|
12
14
|
key : str
|
13
|
-
nodes :
|
15
|
+
nodes : List[MGraph__Node]
|
14
16
|
|
15
17
|
|
16
18
|
def __init__(self, **kwargs):
|
osbot_utils/helpers/ast/Ast.py
CHANGED
@@ -11,9 +11,12 @@ class Ast:
|
|
11
11
|
pass
|
12
12
|
|
13
13
|
def source_code__from(self, target):
|
14
|
-
|
15
|
-
|
16
|
-
|
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 :
|
10
|
-
meta :
|
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 :
|
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 :
|
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 :
|
21
|
-
clients_connected:
|
22
|
-
rooms :
|
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):
|
@@ -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 :
|
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:
|
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
|
-
|
19
|
-
|
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
|
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):
|
25
|
-
return
|
26
|
-
|
27
|
-
|
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
|
-
|
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:
|
osbot_utils/testing/Profiler.py
CHANGED
@@ -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()
|
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/testing/Temp_File.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
from osbot_utils.utils.Files import Files, file_delete, folder_delete_all, files_list, file_create,
|
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)
|
osbot_utils/utils/Env.py
ADDED
@@ -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
|
osbot_utils/utils/Files.py
CHANGED
@@ -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
|
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
|
-
|
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
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
|
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
|
osbot_utils/utils/Objects.py
CHANGED
@@ -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
|
-
|
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
|
osbot_utils/utils/__init__.py
CHANGED
osbot_utils/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
v1.
|
1
|
+
v1.15.0
|
@@ -1,17 +1,20 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: osbot_utils
|
3
|
-
Version: 1.
|
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.
|
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
|
-

|
23
26
|
[](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**:
|
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=
|
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=
|
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
|
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=
|
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=
|
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=
|
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=
|
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=
|
159
|
-
osbot_utils/helpers/pubsub/PubSub__Room.py,sha256=
|
160
|
-
osbot_utils/helpers/pubsub/PubSub__Server.py,sha256=
|
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=
|
173
|
-
osbot_utils/helpers/sqlite/Sqlite__Database.py,sha256=
|
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=
|
177
|
-
osbot_utils/helpers/sqlite/Sqlite__Table__Create.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
247
|
-
osbot_utils/utils/Objects.py,sha256=
|
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=
|
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=
|
255
|
-
osbot_utils/utils/__init__.py,sha256=
|
256
|
-
osbot_utils/version,sha256=
|
257
|
-
osbot_utils-1.
|
258
|
-
osbot_utils-1.
|
259
|
-
osbot_utils-1.
|
260
|
-
osbot_utils-1.
|
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,,
|
File without changes
|
File without changes
|