claude-mpm 4.4.3__py3-none-any.whl → 4.4.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/agent_loader.py +3 -2
  3. claude_mpm/agents/agent_loader_integration.py +2 -1
  4. claude_mpm/agents/async_agent_loader.py +2 -2
  5. claude_mpm/agents/base_agent_loader.py +2 -2
  6. claude_mpm/agents/frontmatter_validator.py +1 -0
  7. claude_mpm/agents/system_agent_config.py +2 -1
  8. claude_mpm/cli/commands/doctor.py +44 -5
  9. claude_mpm/cli/commands/mpm_init.py +116 -62
  10. claude_mpm/cli/parsers/configure_parser.py +3 -1
  11. claude_mpm/cli/startup_logging.py +1 -3
  12. claude_mpm/config/agent_config.py +1 -1
  13. claude_mpm/config/paths.py +2 -1
  14. claude_mpm/core/agent_name_normalizer.py +1 -0
  15. claude_mpm/core/config.py +2 -1
  16. claude_mpm/core/config_aliases.py +2 -1
  17. claude_mpm/core/file_utils.py +0 -1
  18. claude_mpm/core/framework/__init__.py +6 -6
  19. claude_mpm/core/framework/formatters/__init__.py +2 -2
  20. claude_mpm/core/framework/formatters/capability_generator.py +19 -8
  21. claude_mpm/core/framework/formatters/content_formatter.py +8 -3
  22. claude_mpm/core/framework/formatters/context_generator.py +7 -3
  23. claude_mpm/core/framework/loaders/__init__.py +3 -3
  24. claude_mpm/core/framework/loaders/agent_loader.py +7 -3
  25. claude_mpm/core/framework/loaders/file_loader.py +16 -6
  26. claude_mpm/core/framework/loaders/instruction_loader.py +16 -6
  27. claude_mpm/core/framework/loaders/packaged_loader.py +36 -12
  28. claude_mpm/core/framework/processors/__init__.py +2 -2
  29. claude_mpm/core/framework/processors/memory_processor.py +14 -6
  30. claude_mpm/core/framework/processors/metadata_processor.py +5 -5
  31. claude_mpm/core/framework/processors/template_processor.py +12 -6
  32. claude_mpm/core/framework_loader.py +44 -20
  33. claude_mpm/core/log_manager.py +2 -1
  34. claude_mpm/core/tool_access_control.py +1 -0
  35. claude_mpm/core/unified_agent_registry.py +2 -1
  36. claude_mpm/core/unified_paths.py +1 -0
  37. claude_mpm/experimental/cli_enhancements.py +1 -0
  38. claude_mpm/hooks/base_hook.py +1 -0
  39. claude_mpm/hooks/instruction_reinforcement.py +1 -0
  40. claude_mpm/hooks/kuzu_memory_hook.py +20 -13
  41. claude_mpm/hooks/validation_hooks.py +1 -1
  42. claude_mpm/scripts/mpm_doctor.py +1 -0
  43. claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
  44. claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
  45. claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
  46. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
  47. claude_mpm/services/agents/management/agent_management_service.py +1 -1
  48. claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
  49. claude_mpm/services/agents/memory/memory_file_service.py +6 -2
  50. claude_mpm/services/agents/memory/memory_format_service.py +0 -1
  51. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  52. claude_mpm/services/async_session_logger.py +1 -1
  53. claude_mpm/services/claude_session_logger.py +1 -0
  54. claude_mpm/services/core/path_resolver.py +1 -0
  55. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  56. claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
  57. claude_mpm/services/diagnostics/checks/mcp_services_check.py +451 -0
  58. claude_mpm/services/diagnostics/diagnostic_runner.py +3 -0
  59. claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
  60. claude_mpm/services/event_bus/direct_relay.py +2 -1
  61. claude_mpm/services/event_bus/event_bus.py +1 -0
  62. claude_mpm/services/event_bus/relay.py +3 -2
  63. claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
  64. claude_mpm/services/infrastructure/daemon_manager.py +1 -1
  65. claude_mpm/services/mcp_config_manager.py +301 -54
  66. claude_mpm/services/mcp_gateway/core/process_pool.py +62 -23
  67. claude_mpm/services/mcp_gateway/tools/__init__.py +6 -5
  68. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +3 -1
  69. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +16 -31
  70. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  71. claude_mpm/services/project/archive_manager.py +159 -96
  72. claude_mpm/services/project/documentation_manager.py +64 -45
  73. claude_mpm/services/project/enhanced_analyzer.py +132 -89
  74. claude_mpm/services/project/project_organizer.py +225 -131
  75. claude_mpm/services/response_tracker.py +1 -1
  76. claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
  77. claude_mpm/services/unified/__init__.py +1 -1
  78. claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
  79. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
  80. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
  81. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
  82. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
  83. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
  84. claude_mpm/services/unified/config_strategies/__init__.py +111 -126
  85. claude_mpm/services/unified/config_strategies/config_schema.py +157 -111
  86. claude_mpm/services/unified/config_strategies/context_strategy.py +91 -89
  87. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +183 -173
  88. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +160 -152
  89. claude_mpm/services/unified/config_strategies/unified_config_service.py +124 -112
  90. claude_mpm/services/unified/config_strategies/validation_strategy.py +298 -259
  91. claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
  92. claude_mpm/services/unified/deployment_strategies/base.py +24 -28
  93. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
  94. claude_mpm/services/unified/deployment_strategies/local.py +49 -34
  95. claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
  96. claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
  97. claude_mpm/services/unified/interfaces.py +0 -26
  98. claude_mpm/services/unified/migration.py +17 -40
  99. claude_mpm/services/unified/strategies.py +9 -26
  100. claude_mpm/services/unified/unified_analyzer.py +48 -44
  101. claude_mpm/services/unified/unified_config.py +21 -19
  102. claude_mpm/services/unified/unified_deployment.py +21 -26
  103. claude_mpm/storage/state_storage.py +1 -0
  104. claude_mpm/utils/agent_dependency_loader.py +18 -6
  105. claude_mpm/utils/common.py +14 -12
  106. claude_mpm/utils/database_connector.py +15 -12
  107. claude_mpm/utils/error_handler.py +1 -0
  108. claude_mpm/utils/log_cleanup.py +1 -0
  109. claude_mpm/utils/path_operations.py +1 -0
  110. claude_mpm/utils/session_logging.py +1 -1
  111. claude_mpm/utils/subprocess_utils.py +1 -0
  112. claude_mpm/validation/agent_validator.py +1 -1
  113. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.5.dist-info}/METADATA +35 -15
  114. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.5.dist-info}/RECORD +118 -117
  115. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.5.dist-info}/WHEEL +0 -0
  116. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.5.dist-info}/entry_points.txt +0 -0
  117. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.5.dist-info}/licenses/LICENSE +0 -0
  118. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.5.dist-info}/top_level.txt +0 -0
