osbot-utils 1.11.0__py3-none-any.whl → 1.13.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.
@@ -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,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,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:
@@ -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,76 @@
1
+ # In Misc.py
2
+ import os
3
+
4
+ from osbot_utils.utils.Files import all_parent_folders
5
+ from osbot_utils.utils.Misc import list_set
6
+ from osbot_utils.utils.Str import strip_quotes
7
+
8
+
9
+ def env_value(var_name):
10
+ return env_vars().get(var_name, None)
11
+
12
+ def env_vars_list():
13
+ return list_set(env_vars())
14
+
15
+ def env_vars(reload_vars=False):
16
+ """
17
+ if reload_vars reload data from .env file
18
+ then return dictionary with current environment variables
19
+ """
20
+ if reload_vars:
21
+ load_dotenv()
22
+ vars = os.environ
23
+ data = {}
24
+ for key in vars:
25
+ data[key] = vars[key]
26
+ return data
27
+
28
+ def env_load_from_file(path, override=False):
29
+ if os.path.exists(path):
30
+ with open(path) as f:
31
+ for line in f:
32
+ line = line.strip()
33
+ if not line or line.startswith('#'): # Strip whitespace and ignore comments
34
+ continue
35
+ key, value = line.split(sep='=', maxsplit=1) # Split the line into key and value
36
+ value = strip_quotes(value.strip()) # Handle case when the value is in quotes
37
+ if override or key.strip() not in os.environ: # Set the environment variable
38
+ os.environ[key.strip()] = value.strip()
39
+
40
+ def env_unload_from_file(path):
41
+ if os.path.exists(path):
42
+ with open(path) as f:
43
+ for line in f:
44
+ line = line.strip()
45
+ if not line or line.startswith('#'): # Strip whitespace and ignore comments
46
+ continue
47
+ key, _ = line.split(sep='=', maxsplit=1) # Split the line into key and value
48
+ key = key.strip()
49
+ if key in os.environ: # Remove the environment variable if it exists
50
+ del os.environ[key]
51
+
52
+ def load_dotenv(dotenv_path=None, override=False):
53
+ if dotenv_path: # If a specific dotenv path is provided, load from it
54
+ env_load_from_file(dotenv_path, override)
55
+ else:
56
+ directories = all_parent_folders(include_path=True) # Define the possible directories to search for the .env file (which is this and all parent folders)
57
+ for directory in directories: # Iterate through the directories and load the .env file if found
58
+ env_path = os.path.join(directory, '.env') # Define the path to the .env file
59
+ if os.path.exists(env_path): # If we found one
60
+ env_load_from_file(env_path, override) # Process it
61
+ break # Stop after loading the first .env file # Stop after loading the first .env file
62
+
63
+
64
+ def unload_dotenv(dotenv_path=None):
65
+ if dotenv_path: # If a specific dotenv path is provided, unload from it
66
+ env_unload_from_file(dotenv_path)
67
+ else:
68
+ directories = all_parent_folders(include_path=True) # Define the possible directories to search for the .env file (which is this and all parent folders)
69
+ for directory in directories: # Iterate through the directories and unload the .env file if found
70
+ env_path = os.path.join(directory, '.env') # Define the path to the .env file
71
+ if os.path.exists(env_path): # If we found one
72
+ env_unload_from_file(env_path) # Process it
73
+ break # Stop after unloading the first .env file
74
+
75
+
76
+ 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
@@ -7,8 +7,6 @@ import pickle
7
7
  import types
8
8
  from typing import get_origin, Union, get_args
9
9
 
10
- from dotenv import load_dotenv
11
-
12
10
  from osbot_utils.utils.Misc import list_set
13
11
  from osbot_utils.utils.Str import str_unicode_escape, str_max_width
14
12
 
@@ -88,24 +86,6 @@ def enum_from_value(enum_type, value):
88
86
  except KeyError:
89
87
  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
88
 
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
89
 
110
90
  def get_field(target, field, default=None):
111
91
  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.13.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: osbot_utils
3
- Version: 1.11.0
3
+ Version: 1.13.0
4
4
  Summary: OWASP Security Bot - Utils
5
5
  Home-page: https://github.com/owasp-sbot/OSBot-Utils
6
6
  License: MIT
