osbot-utils 2.78.0__py3-none-any.whl → 2.80.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.
@@ -1,6 +1,3 @@
1
- from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
-
3
-
4
1
  class Dict_To_Attr:
5
2
  def __init__(self, dictionary):
6
3
  for key, value in dictionary.items():
@@ -0,0 +1,211 @@
1
+ from typing import Dict, Any, List, Set, Tuple, Union
2
+ from osbot_utils.type_safe.Type_Safe import Type_Safe
3
+ from osbot_utils.type_safe.type_safe_core.decorators.type_safe import type_safe
4
+
5
+
6
+ class Dict__To__Toml(Type_Safe):
7
+
8
+ @type_safe
9
+ def convert(self, data: Dict[str, Any] # Dictionary to convert to TOML
10
+ ) -> str: # Returns TOML formatted string
11
+ toml_str = ""
12
+
13
+ # First, process top-level simple values
14
+ toml_str += self._process_simple_values(data, indent_level=0)
15
+
16
+ # Process top-level arrays of simple types
17
+ toml_str += self._process_simple_arrays(data, indent_level=0)
18
+
19
+ # Process top-level dictionaries as sections
20
+ toml_str += self._process_sections(data, parent_key="")
21
+
22
+ # Process arrays of tables
23
+ toml_str += self._process_array_of_tables(data, parent_key="")
24
+
25
+ return toml_str
26
+
27
+ @type_safe
28
+ def _format_string_value(self, value: str # String to format
29
+ ) -> str: # Returns formatted TOML string
30
+ """Format a string value for TOML output"""
31
+ # For TOML basic strings, we need to handle:
32
+ # 1. If string contains single quotes, use double quotes
33
+ # 2. If string contains double quotes but no single quotes, use single quotes
34
+ # 3. If string contains both, use triple quotes (multi-line literal)
35
+
36
+ has_single = "'" in value
37
+ has_double = '"' in value
38
+ has_newline = '\n' in value or '\r' in value
39
+
40
+ # Use triple quotes for complex strings (multilineelif has_single: or with both quote types)
41
+ if has_newline or (has_single and has_double):
42
+ # Use multi-line literal string (triple single quotes)
43
+ # This preserves the exact content without escaping
44
+ return f"'''\n{value}'''"
45
+ elif has_single:
46
+ # Use double quotes and escape any double quotes
47
+ escaped = value.replace('"', '\\"')
48
+ return f'"{escaped}"'
49
+ else:
50
+ # Use single quotes (default) - no escaping needed
51
+ return f"'{value}'"
52
+
53
+ @type_safe
54
+ def _process_simple_values(self, data : Dict[str, Any] , # Data to process
55
+ indent_level : int = 0 # Current indentation level
56
+ ) -> str: # Returns TOML for simple values
57
+ toml_str = ""
58
+ indent = " " * indent_level
59
+
60
+ for key, value in data.items():
61
+ if not isinstance(value, (dict, list, tuple, set)):
62
+ if isinstance(value, str):
63
+ toml_str += f"{indent}{key} = {self._format_string_value(value)}\n"
64
+ elif isinstance(value, bool):
65
+ toml_str += f"{indent}{key} = {str(value).lower()}\n"
66
+ elif value is not None:
67
+ toml_str += f"{indent}{key} = {value}\n"
68
+
69
+ return toml_str
70
+
71
+ @type_safe
72
+ def _process_simple_arrays(self, data : Dict[str, Any] , # Data to process
73
+ indent_level : int = 0 # Current indentation level
74
+ ) -> str: # Returns TOML for simple arrays
75
+ toml_str = ""
76
+ indent = " " * indent_level
77
+
78
+ for key, value in data.items():
79
+ if isinstance(value, (list, tuple, set)):
80
+ if not value: # Empty collection
81
+ toml_str += f"{indent}{key} = []\n"
82
+ elif not any(isinstance(item, dict) for item in value): # No dict items
83
+ toml_str += f"{indent}{key} = [\n"
84
+ for item in value:
85
+ if isinstance(item, str):
86
+ toml_str += f"{indent} {self._format_string_value(item)},\n"
87
+ elif isinstance(item, bool):
88
+ toml_str += f"{indent} {str(item).lower()},\n"
89
+ elif item is not None:
90
+ toml_str += f"{indent} {item},\n"
91
+ toml_str += f"{indent}]\n"
92
+
93
+ return toml_str
94
+
95
+ @type_safe
96
+ def _process_sections(self, data : Dict[str, Any] , # Data to process
97
+ parent_key : str = "" # Parent section key
98
+ ) -> str: # Returns TOML sections
99
+ toml_str = ""
100
+
101
+ for key, value in data.items():
102
+ if isinstance(value, dict):
103
+ section_key = f"{parent_key}.{key}" if parent_key else key
104
+ toml_str += f"[{section_key}]\n"
105
+ toml_str += self._process_section_content(value, section_key)
106
+
107
+ return toml_str
108
+
109
+ @type_safe
110
+ def _process_section_content(self, section_data : Dict[str, Any] , # Section data to process
111
+ section_key : str # Current section key
112
+ ) -> str: # Returns section content
113
+ toml_str = ""
114
+ indent = " "
115
+
116
+ # Process simple values in section
117
+ for key, value in section_data.items():
118
+ if isinstance(value, str):
119
+ toml_str += f"{indent}{key} = {self._format_string_value(value)}\n"
120
+ elif isinstance(value, bool):
121
+ toml_str += f"{indent}{key} = {str(value).lower()}\n"
122
+ elif isinstance(value, (int, float)) and value is not None:
123
+ toml_str += f"{indent}{key} = {value}\n"
124
+ elif isinstance(value, (list, tuple, set)) and not value:
125
+ toml_str += f"{indent}{key} = []\n"
126
+ elif isinstance(value, (list, tuple, set)):
127
+ if not any(isinstance(item, dict) for item in value):
128
+ toml_str += f"{indent}{key} = [\n"
129
+ for item in value:
130
+ if isinstance(item, str):
131
+ toml_str += f"{indent}{indent}{self._format_string_value(item)},\n"
132
+ elif isinstance(item, bool):
133
+ toml_str += f"{indent}{indent}{str(item).lower()},\n"
134
+ elif item is not None:
135
+ toml_str += f"{indent}{indent}{item},\n"
136
+ toml_str += f"{indent}]\n"
137
+
138
+ # Process arrays of tables within sections (NEW)
139
+ for key, value in section_data.items():
140
+ if isinstance(value, (list, tuple, set)):
141
+ if value and all(isinstance(item, dict) for item in value):
142
+ for item in value:
143
+ toml_str += f"[[{section_key}.{key}]]\n"
144
+ for item_key, item_value in item.items():
145
+ if isinstance(item_value, str):
146
+ toml_str += f"{indent}{item_key} = {self._format_string_value(item_value)}\n"
147
+ elif isinstance(item_value, bool):
148
+ toml_str += f"{indent}{item_key} = {str(item_value).lower()}\n"
149
+ elif isinstance(item_value, (int, float)) and item_value is not None:
150
+ toml_str += f"{indent}{item_key} = {item_value}\n"
151
+
152
+ # Process nested dictionaries
153
+ for key, value in section_data.items():
154
+ if isinstance(value, dict):
155
+ nested_key = f"{section_key}.{key}"
156
+ toml_str += f"[{nested_key}]\n"
157
+ toml_str += self._process_section_content(value, nested_key)
158
+
159
+ return toml_str
160
+
161
+ @type_safe
162
+ def _process_array_of_tables(self, data : Dict[str, Any] , # Data to process
163
+ parent_key : str = "" # Parent section key
164
+ ) -> str: # Returns array of tables
165
+ toml_str = ""
166
+
167
+ for key, value in data.items():
168
+ if isinstance(value, (list, tuple, set)):
169
+ if value and all(isinstance(item, dict) for item in value):
170
+ for item in value:
171
+ section_key = f"{parent_key}.{key}" if parent_key else key
172
+ toml_str += f"[[{section_key}]]\n"
173
+ toml_str += self._process_table_item(item, section_key)
174
+
175
+ return toml_str
176
+
177
+ @type_safe
178
+ def _process_table_item(self, item : Dict[str, Any] , # Table item to process
179
+ section_key : str # Current section key
180
+ ) -> str: # Returns formatted table item
181
+ toml_str = ""
182
+ indent = " "
183
+
184
+ # Process simple values in table item
185
+ for key, value in item.items():
186
+ if isinstance(value, str):
187
+ toml_str += f"{indent}{key} = {self._format_string_value(value)}\n"
188
+ elif isinstance(value, bool):
189
+ toml_str += f"{indent}{key} = {str(value).lower()}\n"
190
+ elif isinstance(value, (int, float)) and value is not None:
191
+ toml_str += f"{indent}{key} = {value}\n"
192
+ elif isinstance(value, (list, tuple, set)) and not value:
193
+ toml_str += f"{indent}{key} = []\n"
194
+ elif isinstance(value, (list, tuple, set)):
195
+ if not any(isinstance(sub_item, dict) for sub_item in value):
196
+ toml_str += f"{indent}{key} = [\n"
197
+ for sub_item in value:
198
+ if isinstance(sub_item, str):
199
+ toml_str += f"{indent}{indent}{self._format_string_value(sub_item)},\n"
200
+ else:
201
+ toml_str += f"{indent}{indent}{sub_item},\n"
202
+ toml_str += f"{indent}]\n"
203
+
204
+ # Process nested dicts in table items
205
+ for key, value in item.items():
206
+ if isinstance(value, dict):
207
+ nested_section = f"{section_key}.{key}"
208
+ toml_str += f"[{nested_section}]\n"
209
+ toml_str += self._process_section_content(value, nested_section)
210
+
211
+ return toml_str
@@ -114,10 +114,12 @@ class Type_Safe__Method:
114
114
  for i, item in enumerate(param_value): # Validate each dict in list
