pomera-ai-commander 1.1.1 → 1.2.2

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 (213) 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 +1199 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_directory.py +549 -0
  12. package/core/data_validator.py +1066 -1066
  13. package/core/database_connection_manager.py +744 -744
  14. package/core/database_curl_settings_manager.py +608 -608
  15. package/core/database_promera_ai_settings_manager.py +446 -446
  16. package/core/database_schema.py +411 -411
  17. package/core/database_schema_manager.py +395 -395
  18. package/core/database_settings_manager.py +1507 -1507
  19. package/core/database_settings_manager_interface.py +456 -456
  20. package/core/dialog_manager.py +734 -734
  21. package/core/diff_utils.py +239 -0
  22. package/core/efficient_line_numbers.py +540 -510
  23. package/core/error_handler.py +746 -746
  24. package/core/error_service.py +431 -431
  25. package/core/event_consolidator.py +511 -511
  26. package/core/mcp/__init__.py +43 -43
  27. package/core/mcp/find_replace_diff.py +334 -0
  28. package/core/mcp/protocol.py +288 -288
  29. package/core/mcp/schema.py +251 -251
  30. package/core/mcp/server_stdio.py +299 -299
  31. package/core/mcp/tool_registry.py +2699 -2345
  32. package/core/memento.py +275 -0
  33. package/core/memory_efficient_text_widget.py +711 -711
  34. package/core/migration_manager.py +914 -914
  35. package/core/migration_test_suite.py +1085 -1085
  36. package/core/migration_validator.py +1143 -1143
  37. package/core/optimized_find_replace.py +714 -714
  38. package/core/optimized_pattern_engine.py +424 -424
  39. package/core/optimized_search_highlighter.py +552 -552
  40. package/core/performance_monitor.py +674 -674
  41. package/core/persistence_manager.py +712 -712
  42. package/core/progressive_stats_calculator.py +632 -632
  43. package/core/regex_pattern_cache.py +529 -529
  44. package/core/regex_pattern_library.py +350 -350
  45. package/core/search_operation_manager.py +434 -434
  46. package/core/settings_defaults_registry.py +1087 -1087
  47. package/core/settings_integrity_validator.py +1111 -1111
  48. package/core/settings_serializer.py +557 -557
  49. package/core/settings_validator.py +1823 -1823
  50. package/core/smart_stats_calculator.py +709 -709
  51. package/core/statistics_update_manager.py +619 -619
  52. package/core/stats_config_manager.py +858 -858
  53. package/core/streaming_text_handler.py +723 -723
  54. package/core/task_scheduler.py +596 -596
  55. package/core/update_pattern_library.py +168 -168
  56. package/core/visibility_monitor.py +596 -596
  57. package/core/widget_cache.py +498 -498
  58. package/mcp.json +51 -61
  59. package/migrate_data.py +127 -0
  60. package/package.json +64 -57
  61. package/pomera.py +7883 -7482
  62. package/pomera_mcp_server.py +183 -144
  63. package/requirements.txt +33 -0
  64. package/scripts/Dockerfile.alpine +43 -0
  65. package/scripts/Dockerfile.gui-test +54 -0
  66. package/scripts/Dockerfile.linux +43 -0
  67. package/scripts/Dockerfile.test-linux +80 -0
  68. package/scripts/Dockerfile.ubuntu +39 -0
  69. package/scripts/README.md +53 -0
  70. package/scripts/build-all.bat +113 -0
  71. package/scripts/build-docker.bat +53 -0
  72. package/scripts/build-docker.sh +55 -0
  73. package/scripts/build-optimized.bat +101 -0
  74. package/scripts/build.sh +78 -0
  75. package/scripts/docker-compose.test.yml +27 -0
  76. package/scripts/docker-compose.yml +32 -0
  77. package/scripts/postinstall.js +62 -0
  78. package/scripts/requirements-minimal.txt +33 -0
  79. package/scripts/test-linux-simple.bat +28 -0
  80. package/scripts/validate-release-workflow.py +450 -0
  81. package/tools/__init__.py +4 -4
  82. package/tools/ai_tools.py +2891 -2891
  83. package/tools/ascii_art_generator.py +352 -352
  84. package/tools/base64_tools.py +183 -183
  85. package/tools/base_tool.py +511 -511
  86. package/tools/case_tool.py +308 -308
  87. package/tools/column_tools.py +395 -395
  88. package/tools/cron_tool.py +884 -884
  89. package/tools/curl_history.py +600 -600
  90. package/tools/curl_processor.py +1207 -1207
  91. package/tools/curl_settings.py +502 -502
  92. package/tools/curl_tool.py +5467 -5467
  93. package/tools/diff_viewer.py +1817 -1072
  94. package/tools/email_extraction_tool.py +248 -248
  95. package/tools/email_header_analyzer.py +425 -425
  96. package/tools/extraction_tools.py +250 -250
  97. package/tools/find_replace.py +2289 -1750
  98. package/tools/folder_file_reporter.py +1463 -1463
  99. package/tools/folder_file_reporter_adapter.py +480 -480
  100. package/tools/generator_tools.py +1216 -1216
  101. package/tools/hash_generator.py +255 -255
  102. package/tools/html_tool.py +656 -656
  103. package/tools/jsonxml_tool.py +729 -729
  104. package/tools/line_tools.py +419 -419
  105. package/tools/markdown_tools.py +561 -561
  106. package/tools/mcp_widget.py +1417 -1417
  107. package/tools/notes_widget.py +978 -973
  108. package/tools/number_base_converter.py +372 -372
  109. package/tools/regex_extractor.py +571 -571
  110. package/tools/slug_generator.py +310 -310
  111. package/tools/sorter_tools.py +458 -458
  112. package/tools/string_escape_tool.py +392 -392
  113. package/tools/text_statistics_tool.py +365 -365
  114. package/tools/text_wrapper.py +430 -430
  115. package/tools/timestamp_converter.py +421 -421
  116. package/tools/tool_loader.py +710 -710
  117. package/tools/translator_tools.py +522 -522
  118. package/tools/url_link_extractor.py +261 -261
  119. package/tools/url_parser.py +204 -204
  120. package/tools/whitespace_tools.py +355 -355
  121. package/tools/word_frequency_counter.py +146 -146
  122. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  123. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  124. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  125. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  126. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  127. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  128. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  129. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  131. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  132. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  134. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  135. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  136. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  137. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  138. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  139. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  140. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  141. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  142. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  143. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  144. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  145. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  146. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  147. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  148. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  149. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  150. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  151. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  152. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  153. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  154. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  155. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  156. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  157. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  158. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  159. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  160. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  161. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  162. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  163. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  164. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  165. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  166. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  167. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  168. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  169. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  170. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  192. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  193. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  194. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  195. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  196. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  197. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  198. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  199. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  200. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  201. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  202. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  203. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  204. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  205. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  206. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  207. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  208. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  209. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  210. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  211. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  212. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  213. 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