@@ -3,23 +3,24 @@ Validation Strategy - Reduces 236 validation functions to 15 composable validato
3
3
  Part of Phase 3 Configuration Consolidation
4
4
  """
5
5
 
6
+ import ipaddress
7
+ import re
8
+ import urllib.parse
6
9
  from abc import ABC, abstractmethod
7
- from typing import Any, Dict, List, Optional, Union, Callable, Pattern, Type
8
10
  from dataclasses import dataclass, field
11
+ from datetime import datetime
9
12
  from enum import Enum
10
- import re
11
- from datetime import datetime, date
12
13
  from pathlib import Path
13
- import ipaddress
14
- import urllib.parse
15
- from collections.abc import Iterable
14
+ from typing import Any, Callable, Dict, List, Optional, Pattern, Union
16
15
 
17
16
  from claude_mpm.core.logging_utils import get_logger
17
+
18
18
  from .unified_config_service import IConfigStrategy
19
19
 
20
20
 
21
21
  class ValidationType(Enum):
22
22
  """Types of validation operations"""
23
+
23
24
  TYPE = "type"
24
25
  REQUIRED = "required"
25
26
  RANGE = "range"
@@ -40,6 +41,7 @@ class ValidationType(Enum):
40
41
  @dataclass
41
42
  class ValidationRule:
42
43
  """Single validation rule definition"""
44
+
43
45
  type: ValidationType
44
46
  params: Dict[str, Any] = field(default_factory=dict)
45
47
  message: Optional[str] = None
@@ -50,6 +52,7 @@ class ValidationRule:
50
52
  @dataclass
51
53
  class ValidationResult:
52
54
  """Result of validation operation"""
55
+
53
56
  valid: bool
54
57
  errors: List[str] = field(default_factory=list)
55
58
  warnings: List[str] = field(default_factory=list)
@@ -64,11 +67,14 @@ class BaseValidator(ABC):
64
67
  self.logger = get_logger(self.__class__.__name__)
65
68
 
66
69
  @abstractmethod
67
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
70
+ def validate(
71
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
72
+ ) -> ValidationResult:
68
73
  """Perform validation"""
69
- pass
70
74
 
71
- def _create_result(self, valid: bool, message: str = None, severity: str = "error") -> ValidationResult:
75
+ def _create_result(
76
+ self, valid: bool, message: str = None, severity: str = "error"
77
+ ) -> ValidationResult:
72
78
  """Create validation result"""
73
79
  result = ValidationResult(valid=valid)
74
80
 
@@ -87,26 +93,28 @@ class TypeValidator(BaseValidator):
87
93
  """Validates data types - replaces 45 type validation functions"""
88
94
 
89
95
  TYPE_MAP = {
90
- 'string': str,
91
- 'str': str,
92
- 'integer': int,
93
- 'int': int,
94
- 'float': float,
95
- 'number': (int, float),
96
- 'boolean': bool,
97
- 'bool': bool,
98
- 'array': list,
99
- 'list': list,
100
- 'object': dict,
101
- 'dict': dict,
102
- 'null': type(None),
103
- 'none': type(None),
104
- 'any': object
96
+ "string": str,
97
+ "str": str,
98
+ "integer": int,
99
+ "int": int,
100
+ "float": float,
101
+ "number": (int, float),
102
+ "boolean": bool,
103
+ "bool": bool,
104
+ "array": list,
105
+ "list": list,
106
+ "object": dict,
107
+ "dict": dict,
108
+ "null": type(None),
109
+ "none": type(None),
110
+ "any": object,
105
111
  }
106
112
 
107
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
113
+ def validate(
114
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
115
+ ) -> ValidationResult:
108
116
  """Validate value type"""
109
- expected_type = rule.params.get('type')
117
+ expected_type = rule.params.get("type")
110
118
 
111
119
  if not expected_type:
112
120
  return self._create_result(True)
@@ -122,11 +130,11 @@ class TypeValidator(BaseValidator):
122
130
  if isinstance(value, type_obj):
123
131
  return self._create_result(True)
124
132
 
125
- types_str = ', '.join(str(t) for t in expected_type)
133
+ types_str = ", ".join(str(t) for t in expected_type)
126
134
  return self._create_result(
127
135
  False,
128
136
  f"Value must be one of types: {types_str}, got {type(value).__name__}",
129
- rule.severity
137
+ rule.severity,
130
138
  )
131
139
 
132
140
  # Single type validation
@@ -134,7 +142,7 @@ class TypeValidator(BaseValidator):
134
142
  return self._create_result(
135
143
  False,
136
144
  f"Expected type {expected_type}, got {type(value).__name__}",
137
- rule.severity
145
+ rule.severity,
138
146
  )
139
147
 
140
148
  return self._create_result(True)
@@ -143,34 +151,33 @@ class TypeValidator(BaseValidator):
143
151
  class RequiredValidator(BaseValidator):
144
152
  """Validates required fields - replaces 35 required validation functions"""
145
153
 
146
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
154
+ def validate(
155
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
156
+ ) -> ValidationResult:
147
157
  """Validate required fields"""
148
- required_fields = rule.params.get('fields', [])
149
- config = context.get('config', {})
158
+ required_fields = rule.params.get("fields", [])
159
+ config = context.get("config", {})
150
160
 
151
161
  missing = []
152
162
  for field in required_fields:
153
- if '.' in field:
163
+ if "." in field:
154
164
  # Nested field check
155
165
  if not self._check_nested_field(config, field):
156
166
  missing.append(field)
157
- else:
158
- # Simple field check
159
- if field not in config or config[field] is None:
160
- missing.append(field)
167
+ # Simple field check
168
+ elif field not in config or config[field] is None:
169
+ missing.append(field)
161
170
 
162
171
  if missing:
163
172
  return self._create_result(
164
- False,
165
- f"Required fields missing: {', '.join(missing)}",
166
- rule.severity
173
+ False, f"Required fields missing: {', '.join(missing)}", rule.severity
167
174
  )
168
175
 
169
176
  return self._create_result(True)
170
177
 
171
178
  def _check_nested_field(self, obj: Dict, path: str) -> bool:
172
179
  """Check if nested field exists"""
173
- parts = path.split('.')
180
+ parts = path.split(".")
174
181
  current = obj
175
182
 
176
183
  for part in parts:
@@ -184,15 +191,17 @@ class RequiredValidator(BaseValidator):
184
191
  class RangeValidator(BaseValidator):
185
192
  """Validates numeric ranges - replaces 28 range validation functions"""
186
193
 
187
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
194
+ def validate(
195
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
196
+ ) -> ValidationResult:
188
197
  """Validate numeric range"""
189
198
  if not isinstance(value, (int, float)):
190
199
  return self._create_result(True) # Skip non-numeric values
191
200
 
192
- min_val = rule.params.get('min')
193
- max_val = rule.params.get('max')
194
- exclusive_min = rule.params.get('exclusive_min', False)
195
- exclusive_max = rule.params.get('exclusive_max', False)
201
+ min_val = rule.params.get("min")
202
+ max_val = rule.params.get("max")
203
+ exclusive_min = rule.params.get("exclusive_min", False)
204
+ exclusive_max = rule.params.get("exclusive_max", False)
196
205
 
197
206
  # Check minimum
198
207
  if min_val is not None:
@@ -200,28 +209,22 @@ class RangeValidator(BaseValidator):
200
209
  return self._create_result(
201
210
  False,
202
211
  f"Value {value} must be greater than {min_val}",
203
- rule.severity
212
+ rule.severity,
204
213
  )
205
- elif not exclusive_min and value < min_val:
214
+ if not exclusive_min and value < min_val:
206
215
  return self._create_result(
207
- False,
208
- f"Value {value} must be at least {min_val}",
209
- rule.severity
216
+ False, f"Value {value} must be at least {min_val}", rule.severity
210
217
  )
211
218
 
212
219
  # Check maximum
213
220
  if max_val is not None:
214
221
  if exclusive_max and value >= max_val:
215
222
  return self._create_result(
216
- False,
217
- f"Value {value} must be less than {max_val}",
218
- rule.severity
223
+ False, f"Value {value} must be less than {max_val}", rule.severity
219
224
  )
220
- elif not exclusive_max and value > max_val:
225
+ if not exclusive_max and value > max_val:
221
226
  return self._create_result(
222
- False,
223
- f"Value {value} must be at most {max_val}",
224
- rule.severity
227
+ False, f"Value {value} must be at most {max_val}", rule.severity
225
228
  )
226
229
 
227
230
  return self._create_result(True)
@@ -230,22 +233,24 @@ class RangeValidator(BaseValidator):
230
233
  class LengthValidator(BaseValidator):
231
234
  """Validates string/array lengths - replaces 22 length validation functions"""
232
235
 
233
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
236
+ def validate(
237
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
238
+ ) -> ValidationResult:
234
239
  """Validate length constraints"""
235
- if not hasattr(value, '__len__'):
240
+ if not hasattr(value, "__len__"):
236
241
  return self._create_result(True) # Skip non-sized values
237
242
 
238
243
  length = len(value)
239
- min_length = rule.params.get('min')
240
- max_length = rule.params.get('max')
241
- exact_length = rule.params.get('exact')
244
+ min_length = rule.params.get("min")
245
+ max_length = rule.params.get("max")
246
+ exact_length = rule.params.get("exact")
242
247
 
243
248
  # Check exact length
244
249
  if exact_length is not None and length != exact_length:
245
250
  return self._create_result(
246
251
  False,
247
252
  f"Length must be exactly {exact_length}, got {length}",
248
- rule.severity
253
+ rule.severity,
249
254
  )
250
255
 
251
256
  # Check minimum length
@@ -253,7 +258,7 @@ class LengthValidator(BaseValidator):
253
258
  return self._create_result(
254
259
  False,
255
260
  f"Length must be at least {min_length}, got {length}",
256
- rule.severity
261
+ rule.severity,
257
262
  )
258
263
 
259
264
  # Check maximum length
@@ -261,7 +266,7 @@ class LengthValidator(BaseValidator):
261
266
  return self._create_result(
262
267
  False,
263
268
  f"Length must be at most {max_length}, got {length}",
264
- rule.severity
269
+ rule.severity,
265
270
  )
266
271
 
267
272
  return self._create_result(True)
@@ -274,12 +279,14 @@ class PatternValidator(BaseValidator):
274
279
  super().__init__()
275
280
  self._compiled_patterns: Dict[str, Pattern] = {}
276
281
 
277
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
282
+ def validate(
283
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
284
+ ) -> ValidationResult:
278
285
  """Validate against regex pattern"""
279
286
  if not isinstance(value, str):
280
287
  return self._create_result(True) # Skip non-string values
281
288
 
282
- pattern = rule.params.get('pattern')
289
+ pattern = rule.params.get("pattern")
283
290
  if not pattern:
284
291
  return self._create_result(True)
285
292
 
@@ -294,7 +301,9 @@ class PatternValidator(BaseValidator):
294
301
 
295
302
  # Check match
296
303
  if not regex.match(value):
297
- message = rule.params.get('message', f"Value does not match pattern: {pattern}")
304
+ message = rule.params.get(
305
+ "message", f"Value does not match pattern: {pattern}"
306
+ )
298
307
  return self._create_result(False, message, rule.severity)
299
308
 
300
309
  return self._create_result(True)
@@ -303,33 +312,36 @@ class PatternValidator(BaseValidator):
303
312
  class EnumValidator(BaseValidator):
304
313
  """Validates enum values - replaces 18 enum validation functions"""
305
314
 
306
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
315
+ def validate(
316
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
317
+ ) -> ValidationResult:
307
318
  """Validate enum membership"""
308
- allowed_values = rule.params.get('values', [])
319
+ allowed_values = rule.params.get("values", [])
309
320
 
310
321
  if not allowed_values:
311
322
  return self._create_result(True)
312
323
 
313
324
  # Handle case sensitivity
314
- case_sensitive = rule.params.get('case_sensitive', True)
325
+ case_sensitive = rule.params.get("case_sensitive", True)
315
326
 
316
327
  if not case_sensitive and isinstance(value, str):
317
328
  value_lower = value.lower()
318
- allowed_lower = [v.lower() if isinstance(v, str) else v for v in allowed_values]
329
+ allowed_lower = [
330
+ v.lower() if isinstance(v, str) else v for v in allowed_values
331
+ ]
319
332
 
320
333
  if value_lower not in allowed_lower:
321
334
  return self._create_result(
322
335
  False,
323
336
  f"Value '{value}' not in allowed values: {allowed_values}",
324
- rule.severity
325
- )
326
- else:
327
- if value not in allowed_values:
328
- return self._create_result(
329
- False,
330
- f"Value '{value}' not in allowed values: {allowed_values}",
331
- rule.severity
337
+ rule.severity,
332
338
  )
339
+ elif value not in allowed_values:
340
+ return self._create_result(
341
+ False,
342
+ f"Value '{value}' not in allowed values: {allowed_values}",
343
+ rule.severity,
344
+ )
333
345
 
334
346
  return self._create_result(True)
335
347
 
@@ -338,50 +350,51 @@ class FormatValidator(BaseValidator):
338
350
  """Validates common formats - replaces 24 format validation functions"""
339
351
 
340
352
  FORMAT_VALIDATORS = {
341
- 'email': lambda v: '@' in v and '.' in v.split('@')[1],
342
- 'url': lambda v: FormatValidator._validate_url(v),
343
- 'uri': lambda v: FormatValidator._validate_uri(v),
344
- 'uuid': lambda v: FormatValidator._validate_uuid(v),
345
- 'ipv4': lambda v: FormatValidator._validate_ipv4(v),
346
- 'ipv6': lambda v: FormatValidator._validate_ipv6(v),
347
- 'ip': lambda v: FormatValidator._validate_ip(v),
348
- 'hostname': lambda v: FormatValidator._validate_hostname(v),
349
- 'date': lambda v: FormatValidator._validate_date(v),
350
- 'time': lambda v: FormatValidator._validate_time(v),
351
- 'datetime': lambda v: FormatValidator._validate_datetime(v),
352
- 'json': lambda v: FormatValidator._validate_json(v),
353
- 'base64': lambda v: FormatValidator._validate_base64(v),
354
- 'path': lambda v: FormatValidator._validate_path(v),
355
- 'semver': lambda v: FormatValidator._validate_semver(v)
353
+ "email": lambda v: "@" in v and "." in v.split("@")[1],
354
+ "url": lambda v: FormatValidator._validate_url(v),
355
+ "uri": lambda v: FormatValidator._validate_uri(v),
356
+ "uuid": lambda v: FormatValidator._validate_uuid(v),
357
+ "ipv4": lambda v: FormatValidator._validate_ipv4(v),
358
+ "ipv6": lambda v: FormatValidator._validate_ipv6(v),
359
+ "ip": lambda v: FormatValidator._validate_ip(v),
360
+ "hostname": lambda v: FormatValidator._validate_hostname(v),
361
+ "date": lambda v: FormatValidator._validate_date(v),
362
+ "time": lambda v: FormatValidator._validate_time(v),
363
+ "datetime": lambda v: FormatValidator._validate_datetime(v),
364
+ "json": lambda v: FormatValidator._validate_json(v),
365
+ "base64": lambda v: FormatValidator._validate_base64(v),
366
+ "path": lambda v: FormatValidator._validate_path(v),
367
+ "semver": lambda v: FormatValidator._validate_semver(v),
356
368
  }
357
369
 
358
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
370
+ def validate(
371
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
372
+ ) -> ValidationResult:
359
373
  """Validate format"""
360
374
  if not isinstance(value, str):
361
375
  return self._create_result(True)
362
376
 
363
- format_type = rule.params.get('format')
377
+ format_type = rule.params.get("format")
364
378
  if not format_type:
365
379
  return self._create_result(True)
366
380
 
367
381
  validator = self.FORMAT_VALIDATORS.get(format_type)
368
382
  if not validator:
369
- return self._create_result(False, f"Unknown format: {format_type}", "warning")
383
+ return self._create_result(
384
+ False, f"Unknown format: {format_type}", "warning"
385
+ )
370
386
 
371
387
  try:
372
388
  if validator(value):
373
389
  return self._create_result(True)
374
- else:
375
- return self._create_result(
376
- False,
377
- f"Value '{value}' is not a valid {format_type}",
378
- rule.severity
379
- )
380
- except Exception as e:
381
390
  return self._create_result(
382
391
  False,
383
- f"Format validation failed: {e}",
384
- rule.severity
392
+ f"Value '{value}' is not a valid {format_type}",
393
+ rule.severity,
394
+ )
395
+ except Exception as e:
396
+ return self._create_result(
397
+ False, f"Format validation failed: {e}", rule.severity
385
398
  )
386
399
 
387
400
  @staticmethod
@@ -403,6 +416,7 @@ class FormatValidator(BaseValidator):
403
416
  @staticmethod
404
417
  def _validate_uuid(value: str) -> bool:
405
418
  import uuid
419
+
406
420
  try:
407
421
  uuid.UUID(value)
408
422
  return True
@@ -436,16 +450,16 @@ class FormatValidator(BaseValidator):
436
450
  @staticmethod
437
451
  def _validate_hostname(value: str) -> bool:
438
452
  pattern = re.compile(
439
- r'^(?=.{1,253}$)(?!-)(?!.*--)'
440
- r'[a-zA-Z0-9-]{1,63}'
441
- r'(?:\.[a-zA-Z0-9-]{1,63})*$'
453
+ r"^(?=.{1,253}$)(?!-)(?!.*--)"
454
+ r"[a-zA-Z0-9-]{1,63}"
455
+ r"(?:\.[a-zA-Z0-9-]{1,63})*$"
442
456
  )
443
457
  return bool(pattern.match(value))
444
458
 
445
459
  @staticmethod
446
460
  def _validate_date(value: str) -> bool:
447
461
  try:
448
- datetime.strptime(value, '%Y-%m-%d')
462
+ datetime.strptime(value, "%Y-%m-%d")
449
463
  return True
450
464
  except:
451
465
  return False
@@ -453,11 +467,11 @@ class FormatValidator(BaseValidator):
453
467
  @staticmethod
454
468
  def _validate_time(value: str) -> bool:
455
469
  try:
456
- datetime.strptime(value, '%H:%M:%S')
470
+ datetime.strptime(value, "%H:%M:%S")
457
471
  return True
458
472
  except:
459
473
  try:
460
- datetime.strptime(value, '%H:%M')
474
+ datetime.strptime(value, "%H:%M")
461
475
  return True
462
476
  except:
463
477
  return False
@@ -465,10 +479,10 @@ class FormatValidator(BaseValidator):
465
479
  @staticmethod
466
480
  def _validate_datetime(value: str) -> bool:
467
481
  formats = [
468
- '%Y-%m-%d %H:%M:%S',
469
- '%Y-%m-%dT%H:%M:%S',
470
- '%Y-%m-%dT%H:%M:%SZ',
471
- '%Y-%m-%dT%H:%M:%S%z'
482
+ "%Y-%m-%d %H:%M:%S",
483
+ "%Y-%m-%dT%H:%M:%S",
484
+ "%Y-%m-%dT%H:%M:%SZ",
485
+ "%Y-%m-%dT%H:%M:%S%z",
472
486
  ]
473
487
  for fmt in formats:
474
488
  try:
@@ -481,6 +495,7 @@ class FormatValidator(BaseValidator):
481
495
  @staticmethod
482
496
  def _validate_json(value: str) -> bool:
483
497
  import json
498
+
484
499
  try:
485
500
  json.loads(value)
486
501
  return True
@@ -490,6 +505,7 @@ class FormatValidator(BaseValidator):
490
505
  @staticmethod
491
506
  def _validate_base64(value: str) -> bool:
492
507
  import base64
508
+
493
509
  try:
494
510
  base64.b64decode(value, validate=True)
495
511
  return True
@@ -507,10 +523,10 @@ class FormatValidator(BaseValidator):
507
523
  @staticmethod
508
524
  def _validate_semver(value: str) -> bool:
509
525
  pattern = re.compile(
510
- r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)'
511
- r'(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)'
512
- r'(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?'
513
- r'(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
526
+ r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)"
527
+ r"(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
528
+ r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
529
+ r"(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
514
530
  )
515
531
  return bool(pattern.match(value))
516
532
 
@@ -518,10 +534,12 @@ class FormatValidator(BaseValidator):
518
534
  class DependencyValidator(BaseValidator):
519
535
  """Validates field dependencies - replaces 16 dependency validation functions"""
520
536
 
521
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
537
+ def validate(
538
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
539
+ ) -> ValidationResult:
522
540
  """Validate field dependencies"""
523
- config = context.get('config', {})
524
- dependencies = rule.params.get('dependencies', {})
541
+ config = context.get("config", {})
542
+ dependencies = rule.params.get("dependencies", {})
525
543
 
526
544
  errors = []
527
545
 
@@ -552,7 +570,9 @@ class DependencyValidator(BaseValidator):
552
570
  class UniqueValidator(BaseValidator):
553
571
  """Validates unique values - replaces 12 unique validation functions"""
554
572
 
555
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
573
+ def validate(
574
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
575
+ ) -> ValidationResult:
556
576
  """Validate unique values in collections"""
557
577
  if not isinstance(value, (list, tuple)):
558
578
  return self._create_result(True)
@@ -573,9 +593,7 @@ class UniqueValidator(BaseValidator):
573
593
 
574
594
  if duplicates:
575
595
  return self._create_result(
576
- False,
577
- f"Duplicate values found: {duplicates}",
578
- rule.severity
596
+ False, f"Duplicate values found: {duplicates}", rule.severity
579
597
  )
580
598
 
581
599
  return self._create_result(True)
@@ -584,9 +602,11 @@ class UniqueValidator(BaseValidator):
584
602
  class CustomValidator(BaseValidator):
585
603
  """Executes custom validation functions - replaces 20 custom validation functions"""
586
604
 
587
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
605
+ def validate(
606
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
607
+ ) -> ValidationResult:
588
608
  """Execute custom validation function"""
589
- validator_func = rule.params.get('function')
609
+ validator_func = rule.params.get("function")
590
610
 
591
611
  if not validator_func or not callable(validator_func):
592
612
  return self._create_result(True)
@@ -598,44 +618,41 @@ class CustomValidator(BaseValidator):
598
618
  # Handle different return types
599
619
  if isinstance(result, bool):
600
620
  if not result:
601
- message = rule.params.get('message', 'Custom validation failed')
621
+ message = rule.params.get("message", "Custom validation failed")
602
622
  return self._create_result(False, message, rule.severity)
603
623
  return self._create_result(True)
604
624
 
605
- elif isinstance(result, str):
625
+ if isinstance(result, str):
606
626
  # String means validation failed with that message
607
627
  return self._create_result(False, result, rule.severity)
608
628
 
609
- elif isinstance(result, tuple):
629
+ if isinstance(result, tuple):
610
630
  # (valid, message) tuple
611
631
  valid, message = result
612
632
  if not valid:
613
633
  return self._create_result(False, message, rule.severity)
614
634
  return self._create_result(True)
615
635
 
616
- elif isinstance(result, ValidationResult):
636
+ if isinstance(result, ValidationResult):
617
637
  return result
618
638
 
619
- else:
620
- # Unknown return type, assume success if truthy
621
- return self._create_result(bool(result))
639
+ # Unknown return type, assume success if truthy
640
+ return self._create_result(bool(result))
622
641
 
623
642
  except Exception as e:
624
- return self._create_result(
625
- False,
626
- f"Custom validation error: {e}",
627
- "error"
628
- )
643
+ return self._create_result(False, f"Custom validation error: {e}", "error")
629
644
 
630
645
 
631
646
  class ConditionalValidator(BaseValidator):
632
647
  """Validates based on conditions - replaces 15 conditional validation functions"""
633
648
 
634
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
649
+ def validate(
650
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
651
+ ) -> ValidationResult:
635
652
  """Perform conditional validation"""
636
- condition = rule.params.get('if')
637
- then_rule = rule.params.get('then')
638
- else_rule = rule.params.get('else')
653
+ condition = rule.params.get("if")
654
+ then_rule = rule.params.get("then")
655
+ else_rule = rule.params.get("else")
639
656
 
640
657
  if not condition:
641
658
  return self._create_result(True)
@@ -646,23 +663,25 @@ class ConditionalValidator(BaseValidator):
646
663
  # Apply appropriate rule
647
664
  if condition_met and then_rule:
648
665
  return self._apply_rule(then_rule, value, context)
649
- elif not condition_met and else_rule:
666
+ if not condition_met and else_rule:
650
667
  return self._apply_rule(else_rule, value, context)
651
668
 
652
669
  return self._create_result(True)
653
670
 
654
- def _evaluate_condition(self, condition: Any, value: Any, context: Dict[str, Any]) -> bool:
671
+ def _evaluate_condition(
672
+ self, condition: Any, value: Any, context: Dict[str, Any]
673
+ ) -> bool:
655
674
  """Evaluate condition"""
656
675
  if callable(condition):
657
676
  return condition(value, context)
658
677
 
659
678
  if isinstance(condition, dict):
660
679
  # Field comparison condition
661
- field = condition.get('field')
662
- operator = condition.get('operator', '==')
663
- expected = condition.get('value')
680
+ field = condition.get("field")
681
+ operator = condition.get("operator", "==")
682
+ expected = condition.get("value")
664
683
 
665
- config = context.get('config', {})
684
+ config = context.get("config", {})
666
685
  actual = config.get(field)
667
686
 
668
687
  return self._compare_values(actual, operator, expected)
@@ -672,16 +691,16 @@ class ConditionalValidator(BaseValidator):
672
691
  def _compare_values(self, actual: Any, operator: str, expected: Any) -> bool:
673
692
  """Compare values with operator"""
674
693
  operators = {
675
- '==': lambda a, e: a == e,
676
- '!=': lambda a, e: a != e,
677
- '<': lambda a, e: a < e,
678
- '<=': lambda a, e: a <= e,
679
- '>': lambda a, e: a > e,
680
- '>=': lambda a, e: a >= e,
681
- 'in': lambda a, e: a in e,
682
- 'not_in': lambda a, e: a not in e,
683
- 'contains': lambda a, e: e in a,
684
- 'matches': lambda a, e: re.match(e, str(a)) is not None
694
+ "==": lambda a, e: a == e,
695
+ "!=": lambda a, e: a != e,
696
+ "<": lambda a, e: a < e,
697
+ "<=": lambda a, e: a <= e,
698
+ ">": lambda a, e: a > e,
699
+ ">=": lambda a, e: a >= e,
700
+ "in": lambda a, e: a in e,
701
+ "not_in": lambda a, e: a not in e,
702
+ "contains": lambda a, e: e in a,
703
+ "matches": lambda a, e: re.match(e, str(a)) is not None,
685
704
  }
686
705
 
687
706
  comparator = operators.get(operator)
@@ -693,14 +712,16 @@ class ConditionalValidator(BaseValidator):
693
712
  except:
694
713
  return False
695
714
 
696
- def _apply_rule(self, rule_def: Dict, value: Any, context: Dict[str, Any]) -> ValidationResult:
715
+ def _apply_rule(
716
+ self, rule_def: Dict, value: Any, context: Dict[str, Any]
717
+ ) -> ValidationResult:
697
718
  """Apply validation rule"""
698
719
  # Create and execute rule
699
720
  rule = ValidationRule(
700
- type=ValidationType[rule_def.get('type', 'CUSTOM').upper()],
701
- params=rule_def.get('params', {}),
702
- message=rule_def.get('message'),
703
- severity=rule_def.get('severity', 'error')
721
+ type=ValidationType[rule_def.get("type", "CUSTOM").upper()],
722
+ params=rule_def.get("params", {}),
723
+ message=rule_def.get("message"),
724
+ severity=rule_def.get("severity", "error"),
704
725
  )
705
726
 
706
727
  # Find appropriate validator
@@ -714,14 +735,18 @@ class ConditionalValidator(BaseValidator):
714
735
  class RecursiveValidator(BaseValidator):
715
736
  """Validates nested structures recursively - replaces 10 recursive validation functions"""
716
737
 
717
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
738
+ def validate(
739
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
740
+ ) -> ValidationResult:
718
741
  """Recursively validate nested structures"""
719
- schema = rule.params.get('schema', {})
720
- max_depth = rule.params.get('max_depth', 10)
721
- current_depth = context.get('_depth', 0)
742
+ schema = rule.params.get("schema", {})
743
+ max_depth = rule.params.get("max_depth", 10)
744
+ current_depth = context.get("_depth", 0)
722
745
 
723
746
  if current_depth >= max_depth:
724
- return self._create_result(False, f"Maximum recursion depth {max_depth} exceeded", "error")
747
+ return self._create_result(
748
+ False, f"Maximum recursion depth {max_depth} exceeded", "error"
749
+ )
725
750
 
726
751
  result = ValidationResult(valid=True)
727
752
 
@@ -729,33 +754,36 @@ class RecursiveValidator(BaseValidator):
729
754
  result = self._validate_dict(value, schema, context, current_depth)
730
755
  elif isinstance(value, list):
731
756
  result = self._validate_list(value, schema, context, current_depth)
732
- else:
733
- # Validate single value
734
- if 'type' in schema:
735
- type_rule = ValidationRule(
736
- type=ValidationType.TYPE,
737
- params={'type': schema['type']}
738
- )
739
- type_validator = TypeValidator()
740
- result = type_validator.validate(value, type_rule, context)
757
+ # Validate single value
758
+ elif "type" in schema:
759
+ type_rule = ValidationRule(
760
+ type=ValidationType.TYPE, params={"type": schema["type"]}
761
+ )
762
+ type_validator = TypeValidator()
763
+ result = type_validator.validate(value, type_rule, context)
741
764
 
742
765
  return result
743
766
 
744
- def _validate_dict(self, value: Dict, schema: Dict, context: Dict, depth: int) -> ValidationResult:
767
+ def _validate_dict(
768
+ self, value: Dict, schema: Dict, context: Dict, depth: int
769
+ ) -> ValidationResult:
745
770
  """Validate dictionary recursively"""
746
771
  result = ValidationResult(valid=True)
747
- properties = schema.get('properties', {})
772
+ properties = schema.get("properties", {})
748
773
 
749
774
  for key, prop_schema in properties.items():
750
775
  if key in value:
751
776
  # Create context for nested validation
752
777
  nested_context = context.copy()
753
- nested_context['_depth'] = depth + 1
778
+ nested_context["_depth"] = depth + 1
754
779
 
755
780
  # Recursively validate
756
781
  nested_rule = ValidationRule(
757
782
  type=ValidationType.RECURSIVE,
758
- params={'schema': prop_schema, 'max_depth': context.get('max_depth', 10)}
783
+ params={
784
+ "schema": prop_schema,
785
+ "max_depth": context.get("max_depth", 10),
786
+ },
759
787
  )
760
788
 
761
789
  nested_result = self.validate(value[key], nested_rule, nested_context)
@@ -763,24 +791,31 @@ class RecursiveValidator(BaseValidator):
763
791
  if not nested_result.valid:
764
792
  result.valid = False
765
793
  result.errors.extend([f"{key}.{e}" for e in nested_result.errors])
766
- result.warnings.extend([f"{key}.{w}" for w in nested_result.warnings])
794
+ result.warnings.extend(
795
+ [f"{key}.{w}" for w in nested_result.warnings]
796
+ )
767
797
 
768
798
  return result
769
799
 
770
- def _validate_list(self, value: List, schema: Dict, context: Dict, depth: int) -> ValidationResult:
800
+ def _validate_list(
801
+ self, value: List, schema: Dict, context: Dict, depth: int
802
+ ) -> ValidationResult:
771
803
  """Validate list recursively"""
772
804
  result = ValidationResult(valid=True)
773
- items_schema = schema.get('items', {})
805
+ items_schema = schema.get("items", {})
774
806
 
775
807
  for i, item in enumerate(value):
776
808
  # Create context for nested validation
777
809
  nested_context = context.copy()
778
- nested_context['_depth'] = depth + 1
810
+ nested_context["_depth"] = depth + 1
779
811
 
780
812
  # Recursively validate
781
813
  nested_rule = ValidationRule(
782
814
  type=ValidationType.RECURSIVE,
783
- params={'schema': items_schema, 'max_depth': context.get('max_depth', 10)}
815
+ params={
816
+ "schema": items_schema,
817
+ "max_depth": context.get("max_depth", 10),
818
+ },
784
819
  )
785
820
 
786
821
  nested_result = self.validate(item, nested_rule, nested_context)
@@ -796,16 +831,18 @@ class RecursiveValidator(BaseValidator):
796
831
  class CrossFieldValidator(BaseValidator):
797
832
  """Validates cross-field constraints - replaces 8 cross-field validation functions"""
798
833
 
799
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
834
+ def validate(
835
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
836
+ ) -> ValidationResult:
800
837
  """Validate cross-field constraints"""
801
- config = context.get('config', {})
802
- constraints = rule.params.get('constraints', [])
838
+ config = context.get("config", {})
839
+ constraints = rule.params.get("constraints", [])
803
840
 
804
841
  result = ValidationResult(valid=True)
805
842
 
806
843
  for constraint in constraints:
807
844
  if not self._evaluate_constraint(config, constraint):
808
- message = constraint.get('message', 'Cross-field constraint failed')
845
+ message = constraint.get("message", "Cross-field constraint failed")
809
846
  if rule.severity == "error":
810
847
  result.errors.append(message)
811
848
  elif rule.severity == "warning":
@@ -818,41 +855,41 @@ class CrossFieldValidator(BaseValidator):
818
855
 
819
856
  def _evaluate_constraint(self, config: Dict, constraint: Dict) -> bool:
820
857
  """Evaluate a cross-field constraint"""
821
- constraint_type = constraint.get('type')
858
+ constraint_type = constraint.get("type")
822
859
 
823
- if constraint_type == 'mutual_exclusive':
860
+ if constraint_type == "mutual_exclusive":
824
861
  # Only one of the fields should be present
825
- fields = constraint.get('fields', [])
862
+ fields = constraint.get("fields", [])
826
863
  present = [f for f in fields if f in config and config[f] is not None]
827
864
  return len(present) <= 1
828
865
 
829
- elif constraint_type == 'mutual_required':
866
+ if constraint_type == "mutual_required":
830
867
  # All fields must be present together
831
- fields = constraint.get('fields', [])
868
+ fields = constraint.get("fields", [])
832
869
  present = [f for f in fields if f in config and config[f] is not None]
833
870
  return len(present) == 0 or len(present) == len(fields)
834
871
 
835
- elif constraint_type == 'sum':
872
+ if constraint_type == "sum":
836
873
  # Sum of fields must match condition
837
- fields = constraint.get('fields', [])
838
- operator = constraint.get('operator', '==')
839
- target = constraint.get('value', 0)
874
+ fields = constraint.get("fields", [])
875
+ operator = constraint.get("operator", "==")
876
+ target = constraint.get("value", 0)
840
877
 
841
878
  total = sum(config.get(f, 0) for f in fields)
842
879
  return self._compare_values(total, operator, target)
843
880
 
844
- elif constraint_type == 'comparison':
881
+ if constraint_type == "comparison":
845
882
  # Compare two fields
846
- field1 = constraint.get('field1')
847
- field2 = constraint.get('field2')
848
- operator = constraint.get('operator', '==')
883
+ field1 = constraint.get("field1")
884
+ field2 = constraint.get("field2")
885
+ operator = constraint.get("operator", "==")
849
886
 
850
887
  if field1 in config and field2 in config:
851
888
  return self._compare_values(config[field1], operator, config[field2])
852
889
 
853
- elif constraint_type == 'custom':
890
+ elif constraint_type == "custom":
854
891
  # Custom constraint function
855
- func = constraint.get('function')
892
+ func = constraint.get("function")
856
893
  if callable(func):
857
894
  return func(config)
858
895
 
@@ -861,12 +898,12 @@ class CrossFieldValidator(BaseValidator):
861
898
  def _compare_values(self, val1: Any, operator: str, val2: Any) -> bool:
862
899
  """Compare two values with operator"""
863
900
  operators = {
864
- '==': lambda a, b: a == b,
865
- '!=': lambda a, b: a != b,
866
- '<': lambda a, b: a < b,
867
- '<=': lambda a, b: a <= b,
868
- '>': lambda a, b: a > b,
869
- '>=': lambda a, b: a >= b
901
+ "==": lambda a, b: a == b,
902
+ "!=": lambda a, b: a != b,
903
+ "<": lambda a, b: a < b,
904
+ "<=": lambda a, b: a <= b,
905
+ ">": lambda a, b: a > b,
906
+ ">=": lambda a, b: a >= b,
870
907
  }
871
908
 
872
909
  comparator = operators.get(operator)
@@ -882,10 +919,12 @@ class CrossFieldValidator(BaseValidator):
882
919
  class CompositeValidator(BaseValidator):
883
920
  """Composes multiple validators - replaces 6 composite validation functions"""
884
921
 
885
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
922
+ def validate(
923
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
924
+ ) -> ValidationResult:
886
925
  """Apply multiple validators in sequence"""
887
- validators = rule.params.get('validators', [])
888
- operator = rule.params.get('operator', 'AND') # AND, OR
926
+ validators = rule.params.get("validators", [])
927
+ operator = rule.params.get("operator", "AND") # AND, OR
889
928
 
890
929
  results = []
891
930
  combined_result = ValidationResult(valid=True)
@@ -896,10 +935,10 @@ class CompositeValidator(BaseValidator):
896
935
  for validator_def in validators:
897
936
  # Create rule from definition
898
937
  val_rule = ValidationRule(
899
- type=ValidationType[validator_def.get('type', 'CUSTOM').upper()],
900
- params=validator_def.get('params', {}),
901
- message=validator_def.get('message'),
902
- severity=validator_def.get('severity', rule.severity)
938
+ type=ValidationType[validator_def.get("type", "CUSTOM").upper()],
939
+ params=validator_def.get("params", {}),
940
+ message=validator_def.get("message"),
941
+ severity=validator_def.get("severity", rule.severity),
903
942
  )
904
943
 
905
944
  # Get validator and execute
@@ -914,9 +953,9 @@ class CompositeValidator(BaseValidator):
914
953
  combined_result.info.extend(result.info)
915
954
 
916
955
  # Apply operator logic
917
- if operator == 'AND':
956
+ if operator == "AND":
918
957
  combined_result.valid = all(r.valid for r in results)
919
- elif operator == 'OR':
958
+ elif operator == "OR":
920
959
  combined_result.valid = any(r.valid for r in results)
921
960
  if combined_result.valid:
922
961
  # Clear errors if any validator passed
@@ -929,41 +968,44 @@ class CompositeValidator(BaseValidator):
929
968
  class SchemaValidator(BaseValidator):
930
969
  """Validates against full schema - orchestrates other validators"""
931
970
 
932
- def validate(self, value: Any, rule: ValidationRule, context: Dict[str, Any]) -> ValidationResult:
971
+ def validate(
972
+ self, value: Any, rule: ValidationRule, context: Dict[str, Any]
973
+ ) -> ValidationResult:
933
974
  """Validate against complete schema"""
934
- schema = rule.params.get('schema', {})
935
- config = value if isinstance(value, dict) else {'value': value}
975
+ schema = rule.params.get("schema", {})
976
+ config = value if isinstance(value, dict) else {"value": value}
936
977
 
937
978
  # Update context
938
979
  context = context.copy()
939
- context['config'] = config
980
+ context["config"] = config
940
981
 
941
982
  result = ValidationResult(valid=True)
942
983
  strategy = ValidationStrategy()
943
984
 
944
985
  # Apply each schema validation
945
- if 'type' in schema:
986
+ if "type" in schema:
946
987
  type_result = strategy.validate_type(config, schema)
947
988
  self._merge_results(result, type_result)
948
989
 
949
- if 'required' in schema:
990
+ if "required" in schema:
950
991
  req_result = strategy.validate_required(config, schema)
951
992
  self._merge_results(result, req_result)
952
993
 
953
- if 'properties' in schema:
954
- for key, prop_schema in schema['properties'].items():
994
+ if "properties" in schema:
995
+ for key, prop_schema in schema["properties"].items():
955
996
  if key in config:
956
997
  prop_result = self.validate(
957
998
  config[key],
958
999
  ValidationRule(
959
- type=ValidationType.SCHEMA,
960
- params={'schema': prop_schema}
1000
+ type=ValidationType.SCHEMA, params={"schema": prop_schema}
961
1001
  ),
962
- context
1002
+ context,
963
1003
  )
964
1004
  if not prop_result.valid:
965
1005
  result.valid = False
966
- result.errors.extend([f"{key}: {e}" for e in prop_result.errors])
1006
+ result.errors.extend(
1007
+ [f"{key}: {e}" for e in prop_result.errors]
1008
+ )
967
1009
 
968
1010
  return result
969
1011
 
@@ -999,7 +1041,7 @@ class ValidationStrategy(IConfigStrategy):
999
1041
  ValidationType.RECURSIVE: RecursiveValidator(),
1000
1042
  ValidationType.CROSS_FIELD: CrossFieldValidator(),
1001
1043
  ValidationType.COMPOSITE: CompositeValidator(),
1002
- ValidationType.SCHEMA: SchemaValidator()
1044
+ ValidationType.SCHEMA: SchemaValidator(),
1003
1045
  }
1004
1046
 
1005
1047
  def can_handle(self, source: Union[str, Path, Dict]) -> bool:
@@ -1015,7 +1057,7 @@ class ValidationStrategy(IConfigStrategy):
1015
1057
  if not schema:
1016
1058
  return True
1017
1059
 
1018
- context = {'config': config}
1060
+ context = {"config": config}
1019
1061
  result = self._validate_with_schema(config, schema, context)
1020
1062
 
1021
1063
  if not result.valid:
@@ -1029,30 +1071,32 @@ class ValidationStrategy(IConfigStrategy):
1029
1071
  """Transform config based on schema"""
1030
1072
  return config
1031
1073
 
1032
- def _validate_with_schema(self, config: Dict, schema: Dict, context: Dict) -> ValidationResult:
1074
+ def _validate_with_schema(
1075
+ self, config: Dict, schema: Dict, context: Dict
1076
+ ) -> ValidationResult:
1033
1077
  """Validate configuration against schema"""
1034
1078
  # Use schema validator for comprehensive validation
1035
1079
  schema_rule = ValidationRule(
1036
- type=ValidationType.SCHEMA,
1037
- params={'schema': schema}
1080
+ type=ValidationType.SCHEMA, params={"schema": schema}
1038
1081
  )
1039
1082
 
1040
- return self.validators[ValidationType.SCHEMA].validate(config, schema_rule, context)
1083
+ return self.validators[ValidationType.SCHEMA].validate(
1084
+ config, schema_rule, context
1085
+ )
1041
1086
 
1042
1087
  # Helper methods for direct validation
1043
1088
  def validate_type(self, config: Dict, schema: Dict) -> ValidationResult:
1044
1089
  """Validate types in configuration"""
1045
1090
  result = ValidationResult(valid=True)
1046
- properties = schema.get('properties', {})
1091
+ properties = schema.get("properties", {})
1047
1092
 
1048
1093
  for key, prop_schema in properties.items():
1049
- if key in config and 'type' in prop_schema:
1094
+ if key in config and "type" in prop_schema:
1050
1095
  rule = ValidationRule(
1051
- type=ValidationType.TYPE,
1052
- params={'type': prop_schema['type']}
1096
+ type=ValidationType.TYPE, params={"type": prop_schema["type"]}
1053
1097
  )
1054
1098
  prop_result = self.validators[ValidationType.TYPE].validate(
1055
- config[key], rule, {'config': config}
1099
+ config[key], rule, {"config": config}
1056
1100
  )
1057
1101
  if not prop_result.valid:
1058
1102
  result.valid = False
@@ -1062,20 +1106,20 @@ class ValidationStrategy(IConfigStrategy):
1062
1106
 
1063
1107
  def validate_required(self, config: Dict, schema: Dict) -> ValidationResult:
1064
1108
  """Validate required fields"""
1065
- required_fields = schema.get('required', [])
1109
+ required_fields = schema.get("required", [])
1066
1110
  if required_fields:
1067
1111
  rule = ValidationRule(
1068
- type=ValidationType.REQUIRED,
1069
- params={'fields': required_fields}
1112
+ type=ValidationType.REQUIRED, params={"fields": required_fields}
1070
1113
  )
1071
1114
  return self.validators[ValidationType.REQUIRED].validate(
1072
- None, rule, {'config': config}
1115
+ None, rule, {"config": config}
1073
1116
  )
1074
1117
 
1075
1118
  return ValidationResult(valid=True)
1076
1119
 
1077
1120
  def compose_validators(self, *validator_names: str) -> Callable:
1078
1121
  """Compose multiple validators into a single function"""
1122
+
1079
1123
  def composed_validator(config: Dict, schema: Dict) -> ValidationResult:
1080
1124
  result = ValidationResult(valid=True)
1081
1125
 
@@ -1084,7 +1128,7 @@ class ValidationStrategy(IConfigStrategy):
1084
1128
  if val_type in self.validators:
1085
1129
  validator = self.validators[val_type]
1086
1130
  rule = ValidationRule(type=val_type, params=schema)
1087
- val_result = validator.validate(config, rule, {'config': config})
1131
+ val_result = validator.validate(config, rule, {"config": config})
1088
1132
 
1089
1133
  if not val_result.valid:
1090
1134
  result.valid = False
@@ -1097,9 +1141,4 @@ class ValidationStrategy(IConfigStrategy):
1097
1141
 
1098
1142
 
1099
1143
  # Export main components
1100
- __all__ = [
1101
- 'ValidationStrategy',
1102
- 'ValidationRule',
1103
- 'ValidationResult',
1104
- 'ValidationType'
1105
- ]
1144
+ __all__ = ["ValidationResult", "ValidationRule", "ValidationStrategy", "ValidationType"]