115
115
  if not isinstance(item, dict): # Check item is a dict
116
116
  raise ValueError(f"List item at index {i} expected dict but got {type(item)}") # Raise error if not dict
117
+ if key_type is Any and value_type is Any: # Skip validation if both are Any
118
+ continue # No type checking needed
117
119
  for k, v in item.items(): # Validate dict contents
118
- if not isinstance(k, key_type): # Check key type
120
+ if key_type is not Any and not isinstance(k, key_type): # Check key type if not Any
119
121
  raise ValueError(f"Dict key '{k}' at index {i} expected type {key_type}, but got {type(k)}") # Raise error for invalid key
120
- if not isinstance(v, value_type): # Check value type
122
+ if value_type is not Any and not isinstance(v, value_type): # Check value type if not Any
121
123
  raise ValueError(f"Dict value for key '{k}' at index {i} expected type {value_type}, but got {type(v)}") # Raise error for invalid value
122
124
  elif item_origin is collections.abc.Callable: # Handle Callable[[...], ...] items
123
125
  for i, item in enumerate(param_value): # Validate each callable in list
osbot_utils/utils/Toml.py CHANGED
@@ -1,29 +1,11 @@
1
1
  import sys
2
- from osbot_utils.utils.Files import file_create, file_contents
3
- from osbot_utils.utils.Objects import dict_to_obj
4
-
5
-
6
- def dict_to_toml(data, indent_level=0):
7
- toml_str = ""
8
- indent = " " * (indent_level * 4)
9
-
10
- for key, value in data.items():
11
- if isinstance(value, dict):
12
- toml_str += f"{indent}[{key}]\n"
13
- toml_str += dict_to_toml(value, indent_level + 1)
14
- elif isinstance(value, (list, tuple, set)):
15
- toml_str += f"{indent}{key} = [\n"
16
- for item in value:
17
- toml_str += f"{indent} {repr(item)},\n"
18
- toml_str += f"{indent}]\n"
19
- elif isinstance(value, str):
20
- toml_str += f"{indent}{key} = '{value}'\n"
21
- elif isinstance(value, bool):
22
- toml_str += f"{indent}{key} = {str(value).lower()}\n"
23
- else:
24
- toml_str += f"{indent}{key} = {value}\n"
25
-
26
- return toml_str
2
+ from osbot_utils.helpers.transformers.Dict__To__Toml import Dict__To__Toml
3
+ from osbot_utils.utils.Files import file_create, file_contents
4
+ from osbot_utils.utils.Objects import dict_to_obj
5
+
6
+
7
+ def dict_to_toml(data):
8
+ return Dict__To__Toml().convert(data) # Singleton instance for convenience
27
9
 
