osbot-utils 2.77.0__py3-none-any.whl → 2.79.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
@@ -1,3 +1,4 @@
1
+ import collections
1
2
  import inspect # For function introspection
2
3
  from enum import Enum
3
4
  from typing import get_args, get_origin, Union, List, Any, Dict # For type hinting utilities
@@ -105,14 +106,32 @@ class Type_Safe__Method:
105
106
  if not isinstance(param_value, list): # Check if value is a list
106
107
  raise ValueError(f"Parameter '{param_name}' expected a list but got {type(param_value)}") # Raise error if not list
107
108
 
108
- item_type = get_args(expected_type)[0] # Get list item type
109
- if get_origin(item_type) is not None: # Handle the case when item_type is a subscripted type (which is not supported at the moment)
109
+ item_type = get_args(expected_type)[0] # Get list item type
110
+ item_origin = get_origin(item_type) # Get origin of item type
111
+
112
+ if item_origin is dict or item_origin is Dict: # Handle Dict[K, V] items
113
+ key_type, value_type = get_args(item_type) # Extract key and value types
114
+ for i, item in enumerate(param_value): # Validate each dict in list
115
+ if not isinstance(item, dict): # Check item is a dict
116
+ raise ValueError(f"List item at index {i} expected dict but got {type(item)}") # Raise error if not dict
117
+ for k, v in item.items(): # Validate dict contents
118
+ if not isinstance(k, key_type): # Check key type
119
+ 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
121
+ 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
+ elif item_origin is collections.abc.Callable: # Handle Callable[[...], ...] items
123
+ for i, item in enumerate(param_value): # Validate each callable in list
124
+ if not callable(item): # Check item is callable
125
+ raise ValueError(f"List item at index {i} expected callable but got {type(item)}") # Raise error if not callable
126
+ # Note: Full signature validation would require is_callable_compatible method
127
+ elif item_origin is not None: # Handle other subscripted types
110
128
  raise NotImplementedError(f"Validation for list items with subscripted type"
111
129
  f" '{item_type}' is not yet supported "
112
- f"in parameter '{param_name}'.") # todo: add support for checking for subscripted types
113
- for i, item in enumerate(param_value): # Check each list item
114
- if not isinstance(item, item_type): # Validate item type
115
- raise ValueError(f"List item at index {i} expected type {item_type}, but got {type(item)}") # Raise error for invalid item
130
+ f"in parameter '{param_name}'.") # todo: add support for checking for subscripted types
131
+ else: # Handle non-subscripted types
132
+ for i, item in enumerate(param_value): # Check each list item
133
+ if not isinstance(item, item_type): # Validate item type
134
+ raise ValueError(f"List item at index {i} expected type {item_type}, but got {type(item)}") # Raise error for invalid item
116
135
 
117
136
  def validate_type_parameter(self, param_name: str, param_value: Any, expected_type: Any): # Validate a Type[T] parameter
118
137
  if not isinstance(param_value, type):
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.77.0
1
+ v2.79.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: osbot_utils
3
- Version: 2.77.0
3
+ Version: 2.79.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.77.0-blue)
24
+ ![Current Release](https://img.shields.io/badge/release-v2.79.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=5Kf6vRrU_SRV88dN7HRtM-OdT2ZeoNVy5KLhbs0Cz0Q,16807
420
+ osbot_utils/type_safe/type_safe_core/methods/Type_Safe__Method.py,sha256=-EHM5goVg-CvVufeMFi_jUSTldpla4FQiq6XO5ZJtUs,18776
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=Ee_VvYF0FneCzkUtpqDTXDcXCaviuUXqsJ-W8pEco9k,8
472
- osbot_utils-2.77.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
473
- osbot_utils-2.77.0.dist-info/METADATA,sha256=WX8X7ud_stauh0dxr7wvg7IPLgbn5-0X1Nm993_tUQc,7918
474
- osbot_utils-2.77.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
475
- osbot_utils-2.77.0.dist-info/RECORD,,
472
+ osbot_utils/version,sha256=LBnSHfeuqdNUDCaJ0l1-HSUQy_-YezgRfKFIWsPg7Q4,8
473
+ osbot_utils-2.79.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
474
+ osbot_utils-2.79.0.dist-info/METADATA,sha256=EYa9xfe3-uAcOGHcGHDaX0LIrRtztORvnB_PJ8cS-G0,7918
475
+ osbot_utils-2.79.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
476
+ osbot_utils-2.79.0.dist-info/RECORD,,