osbot-utils 1.81.0__py3-none-any.whl → 1.83.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osbot_utils/base_classes/Type_Safe.py +60 -31
- osbot_utils/decorators/lists/group_by.py +2 -1
- osbot_utils/decorators/lists/index_by.py +3 -3
- osbot_utils/decorators/methods/cache_on_self.py +28 -28
- osbot_utils/decorators/methods/remove_return_value.py +3 -6
- osbot_utils/helpers/Random_Guid.py +3 -1
- osbot_utils/helpers/Timestamp_Now.py +4 -1
- osbot_utils/helpers/generators/Generator_Manager.py +16 -0
- osbot_utils/helpers/sqlite/Sqlite__Database.py +9 -9
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py +6 -4
- osbot_utils/helpers/trace/Trace_Call__Config.py +5 -3
- osbot_utils/utils/Dev.py +10 -5
- osbot_utils/utils/Env.py +40 -10
- osbot_utils/utils/Files.py +46 -13
- osbot_utils/utils/Json.py +28 -6
- osbot_utils/utils/Misc.py +62 -26
- osbot_utils/utils/Objects.py +36 -21
- osbot_utils/utils/Toml.py +5 -7
- osbot_utils/version +1 -1
- {osbot_utils-1.81.0.dist-info → osbot_utils-1.83.0.dist-info}/METADATA +2 -2
- {osbot_utils-1.81.0.dist-info → osbot_utils-1.83.0.dist-info}/RECORD +23 -23
- {osbot_utils-1.81.0.dist-info → osbot_utils-1.83.0.dist-info}/LICENSE +0 -0
- {osbot_utils-1.81.0.dist-info → osbot_utils-1.83.0.dist-info}/WHEEL +0 -0
osbot_utils/utils/Files.py
CHANGED
@@ -1,17 +1,5 @@
|
|
1
|
-
import gzip
|
2
|
-
|
3
1
|
import os
|
4
|
-
import
|
5
|
-
import pickle
|
6
|
-
import re
|
7
|
-
import shutil
|
8
|
-
import tempfile
|
9
|
-
from os.path import abspath, join
|
10
|
-
from pathlib import Path, PosixPath
|
11
|
-
from typing import Union
|
12
|
-
|
13
|
-
from osbot_utils.utils.Misc import bytes_to_base64, base64_to_bytes, random_string
|
14
|
-
|
2
|
+
from typing import Union
|
15
3
|
|
16
4
|
class Files:
|
17
5
|
@staticmethod
|
@@ -21,6 +9,7 @@ class Files:
|
|
21
9
|
|
22
10
|
@staticmethod
|
23
11
|
def copy(source:str, destination:str) -> str:
|
12
|
+
import shutil
|
24
13
|
if file_exists(source): # make sure source file exists
|
25
14
|
destination_parent_folder = parent_folder(destination) # get target parent folder
|
26
15
|
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)
|
@@ -78,10 +67,14 @@ class Files:
|
|
78
67
|
|
79
68
|
@staticmethod
|
80
69
|
def find(path_pattern, recursive=True):
|
70
|
+
import glob
|
71
|
+
|
81
72
|
return glob.glob(path_pattern, recursive=recursive)
|
82
73
|
|
83
74
|
@staticmethod
|
84
75
|
def files(path, pattern= '*', only_files=True):
|
76
|
+
from pathlib import Path
|
77
|
+
|
85
78
|
result = []
|
86
79
|
for file in Path(path).rglob(pattern):
|
87
80
|
if only_files and is_not_file(file):
|
@@ -99,6 +92,8 @@ class Files:
|
|
99
92
|
|
100
93
|
@staticmethod
|
101
94
|
def file_create_all_parent_folders(file_path):
|
95
|
+
from pathlib import Path
|
96
|
+
|
102
97
|
if file_path:
|
103
98
|
parent_path = parent_folder(file_path)
|
104
99
|
if parent_path:
|
@@ -136,10 +131,14 @@ class Files:
|
|
136
131
|
|
137
132
|
@staticmethod
|
138
133
|
def file_to_base64(path):
|
134
|
+
from osbot_utils.utils.Misc import bytes_to_base64
|
135
|
+
|
139
136
|
return bytes_to_base64(file_bytes(path))
|
140
137
|
|
141
138
|
@staticmethod
|
142
139
|
def file_from_base64(bytes_base64, path=None, extension=None):
|
140
|
+
from osbot_utils.utils.Misc import base64_to_bytes
|
141
|
+
|
143
142
|
bytes_ = base64_to_bytes(bytes_base64)
|
144
143
|
return file_create_bytes(bytes=bytes_, path=path, extension=None)
|
145
144
|
|
@@ -181,6 +180,8 @@ class Files:
|
|
181
180
|
|
182
181
|
@staticmethod
|
183
182
|
def folder_copy(source, destination, ignore_pattern=None):
|
183
|
+
import shutil
|
184
|
+
|
184
185
|
if ignore_pattern:
|
185
186
|
if type(ignore_pattern) is str:
|
186
187
|
ignore_pattern = [ignore_pattern]
|
@@ -214,6 +215,8 @@ class Files:
|
|
214
215
|
|
215
216
|
@staticmethod
|
216
217
|
def folder_delete_all(path): # this will remove recursively
|
218
|
+
import shutil
|
219
|
+
|
217
220
|
if folder_exists(path):
|
218
221
|
shutil.rmtree(path)
|
219
222
|
return folder_exists(path) is False
|
@@ -267,6 +270,8 @@ class Files:
|
|
267
270
|
|
268
271
|
@staticmethod
|
269
272
|
def is_file(target):
|
273
|
+
from pathlib import Path
|
274
|
+
|
270
275
|
if isinstance(target, Path):
|
271
276
|
return target.is_file()
|
272
277
|
if type(target) is str:
|
@@ -276,6 +281,8 @@ class Files:
|
|
276
281
|
|
277
282
|
@staticmethod
|
278
283
|
def is_folder(target):
|
284
|
+
from pathlib import Path
|
285
|
+
|
279
286
|
if isinstance(target, Path):
|
280
287
|
return target.is_dir()
|
281
288
|
if type(target) is str:
|
@@ -290,6 +297,8 @@ class Files:
|
|
290
297
|
|
291
298
|
@staticmethod
|
292
299
|
def lines_gz(path):
|
300
|
+
import gzip
|
301
|
+
|
293
302
|
with gzip.open(path, "rt") as file:
|
294
303
|
for line in file:
|
295
304
|
yield line
|
@@ -304,6 +313,8 @@ class Files:
|
|
304
313
|
|
305
314
|
@staticmethod
|
306
315
|
def open_gz(path, mode='r'):
|
316
|
+
import gzip
|
317
|
+
|
307
318
|
return gzip.open(path, mode=mode)
|
308
319
|
|
309
320
|
@staticmethod
|
@@ -320,6 +331,8 @@ class Files:
|
|
320
331
|
# return abspath(join(parent_path,sub_path))
|
321
332
|
|
322
333
|
def path_combine(path1: Union[str, os.PathLike], path2: Union[str, os.PathLike]) -> str:
|
334
|
+
from os.path import abspath, join
|
335
|
+
|
323
336
|
if path1 is None or path2 is None:
|
324
337
|
raise ValueError("Both paths must be provided")
|
325
338
|
|
@@ -345,6 +358,8 @@ class Files:
|
|
345
358
|
|
346
359
|
@staticmethod
|
347
360
|
def pickle_save_to_file(object_to_save, path=None):
|
361
|
+
import pickle
|
362
|
+
|
348
363
|
path = path or temp_file(extension=".pickle")
|
349
364
|
file_to_store = open(path, "wb")
|
350
365
|
pickle.dump(object_to_save, file_to_store)
|
@@ -353,6 +368,8 @@ class Files:
|
|
353
368
|
|
354
369
|
@staticmethod
|
355
370
|
def pickle_load_from_file(path=None):
|
371
|
+
import pickle
|
372
|
+
|
356
373
|
file_to_read = open(path, "rb")
|
357
374
|
loaded_object = pickle.load(file_to_read)
|
358
375
|
file_to_read.close()
|
@@ -360,6 +377,8 @@ class Files:
|
|
360
377
|
|
361
378
|
@staticmethod
|
362
379
|
def safe_file_name(file_name):
|
380
|
+
import re
|
381
|
+
|
363
382
|
if type(file_name) is not str:
|
364
383
|
file_name = f"{file_name}"
|
365
384
|
return re.sub(r'[^a-zA-Z0-9_.-]', '_',file_name or '')
|
@@ -388,6 +407,8 @@ class Files:
|
|
388
407
|
|
389
408
|
@staticmethod
|
390
409
|
def temp_file(extension = '.tmp', contents=None, target_folder=None):
|
410
|
+
import tempfile
|
411
|
+
|
391
412
|
extension = file_extension_fix(extension)
|
392
413
|
if target_folder is None:
|
393
414
|
(fd, tmp_file) = tempfile.mkstemp(extension)
|
@@ -401,6 +422,8 @@ class Files:
|
|
401
422
|
|
402
423
|
@staticmethod
|
403
424
|
def temp_file_in_folder(target_folder, prefix="temp_file_", postfix='.txt'):
|
425
|
+
from osbot_utils.utils.Misc import random_string
|
426
|
+
|
404
427
|
if is_folder(target_folder):
|
405
428
|
path_to_file = path_combine(target_folder, random_string(prefix=prefix, postfix=postfix))
|
406
429
|
file_create(path_to_file, random_string())
|
@@ -414,10 +437,14 @@ class Files:
|
|
414
437
|
|
415
438
|
@staticmethod
|
416
439
|
def temp_folder(prefix=None, suffix=None,target_folder=None):
|
440
|
+
import tempfile
|
441
|
+
|
417
442
|
return tempfile.mkdtemp(suffix, prefix, target_folder)
|
418
443
|
|
419
444
|
@staticmethod
|
420
445
|
def temp_folder_current():
|
446
|
+
import tempfile
|
447
|
+
|
421
448
|
return tempfile.gettempdir()
|
422
449
|
|
423
450
|
@staticmethod
|
@@ -440,6 +467,8 @@ class Files:
|
|
440
467
|
|
441
468
|
@staticmethod
|
442
469
|
def write_gz(path=None, contents=None):
|
470
|
+
import gzip
|
471
|
+
|
443
472
|
path = path or temp_file(extension='.gz')
|
444
473
|
contents = contents or ''
|
445
474
|
if type(contents) is str:
|
@@ -450,6 +479,8 @@ class Files:
|
|
450
479
|
|
451
480
|
# todo: refactor the methods above into static methods (as bellow)
|
452
481
|
def absolute_path(path):
|
482
|
+
from os.path import abspath
|
483
|
+
|
453
484
|
return abspath(path)
|
454
485
|
|
455
486
|
def all_parent_folders(path=None, include_path=False):
|
@@ -499,6 +530,8 @@ def files_names_in_folder(target, with_extension=False):
|
|
499
530
|
return files_names_without_extension(files_in_folder(target))
|
500
531
|
|
501
532
|
def files_in_folder(path,pattern='*', only_files=True):
|
533
|
+
from pathlib import Path
|
534
|
+
|
502
535
|
result = []
|
503
536
|
for file in Path(path).glob(pattern):
|
504
537
|
if only_files and is_not_file(file):
|
osbot_utils/utils/Json.py
CHANGED
@@ -1,14 +1,11 @@
|
|
1
|
-
import json
|
2
|
-
import os
|
3
|
-
|
4
|
-
from osbot_utils.utils.Misc import str_lines, str_md5, str_sha256
|
5
|
-
from osbot_utils.utils.Files import file_create_gz, file_create, load_file_gz, file_contents, file_lines, file_lines_gz
|
6
|
-
from osbot_utils.utils.Zip import str_to_gz, gz_to_str
|
7
1
|
|
8
2
|
def bytes_to_json_loads(data):
|
3
|
+
import json
|
9
4
|
return json.loads(data.decode())
|
10
5
|
|
11
6
|
def json_dumps(python_object, indent=4, pretty=True, sort_keys=False, default=str, raise_exception=False):
|
7
|
+
import json
|
8
|
+
|
12
9
|
if python_object:
|
13
10
|
try:
|
14
11
|
if pretty:
|
@@ -25,6 +22,8 @@ def json_dumps_to_bytes(*args, **kwargs):
|
|
25
22
|
return json_dumps(*args, **kwargs).encode()
|
26
23
|
|
27
24
|
def json_lines_file_load(target_path):
|
25
|
+
from osbot_utils.utils.Files import file_lines
|
26
|
+
|
28
27
|
raw_json = '[' # start the json array
|
29
28
|
lines = file_lines(target_path) # get all lines from the file provided in target_path
|
30
29
|
raw_json += ','.join(lines) # add lines to raw_json split by json array separator
|
@@ -32,6 +31,8 @@ def json_lines_file_load(target_path):
|
|
32
31
|
return json_parse(raw_json) # convert json data into a python object
|
33
32
|
|
34
33
|
def json_lines_file_load_gz(target_path):
|
34
|
+
from osbot_utils.utils.Files import file_lines_gz
|
35
|
+
|
35
36
|
raw_json = '[' # start the json array
|
36
37
|
lines = file_lines_gz(target_path) # get all lines from the file provided in target_path
|
37
38
|
raw_json += ','.join(lines) # add lines to raw_json split by json array separator
|
@@ -40,14 +41,19 @@ def json_lines_file_load_gz(target_path):
|
|
40
41
|
|
41
42
|
|
42
43
|
def json_sha_256(target):
|
44
|
+
from osbot_utils.utils.Misc import str_sha256
|
43
45
|
return str_sha256(json_dumps(target))
|
44
46
|
|
45
47
|
|
46
48
|
def json_to_gz(data):
|
49
|
+
from osbot_utils.utils.Zip import str_to_gz
|
47
50
|
value = json_dumps(data, pretty=False)
|
48
51
|
return str_to_gz(value)
|
49
52
|
|
50
53
|
def gz_to_json(gz_data):
|
54
|
+
import json
|
55
|
+
from osbot_utils.utils.Zip import gz_to_str
|
56
|
+
|
51
57
|
data = gz_to_str(gz_data)
|
52
58
|
return json.loads(data)
|
53
59
|
|
@@ -62,11 +68,15 @@ class Json:
|
|
62
68
|
Note: will not throw errors and will return {} as default
|
63
69
|
errors are logged to Json.log
|
64
70
|
"""
|
71
|
+
from osbot_utils.utils.Files import file_contents
|
72
|
+
|
65
73
|
json_data = file_contents(path)
|
66
74
|
return json_loads(json_data)
|
67
75
|
|
68
76
|
@staticmethod
|
69
77
|
def load_file_and_delete(path):
|
78
|
+
import os
|
79
|
+
|
70
80
|
data = json_load_file(path)
|
71
81
|
if data:
|
72
82
|
os.remove(path)
|
@@ -74,11 +84,14 @@ class Json:
|
|
74
84
|
|
75
85
|
@staticmethod
|
76
86
|
def load_file_gz(path):
|
87
|
+
from osbot_utils.utils.Files import load_file_gz
|
77
88
|
data = load_file_gz(path)
|
78
89
|
return json_loads(data)
|
79
90
|
|
80
91
|
@staticmethod
|
81
92
|
def load_file_gz_and_delete(path):
|
93
|
+
import os
|
94
|
+
|
82
95
|
data = json_load_file_gz(path)
|
83
96
|
if data:
|
84
97
|
os.remove(path)
|
@@ -91,6 +104,8 @@ class Json:
|
|
91
104
|
Note: will not throw errors and will return {} as default
|
92
105
|
errors are logged to Json.log
|
93
106
|
"""
|
107
|
+
import json
|
108
|
+
|
94
109
|
if json_data:
|
95
110
|
try:
|
96
111
|
return json.loads(json_data)
|
@@ -103,11 +118,15 @@ class Json:
|
|
103
118
|
|
104
119
|
@staticmethod
|
105
120
|
def loads_json_lines(json_lines):
|
121
|
+
from osbot_utils.utils.Misc import str_lines
|
122
|
+
|
106
123
|
json_data = '[' + ','.join(str_lines(json_lines.strip())) + ']'
|
107
124
|
return json_loads(json_data)
|
108
125
|
|
109
126
|
@staticmethod
|
110
127
|
def md5(data):
|
128
|
+
from osbot_utils.utils.Misc import str_md5
|
129
|
+
|
111
130
|
return str_md5(json_dump(data))
|
112
131
|
|
113
132
|
@staticmethod
|
@@ -116,6 +135,8 @@ class Json:
|
|
116
135
|
|
117
136
|
@staticmethod
|
118
137
|
def save_file(python_object, path=None, pretty=False, sort_keys=False):
|
138
|
+
from osbot_utils.utils.Files import file_create
|
139
|
+
|
119
140
|
json_data = json_dumps(python_object=python_object, indent=2, pretty=pretty, sort_keys=sort_keys)
|
120
141
|
return file_create(path=path, contents=json_data)
|
121
142
|
|
@@ -125,6 +146,7 @@ class Json:
|
|
125
146
|
|
126
147
|
@staticmethod
|
127
148
|
def save_file_gz(python_object, path=None, pretty=False):
|
149
|
+
from osbot_utils.utils.Files import file_create_gz
|
128
150
|
json_data = json_dumps(python_object,indent=2, pretty=pretty)
|
129
151
|
return file_create_gz(path=path, contents=json_data)
|
130
152
|
|
osbot_utils/utils/Misc.py
CHANGED
@@ -1,22 +1,4 @@
|
|
1
|
-
import base64
|
2
|
-
import hashlib
|
3
|
-
import importlib
|
4
|
-
import logging
|
5
|
-
import os
|
6
|
-
import random
|
7
|
-
import string
|
8
1
|
import sys
|
9
|
-
import textwrap
|
10
|
-
import re
|
11
|
-
import threading
|
12
|
-
import uuid
|
13
|
-
import warnings
|
14
|
-
from datetime import datetime, timedelta
|
15
|
-
from secrets import token_bytes
|
16
|
-
from time import sleep
|
17
|
-
from typing import Iterable
|
18
|
-
from urllib.parse import quote_plus, unquote_plus
|
19
|
-
|
20
2
|
|
21
3
|
if sys.version_info >= (3, 11):
|
22
4
|
from datetime import UTC
|
@@ -28,21 +10,26 @@ def append_random_string(target, length=6, prefix='-'):
|
|
28
10
|
return f'{target}{random_string(length, prefix)}'
|
29
11
|
|
30
12
|
def attr_value_from_module_name(module_name, attr_name, default_value=None):
|
13
|
+
import importlib
|
31
14
|
module = importlib.import_module(module_name)
|
32
15
|
if hasattr(module, attr_name):
|
33
16
|
return getattr(module, attr_name)
|
34
17
|
return default_value
|
35
18
|
|
36
19
|
def bytes_md5(target : bytes):
|
20
|
+
import hashlib
|
37
21
|
return hashlib.md5(target).hexdigest()
|
38
22
|
|
39
23
|
def bytes_sha256(target : bytes):
|
24
|
+
import hashlib
|
40
25
|
return hashlib.sha256(target).hexdigest()
|
41
26
|
|
42
27
|
def bytes_sha384(target : bytes):
|
28
|
+
import hashlib
|
43
29
|
return hashlib.sha384(target).hexdigest()
|
44
30
|
|
45
31
|
def base64_to_bytes(bytes_base64):
|
32
|
+
import base64
|
46
33
|
if type(bytes_base64) is str:
|
47
34
|
bytes_base64 = bytes_base64.encode()
|
48
35
|
return base64.decodebytes(bytes_base64)
|
@@ -51,12 +38,14 @@ def base64_to_str(target, encoding='ascii'):
|
|
51
38
|
return bytes_to_str(base64_to_bytes(target), encoding=encoding)
|
52
39
|
|
53
40
|
def bytes_to_base64(target):
|
41
|
+
import base64
|
54
42
|
return base64.b64encode(target).decode()
|
55
43
|
|
56
44
|
def bytes_to_str(target, encoding='ascii'):
|
57
45
|
return target.decode(encoding=encoding)
|
58
46
|
|
59
47
|
def convert_to_number(value):
|
48
|
+
import re
|
60
49
|
if value:
|
61
50
|
try:
|
62
51
|
if value[0] in ['£','$','€']:
|
@@ -69,10 +58,12 @@ def convert_to_number(value):
|
|
69
58
|
return 0
|
70
59
|
|
71
60
|
def current_thread_id():
|
61
|
+
import threading
|
72
62
|
return threading.current_thread().native_id
|
73
63
|
|
74
64
|
|
75
65
|
def date_time_from_to_str(date_time_str, format_from, format_to, print_conversion_error=False):
|
66
|
+
from datetime import datetime
|
76
67
|
try:
|
77
68
|
date_time = datetime.strptime(date_time_str, format_from)
|
78
69
|
return date_time.strftime(format_to)
|
@@ -96,10 +87,9 @@ def date_now(use_utc=True, return_str=True):
|
|
96
87
|
return value
|
97
88
|
|
98
89
|
def date_time_now(use_utc=True, return_str=True, milliseconds_numbers=0, date_time_format='%Y-%m-%d %H:%M:%S.%f'):
|
90
|
+
from datetime import datetime
|
99
91
|
if use_utc:
|
100
92
|
value = datetime.now(UTC)
|
101
|
-
#value = datetime.utcnow() # todo: this has been marked for depreciation in python 11
|
102
|
-
# value = datetime.now(UTC) # but this doesn't seem to work in python 10.x : E ImportError: cannot import name 'UTC' from 'datetime' (/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/datetime.py)
|
103
93
|
|
104
94
|
else:
|
105
95
|
value = datetime.now()
|
@@ -107,18 +97,18 @@ def date_time_now(use_utc=True, return_str=True, milliseconds_numbers=0, date_ti
|
|
107
97
|
return date_time_to_str(value, milliseconds_numbers=milliseconds_numbers, date_time_format=date_time_format)
|
108
98
|
return value
|
109
99
|
|
110
|
-
# def date_time_parse(value):
|
111
|
-
# if type(value) is datetime:
|
112
|
-
# return value
|
113
|
-
# return parser.parse(value)
|
114
100
|
|
115
101
|
def date_time_less_time_delta(date_time, days=0, hours=0, minutes=0, seconds=0, date_time_format='%Y-%m-%d %H:%M:%S' , return_str=True):
|
102
|
+
from datetime import timedelta
|
103
|
+
|
116
104
|
new_date_time = date_time - timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
|
117
105
|
if return_str:
|
118
106
|
return date_time_to_str(new_date_time, date_time_format=date_time_format)
|
119
107
|
return new_date_time
|
120
108
|
|
121
109
|
def date_time_now_less_time_delta(days=0,hours=0, minutes=0, seconds=0, date_time_format='%Y-%m-%d %H:%M:%S', return_str=True):
|
110
|
+
from datetime import datetime
|
111
|
+
|
122
112
|
return date_time_less_time_delta(datetime.now(UTC),days=days, hours=hours, minutes=minutes, seconds=seconds,date_time_format=date_time_format, return_str=return_str)
|
123
113
|
|
124
114
|
def date_to_str(date, date_format='%Y-%m-%d'):
|
@@ -181,6 +171,7 @@ def is_float(value):
|
|
181
171
|
return False
|
182
172
|
|
183
173
|
def is_guid(value):
|
174
|
+
import uuid
|
184
175
|
try:
|
185
176
|
uuid_obj = uuid.UUID(value)
|
186
177
|
return str(uuid_obj) == value.lower()
|
@@ -189,6 +180,7 @@ def is_guid(value):
|
|
189
180
|
|
190
181
|
|
191
182
|
def ignore_warning__unclosed_ssl():
|
183
|
+
import warnings
|
192
184
|
warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")
|
193
185
|
|
194
186
|
|
@@ -213,15 +205,18 @@ def log_to_file(level="INFO"):
|
|
213
205
|
return logger_add_handler__file()
|
214
206
|
|
215
207
|
def logger():
|
208
|
+
import logging
|
216
209
|
return logging.getLogger()
|
217
210
|
|
218
211
|
def logger_add_handler(handler):
|
219
212
|
logger().addHandler(handler)
|
220
213
|
|
221
214
|
def logger_add_handler__console():
|
215
|
+
import logging
|
222
216
|
logger_add_handler(logging.StreamHandler())
|
223
217
|
|
224
218
|
def logger_add_handler__file(log_file=None):
|
219
|
+
import logging
|
225
220
|
from osbot_utils.utils.Files import temp_file
|
226
221
|
log_file = log_file or temp_file(extension=".log")
|
227
222
|
logger_add_handler(logging.FileHandler(filename=log_file))
|
@@ -275,6 +270,8 @@ def str_sha384(text:str):
|
|
275
270
|
return
|
276
271
|
|
277
272
|
def str_sha384_as_base64(text:str, include_prefix=True):
|
273
|
+
import hashlib
|
274
|
+
import base64
|
278
275
|
if type(text) is str:
|
279
276
|
hash_object = hashlib.sha384(text.encode())
|
280
277
|
digest = hash_object.digest() # Getting the digest of the hash
|
@@ -306,6 +303,8 @@ def time_delta_in_days_hours_or_minutes(time_delta):
|
|
306
303
|
|
307
304
|
|
308
305
|
def time_now(use_utc=True, milliseconds_numbers=1):
|
306
|
+
from datetime import datetime
|
307
|
+
|
309
308
|
if use_utc:
|
310
309
|
datetime_now = datetime.now(UTC)
|
311
310
|
else:
|
@@ -317,6 +316,8 @@ def time_to_str(datetime_value, time_format='%H:%M:%S.%f', milliseconds_numbers=
|
|
317
316
|
return time_str_milliseconds(datetime_str=time_str, datetime_format=time_format, milliseconds_numbers=milliseconds_numbers)
|
318
317
|
|
319
318
|
def timestamp_utc_now():
|
319
|
+
from datetime import datetime
|
320
|
+
|
320
321
|
return int(datetime.now(UTC).timestamp() * 1000)
|
321
322
|
|
322
323
|
def timestamp_utc_now_less_delta(days=0,hours=0, minutes=0, seconds=0):
|
@@ -327,6 +328,8 @@ def datetime_to_timestamp(datetime):
|
|
327
328
|
return int(datetime.timestamp() * 1000)
|
328
329
|
|
329
330
|
def timestamp_to_datetime(timestamp):
|
331
|
+
from datetime import datetime
|
332
|
+
|
330
333
|
timestamp = float(timestamp) # handle cases when timestamp is a Decimal
|
331
334
|
return datetime.fromtimestamp(timestamp/1000)
|
332
335
|
|
@@ -346,9 +349,13 @@ def to_string(target):
|
|
346
349
|
return ''
|
347
350
|
|
348
351
|
def random_bytes(length=24):
|
352
|
+
from secrets import token_bytes
|
349
353
|
return token_bytes(length)
|
350
354
|
|
351
355
|
def random_filename(extension='.tmp', length=10):
|
356
|
+
import random
|
357
|
+
import string
|
358
|
+
|
352
359
|
from osbot_utils.utils.Files import file_extension_fix
|
353
360
|
extension = file_extension_fix(extension)
|
354
361
|
return '{0}{1}'.format(''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) , extension)
|
@@ -357,9 +364,12 @@ def random_port(min=20000,max=65000):
|
|
357
364
|
return random_number(min, max)
|
358
365
|
|
359
366
|
def random_number(min=1,max=65000):
|
367
|
+
import random
|
360
368
|
return random.randint(min, max)
|
361
369
|
|
362
370
|
def random_password(length=24, prefix=''):
|
371
|
+
import random
|
372
|
+
import string
|
363
373
|
password = prefix + ''.join(random.choices(string.ascii_lowercase +
|
364
374
|
string.ascii_uppercase +
|
365
375
|
string.punctuation +
|
@@ -372,6 +382,10 @@ def random_password(length=24, prefix=''):
|
|
372
382
|
return password
|
373
383
|
|
374
384
|
def random_string(length:int=8, prefix:str='', postfix:str=''):
|
385
|
+
import string
|
386
|
+
|
387
|
+
import random
|
388
|
+
|
375
389
|
if is_int(length):
|
376
390
|
length -= 1 # so that we get the exact length when the value is provided
|
377
391
|
else:
|
@@ -383,6 +397,9 @@ def random_string_short(prefix:str = None):
|
|
383
397
|
return random_id(prefix=prefix, length=6).lower()
|
384
398
|
|
385
399
|
def random_string_and_numbers(length:int=6,prefix:str=''):
|
400
|
+
import random
|
401
|
+
import string
|
402
|
+
|
386
403
|
return prefix + ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))
|
387
404
|
|
388
405
|
def random_text(prefix:str=None,length:int=12, lowercase=False):
|
@@ -395,21 +412,27 @@ def random_text(prefix:str=None,length:int=12, lowercase=False):
|
|
395
412
|
return value
|
396
413
|
|
397
414
|
def random_uuid():
|
415
|
+
import uuid
|
398
416
|
return str(uuid.uuid4())
|
399
417
|
|
400
418
|
def random_uuid_short():
|
419
|
+
import uuid
|
401
420
|
return str(uuid.uuid4())[0:6]
|
402
421
|
|
403
422
|
def remove(target_string, string_to_remove): # todo: refactor to str_*
|
404
423
|
return replace(target_string, string_to_remove, '')
|
405
424
|
|
406
425
|
def remove_multiple_spaces(target): # todo: refactor to str_*
|
426
|
+
import re
|
427
|
+
|
407
428
|
return re.sub(' +', ' ', target)
|
408
429
|
|
409
430
|
def replace(target_string, string_to_find, string_to_replace): # todo: refactor to str_*
|
410
431
|
return target_string.replace(string_to_find, string_to_replace)
|
411
432
|
|
412
433
|
def remove_html_tags(html):
|
434
|
+
import re
|
435
|
+
|
413
436
|
if html:
|
414
437
|
TAG_RE = re.compile(r'<[^>]+>')
|
415
438
|
return TAG_RE.sub('', html).replace(' ', ' ')
|
@@ -420,8 +443,9 @@ def split_lines(text):
|
|
420
443
|
def split_spaces(target):
|
421
444
|
return remove_multiple_spaces(target).split(' ')
|
422
445
|
|
423
|
-
def sorted_set(target
|
424
|
-
|
446
|
+
def sorted_set(target):
|
447
|
+
from typing import Iterable
|
448
|
+
if isinstance(target, Iterable) and target:
|
425
449
|
return sorted(set(target))
|
426
450
|
return []
|
427
451
|
|
@@ -437,9 +461,13 @@ def str_to_bool(value):
|
|
437
461
|
return False
|
438
462
|
|
439
463
|
def str_to_date(str_date, format='%Y-%m-%d %H:%M:%S.%f'):
|
464
|
+
from datetime import datetime
|
465
|
+
|
440
466
|
return datetime.strptime(str_date,format)
|
441
467
|
|
442
468
|
def str_to_date_time(str_date, format='%Y-%m-%d %H:%M:%S'):
|
469
|
+
from datetime import datetime
|
470
|
+
|
443
471
|
return datetime.strptime(str_date,format)
|
444
472
|
|
445
473
|
def str_to_int(str_data):
|
@@ -457,14 +485,18 @@ def under_debugger():
|
|
457
485
|
|
458
486
|
|
459
487
|
def url_encode(data):
|
488
|
+
from urllib.parse import quote_plus
|
460
489
|
if type(data) is str:
|
461
490
|
return quote_plus(data)
|
462
491
|
|
463
492
|
def url_decode(data):
|
493
|
+
from urllib.parse import unquote_plus
|
464
494
|
if type(data) is str:
|
465
495
|
return unquote_plus(data)
|
466
496
|
|
467
497
|
def utc_now():
|
498
|
+
from datetime import datetime
|
499
|
+
|
468
500
|
return datetime.now(UTC)
|
469
501
|
|
470
502
|
def upper(target : str):
|
@@ -473,10 +505,13 @@ def upper(target : str):
|
|
473
505
|
return ""
|
474
506
|
|
475
507
|
def wait(seconds):
|
508
|
+
from time import sleep
|
509
|
+
|
476
510
|
if seconds and seconds > 0:
|
477
511
|
sleep(seconds)
|
478
512
|
|
479
513
|
def word_wrap(text,length = 40):
|
514
|
+
import textwrap
|
480
515
|
if text:
|
481
516
|
wrapped_text = ""
|
482
517
|
for line in text.splitlines(): # handle case when there are newlines inside the text value
|
@@ -486,6 +521,7 @@ def word_wrap(text,length = 40):
|
|
486
521
|
return ''
|
487
522
|
|
488
523
|
def word_wrap_escaped(text,length = 40):
|
524
|
+
import textwrap
|
489
525
|
if text:
|
490
526
|
return '\\n'.join(textwrap.wrap(text, length))
|
491
527
|
|