28
10
  def toml_dict_to_file(toml_file, data):
29
11
  str_toml = dict_to_toml(data)
@@ -33,7 +15,6 @@ def toml_dict_from_file(toml_file):
33
15
  str_toml = file_contents(toml_file)
34
16
  return toml_to_dict(str_toml)
35
17
 
36
-
37
18
  def toml_to_dict(str_toml):
38
19
  if sys.version_info >= (3, 11):
39
20
  import tomllib
osbot_utils/version CHANGED
@@ -1 +1 @@
1
- v2.78.0
1
+ v2.80.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: osbot_utils
3
- Version: 2.78.0
3
+ Version: 2.80.0
4
4
  Summary: OWASP Security Bot - Utils
5
5
  License: MIT
6
6
  Author: Dinis Cruz
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  # OSBot-Utils
23
23
 
24
- ![Current Release](https://img.shields.io/badge/release-v2.78.0-blue)
24
+ ![Current Release](https://img.shields.io/badge/release-v2.80.0-blue)
25
25
  ![Python](https://img.shields.io/badge/python-3.8+-green)
26
26
  ![Type-Safe](https://img.shields.io/badge/Type--Safe-✓-brightgreen)
27
27
  ![Caching](https://img.shields.io/badge/Caching-Built--In-orange)
@@ -33,7 +33,7 @@ osbot_utils/fluent/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVG
33
33
  osbot_utils/helpers/CFormat.py,sha256=QviXlx3cQF7Wq6bNQmvWPHknDDMQXg5bh5dsRncrg-U,6805
34
34
  osbot_utils/helpers/CPrint.py,sha256=8iCkLaPozi3anzLQ-kPfJTtfLZdOrgpGNJs5UNjGJ9g,1346
35
35
  osbot_utils/helpers/Dependency_Manager.py,sha256=79YRYnVfchewq8iSMJ5dzwW2D5u8chWcIqYE-G9YrSo,1337
36
- osbot_utils/helpers/Dict_To_Attr.py,sha256=NdhXl5mJH7-NaBk213amzc5Nfy3tJgW-N_uYIRE4hoc,208
36
+ osbot_utils/helpers/Dict_To_Attr.py,sha256=OWAChOCTM03ECfL4ME7Y0026ZnfA7OmSTIWYKlW9Jmg,139
37
37
  osbot_utils/helpers/Hashicorp_Secrets.py,sha256=e2fWWHK6bubpAm1sw5y8X5kh2Hk5d4JyZCnUovZip5A,4232
38
38
  osbot_utils/helpers/Local_Cache.py,sha256=67qmeVXUBhfqLUQhjFBE9Pjt5dcjpQYhqCTGQNWgVSU,3232
39
39
  osbot_utils/helpers/Local_Caches.py,sha256=LUeNJX07Ccnp1SIhYvhqqQFVF6r_Ez7bWha8ssomuvY,1957
@@ -306,6 +306,7 @@ osbot_utils/helpers/trace/Trace_Call__Stats.py,sha256=gmiotIrOXe2ssxodzQQ56t8eGT
306
306
  osbot_utils/helpers/trace/Trace_Call__View_Model.py,sha256=a40nn6agCEMd2ecsJ93n8vXij0omh0D69QilqwmN_ao,4545
307
307
  osbot_utils/helpers/trace/Trace_Files.py,sha256=SNpAmuBlSUS9NyVocgZ5vevzqVaIqoh622yZge3a53A,978
308
308
  osbot_utils/helpers/trace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
309
+ osbot_utils/helpers/transformers/Dict__To__Toml.py,sha256=x_5_9xwS6ytSVzpr74AJOjYTnrkTCLPb8MoHyRkWuvA,10791
309
310
  osbot_utils/helpers/xml/Xml__Attribute.py,sha256=_dIVyp0WHfdv306vAj5bpEtiqKa83MLKRH925rjKa94,145
310
311
  osbot_utils/helpers/xml/Xml__Element.py,sha256=NLRdiTsRhqRf0I0ScAdN-tHuSh2qNuKP_tldx7iiSv4,868
311
312
  osbot_utils/helpers/xml/Xml__File.py,sha256=ECR4WD57ePyA88uioKVt5GVbWXddM_Y1OsWAJNzAg74,420
@@ -416,7 +417,7 @@ osbot_utils/type_safe/type_safe_core/collections/Type_Safe__Tuple.py,sha256=Kx7C
416
417
  osbot_utils/type_safe/type_safe_core/collections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
417
418
  osbot_utils/type_safe/type_safe_core/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
418
419
  osbot_utils/type_safe/type_safe_core/decorators/type_safe.py,sha256=wOTMvZBl5hWMz-HuIRZpPBGN0oSbXWd9wH5xxZkfLlA,1796
419
- osbot_utils/type_safe/type_safe_core/methods/Type_Safe__Method.py,sha256=-EHM5goVg-CvVufeMFi_jUSTldpla4FQiq6XO5ZJtUs,18776
420
+ osbot_utils/type_safe/type_safe_core/methods/Type_Safe__Method.py,sha256=J5hKfNdF2CPiaiC2Sb1qVd_VwfZcavo77DQ48NOUS50,19030
420
421
  osbot_utils/type_safe/type_safe_core/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
421
422
  osbot_utils/type_safe/type_safe_core/methods/type_safe_property.py,sha256=DcJkOIs6swJtkglsZVKLyFSczCGSJISOVwAmvjCOQvo,1425
422
423
  osbot_utils/type_safe/type_safe_core/shared/Type_Safe__Annotations.py,sha256=kabSiRPYPjpMBJxfjDB5AFRTx-hX17tOznZAd_qQID4,1147
@@ -464,12 +465,12 @@ osbot_utils/utils/Regex.py,sha256=MtHhk69ax7Nwu4CQZK7y4KXHZ6VREwEpIchuioB168c,96
464
465
  osbot_utils/utils/Status.py,sha256=OjqLwUhHqY-j-JeRN-hIaVZQHPRdyjR7y6i6ujsB-Yc,4287
465
466
  osbot_utils/utils/Str.py,sha256=KQVfh0o3BxJKVm24yhAhgIGH5QYfzpP1G-siVv2zQws,3301
466
467
  osbot_utils/utils/Threads.py,sha256=YI1T382AtJpHVsa-BK7SycmEYhnkqHIiYyK_5HSmUtw,4329
467
- osbot_utils/utils/Toml.py,sha256=Rxl8gx7mni5CvBAK-Ai02EKw-GwtJdd3yeHT2kMloik,1667
468
+ osbot_utils/utils/Toml.py,sha256=grjWkVPIMVkawJ499FVIJKxQp8FJ2wcsd0Z3YIR4drM,1148
468
469
  osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
469
470
  osbot_utils/utils/Zip.py,sha256=mG42lgTY0tnm14T3P1-DSAIZKkTiYoO3odZ1aOUdc1I,14394
470
471
  osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
471
- osbot_utils/version,sha256=FLt36i-Z9JlSrMuWB_X-ax-Q6m5iqCavlXxD3VyyqsY,8
472
- osbot_utils-2.78.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
473
- osbot_utils-2.78.0.dist-info/METADATA,sha256=eCXicYRH0eHxjGxH6YOcx4ivHlHr2x70aoYujqC_wPs,7918
474
- osbot_utils-2.78.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
475
- osbot_utils-2.78.0.dist-info/RECORD,,
472
+ osbot_utils/version,sha256=OvhuEsV_06ng4M340O91vaNZ7flMDhiGOxhvz2bAo34,8
473
+ osbot_utils-2.80.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
474
+ osbot_utils-2.80.0.dist-info/METADATA,sha256=7S4iF13GsJEefH8A1UQ82gfoVP83vDdw_JLCoJWCMWQ,7918
475
+ osbot_utils-2.80.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
476
+ osbot_utils-2.80.0.dist-info/RECORD,,