@@ -11,7 +11,6 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: python-dotenv
15
14
  Project-URL: Repository, https://github.com/owasp-sbot/OSBot-Utils
16
15
  Description-Content-Type: text/markdown
17
16
 
@@ -19,14 +18,14 @@ Description-Content-Type: text/markdown
19
18
 
20
19
  Powerful Python util methods and classes that simplify common apis and tasks.
21
20
 
22
- ![Current Release](https://img.shields.io/badge/release-v1.11.0-blue)
21
+ ![Current Release](https://img.shields.io/badge/release-v1.13.0-blue)
23
22
  [![codecov](https://codecov.io/gh/owasp-sbot/OSBot-Utils/graph/badge.svg?token=GNVW0COX1N)](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
24
23
 
25
24
 
26
25
 
27
26
  ## Install - Release 1.x
28
27
 
29
- **for main branch**: add to requirements.txt
28
+ **for main branch**: just get it from pypi
30
29
 
31
30
  ```
32
31
  pip install osbot-utils
@@ -169,21 +169,21 @@ 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
176
+ osbot_utils/helpers/sqlite/Sqlite__Table.py,sha256=FsFSolFN2N5V8DfZPp4gZL9xmCXaOhmG38wQmgRrvp8,15145
177
177
  osbot_utils/helpers/sqlite/Sqlite__Table__Create.py,sha256=3Z6erCsfuVWU9AjeaXsIZQbM-a0_dKrzEOksp1H3ZHo,3825
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
@@ -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
@@ -222,7 +222,7 @@ osbot_utils/testing/Patch_Print.py,sha256=RfY4HGIM06dF6JTKibnN1Uco8YL05xPWe1jS4M
222
222
  osbot_utils/testing/Profiler.py,sha256=K2KjIQVmrKW1bp6vwm90PO2rzFbjWaAQ_m1kDcUXo4M,3255
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,8 +235,9 @@ osbot_utils/utils/Assert.py,sha256=u9XLgYn91QvNWZGyPi29SjPJSXRHlm9andIn3NJEVog,1
235
235
  osbot_utils/utils/Call_Stack.py,sha256=MAq_0vMxnbeLfCe9qQz7GwJYaOuXpt3qtQwN6wiXsU0,6595
236
236
  osbot_utils/utils/Csv.py,sha256=oHLVpjRJqrLMz9lubMCNEoThXWju5rNTprcwHc1zq2c,1012
237
237
  osbot_utils/utils/Dev.py,sha256=HibpQutYy_iG8gGV8g1GztxNN4l29E4Bi7UZaVL6-L8,1203
238
+ osbot_utils/utils/Env.py,sha256=J_Viw_Lt7a845WUK9u7WD1_YAgtjR31EIG9OEljHrgg,3739
238
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
243
  osbot_utils/utils/Int.py,sha256=oDWQmop18IFfYVa2drz4qA5LxlL8T5Isjjo3fFN_vBw,130
@@ -244,17 +245,17 @@ osbot_utils/utils/Json.py,sha256=AfOXYBxAGzqwaNeHZ-aC-RpurNmHzPxGoym7MMY8UN4,636
244
245
  osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
245
246
  osbot_utils/utils/Lists.py,sha256=CLEjgZwAixJAFlubWEKjnUUhUN85oqvR7UqExVW7rdY,5502
246
247
  osbot_utils/utils/Misc.py,sha256=R7BMg4XcQ_83PntNP4mNylkpLjoIwMnsswllaDJVbbY,16486
247
- osbot_utils/utils/Objects.py,sha256=6MBSqQH4XedEyWmHDnf2OizRx38NTsCHUoaWj16V9_Q,13817
248
+ osbot_utils/utils/Objects.py,sha256=bsbuYniXM5ztEEY2xBcZfIpiM1qT5Rlsd0ZSeikx9Uw,13361
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=rUUdM3JyA9c1QRTbuda9dilaTWxvsSHNvG8GnX2tEQw,8
258
+ osbot_utils-1.13.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
259
+ osbot_utils-1.13.0.dist-info/METADATA,sha256=mnNYG7ohU7BXICZbanuZR2R8oFllk1lm3P_Df3mygt0,1066
260
+ osbot_utils-1.13.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
261
+ osbot_utils-1.13.0.dist-info/RECORD,,