claude-mpm 4.3.22__py3-none-any.whl → 4.4.3__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 (74) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/WORKFLOW.md +2 -14
  3. claude_mpm/cli/commands/configure.py +2 -29
  4. claude_mpm/cli/commands/doctor.py +2 -2
  5. claude_mpm/cli/commands/mpm_init.py +3 -3
  6. claude_mpm/cli/parsers/configure_parser.py +4 -15
  7. claude_mpm/core/framework/__init__.py +38 -0
  8. claude_mpm/core/framework/formatters/__init__.py +11 -0
  9. claude_mpm/core/framework/formatters/capability_generator.py +356 -0
  10. claude_mpm/core/framework/formatters/content_formatter.py +283 -0
  11. claude_mpm/core/framework/formatters/context_generator.py +180 -0
  12. claude_mpm/core/framework/loaders/__init__.py +13 -0
  13. claude_mpm/core/framework/loaders/agent_loader.py +202 -0
  14. claude_mpm/core/framework/loaders/file_loader.py +213 -0
  15. claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
  16. claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
  17. claude_mpm/core/framework/processors/__init__.py +11 -0
  18. claude_mpm/core/framework/processors/memory_processor.py +222 -0
  19. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  20. claude_mpm/core/framework/processors/template_processor.py +238 -0
  21. claude_mpm/core/framework_loader.py +277 -1798
  22. claude_mpm/hooks/__init__.py +9 -1
  23. claude_mpm/hooks/kuzu_memory_hook.py +352 -0
  24. claude_mpm/hooks/memory_integration_hook.py +1 -1
  25. claude_mpm/services/agents/memory/content_manager.py +5 -2
  26. claude_mpm/services/agents/memory/memory_file_service.py +1 -0
  27. claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
  28. claude_mpm/services/core/path_resolver.py +1 -0
  29. claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
  30. claude_mpm/services/mcp_config_manager.py +67 -4
  31. claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
  32. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  33. claude_mpm/services/mcp_gateway/main.py +3 -13
  34. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  35. claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
  36. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
  37. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
  38. claude_mpm/services/shared/__init__.py +2 -1
  39. claude_mpm/services/shared/service_factory.py +8 -5
  40. claude_mpm/services/unified/__init__.py +65 -0
  41. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  42. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
  43. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
  44. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
  45. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
  46. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
  47. claude_mpm/services/unified/config_strategies/__init__.py +190 -0
  48. claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
  49. claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
  50. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
  51. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
  52. claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
  53. claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
  54. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  55. claude_mpm/services/unified/deployment_strategies/base.py +557 -0
  56. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
  57. claude_mpm/services/unified/deployment_strategies/local.py +594 -0
  58. claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
  59. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  60. claude_mpm/services/unified/interfaces.py +499 -0
  61. claude_mpm/services/unified/migration.py +532 -0
  62. claude_mpm/services/unified/strategies.py +551 -0
  63. claude_mpm/services/unified/unified_analyzer.py +534 -0
  64. claude_mpm/services/unified/unified_config.py +688 -0
  65. claude_mpm/services/unified/unified_deployment.py +470 -0
  66. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
  67. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
  68. claude_mpm/cli/commands/configure_tui.py +0 -1927
  69. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  70. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  71. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
  72. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
  73. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
  74. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,689 @@
