pomera-ai-commander 0.1.0 → 1.2.1

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.
Files changed (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -680
  3. package/bin/pomera-ai-commander.js +62 -62
  4. package/core/__init__.py +65 -65
  5. package/core/app_context.py +482 -482
  6. package/core/async_text_processor.py +421 -421
  7. package/core/backup_manager.py +655 -655
  8. package/core/backup_recovery_manager.py +1033 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_validator.py +1066 -1066
  12. package/core/database_connection_manager.py +744 -744
  13. package/core/database_curl_settings_manager.py +608 -608
  14. package/core/database_promera_ai_settings_manager.py +446 -446
  15. package/core/database_schema.py +411 -411
  16. package/core/database_schema_manager.py +395 -395
  17. package/core/database_settings_manager.py +1507 -1507
  18. package/core/database_settings_manager_interface.py +456 -456
  19. package/core/dialog_manager.py +734 -734
  20. package/core/efficient_line_numbers.py +510 -510
  21. package/core/error_handler.py +746 -746
  22. package/core/error_service.py +431 -431
  23. package/core/event_consolidator.py +511 -511
  24. package/core/mcp/__init__.py +43 -43
  25. package/core/mcp/protocol.py +288 -288
  26. package/core/mcp/schema.py +251 -251
  27. package/core/mcp/server_stdio.py +299 -299
  28. package/core/mcp/tool_registry.py +2372 -2345
  29. package/core/memory_efficient_text_widget.py +711 -711
  30. package/core/migration_manager.py +914 -914
  31. package/core/migration_test_suite.py +1085 -1085
  32. package/core/migration_validator.py +1143 -1143
  33. package/core/optimized_find_replace.py +714 -714
  34. package/core/optimized_pattern_engine.py +424 -424
  35. package/core/optimized_search_highlighter.py +552 -552
  36. package/core/performance_monitor.py +674 -674
  37. package/core/persistence_manager.py +712 -712
  38. package/core/progressive_stats_calculator.py +632 -632
  39. package/core/regex_pattern_cache.py +529 -529
  40. package/core/regex_pattern_library.py +350 -350
  41. package/core/search_operation_manager.py +434 -434
  42. package/core/settings_defaults_registry.py +1087 -1087
  43. package/core/settings_integrity_validator.py +1111 -1111
  44. package/core/settings_serializer.py +557 -557
  45. package/core/settings_validator.py +1823 -1823
  46. package/core/smart_stats_calculator.py +709 -709
  47. package/core/statistics_update_manager.py +619 -619
  48. package/core/stats_config_manager.py +858 -858
  49. package/core/streaming_text_handler.py +723 -723
  50. package/core/task_scheduler.py +596 -596
  51. package/core/update_pattern_library.py +168 -168
  52. package/core/visibility_monitor.py +596 -596
  53. package/core/widget_cache.py +498 -498
  54. package/mcp.json +51 -61
  55. package/package.json +61 -57
  56. package/pomera.py +7482 -7482
  57. package/pomera_mcp_server.py +183 -144
  58. package/requirements.txt +32 -0
  59. package/tools/__init__.py +4 -4
  60. package/tools/ai_tools.py +2891 -2891
  61. package/tools/ascii_art_generator.py +352 -352
  62. package/tools/base64_tools.py +183 -183
  63. package/tools/base_tool.py +511 -511
  64. package/tools/case_tool.py +308 -308
  65. package/tools/column_tools.py +395 -395
  66. package/tools/cron_tool.py +884 -884
  67. package/tools/curl_history.py +600 -600
  68. package/tools/curl_processor.py +1207 -1207
  69. package/tools/curl_settings.py +502 -502
  70. package/tools/curl_tool.py +5467 -5467
  71. package/tools/diff_viewer.py +1071 -1071
  72. package/tools/email_extraction_tool.py +248 -248
  73. package/tools/email_header_analyzer.py +425 -425
  74. package/tools/extraction_tools.py +250 -250
  75. package/tools/find_replace.py +1750 -1750
  76. package/tools/folder_file_reporter.py +1463 -1463
  77. package/tools/folder_file_reporter_adapter.py +480 -480
  78. package/tools/generator_tools.py +1216 -1216
  79. package/tools/hash_generator.py +255 -255
  80. package/tools/html_tool.py +656 -656
  81. package/tools/jsonxml_tool.py +729 -729
  82. package/tools/line_tools.py +419 -419
  83. package/tools/markdown_tools.py +561 -561
  84. package/tools/mcp_widget.py +1417 -1417
  85. package/tools/notes_widget.py +973 -973
  86. package/tools/number_base_converter.py +372 -372
  87. package/tools/regex_extractor.py +571 -571
  88. package/tools/slug_generator.py +310 -310
  89. package/tools/sorter_tools.py +458 -458
  90. package/tools/string_escape_tool.py +392 -392
  91. package/tools/text_statistics_tool.py +365 -365
  92. package/tools/text_wrapper.py +430 -430
  93. package/tools/timestamp_converter.py +421 -421
  94. package/tools/tool_loader.py +710 -710
  95. package/tools/translator_tools.py +522 -522
  96. package/tools/url_link_extractor.py +261 -261
  97. package/tools/url_parser.py +204 -204
  98. package/tools/whitespace_tools.py +355 -355
  99. package/tools/word_frequency_counter.py +146 -146
  100. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  102. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  103. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  104. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  105. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  106. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  107. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  108. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  109. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  110. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  111. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  112. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  113. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  114. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  115. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  116. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  117. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  118. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  119. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  120. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  121. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  122. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  123. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  124. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  125. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  126. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  127. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  128. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  129. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  131. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  132. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  134. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  135. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  136. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  137. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  138. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  139. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  140. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  141. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  142. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  143. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  144. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  145. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  146. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  147. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  148. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  151. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  152. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  153. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  154. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  155. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  156. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  157. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  158. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  159. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  160. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  161. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  162. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  163. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  164. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  165. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  166. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  167. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  168. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  169. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  170. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -1,558 +1,558 @@
1
- """
2
- Settings Serialization System for Database Migration
3
-
4
- This module provides type-aware serialization for complex data structures,
5
- handling all settings types found in the production codebase analysis.
6
- Supports simple types, nested objects, arrays, encrypted API keys, and
7
- platform-specific settings with proper fallback mechanisms.
8
-
9
- Based on analysis of 45 production Python files with 579+ config operations.
10
- """
11
-
12
- import json
13
- import re
14
- import logging
15
- from typing import Any, Dict, List, Tuple, Optional, Union
16
- from datetime import datetime
17
- from pathlib import Path
18
-
19
-
20
- class SettingsSerializer:
21
- """
22
- Type-aware serializer for settings data with support for complex structures.
23
-
24
- Features:
25
- - Simple type handling (str, int, float, bool) with type annotations
26
- - JSON serialization for nested objects and arrays
27
- - Special handling for encrypted API keys with "ENC:" prefix preservation
28
- - Nested path notation support (e.g., "async_processing.enabled")
29
- - Platform-specific settings with fallback mechanisms
30
- - Data integrity validation and error handling
31
- """
32
-
33
- def __init__(self):
34
- """Initialize the settings serializer."""
35
- self.logger = logging.getLogger(__name__)
36
-
37
- # Encryption prefix pattern for API keys
38
- self.encryption_prefix = "ENC:"
39
- self.encryption_pattern = re.compile(r'^ENC:[A-Za-z0-9+/=]+$')
40
-
41
- # Platform-specific fallback patterns
42
- self.platform_fallback_keys = {
43
- 'fallback_family_mac',
44
- 'fallback_family_linux',
45
- 'fallback_family_windows'
46
- }
47
-
48
- # Nested path separator
49
- self.path_separator = "."
50
-
51
- # Type mapping for serialization
52
- self.type_mappings = {
53
- str: 'str',
54
- int: 'int',
55
- float: 'float',
56
- bool: 'bool',
57
- list: 'array',
58
- dict: 'json',
59
- type(None): 'json'
60
- }
61
-
62
- def serialize_value(self, value: Any) -> Tuple[str, str]:
63
- """
64
- Serialize a Python value to string with type annotation.
65
-
66
- Args:
67
- value: Python value to serialize
68
-
69
- Returns:
70
- Tuple of (serialized_string, data_type)
71
- """
72
- try:
73
- # Handle None values
74
- if value is None:
75
- return json.dumps(None), 'json'
76
-
77
- # Handle simple types
78
- if isinstance(value, str):
79
- return self._serialize_string(value)
80
- elif isinstance(value, bool): # Check bool before int (bool is subclass of int)
81
- return self._serialize_bool(value)
82
- elif isinstance(value, int):
83
- return self._serialize_int(value)
84
- elif isinstance(value, float):
85
- return self._serialize_float(value)
86
- elif isinstance(value, list):
87
- return self._serialize_array(value)
88
- elif isinstance(value, dict):
89
- return self._serialize_dict(value)
90
- else:
91
- # Fallback to JSON for unknown types
92
- return json.dumps(value, ensure_ascii=False, default=str), 'json'
93
-
94
- except Exception as e:
95
- self.logger.error(f"Serialization failed for value {repr(value)}: {e}")
96
- # Fallback to string representation
97
- return str(value), 'str'
98
-
99
- def _serialize_string(self, value: str) -> Tuple[str, str]:
100
- """
101
- Serialize string value with special handling for encrypted data.
102
-
103
- Args:
104
- value: String value to serialize
105
-
106
- Returns:
107
- Tuple of (serialized_string, data_type)
108
- """
109
- # Preserve encrypted API keys as-is
110
- if self._is_encrypted_value(value):
111
- self.logger.debug(f"Preserving encrypted value: {value[:10]}...")
112
- return value, 'str'
113
-
114
- # Regular string - store as-is
115
- return value, 'str'
116
-
117
- def _serialize_bool(self, value: bool) -> Tuple[str, str]:
118
- """
119
- Serialize boolean value to consistent string representation.
120
-
121
- Args:
122
- value: Boolean value to serialize
123
-
124
- Returns:
125
- Tuple of (serialized_string, data_type)
126
- """
127
- return '1' if value else '0', 'bool'
128
-
129
- def _serialize_int(self, value: int) -> Tuple[str, str]:
130
- """
131
- Serialize integer value to string.
132
-
133
- Args:
134
- value: Integer value to serialize
135
-
136
- Returns:
137
- Tuple of (serialized_string, data_type)
138
- """
139
- return str(value), 'int'
140
-
141
- def _serialize_float(self, value: float) -> Tuple[str, str]:
142
- """
143
- Serialize float value to string with precision preservation.
144
-
145
- Args:
146
- value: Float value to serialize
147
-
148
- Returns:
149
- Tuple of (serialized_string, data_type)
150
- """
151
- return str(value), 'float'
152
-
153
- def _serialize_array(self, value: List[Any]) -> Tuple[str, str]:
154
- """
155
- Serialize array/list to JSON string.
156
-
157
- Args:
158
- value: List value to serialize
159
-
160
- Returns:
161
- Tuple of (serialized_string, data_type)
162
- """
163
- try:
164
- # Use compact JSON representation for arrays
165
- json_str = json.dumps(value, ensure_ascii=False, separators=(',', ':'))
166
- return json_str, 'array'
167
- except (TypeError, ValueError) as e:
168
- self.logger.warning(f"Array serialization failed, using fallback: {e}")
169
- # Fallback to string representation
170
- return str(value), 'str'
171
-
172
- def _serialize_dict(self, value: Dict[str, Any]) -> Tuple[str, str]:
173
- """
174
- Serialize dictionary/object to JSON string.
175
-
176
- Args:
177
- value: Dictionary value to serialize
178
-
179
- Returns:
180
- Tuple of (serialized_string, data_type)
181
- """
182
- try:
183
- # Use pretty JSON for readability of complex objects
184
- json_str = json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True)
185
- return json_str, 'json'
186
- except (TypeError, ValueError) as e:
187
- self.logger.warning(f"Dict serialization failed, using fallback: {e}")
188
- # Fallback to string representation
189
- return str(value), 'str'
190
-
191
- def deserialize_value(self, value_str: str, data_type: str) -> Any:
192
- """
193
- Deserialize string value back to Python type.
194
-
195
- Args:
196
- value_str: Serialized string value
197
- data_type: Type annotation ('str', 'int', 'float', 'bool', 'json', 'array')
198
-
199
- Returns:
200
- Python value in appropriate type
201
- """
202
- try:
203
- if data_type == 'str':
204
- return value_str
205
- elif data_type == 'int':
206
- return int(value_str)
207
- elif data_type == 'float':
208
- return float(value_str)
209
- elif data_type == 'bool':
210
- return value_str == '1'
211
- elif data_type in ('json', 'array'):
212
- return json.loads(value_str)
213
- else:
214
- self.logger.warning(f"Unknown data type '{data_type}', returning as string")
215
- return value_str
216
-
217
- except (ValueError, TypeError, json.JSONDecodeError) as e:
218
- self.logger.error(f"Deserialization failed for '{value_str}' as {data_type}: {e}")
219
- # Fallback to original string
220
- return value_str
221
-
222
- def _is_encrypted_value(self, value: str) -> bool:
223
- """
224
- Check if a string value is an encrypted API key.
225
-
226
- Args:
227
- value: String value to check
228
-
229
- Returns:
230
- True if value appears to be encrypted, False otherwise
231
- """
232
- return bool(self.encryption_pattern.match(value))
233
-
234
- def flatten_nested_dict(self, data: Dict[str, Any], parent_key: str = '') -> Dict[str, Any]:
235
- """
236
- Flatten nested dictionary using dot notation for keys.
237
-
238
- Args:
239
- data: Dictionary to flatten
240
- parent_key: Parent key prefix for nested keys
241
-
242
- Returns:
243
- Flattened dictionary with dot-notation keys
244
- """
245
- items = []
246
-
247
- for key, value in data.items():
248
- # Create full key path
249
- full_key = f"{parent_key}{self.path_separator}{key}" if parent_key else key
250
-
251
- if isinstance(value, dict) and not self._is_special_dict(value):
252
- # Recursively flatten nested dictionaries
253
- items.extend(self.flatten_nested_dict(value, full_key).items())
254
- else:
255
- # Store value as-is (including complex dicts that should stay together)
256
- items.append((full_key, value))
257
-
258
- return dict(items)
259
-
260
- def unflatten_nested_dict(self, flat_data: Dict[str, Any]) -> Dict[str, Any]:
261
- """
262
- Reconstruct nested dictionary from flattened dot-notation keys.
263
-
264
- Args:
265
- flat_data: Flattened dictionary with dot-notation keys
266
-
267
- Returns:
268
- Nested dictionary structure
269
- """
270
- result = {}
271
-
272
- for key, value in flat_data.items():
273
- # Split key path
274
- key_parts = key.split(self.path_separator)
275
-
276
- # Navigate/create nested structure
277
- current = result
278
- for part in key_parts[:-1]:
279
- if part not in current:
280
- current[part] = {}
281
- current = current[part]
282
-
283
- # Set final value
284
- current[key_parts[-1]] = value
285
-
286
- return result
287
-
288
- def _is_special_dict(self, value: Dict[str, Any]) -> bool:
289
- """
290
- Check if a dictionary should be kept as a single JSON object.
291
-
292
- Some dictionaries (like cURL history entries, AI model configs)
293
- should be stored as complete JSON objects rather than flattened.
294
-
295
- Args:
296
- value: Dictionary to check
297
-
298
- Returns:
299
- True if dict should be kept as JSON object, False if it should be flattened
300
- """
301
- # Keep small dictionaries as JSON objects
302
- if len(value) <= 3:
303
- return True
304
-
305
- # Keep dictionaries with array values as JSON objects
306
- if any(isinstance(v, list) for v in value.values()):
307
- return True
308
-
309
- # Keep dictionaries with complex nested structures as JSON objects
310
- nested_depth = self._get_dict_depth(value)
311
- if nested_depth > 2:
312
- return True
313
-
314
- # Keep dictionaries that look like configuration objects
315
- config_indicators = {
316
- 'timestamp', 'created_at', 'updated_at', 'id', 'type', 'status',
317
- 'method', 'url', 'headers', 'body', 'response', 'auth_type'
318
- }
319
- if any(key in config_indicators for key in value.keys()):
320
- return True
321
-
322
- return False
323
-
324
- def _get_dict_depth(self, d: Dict[str, Any], depth: int = 0) -> int:
325
- """
326
- Calculate the maximum nesting depth of a dictionary.
327
-
328
- Args:
329
- d: Dictionary to analyze
330
- depth: Current depth level
331
-
332
- Returns:
333
- Maximum nesting depth
334
- """
335
- if not isinstance(d, dict) or not d:
336
- return depth
337
-
338
- return max(self._get_dict_depth(v, depth + 1) if isinstance(v, dict) else depth + 1
339
- for v in d.values())
340
-
341
- def serialize_tool_settings(self, tool_name: str, settings: Dict[str, Any]) -> List[Tuple[str, str, str, str]]:
342
- """
343
- Serialize tool settings to database format with nested path support.
344
-
345
- Args:
346
- tool_name: Name of the tool
347
- settings: Tool settings dictionary
348
-
349
- Returns:
350
- List of tuples (tool_name, setting_path, serialized_value, data_type)
351
- """
352
- results = []
353
-
354
- try:
355
- # Flatten nested settings
356
- flat_settings = self.flatten_nested_dict(settings)
357
-
358
- for setting_path, value in flat_settings.items():
359
- serialized_value, data_type = self.serialize_value(value)
360
- results.append((tool_name, setting_path, serialized_value, data_type))
361
-
362
- self.logger.debug(f"Serialized {len(results)} settings for tool '{tool_name}'")
363
- return results
364
-
365
- except Exception as e:
366
- self.logger.error(f"Failed to serialize tool settings for '{tool_name}': {e}")
367
- return []
368
-
369
- def deserialize_tool_settings(self, settings_data: List[Tuple[str, str, str]]) -> Dict[str, Any]:
370
- """
371
- Deserialize tool settings from database format back to nested dictionary.
372
-
373
- Args:
374
- settings_data: List of tuples (setting_path, serialized_value, data_type)
375
-
376
- Returns:
377
- Nested dictionary with tool settings
378
- """
379
- try:
380
- flat_settings = {}
381
-
382
- for setting_path, serialized_value, data_type in settings_data:
383
- value = self.deserialize_value(serialized_value, data_type)
384
- flat_settings[setting_path] = value
385
-
386
- # Reconstruct nested structure
387
- nested_settings = self.unflatten_nested_dict(flat_settings)
388
-
389
- self.logger.debug(f"Deserialized {len(settings_data)} settings to nested structure")
390
- return nested_settings
391
-
392
- except Exception as e:
393
- self.logger.error(f"Failed to deserialize tool settings: {e}")
394
- return {}
395
-
396
- def handle_platform_specific_settings(self, settings: Dict[str, Any], current_platform: str = None) -> Dict[str, Any]:
397
- """
398
- Handle platform-specific settings with fallback mechanisms.
399
-
400
- Args:
401
- settings: Settings dictionary that may contain platform-specific keys
402
- current_platform: Current platform ('windows', 'mac', 'linux')
403
-
404
- Returns:
405
- Settings dictionary with platform-specific fallbacks resolved
406
- """
407
- if current_platform is None:
408
- import platform
409
- system = platform.system().lower()
410
- current_platform = {
411
- 'darwin': 'mac',
412
- 'windows': 'windows',
413
- 'linux': 'linux'
414
- }.get(system, 'windows')
415
-
416
- result = settings.copy()
417
-
418
- # Process font settings with platform fallbacks
419
- if 'font_settings' in result:
420
- result['font_settings'] = self._resolve_font_fallbacks(
421
- result['font_settings'], current_platform
422
- )
423
-
424
- # Process any other platform-specific settings
425
- result = self._resolve_platform_fallbacks(result, current_platform)
426
-
427
- return result
428
-
429
- def _resolve_font_fallbacks(self, font_settings: Dict[str, Any], platform: str) -> Dict[str, Any]:
430
- """
431
- Resolve font fallbacks for the current platform.
432
-
433
- Args:
434
- font_settings: Font settings dictionary
435
- platform: Current platform ('windows', 'mac', 'linux')
436
-
437
- Returns:
438
- Font settings with resolved fallbacks
439
- """
440
- result = font_settings.copy()
441
-
442
- for font_type, font_config in result.items():
443
- if isinstance(font_config, dict):
444
- # Check for platform-specific fallback
445
- fallback_key = f'fallback_family_{platform}'
446
- if fallback_key in font_config:
447
- # Use platform-specific fallback as primary fallback
448
- font_config['fallback_family'] = font_config[fallback_key]
449
-
450
- # Clean up platform-specific keys if desired
451
- # (Keep them for now to maintain compatibility)
452
-
453
- return result
454
-
455
- def _resolve_platform_fallbacks(self, settings: Dict[str, Any], platform: str) -> Dict[str, Any]:
456
- """
457
- Resolve platform-specific fallbacks throughout settings.
458
-
459
- Args:
460
- settings: Settings dictionary
461
- platform: Current platform
462
-
463
- Returns:
464
- Settings with platform fallbacks resolved
465
- """
466
- # For now, just return as-is since most platform-specific handling
467
- # is in font settings. This can be extended for other platform-specific
468
- # settings as they are identified.
469
- return settings
470
-
471
- def validate_serialized_data(self, original: Any, serialized: str, data_type: str) -> bool:
472
- """
473
- Validate that serialized data can be correctly deserialized.
474
-
475
- Args:
476
- original: Original Python value
477
- serialized: Serialized string representation
478
- data_type: Data type annotation
479
-
480
- Returns:
481
- True if serialization is valid, False otherwise
482
- """
483
- try:
484
- deserialized = self.deserialize_value(serialized, data_type)
485
-
486
- # For basic types, check exact equality
487
- if data_type in ('str', 'int', 'float', 'bool'):
488
- return original == deserialized
489
-
490
- # For complex types, check structural equality
491
- elif data_type in ('json', 'array'):
492
- return self._deep_equal(original, deserialized)
493
-
494
- return True
495
-
496
- except Exception as e:
497
- self.logger.error(f"Validation failed for {data_type} value: {e}")
498
- return False
499
-
500
- def _deep_equal(self, obj1: Any, obj2: Any) -> bool:
501
- """
502
- Deep equality check for complex objects.
503
-
504
- Args:
505
- obj1: First object to compare
506
- obj2: Second object to compare
507
-
508
- Returns:
509
- True if objects are deeply equal, False otherwise
510
- """
511
- try:
512
- # Use JSON serialization for comparison to handle nested structures
513
- json1 = json.dumps(obj1, sort_keys=True, default=str)
514
- json2 = json.dumps(obj2, sort_keys=True, default=str)
515
- return json1 == json2
516
- except Exception:
517
- # Fallback to direct comparison
518
- return obj1 == obj2
519
-
520
-
521
- # Convenience functions for common serialization tasks
522
- def serialize_settings_dict(settings: Dict[str, Any]) -> Dict[str, Tuple[str, str]]:
523
- """
524
- Serialize an entire settings dictionary.
525
-
526
- Args:
527
- settings: Settings dictionary to serialize
528
-
529
- Returns:
530
- Dictionary mapping keys to (serialized_value, data_type) tuples
531
- """
532
- serializer = SettingsSerializer()
533
- result = {}
534
-
535
- for key, value in settings.items():
536
- serialized_value, data_type = serializer.serialize_value(value)
537
- result[key] = (serialized_value, data_type)
538
-
539
- return result
540
-
541
-
542
- def deserialize_settings_dict(serialized_settings: Dict[str, Tuple[str, str]]) -> Dict[str, Any]:
543
- """
544
- Deserialize a settings dictionary from serialized format.
545
-
546
- Args:
547
- serialized_settings: Dictionary mapping keys to (serialized_value, data_type) tuples
548
-
549
- Returns:
550
- Deserialized settings dictionary
551
- """
552
- serializer = SettingsSerializer()
553
- result = {}
554
-
555
- for key, (serialized_value, data_type) in serialized_settings.items():
556
- result[key] = serializer.deserialize_value(serialized_value, data_type)
557
-
1
+ """
2
+ Settings Serialization System for Database Migration
3
+
4
+ This module provides type-aware serialization for complex data structures,
5
+ handling all settings types found in the production codebase analysis.
6
+ Supports simple types, nested objects, arrays, encrypted API keys, and
7
+ platform-specific settings with proper fallback mechanisms.
8
+
9
+ Based on analysis of 45 production Python files with 579+ config operations.
10
+ """
11
+
12
+ import json
13
+ import re
14
+ import logging
15
+ from typing import Any, Dict, List, Tuple, Optional, Union
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+
19
+
20
+ class SettingsSerializer:
21
+ """
22
+ Type-aware serializer for settings data with support for complex structures.
23
+
24
+ Features:
25
+ - Simple type handling (str, int, float, bool) with type annotations
26
+ - JSON serialization for nested objects and arrays
27
+ - Special handling for encrypted API keys with "ENC:" prefix preservation
28
+ - Nested path notation support (e.g., "async_processing.enabled")
29
+ - Platform-specific settings with fallback mechanisms
30
+ - Data integrity validation and error handling
31
+ """
32
+
33
+ def __init__(self):
34
+ """Initialize the settings serializer."""
35
+ self.logger = logging.getLogger(__name__)
36
+
37
+ # Encryption prefix pattern for API keys
38
+ self.encryption_prefix = "ENC:"
39
+ self.encryption_pattern = re.compile(r'^ENC:[A-Za-z0-9+/=]+$')
40
+
41
+ # Platform-specific fallback patterns
42
+ self.platform_fallback_keys = {
43
+ 'fallback_family_mac',
44
+ 'fallback_family_linux',
45
+ 'fallback_family_windows'
46
+ }
47
+
48
+ # Nested path separator
49
+ self.path_separator = "."
50
+
51
+ # Type mapping for serialization
52
+ self.type_mappings = {
53
+ str: 'str',
54
+ int: 'int',
55
+ float: 'float',
56
+ bool: 'bool',
57
+ list: 'array',
58
+ dict: 'json',
59
+ type(None): 'json'
60
+ }
61
+
62
+ def serialize_value(self, value: Any) -> Tuple[str, str]:
63
+ """
64
+ Serialize a Python value to string with type annotation.
65
+
66
+ Args:
67
+ value: Python value to serialize
68
+
69
+ Returns:
70
+ Tuple of (serialized_string, data_type)
71
+ """
72
+ try:
73
+ # Handle None values
74
+ if value is None:
75
+ return json.dumps(None), 'json'
76
+
77
+ # Handle simple types
78
+ if isinstance(value, str):
79
+ return self._serialize_string(value)
80
+ elif isinstance(value, bool): # Check bool before int (bool is subclass of int)
81
+ return self._serialize_bool(value)
82
+ elif isinstance(value, int):
83
+ return self._serialize_int(value)
84
+ elif isinstance(value, float):
85
+ return self._serialize_float(value)
86
+ elif isinstance(value, list):
87
+ return self._serialize_array(value)
88
+ elif isinstance(value, dict):
89
+ return self._serialize_dict(value)
90
+ else:
91
+ # Fallback to JSON for unknown types
92
+ return json.dumps(value, ensure_ascii=False, default=str), 'json'
93
+
94
+ except Exception as e:
95
+ self.logger.error(f"Serialization failed for value {repr(value)}: {e}")
96
+ # Fallback to string representation
97
+ return str(value), 'str'
98
+
99
+ def _serialize_string(self, value: str) -> Tuple[str, str]:
100
+ """
101
+ Serialize string value with special handling for encrypted data.
102
+
103
+ Args:
104
+ value: String value to serialize
105
+
106
+ Returns:
107
+ Tuple of (serialized_string, data_type)
108
+ """
109
+ # Preserve encrypted API keys as-is
110
+ if self._is_encrypted_value(value):
111
+ self.logger.debug(f"Preserving encrypted value: {value[:10]}...")
112
+ return value, 'str'
113
+
114
+ # Regular string - store as-is
115
+ return value, 'str'
116
+
117
+ def _serialize_bool(self, value: bool) -> Tuple[str, str]:
118
+ """
119
+ Serialize boolean value to consistent string representation.
120
+
121
+ Args:
122
+ value: Boolean value to serialize
123
+
124
+ Returns:
125
+ Tuple of (serialized_string, data_type)
126
+ """
127
+ return '1' if value else '0', 'bool'
128
+
129
+ def _serialize_int(self, value: int) -> Tuple[str, str]:
130
+ """
131
+ Serialize integer value to string.
132
+
133
+ Args:
134
+ value: Integer value to serialize
135
+
136
+ Returns:
137
+ Tuple of (serialized_string, data_type)
138
+ """
139
+ return str(value), 'int'
140
+
141
+ def _serialize_float(self, value: float) -> Tuple[str, str]:
142
+ """
143
+ Serialize float value to string with precision preservation.
144
+
145
+ Args:
146
+ value: Float value to serialize
147
+
148
+ Returns:
149
+ Tuple of (serialized_string, data_type)
150
+ """
151
+ return str(value), 'float'
152
+
153
+ def _serialize_array(self, value: List[Any]) -> Tuple[str, str]:
154
+ """
155
+ Serialize array/list to JSON string.
156
+
157
+ Args:
158
+ value: List value to serialize
159
+
160
+ Returns:
161
+ Tuple of (serialized_string, data_type)
162
+ """
163
+ try:
164
+ # Use compact JSON representation for arrays
165
+ json_str = json.dumps(value, ensure_ascii=False, separators=(',', ':'))
166
+ return json_str, 'array'
167
+ except (TypeError, ValueError) as e:
168
+ self.logger.warning(f"Array serialization failed, using fallback: {e}")
169
+ # Fallback to string representation
170
+ return str(value), 'str'
171
+
172
+ def _serialize_dict(self, value: Dict[str, Any]) -> Tuple[str, str]:
173
+ """
174
+ Serialize dictionary/object to JSON string.
175
+
176
+ Args:
177
+ value: Dictionary value to serialize
178
+
179
+ Returns:
180
+ Tuple of (serialized_string, data_type)
181
+ """
182
+ try:
183
+ # Use pretty JSON for readability of complex objects
184
+ json_str = json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True)
185
+ return json_str, 'json'
186
+ except (TypeError, ValueError) as e:
187
+ self.logger.warning(f"Dict serialization failed, using fallback: {e}")
188
+ # Fallback to string representation
189
+ return str(value), 'str'
190
+
191
+ def deserialize_value(self, value_str: str, data_type: str) -> Any:
192
+ """
193
+ Deserialize string value back to Python type.
194
+
195
+ Args:
196
+ value_str: Serialized string value
197
+ data_type: Type annotation ('str', 'int', 'float', 'bool', 'json', 'array')
198
+
199
+ Returns:
200
+ Python value in appropriate type
201
+ """
202
+ try:
203
+ if data_type == 'str':
204
+ return value_str
205
+ elif data_type == 'int':
206
+ return int(value_str)
207
+ elif data_type == 'float':
208
+ return float(value_str)
209
+ elif data_type == 'bool':
210
+ return value_str == '1'
211
+ elif data_type in ('json', 'array'):
212
+ return json.loads(value_str)
213
+ else:
214
+ self.logger.warning(f"Unknown data type '{data_type}', returning as string")
215
+ return value_str
216
+
217
+ except (ValueError, TypeError, json.JSONDecodeError) as e:
218
+ self.logger.error(f"Deserialization failed for '{value_str}' as {data_type}: {e}")
219
+ # Fallback to original string
220
+ return value_str
221
+
222
+ def _is_encrypted_value(self, value: str) -> bool:
223
+ """
224
+ Check if a string value is an encrypted API key.
225
+
226
+ Args:
227
+ value: String value to check
228
+
229
+ Returns:
230
+ True if value appears to be encrypted, False otherwise
231
+ """
232
+ return bool(self.encryption_pattern.match(value))
233
+
234
+ def flatten_nested_dict(self, data: Dict[str, Any], parent_key: str = '') -> Dict[str, Any]:
235
+ """
236
+ Flatten nested dictionary using dot notation for keys.
237
+
238
+ Args:
239
+ data: Dictionary to flatten
240
+ parent_key: Parent key prefix for nested keys
241
+
242
+ Returns:
243
+ Flattened dictionary with dot-notation keys
244
+ """
245
+ items = []
246
+
247
+ for key, value in data.items():
248
+ # Create full key path
249
+ full_key = f"{parent_key}{self.path_separator}{key}" if parent_key else key
250
+
251
+ if isinstance(value, dict) and not self._is_special_dict(value):
252
+ # Recursively flatten nested dictionaries
253
+ items.extend(self.flatten_nested_dict(value, full_key).items())
254
+ else:
255
+ # Store value as-is (including complex dicts that should stay together)
256
+ items.append((full_key, value))
257
+
258
+ return dict(items)
259
+
260
+ def unflatten_nested_dict(self, flat_data: Dict[str, Any]) -> Dict[str, Any]:
261
+ """
262
+ Reconstruct nested dictionary from flattened dot-notation keys.
263
+
264
+ Args:
265
+ flat_data: Flattened dictionary with dot-notation keys
266
+
267
+ Returns:
268
+ Nested dictionary structure
269
+ """
270
+ result = {}
271
+
272
+ for key, value in flat_data.items():
273
+ # Split key path
274
+ key_parts = key.split(self.path_separator)
275
+
276
+ # Navigate/create nested structure
277
+ current = result
278
+ for part in key_parts[:-1]:
279
+ if part not in current:
280
+ current[part] = {}
281
+ current = current[part]
282
+
283
+ # Set final value
284
+ current[key_parts[-1]] = value
285
+
286
+ return result
287
+
288
+ def _is_special_dict(self, value: Dict[str, Any]) -> bool:
289
+ """
290
+ Check if a dictionary should be kept as a single JSON object.
291
+
292
+ Some dictionaries (like cURL history entries, AI model configs)
293
+ should be stored as complete JSON objects rather than flattened.
294
+
295
+ Args:
296
+ value: Dictionary to check
297
+
298
+ Returns:
299
+ True if dict should be kept as JSON object, False if it should be flattened
300
+ """
301
+ # Keep small dictionaries as JSON objects
302
+ if len(value) <= 3:
303
+ return True
304
+
305
+ # Keep dictionaries with array values as JSON objects
306
+ if any(isinstance(v, list) for v in value.values()):
307
+ return True
308
+
309
+ # Keep dictionaries with complex nested structures as JSON objects
310
+ nested_depth = self._get_dict_depth(value)
311
+ if nested_depth > 2:
312
+ return True
313
+
314
+ # Keep dictionaries that look like configuration objects
315
+ config_indicators = {
316
+ 'timestamp', 'created_at', 'updated_at', 'id', 'type', 'status',
317
+ 'method', 'url', 'headers', 'body', 'response', 'auth_type'
318
+ }
319
+ if any(key in config_indicators for key in value.keys()):
320
+ return True
321
+
322
+ return False
323
+
324
+ def _get_dict_depth(self, d: Dict[str, Any], depth: int = 0) -> int:
325
+ """
326
+ Calculate the maximum nesting depth of a dictionary.
327
+
328
+ Args:
329
+ d: Dictionary to analyze
330
+ depth: Current depth level
331
+
332
+ Returns:
333
+ Maximum nesting depth
334
+ """
335
+ if not isinstance(d, dict) or not d:
336
+ return depth
337
+
338
+ return max(self._get_dict_depth(v, depth + 1) if isinstance(v, dict) else depth + 1
339
+ for v in d.values())
340
+
341
+ def serialize_tool_settings(self, tool_name: str, settings: Dict[str, Any]) -> List[Tuple[str, str, str, str]]:
342
+ """
343
+ Serialize tool settings to database format with nested path support.
344
+
345
+ Args:
346
+ tool_name: Name of the tool
347
+ settings: Tool settings dictionary
348
+
349
+ Returns:
350
+ List of tuples (tool_name, setting_path, serialized_value, data_type)
351
+ """
352
+ results = []
353
+
354
+ try:
355
+ # Flatten nested settings
356
+ flat_settings = self.flatten_nested_dict(settings)
357
+
358
+ for setting_path, value in flat_settings.items():
359
+ serialized_value, data_type = self.serialize_value(value)
360
+ results.append((tool_name, setting_path, serialized_value, data_type))
361
+
362
+ self.logger.debug(f"Serialized {len(results)} settings for tool '{tool_name}'")
363
+ return results
364
+
365
+ except Exception as e:
366
+ self.logger.error(f"Failed to serialize tool settings for '{tool_name}': {e}")
367
+ return []
368
+
369
+ def deserialize_tool_settings(self, settings_data: List[Tuple[str, str, str]]) -> Dict[str, Any]:
370
+ """
371
+ Deserialize tool settings from database format back to nested dictionary.
372
+
373
+ Args:
374
+ settings_data: List of tuples (setting_path, serialized_value, data_type)
375
+
376
+ Returns:
377
+ Nested dictionary with tool settings
378
+ """
379
+ try:
380
+ flat_settings = {}
381
+
382
+ for setting_path, serialized_value, data_type in settings_data:
383
+ value = self.deserialize_value(serialized_value, data_type)
384
+ flat_settings[setting_path] = value
385
+
386
+ # Reconstruct nested structure
387
+ nested_settings = self.unflatten_nested_dict(flat_settings)
388
+
389
+ self.logger.debug(f"Deserialized {len(settings_data)} settings to nested structure")
390
+ return nested_settings
391
+
392
+ except Exception as e:
393
+ self.logger.error(f"Failed to deserialize tool settings: {e}")
394
+ return {}
395
+
396
+ def handle_platform_specific_settings(self, settings: Dict[str, Any], current_platform: str = None) -> Dict[str, Any]:
397
+ """
398
+ Handle platform-specific settings with fallback mechanisms.
399
+
400
+ Args:
401
+ settings: Settings dictionary that may contain platform-specific keys
402
+ current_platform: Current platform ('windows', 'mac', 'linux')
403
+
404
+ Returns:
405
+ Settings dictionary with platform-specific fallbacks resolved
406
+ """
407
+ if current_platform is None:
408
+ import platform
409
+ system = platform.system().lower()
410
+ current_platform = {
411
+ 'darwin': 'mac',
412
+ 'windows': 'windows',
413
+ 'linux': 'linux'
414
+ }.get(system, 'windows')
415
+
416
+ result = settings.copy()
417
+
418
+ # Process font settings with platform fallbacks
419
+ if 'font_settings' in result:
420
+ result['font_settings'] = self._resolve_font_fallbacks(
421
+ result['font_settings'], current_platform
422
+ )
423
+
424
+ # Process any other platform-specific settings
425
+ result = self._resolve_platform_fallbacks(result, current_platform)
426
+
427
+ return result
428
+
429
+ def _resolve_font_fallbacks(self, font_settings: Dict[str, Any], platform: str) -> Dict[str, Any]:
430
+ """
431
+ Resolve font fallbacks for the current platform.
432
+
433
+ Args:
434
+ font_settings: Font settings dictionary
435
+ platform: Current platform ('windows', 'mac', 'linux')
436
+
437
+ Returns:
438
+ Font settings with resolved fallbacks
439
+ """
440
+ result = font_settings.copy()
441
+
442
+ for font_type, font_config in result.items():
443
+ if isinstance(font_config, dict):
444
+ # Check for platform-specific fallback
445
+ fallback_key = f'fallback_family_{platform}'
446
+ if fallback_key in font_config:
447
+ # Use platform-specific fallback as primary fallback
448
+ font_config['fallback_family'] = font_config[fallback_key]
449
+
450
+ # Clean up platform-specific keys if desired
451
+ # (Keep them for now to maintain compatibility)
452
+
453
+ return result
454
+
455
+ def _resolve_platform_fallbacks(self, settings: Dict[str, Any], platform: str) -> Dict[str, Any]:
456
+ """
457
+ Resolve platform-specific fallbacks throughout settings.
458
+
459
+ Args:
460
+ settings: Settings dictionary
461
+ platform: Current platform
462
+
463
+ Returns:
464
+ Settings with platform fallbacks resolved
465
+ """
466
+ # For now, just return as-is since most platform-specific handling
467
+ # is in font settings. This can be extended for other platform-specific
468
+ # settings as they are identified.
469
+ return settings
470
+
471
+ def validate_serialized_data(self, original: Any, serialized: str, data_type: str) -> bool:
472
+ """
473
+ Validate that serialized data can be correctly deserialized.
474
+
475
+ Args:
476
+ original: Original Python value
477
+ serialized: Serialized string representation
478
+ data_type: Data type annotation
479
+
480
+ Returns:
481
+ True if serialization is valid, False otherwise
482
+ """
483
+ try:
484
+ deserialized = self.deserialize_value(serialized, data_type)
485
+
486
+ # For basic types, check exact equality
487
+ if data_type in ('str', 'int', 'float', 'bool'):
488
+ return original == deserialized
489
+
490
+ # For complex types, check structural equality
491
+ elif data_type in ('json', 'array'):
492
+ return self._deep_equal(original, deserialized)
493
+
494
+ return True
495
+
496
+ except Exception as e:
497
+ self.logger.error(f"Validation failed for {data_type} value: {e}")
498
+ return False
499
+
500
+ def _deep_equal(self, obj1: Any, obj2: Any) -> bool:
501
+ """
502
+ Deep equality check for complex objects.
503
+
504
+ Args:
505
+ obj1: First object to compare
506
+ obj2: Second object to compare
507
+
508
+ Returns:
509
+ True if objects are deeply equal, False otherwise
510
+ """
511
+ try:
512
+ # Use JSON serialization for comparison to handle nested structures
513
+ json1 = json.dumps(obj1, sort_keys=True, default=str)
514
+ json2 = json.dumps(obj2, sort_keys=True, default=str)
515
+ return json1 == json2
516
+ except Exception:
517
+ # Fallback to direct comparison
518
+ return obj1 == obj2
519
+
520
+
521
+ # Convenience functions for common serialization tasks
522
+ def serialize_settings_dict(settings: Dict[str, Any]) -> Dict[str, Tuple[str, str]]:
523
+ """
524
+ Serialize an entire settings dictionary.
525
+
526
+ Args:
527
+ settings: Settings dictionary to serialize
528
+
529
+ Returns:
530
+ Dictionary mapping keys to (serialized_value, data_type) tuples
531
+ """
532
+ serializer = SettingsSerializer()
533
+ result = {}
534
+
535
+ for key, value in settings.items():
536
+ serialized_value, data_type = serializer.serialize_value(value)
537
+ result[key] = (serialized_value, data_type)
538
+
539
+ return result
540
+
541
+
542
+ def deserialize_settings_dict(serialized_settings: Dict[str, Tuple[str, str]]) -> Dict[str, Any]:
543
+ """
544
+ Deserialize a settings dictionary from serialized format.
545
+
546
+ Args:
547
+ serialized_settings: Dictionary mapping keys to (serialized_value, data_type) tuples
548
+
549
+ Returns:
550
+ Deserialized settings dictionary
551
+ """
552
+ serializer = SettingsSerializer()
553
+ result = {}
554
+
555
+ for key, (serialized_value, data_type) in serialized_settings.items():
556
+ result[key] = serializer.deserialize_value(serialized_value, data_type)
557
+
558
558
  return result