1
+ """
2
+ Configuration Schema - Declarative configuration with automatic validation
3
+ Part of Phase 3 Configuration Consolidation
4
+ """
5
+
6
+ from typing import Any, Dict, List, Optional, Union, Type, Callable, Generic, TypeVar
7
+ from dataclasses import dataclass, field, asdict
8
+ from enum import Enum
9
+ from datetime import datetime
10
+ import json
11
+ from pathlib import Path
12
+
13
+ from claude_mpm.core.logging_utils import get_logger
14
+
15
+ T = TypeVar('T')
16
+
17
+
18
+ class SchemaType(Enum):
19
+ """Supported schema types"""
20
+ STRING = "string"
21
+ INTEGER = "integer"
22
+ NUMBER = "number"
23
+ BOOLEAN = "boolean"
24
+ ARRAY = "array"
25
+ OBJECT = "object"
26
+ NULL = "null"
27
+ ANY = "any"
28
+
29
+
30
+ class SchemaFormat(Enum):
31
+ """Supported format constraints"""
32
+ DATE = "date"
33
+ DATETIME = "datetime"
34
+ TIME = "time"
35
+ EMAIL = "email"
36
+ URI = "uri"
37
+ UUID = "uuid"
38
+ IPV4 = "ipv4"
39
+ IPV6 = "ipv6"
40
+ HOSTNAME = "hostname"
41
+ PATH = "path"
42
+ REGEX = "regex"
43
+ JSON = "json"
44
+ BASE64 = "base64"
45
+ SEMVER = "semver"
46
+
47
+
48
+ @dataclass
49
+ class SchemaProperty:
50
+ """Schema property definition"""
51
+ type: Union[SchemaType, List[SchemaType]]
52
+ description: Optional[str] = None
53
+ default: Any = None
54
+ required: bool = False
55
+ nullable: bool = False
56
+
57
+ # Constraints
58
+ minimum: Optional[Union[int, float]] = None
59
+ maximum: Optional[Union[int, float]] = None
60
+ exclusive_minimum: Optional[Union[int, float]] = None
61
+ exclusive_maximum: Optional[Union[int, float]] = None
62
+
63
+ min_length: Optional[int] = None
64
+ max_length: Optional[int] = None
65
+ pattern: Optional[str] = None
66
+ format: Optional[SchemaFormat] = None
67
+
68
+ enum: Optional[List[Any]] = None
69
+ const: Optional[Any] = None
70
+
71
+ # Array constraints
72
+ min_items: Optional[int] = None
73
+ max_items: Optional[int] = None
74
+ unique_items: bool = False
75
+ items: Optional['SchemaProperty'] = None
76
+
77
+ # Object constraints
78
+ properties: Optional[Dict[str, 'SchemaProperty']] = None
79
+ additional_properties: Union[bool, 'SchemaProperty'] = True
80
+ required_properties: Optional[List[str]] = None
81
+
82
+ # Advanced
83
+ dependencies: Optional[Dict[str, Union[List[str], 'SchemaProperty']]] = None
84
+ one_of: Optional[List['SchemaProperty']] = None
85
+ any_of: Optional[List['SchemaProperty']] = None
86
+ all_of: Optional[List['SchemaProperty']] = None
87
+ not_schema: Optional['SchemaProperty'] = None
88
+
89
+ # Custom validation
90
+ validator: Optional[Callable[[Any], bool]] = None
91
+ transformer: Optional[Callable[[Any], Any]] = None
92
+
93
+ # Metadata
94
+ deprecated: bool = False
95
+ examples: Optional[List[Any]] = None
96
+ read_only: bool = False
97
+ write_only: bool = False
98
+
99
+
100
+ @dataclass
101
+ class ConfigSchema:
102
+ """Complete configuration schema"""
103
+ title: str
104
+ description: Optional[str] = None
105
+ version: str = "1.0.0"
106
+ properties: Dict[str, SchemaProperty] = field(default_factory=dict)
107
+ required: List[str] = field(default_factory=list)
108
+ additional_properties: Union[bool, SchemaProperty] = True
109
+
110
+ # Schema metadata
111
+ schema_id: Optional[str] = None
112
+ schema_uri: Optional[str] = None
113
+
114
+ # Defaults
115
+ defaults: Dict[str, Any] = field(default_factory=dict)
116
+
117
+ # Validation rules
118
+ dependencies: Optional[Dict[str, Union[List[str], SchemaProperty]]] = None
119
+ pattern_properties: Optional[Dict[str, SchemaProperty]] = None
120
+
121
+ # Conditional schemas
122
+ if_schema: Optional['ConfigSchema'] = None
123
+ then_schema: Optional['ConfigSchema'] = None
124
+ else_schema: Optional['ConfigSchema'] = None
125
+
126
+ # Composition
127
+ all_of: Optional[List['ConfigSchema']] = None
128
+ any_of: Optional[List['ConfigSchema']] = None
129
+ one_of: Optional[List['ConfigSchema']] = None
130
+ not_schema: Optional['ConfigSchema'] = None
131
+
132
+ # Custom handlers
133
+ pre_validators: List[Callable] = field(default_factory=list)
134
+ post_validators: List[Callable] = field(default_factory=list)
135
+ transformers: List[Callable] = field(default_factory=list)
136
+
137
+
138
+ class SchemaBuilder:
139
+ """Builder for creating configuration schemas fluently"""
140
+
141
+ def __init__(self, title: str):
142
+ self.schema = ConfigSchema(title=title)
143
+ self.logger = get_logger(self.__class__.__name__)
144
+
145
+ def description(self, desc: str) -> 'SchemaBuilder':
146
+ """Set schema description"""
147
+ self.schema.description = desc
148
+ return self
149
+
150
+ def version(self, ver: str) -> 'SchemaBuilder':
151
+ """Set schema version"""
152
+ self.schema.version = ver
153
+ return self
154
+
155
+ def property(
156
+ self,
157
+ name: str,
158
+ type: Union[SchemaType, str],
159
+ **kwargs
160
+ ) -> 'SchemaBuilder':
161
+ """Add a property to the schema"""
162
+ if isinstance(type, str):
163
+ type = SchemaType(type)
164
+
165
+ prop = SchemaProperty(type=type, **kwargs)
166
+ self.schema.properties[name] = prop
167
+
168
+ if kwargs.get('required', False):
169
+ if name not in self.schema.required:
170
+ self.schema.required.append(name)
171
+
172
+ return self
173
+
174
+ def string(self, name: str, **kwargs) -> 'SchemaBuilder':
175
+ """Add string property"""
176
+ return self.property(name, SchemaType.STRING, **kwargs)
177
+
178
+ def integer(self, name: str, **kwargs) -> 'SchemaBuilder':
179
+ """Add integer property"""
180
+ return self.property(name, SchemaType.INTEGER, **kwargs)
181
+
182
+ def number(self, name: str, **kwargs) -> 'SchemaBuilder':
183
+ """Add number property"""
184
+ return self.property(name, SchemaType.NUMBER, **kwargs)
185
+
186
+ def boolean(self, name: str, **kwargs) -> 'SchemaBuilder':
187
+ """Add boolean property"""
188
+ return self.property(name, SchemaType.BOOLEAN, **kwargs)
189
+
190
+ def array(self, name: str, items: Optional[SchemaProperty] = None, **kwargs) -> 'SchemaBuilder':
191
+ """Add array property"""
192
+ return self.property(name, SchemaType.ARRAY, items=items, **kwargs)
193
+
194
+ def object(self, name: str, properties: Optional[Dict[str, SchemaProperty]] = None, **kwargs) -> 'SchemaBuilder':
195
+ """Add object property"""
196
+ return self.property(name, SchemaType.OBJECT, properties=properties, **kwargs)
197
+
198
+ def enum(self, name: str, values: List[Any], **kwargs) -> 'SchemaBuilder':
199
+ """Add enum property"""
200
+ return self.property(name, SchemaType.STRING, enum=values, **kwargs)
201
+
202
+ def required_fields(self, *fields: str) -> 'SchemaBuilder':
203
+ """Mark fields as required"""
204
+ for field in fields:
205
+ if field not in self.schema.required:
206
+ self.schema.required.append(field)
207
+ return self
208
+
209
+ def default(self, name: str, value: Any) -> 'SchemaBuilder':
210
+ """Set default value for property"""
211
+ if name in self.schema.properties:
212
+ self.schema.properties[name].default = value
213
+ self.schema.defaults[name] = value
214
+ return self
215
+
216
+ def dependency(self, field: str, depends_on: Union[str, List[str]]) -> 'SchemaBuilder':
217
+ """Add field dependency"""
218
+ if self.schema.dependencies is None:
219
+ self.schema.dependencies = {}
220
+
221
+ if isinstance(depends_on, str):
222
+ depends_on = [depends_on]
223
+
224
+ self.schema.dependencies[field] = depends_on
225
+ return self
226
+
227
+ def validator(self, func: Callable) -> 'SchemaBuilder':
228
+ """Add custom validator"""
229
+ self.schema.post_validators.append(func)
230
+ return self
231
+
232
+ def transformer(self, func: Callable) -> 'SchemaBuilder':
233
+ """Add transformer function"""
234
+ self.schema.transformers.append(func)
235
+ return self
236
+
237
+ def build(self) -> ConfigSchema:
238
+ """Build and return the schema"""
239
+ return self.schema
240
+
241
+
242
+ class SchemaValidator:
243
+ """Validates configurations against schemas"""
244
+
245
+ def __init__(self):
246
+ self.logger = get_logger(self.__class__.__name__)
247
+ self.errors: List[str] = []
248
+ self.warnings: List[str] = []
249
+
250
+ def validate(self, config: Dict[str, Any], schema: ConfigSchema) -> bool:
251
+ """Validate configuration against schema"""
252
+ self.errors = []
253
+ self.warnings = []
254
+
255
+ # Run pre-validators
256
+ for validator in schema.pre_validators:
257
+ if not validator(config):
258
+ self.errors.append(f"Pre-validation failed: {validator.__name__}")
259
+ return False
260
+
261
+ # Validate required fields
262
+ for field in schema.required:
263
+ if field not in config:
264
+ self.errors.append(f"Required field missing: {field}")
265
+
266
+ # Validate properties
267
+ for name, prop in schema.properties.items():
268
+ if name in config:
269
+ self._validate_property(config[name], prop, name)
270
+
271
+ # Check additional properties
272
+ if not schema.additional_properties:
273
+ extra = set(config.keys()) - set(schema.properties.keys())
274
+ if extra:
275
+ self.errors.append(f"Additional properties not allowed: {extra}")
276
+
277
+ # Validate dependencies
278
+ if schema.dependencies:
279
+ self._validate_dependencies(config, schema.dependencies)
280
+
281
+ # Run post-validators
282
+ for validator in schema.post_validators:
283
+ if not validator(config):
284
+ self.errors.append(f"Post-validation failed: {validator.__name__}")
285
+
286
+ return len(self.errors) == 0
287
+
288
+ def _validate_property(self, value: Any, prop: SchemaProperty, path: str):
289
+ """Validate a single property"""
290
+ # Check nullable
291
+ if value is None:
292
+ if not prop.nullable:
293
+ self.errors.append(f"{path}: null value not allowed")
294
+ return
295
+
296
+ # Check type
297
+ if not self._check_type(value, prop.type):
298
+ self.errors.append(f"{path}: type mismatch, expected {prop.type}")
299
+
300
+ # Check constraints based on type
301
+ if isinstance(value, (int, float)):
302
+ self._validate_numeric(value, prop, path)
303
+ elif isinstance(value, str):
304
+ self._validate_string(value, prop, path)
305
+ elif isinstance(value, list):
306
+ self._validate_array(value, prop, path)
307
+ elif isinstance(value, dict):
308
+ self._validate_object(value, prop, path)
309
+
310
+ # Check enum
311
+ if prop.enum and value not in prop.enum:
312
+ self.errors.append(f"{path}: value must be one of {prop.enum}")
313
+
314
+ # Check const
315
+ if prop.const is not None and value != prop.const:
316
+ self.errors.append(f"{path}: value must be {prop.const}")
317
+
318
+ # Run custom validator
319
+ if prop.validator and not prop.validator(value):
320
+ self.errors.append(f"{path}: custom validation failed")
321
+
322
+ def _check_type(self, value: Any, expected: Union[SchemaType, List[SchemaType]]) -> bool:
323
+ """Check if value matches expected type"""
324
+ if isinstance(expected, list):
325
+ return any(self._check_type(value, t) for t in expected)
326
+
327
+ type_map = {
328
+ SchemaType.STRING: str,
329
+ SchemaType.INTEGER: int,
330
+ SchemaType.NUMBER: (int, float),
331
+ SchemaType.BOOLEAN: bool,
332
+ SchemaType.ARRAY: list,
333
+ SchemaType.OBJECT: dict,
334
+ SchemaType.NULL: type(None),
335
+ SchemaType.ANY: object
336
+ }
337
+
338
+ expected_type = type_map.get(expected, object)
339
+ return isinstance(value, expected_type)
340
+
341
+ def _validate_numeric(self, value: Union[int, float], prop: SchemaProperty, path: str):
342
+ """Validate numeric constraints"""
343
+ if prop.minimum is not None and value < prop.minimum:
344
+ self.errors.append(f"{path}: value {value} is below minimum {prop.minimum}")
345
+
346
+ if prop.maximum is not None and value > prop.maximum:
347
+ self.errors.append(f"{path}: value {value} exceeds maximum {prop.maximum}")
348
+
349
+ if prop.exclusive_minimum is not None and value <= prop.exclusive_minimum:
350
+ self.errors.append(f"{path}: value {value} must be greater than {prop.exclusive_minimum}")
351
+
352
+ if prop.exclusive_maximum is not None and value >= prop.exclusive_maximum:
353
+ self.errors.append(f"{path}: value {value} must be less than {prop.exclusive_maximum}")
354
+
355
+ def _validate_string(self, value: str, prop: SchemaProperty, path: str):
356
+ """Validate string constraints"""
357
+ if prop.min_length is not None and len(value) < prop.min_length:
358
+ self.errors.append(f"{path}: length {len(value)} is below minimum {prop.min_length}")
359
+
360
+ if prop.max_length is not None and len(value) > prop.max_length:
361
+ self.errors.append(f"{path}: length {len(value)} exceeds maximum {prop.max_length}")
362
+
363
+ if prop.pattern:
364
+ import re
365
+ if not re.match(prop.pattern, value):
366
+ self.errors.append(f"{path}: does not match pattern {prop.pattern}")
367
+
368
+ if prop.format:
369
+ if not self._validate_format(value, prop.format):
370
+ self.errors.append(f"{path}: invalid format {prop.format.value}")
371
+
372
+ def _validate_array(self, value: List, prop: SchemaProperty, path: str):
373
+ """Validate array constraints"""
374
+ if prop.min_items is not None and len(value) < prop.min_items:
375
+ self.errors.append(f"{path}: array length {len(value)} is below minimum {prop.min_items}")
376
+
377
+ if prop.max_items is not None and len(value) > prop.max_items:
378
+ self.errors.append(f"{path}: array length {len(value)} exceeds maximum {prop.max_items}")
379
+
380
+ if prop.unique_items:
381
+ seen = set()
382
+ for item in value:
383
+ item_key = str(item) if not isinstance(item, (dict, list)) else json.dumps(item, sort_keys=True)
384
+ if item_key in seen:
385
+ self.errors.append(f"{path}: duplicate items not allowed")
386
+ break
387
+ seen.add(item_key)
388
+
389
+ if prop.items:
390
+ for i, item in enumerate(value):
391
+ self._validate_property(item, prop.items, f"{path}[{i}]")
392
+
393
+ def _validate_object(self, value: Dict, prop: SchemaProperty, path: str):
394
+ """Validate object constraints"""
395
+ if prop.properties:
396
+ for name, sub_prop in prop.properties.items():
397
+ if name in value:
398
+ self._validate_property(value[name], sub_prop, f"{path}.{name}")
399
+
400
+ if prop.required_properties:
401
+ for req in prop.required_properties:
402
+ if req not in value:
403
+ self.errors.append(f"{path}: required property '{req}' missing")
404
+
405
+ if not prop.additional_properties:
406
+ if prop.properties:
407
+ extra = set(value.keys()) - set(prop.properties.keys())
408
+ if extra:
409
+ self.errors.append(f"{path}: additional properties not allowed: {extra}")
410
+
411
+ def _validate_dependencies(self, config: Dict, dependencies: Dict):
412
+ """Validate field dependencies"""
413
+ for field, deps in dependencies.items():
414
+ if field in config:
415
+ if isinstance(deps, list):
416
+ for dep in deps:
417
+ if dep not in config:
418
+ self.errors.append(f"Field '{field}' requires '{dep}' to be present")
419
+
420
+ def _validate_format(self, value: str, format: SchemaFormat) -> bool:
421
+ """Validate string format"""
422
+ validators = {
423
+ SchemaFormat.EMAIL: lambda v: '@' in v and '.' in v.split('@')[1],
424
+ SchemaFormat.DATE: lambda v: self._try_parse_date(v, '%Y-%m-%d'),
425
+ SchemaFormat.DATETIME: lambda v: self._try_parse_date(v, '%Y-%m-%dT%H:%M:%S'),
426
+ SchemaFormat.UUID: lambda v: self._validate_uuid(v),
427
+ SchemaFormat.IPV4: lambda v: self._validate_ipv4(v),
428
+ SchemaFormat.IPV6: lambda v: self._validate_ipv6(v),
429
+ SchemaFormat.URI: lambda v: '://' in v,
430
+ SchemaFormat.PATH: lambda v: True, # Any string is valid path
431
+ SchemaFormat.SEMVER: lambda v: self._validate_semver(v)
432
+ }
433
+
434
+ validator = validators.get(format)
435
+ return validator(value) if validator else True
436
+
437
+ def _try_parse_date(self, value: str, format: str) -> bool:
438
+ """Try to parse date string"""
439
+ try:
440
+ datetime.strptime(value, format)
441
+ return True
442
+ except:
443
+ return False
444
+
445
+ def _validate_uuid(self, value: str) -> bool:
446
+ """Validate UUID format"""
447
+ import uuid
448
+ try:
449
+ uuid.UUID(value)
450
+ return True
451
+ except:
452
+ return False
453
+
454
+ def _validate_ipv4(self, value: str) -> bool:
455
+ """Validate IPv4 address"""
456
+ import ipaddress
457
+ try:
458
+ ipaddress.IPv4Address(value)
459
+ return True
460
+ except:
461
+ return False
462
+
463
+ def _validate_ipv6(self, value: str) -> bool:
464
+ """Validate IPv6 address"""
465
+ import ipaddress
466
+ try:
467
+ ipaddress.IPv6Address(value)
468
+ return True
469
+ except:
470
+ return False
471
+
472
+ def _validate_semver(self, value: str) -> bool:
473
+ """Validate semantic version"""
474
+ import re
475
+ pattern = r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
476
+ return bool(re.match(pattern, value))
477
+
478
+
479
+ class SchemaRegistry:
480
+ """Registry for managing configuration schemas"""
481
+
482
+ def __init__(self):
483
+ self.logger = get_logger(self.__class__.__name__)
484
+ self.schemas: Dict[str, ConfigSchema] = {}
485
+ self.versions: Dict[str, Dict[str, ConfigSchema]] = {}
486
+
487
+ def register(self, schema: ConfigSchema, name: Optional[str] = None):
488
+ """Register a schema"""
489
+ name = name or schema.title
490
+
491
+ # Store by name
492
+ self.schemas[name] = schema
493
+
494
+ # Store by version
495
+ if name not in self.versions:
496
+ self.versions[name] = {}
497
+ self.versions[name][schema.version] = schema
498
+
499
+ self.logger.info(f"Registered schema: {name} v{schema.version}")
500
+
501
+ def get(self, name: str, version: Optional[str] = None) -> Optional[ConfigSchema]:
502
+ """Get schema by name and optionally version"""
503
+ if version:
504
+ return self.versions.get(name, {}).get(version)
505
+ return self.schemas.get(name)
506
+
507
+ def list_schemas(self) -> List[str]:
508
+ """List all registered schemas"""
509
+ return list(self.schemas.keys())
510
+
511
+ def list_versions(self, name: str) -> List[str]:
512
+ """List all versions of a schema"""
513
+ return list(self.versions.get(name, {}).keys())
514
+
515
+
516
+ class ConfigMigration:
517
+ """Handles configuration migration between schema versions"""
518
+
519
+ def __init__(self):
520
+ self.logger = get_logger(self.__class__.__name__)
521
+ self.migrations: Dict[tuple, Callable] = {}
522
+
523
+ def register_migration(
524
+ self,
525
+ from_version: str,
526
+ to_version: str,
527
+ migration_func: Callable[[Dict], Dict]
528
+ ):
529
+ """Register a migration function"""
530
+ key = (from_version, to_version)
531
+ self.migrations[key] = migration_func
532
+ self.logger.info(f"Registered migration: {from_version} -> {to_version}")
533
+
534
+ def migrate(
535
+ self,
536
+ config: Dict[str, Any],
537
+ from_version: str,
538
+ to_version: str
539
+ ) -> Dict[str, Any]:
540
+ """Migrate configuration between versions"""
541
+ key = (from_version, to_version)
542
+
543
+ if key in self.migrations:
544
+ # Direct migration available
545
+ return self.migrations[key](config)
546
+
547
+ # Try to find migration path
548
+ path = self._find_migration_path(from_version, to_version)
549
+
550
+ if not path:
551
+ raise ValueError(f"No migration path from {from_version} to {to_version}")
552
+
553
+ # Apply migrations in sequence
554
+ current = config
555
+ for i in range(len(path) - 1):
556
+ key = (path[i], path[i + 1])
557
+ if key in self.migrations:
558
+ current = self.migrations[key](current)
559
+ self.logger.info(f"Applied migration: {path[i]} -> {path[i + 1]}")
560
+
561
+ return current
562
+
563
+ def _find_migration_path(self, from_version: str, to_version: str) -> Optional[List[str]]:
564
+ """Find migration path between versions using BFS"""
565
+ from collections import deque
566
+
567
+ # Build graph of migrations
568
+ graph = {}
569
+ for (from_v, to_v) in self.migrations.keys():
570
+ if from_v not in graph:
571
+ graph[from_v] = []
572
+ graph[from_v].append(to_v)
573
+
574
+ # BFS to find path
575
+ queue = deque([(from_version, [from_version])])
576
+ visited = {from_version}
577
+
578
+ while queue:
579
+ current, path = queue.popleft()
580
+
581
+ if current == to_version:
582
+ return path
583
+
584
+ for next_v in graph.get(current, []):
585
+ if next_v not in visited:
586
+ visited.add(next_v)
587
+ queue.append((next_v, path + [next_v]))
588
+
589
+ return None
590
+
591
+
592
+ class TypedConfig(Generic[T]):
593
+ """Type-safe configuration wrapper"""
594
+
595
+ def __init__(self, schema: ConfigSchema, data: Dict[str, Any]):
596
+ self.schema = schema
597
+ self._data = data
598
+ self._validator = SchemaValidator()
599
+
600
+ # Validate on initialization
601
+ if not self._validator.validate(data, schema):
602
+ raise ValueError(f"Invalid configuration: {self._validator.errors}")
603
+
604
+ def get(self, key: str, default: Any = None) -> Any:
605
+ """Get configuration value"""
606
+ return self._data.get(key, default)
607
+
608
+ def set(self, key: str, value: Any):
609
+ """Set configuration value with validation"""
610
+ # Create temporary config with new value
611
+ temp = self._data.copy()
612
+ temp[key] = value
613
+
614
+ # Validate
615
+ if not self._validator.validate(temp, self.schema):
616
+ raise ValueError(f"Invalid value for {key}: {self._validator.errors}")
617
+
618
+ self._data[key] = value
619
+
620
+ def to_dict(self) -> Dict[str, Any]:
621
+ """Convert to dictionary"""
622
+ return self._data.copy()
623
+
624
+ def __getitem__(self, key: str) -> Any:
625
+ """Dictionary-style access"""
626
+ return self._data[key]
627
+
628
+ def __setitem__(self, key: str, value: Any):
629
+ """Dictionary-style setting with validation"""
630
+ self.set(key, value)
631
+
632
+
633
+ # Predefined common schemas
634
+ def create_database_schema() -> ConfigSchema:
635
+ """Create common database configuration schema"""
636
+ return (SchemaBuilder("Database Configuration")
637
+ .string("host", required=True, default="localhost")
638
+ .integer("port", required=True, minimum=1, maximum=65535, default=5432)
639
+ .string("database", required=True)
640
+ .string("username", required=True)
641
+ .string("password", write_only=True)
642
+ .integer("pool_size", minimum=1, maximum=100, default=10)
643
+ .integer("timeout", minimum=1, default=30)
644
+ .boolean("ssl", default=False)
645
+ .build())
646
+
647
+
648
+ def create_api_schema() -> ConfigSchema:
649
+ """Create common API configuration schema"""
650
+ return (SchemaBuilder("API Configuration")
651
+ .string("base_url", required=True, format=SchemaFormat.URI)
652
+ .string("api_key", required=True, write_only=True)
653
+ .integer("timeout", minimum=1, default=30)
654
+ .integer("retry_count", minimum=0, maximum=10, default=3)
655
+ .number("retry_delay", minimum=0, default=1.0)
656
+ .array("allowed_methods", default=["GET", "POST", "PUT", "DELETE"])
657
+ .object("headers", default={})
658
+ .boolean("verify_ssl", default=True)
659
+ .build())
660
+
661
+
662
+ def create_logging_schema() -> ConfigSchema:
663
+ """Create common logging configuration schema"""
664
+ return (SchemaBuilder("Logging Configuration")
665
+ .enum("level", ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="INFO")
666
+ .string("format", default="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
667
+ .string("file", format=SchemaFormat.PATH)
668
+ .integer("max_size", minimum=1, default=10485760) # 10MB
669
+ .integer("backup_count", minimum=0, default=5)
670
+ .boolean("console", default=True)
671
+ .boolean("file_enabled", default=False)
672
+ .build())
673
+
674
+
675
+ # Export main components
676
+ __all__ = [
677
+ 'ConfigSchema',
678
+ 'SchemaProperty',
679
+ 'SchemaBuilder',
680
+ 'SchemaValidator',
681
+ 'SchemaRegistry',
682
+ 'ConfigMigration',
683
+ 'TypedConfig',
684
+ 'SchemaType',
685
+ 'SchemaFormat',
686
+ 'create_database_schema',
687
+ 'create_api_schema',
688
+ 'create_logging_schema'
689
+ ]