claude-mpm 4.4.3__py3-none-any.whl → 4.4.4__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/agent_loader.py +3 -2
- claude_mpm/agents/agent_loader_integration.py +2 -1
- claude_mpm/agents/async_agent_loader.py +2 -2
- claude_mpm/agents/base_agent_loader.py +2 -2
- claude_mpm/agents/frontmatter_validator.py +1 -0
- claude_mpm/agents/system_agent_config.py +2 -1
- claude_mpm/cli/commands/doctor.py +44 -5
- claude_mpm/cli/commands/mpm_init.py +116 -62
- claude_mpm/cli/parsers/configure_parser.py +3 -1
- claude_mpm/cli/startup_logging.py +1 -3
- claude_mpm/config/agent_config.py +1 -1
- claude_mpm/config/paths.py +2 -1
- claude_mpm/core/agent_name_normalizer.py +1 -0
- claude_mpm/core/config.py +2 -1
- claude_mpm/core/config_aliases.py +2 -1
- claude_mpm/core/file_utils.py +0 -1
- claude_mpm/core/framework/__init__.py +6 -6
- claude_mpm/core/framework/formatters/__init__.py +2 -2
- claude_mpm/core/framework/formatters/capability_generator.py +19 -8
- claude_mpm/core/framework/formatters/content_formatter.py +8 -3
- claude_mpm/core/framework/formatters/context_generator.py +7 -3
- claude_mpm/core/framework/loaders/__init__.py +3 -3
- claude_mpm/core/framework/loaders/agent_loader.py +7 -3
- claude_mpm/core/framework/loaders/file_loader.py +16 -6
- claude_mpm/core/framework/loaders/instruction_loader.py +16 -6
- claude_mpm/core/framework/loaders/packaged_loader.py +36 -12
- claude_mpm/core/framework/processors/__init__.py +2 -2
- claude_mpm/core/framework/processors/memory_processor.py +14 -6
- claude_mpm/core/framework/processors/metadata_processor.py +5 -5
- claude_mpm/core/framework/processors/template_processor.py +12 -6
- claude_mpm/core/framework_loader.py +44 -20
- claude_mpm/core/log_manager.py +2 -1
- claude_mpm/core/tool_access_control.py +1 -0
- claude_mpm/core/unified_agent_registry.py +2 -1
- claude_mpm/core/unified_paths.py +1 -0
- claude_mpm/experimental/cli_enhancements.py +1 -0
- claude_mpm/hooks/base_hook.py +1 -0
- claude_mpm/hooks/instruction_reinforcement.py +1 -0
- claude_mpm/hooks/kuzu_memory_hook.py +20 -13
- claude_mpm/hooks/validation_hooks.py +1 -1
- claude_mpm/scripts/mpm_doctor.py +1 -0
- claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
- claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
- claude_mpm/services/agents/management/agent_management_service.py +1 -1
- claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
- claude_mpm/services/agents/memory/memory_file_service.py +6 -2
- claude_mpm/services/agents/memory/memory_format_service.py +0 -1
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
- claude_mpm/services/async_session_logger.py +1 -1
- claude_mpm/services/claude_session_logger.py +1 -0
- claude_mpm/services/core/path_resolver.py +1 -0
- claude_mpm/services/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +3 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
- claude_mpm/services/event_bus/direct_relay.py +2 -1
- claude_mpm/services/event_bus/event_bus.py +1 -0
- claude_mpm/services/event_bus/relay.py +3 -2
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
- claude_mpm/services/infrastructure/daemon_manager.py +1 -1
- claude_mpm/services/mcp_config_manager.py +10 -10
- claude_mpm/services/mcp_gateway/core/process_pool.py +62 -23
- claude_mpm/services/mcp_gateway/tools/__init__.py +6 -5
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +3 -1
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +16 -31
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/project/archive_manager.py +159 -96
- claude_mpm/services/project/documentation_manager.py +64 -45
- claude_mpm/services/project/enhanced_analyzer.py +132 -89
- claude_mpm/services/project/project_organizer.py +225 -131
- claude_mpm/services/response_tracker.py +1 -1
- claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
- claude_mpm/services/unified/__init__.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
- claude_mpm/services/unified/config_strategies/__init__.py +111 -126
- claude_mpm/services/unified/config_strategies/config_schema.py +157 -111
- claude_mpm/services/unified/config_strategies/context_strategy.py +91 -89
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +183 -173
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +160 -152
- claude_mpm/services/unified/config_strategies/unified_config_service.py +124 -112
- claude_mpm/services/unified/config_strategies/validation_strategy.py +298 -259
- claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
- claude_mpm/services/unified/deployment_strategies/base.py +24 -28
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
- claude_mpm/services/unified/deployment_strategies/local.py +49 -34
- claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
- claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
- claude_mpm/services/unified/interfaces.py +0 -26
- claude_mpm/services/unified/migration.py +17 -40
- claude_mpm/services/unified/strategies.py +9 -26
- claude_mpm/services/unified/unified_analyzer.py +48 -44
- claude_mpm/services/unified/unified_config.py +21 -19
- claude_mpm/services/unified/unified_deployment.py +21 -26
- claude_mpm/storage/state_storage.py +1 -0
- claude_mpm/utils/agent_dependency_loader.py +18 -6
- claude_mpm/utils/common.py +14 -12
- claude_mpm/utils/database_connector.py +15 -12
- claude_mpm/utils/error_handler.py +1 -0
- claude_mpm/utils/log_cleanup.py +1 -0
- claude_mpm/utils/path_operations.py +1 -0
- claude_mpm/utils/session_logging.py +1 -1
- claude_mpm/utils/subprocess_utils.py +1 -0
- claude_mpm/validation/agent_validator.py +1 -1
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +9 -3
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +118 -117
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/top_level.txt +0 -0
@@ -3,20 +3,20 @@ Configuration Schema - Declarative configuration with automatic validation
|
|
3
3
|
Part of Phase 3 Configuration Consolidation
|
4
4
|
"""
|
5
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
6
|
import json
|
11
|
-
from
|
7
|
+
from dataclasses import dataclass, field
|
8
|
+
from datetime import datetime
|
9
|
+
from enum import Enum
|
10
|
+
from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Union
|
12
11
|
|
13
12
|
from claude_mpm.core.logging_utils import get_logger
|
14
13
|
|
15
|
-
T = TypeVar(
|
14
|
+
T = TypeVar("T")
|
16
15
|
|
17
16
|
|
18
17
|
class SchemaType(Enum):
|
19
18
|
"""Supported schema types"""
|
19
|
+
|
20
20
|
STRING = "string"
|
21
21
|
INTEGER = "integer"
|
22
22
|
NUMBER = "number"
|
@@ -29,6 +29,7 @@ class SchemaType(Enum):
|
|
29
29
|
|
30
30
|
class SchemaFormat(Enum):
|
31
31
|
"""Supported format constraints"""
|
32
|
+
|
32
33
|
DATE = "date"
|
33
34
|
DATETIME = "datetime"
|
34
35
|
TIME = "time"
|
@@ -48,6 +49,7 @@ class SchemaFormat(Enum):
|
|
48
49
|
@dataclass
|
49
50
|
class SchemaProperty:
|
50
51
|
"""Schema property definition"""
|
52
|
+
|
51
53
|
type: Union[SchemaType, List[SchemaType]]
|
52
54
|
description: Optional[str] = None
|
53
55
|
default: Any = None
|
@@ -72,19 +74,19 @@ class SchemaProperty:
|
|
72
74
|
min_items: Optional[int] = None
|
73
75
|
max_items: Optional[int] = None
|
74
76
|
unique_items: bool = False
|
75
|
-
items: Optional[
|
77
|
+
items: Optional["SchemaProperty"] = None
|
76
78
|
|
77
79
|
# Object constraints
|
78
|
-
properties: Optional[Dict[str,
|
79
|
-
additional_properties: Union[bool,
|
80
|
+
properties: Optional[Dict[str, "SchemaProperty"]] = None
|
81
|
+
additional_properties: Union[bool, "SchemaProperty"] = True
|
80
82
|
required_properties: Optional[List[str]] = None
|
81
83
|
|
82
84
|
# Advanced
|
83
|
-
dependencies: Optional[Dict[str, Union[List[str],
|
84
|
-
one_of: Optional[List[
|
85
|
-
any_of: Optional[List[
|
86
|
-
all_of: Optional[List[
|
87
|
-
not_schema: Optional[
|
85
|
+
dependencies: Optional[Dict[str, Union[List[str], "SchemaProperty"]]] = None
|
86
|
+
one_of: Optional[List["SchemaProperty"]] = None
|
87
|
+
any_of: Optional[List["SchemaProperty"]] = None
|
88
|
+
all_of: Optional[List["SchemaProperty"]] = None
|
89
|
+
not_schema: Optional["SchemaProperty"] = None
|
88
90
|
|
89
91
|
# Custom validation
|
90
92
|
validator: Optional[Callable[[Any], bool]] = None
|
@@ -100,6 +102,7 @@ class SchemaProperty:
|
|
100
102
|
@dataclass
|
101
103
|
class ConfigSchema:
|
102
104
|
"""Complete configuration schema"""
|
105
|
+
|
103
106
|
title: str
|
104
107
|
description: Optional[str] = None
|
105
108
|
version: str = "1.0.0"
|
@@ -119,15 +122,15 @@ class ConfigSchema:
|
|
119
122
|
pattern_properties: Optional[Dict[str, SchemaProperty]] = None
|
120
123
|
|
121
124
|
# Conditional schemas
|
122
|
-
if_schema: Optional[
|
123
|
-
then_schema: Optional[
|
124
|
-
else_schema: Optional[
|
125
|
+
if_schema: Optional["ConfigSchema"] = None
|
126
|
+
then_schema: Optional["ConfigSchema"] = None
|
127
|
+
else_schema: Optional["ConfigSchema"] = None
|
125
128
|
|
126
129
|
# Composition
|
127
|
-
all_of: Optional[List[
|
128
|
-
any_of: Optional[List[
|
129
|
-
one_of: Optional[List[
|
130
|
-
not_schema: Optional[
|
130
|
+
all_of: Optional[List["ConfigSchema"]] = None
|
131
|
+
any_of: Optional[List["ConfigSchema"]] = None
|
132
|
+
one_of: Optional[List["ConfigSchema"]] = None
|
133
|
+
not_schema: Optional["ConfigSchema"] = None
|
131
134
|
|
132
135
|
# Custom handlers
|
133
136
|
pre_validators: List[Callable] = field(default_factory=list)
|
@@ -142,22 +145,19 @@ class SchemaBuilder:
|
|
142
145
|
self.schema = ConfigSchema(title=title)
|
143
146
|
self.logger = get_logger(self.__class__.__name__)
|
144
147
|
|
145
|
-
def description(self, desc: str) ->
|
148
|
+
def description(self, desc: str) -> "SchemaBuilder":
|
146
149
|
"""Set schema description"""
|
147
150
|
self.schema.description = desc
|
148
151
|
return self
|
149
152
|
|
150
|
-
def version(self, ver: str) ->
|
153
|
+
def version(self, ver: str) -> "SchemaBuilder":
|
151
154
|
"""Set schema version"""
|
152
155
|
self.schema.version = ver
|
153
156
|
return self
|
154
157
|
|
155
158
|
def property(
|
156
|
-
self,
|
157
|
-
|
158
|
-
type: Union[SchemaType, str],
|
159
|
-
**kwargs
|
160
|
-
) -> 'SchemaBuilder':
|
159
|
+
self, name: str, type: Union[SchemaType, str], **kwargs
|
160
|
+
) -> "SchemaBuilder":
|
161
161
|
"""Add a property to the schema"""
|
162
162
|
if isinstance(type, str):
|
163
163
|
type = SchemaType(type)
|
@@ -165,55 +165,64 @@ class SchemaBuilder:
|
|
165
165
|
prop = SchemaProperty(type=type, **kwargs)
|
166
166
|
self.schema.properties[name] = prop
|
167
167
|
|
168
|
-
if kwargs.get(
|
168
|
+
if kwargs.get("required", False):
|
169
169
|
if name not in self.schema.required:
|
170
170
|
self.schema.required.append(name)
|
171
171
|
|
172
172
|
return self
|
173
173
|
|
174
|
-
def string(self, name: str, **kwargs) ->
|
174
|
+
def string(self, name: str, **kwargs) -> "SchemaBuilder":
|
175
175
|
"""Add string property"""
|
176
176
|
return self.property(name, SchemaType.STRING, **kwargs)
|
177
177
|
|
178
|
-
def integer(self, name: str, **kwargs) ->
|
178
|
+
def integer(self, name: str, **kwargs) -> "SchemaBuilder":
|
179
179
|
"""Add integer property"""
|
180
180
|
return self.property(name, SchemaType.INTEGER, **kwargs)
|
181
181
|
|
182
|
-
def number(self, name: str, **kwargs) ->
|
182
|
+
def number(self, name: str, **kwargs) -> "SchemaBuilder":
|
183
183
|
"""Add number property"""
|
184
184
|
return self.property(name, SchemaType.NUMBER, **kwargs)
|
185
185
|
|
186
|
-
def boolean(self, name: str, **kwargs) ->
|
186
|
+
def boolean(self, name: str, **kwargs) -> "SchemaBuilder":
|
187
187
|
"""Add boolean property"""
|
188
188
|
return self.property(name, SchemaType.BOOLEAN, **kwargs)
|
189
189
|
|
190
|
-
def array(
|
190
|
+
def array(
|
191
|
+
self, name: str, items: Optional[SchemaProperty] = None, **kwargs
|
192
|
+
) -> "SchemaBuilder":
|
191
193
|
"""Add array property"""
|
192
194
|
return self.property(name, SchemaType.ARRAY, items=items, **kwargs)
|
193
195
|
|
194
|
-
def object(
|
196
|
+
def object(
|
197
|
+
self,
|
198
|
+
name: str,
|
199
|
+
properties: Optional[Dict[str, SchemaProperty]] = None,
|
200
|
+
**kwargs,
|
201
|
+
) -> "SchemaBuilder":
|
195
202
|
"""Add object property"""
|
196
203
|
return self.property(name, SchemaType.OBJECT, properties=properties, **kwargs)
|
197
204
|
|
198
|
-
def enum(self, name: str, values: List[Any], **kwargs) ->
|
205
|
+
def enum(self, name: str, values: List[Any], **kwargs) -> "SchemaBuilder":
|
199
206
|
"""Add enum property"""
|
200
207
|
return self.property(name, SchemaType.STRING, enum=values, **kwargs)
|
201
208
|
|
202
|
-
def required_fields(self, *fields: str) ->
|
209
|
+
def required_fields(self, *fields: str) -> "SchemaBuilder":
|
203
210
|
"""Mark fields as required"""
|
204
211
|
for field in fields:
|
205
212
|
if field not in self.schema.required:
|
206
213
|
self.schema.required.append(field)
|
207
214
|
return self
|
208
215
|
|
209
|
-
def default(self, name: str, value: Any) ->
|
216
|
+
def default(self, name: str, value: Any) -> "SchemaBuilder":
|
210
217
|
"""Set default value for property"""
|
211
218
|
if name in self.schema.properties:
|
212
219
|
self.schema.properties[name].default = value
|
213
220
|
self.schema.defaults[name] = value
|
214
221
|
return self
|
215
222
|
|
216
|
-
def dependency(
|
223
|
+
def dependency(
|
224
|
+
self, field: str, depends_on: Union[str, List[str]]
|
225
|
+
) -> "SchemaBuilder":
|
217
226
|
"""Add field dependency"""
|
218
227
|
if self.schema.dependencies is None:
|
219
228
|
self.schema.dependencies = {}
|
@@ -224,12 +233,12 @@ class SchemaBuilder:
|
|
224
233
|
self.schema.dependencies[field] = depends_on
|
225
234
|
return self
|
226
235
|
|
227
|
-
def validator(self, func: Callable) ->
|
236
|
+
def validator(self, func: Callable) -> "SchemaBuilder":
|
228
237
|
"""Add custom validator"""
|
229
238
|
self.schema.post_validators.append(func)
|
230
239
|
return self
|
231
240
|
|
232
|
-
def transformer(self, func: Callable) ->
|
241
|
+
def transformer(self, func: Callable) -> "SchemaBuilder":
|
233
242
|
"""Add transformer function"""
|
234
243
|
self.schema.transformers.append(func)
|
235
244
|
return self
|
@@ -319,7 +328,9 @@ class SchemaValidator:
|
|
319
328
|
if prop.validator and not prop.validator(value):
|
320
329
|
self.errors.append(f"{path}: custom validation failed")
|
321
330
|
|
322
|
-
def _check_type(
|
331
|
+
def _check_type(
|
332
|
+
self, value: Any, expected: Union[SchemaType, List[SchemaType]]
|
333
|
+
) -> bool:
|
323
334
|
"""Check if value matches expected type"""
|
324
335
|
if isinstance(expected, list):
|
325
336
|
return any(self._check_type(value, t) for t in expected)
|
@@ -332,13 +343,15 @@ class SchemaValidator:
|
|
332
343
|
SchemaType.ARRAY: list,
|
333
344
|
SchemaType.OBJECT: dict,
|
334
345
|
SchemaType.NULL: type(None),
|
335
|
-
SchemaType.ANY: object
|
346
|
+
SchemaType.ANY: object,
|
336
347
|
}
|
337
348
|
|
338
349
|
expected_type = type_map.get(expected, object)
|
339
350
|
return isinstance(value, expected_type)
|
340
351
|
|
341
|
-
def _validate_numeric(
|
352
|
+
def _validate_numeric(
|
353
|
+
self, value: Union[int, float], prop: SchemaProperty, path: str
|
354
|
+
):
|
342
355
|
"""Validate numeric constraints"""
|
343
356
|
if prop.minimum is not None and value < prop.minimum:
|
344
357
|
self.errors.append(f"{path}: value {value} is below minimum {prop.minimum}")
|
@@ -347,21 +360,30 @@ class SchemaValidator:
|
|
347
360
|
self.errors.append(f"{path}: value {value} exceeds maximum {prop.maximum}")
|
348
361
|
|
349
362
|
if prop.exclusive_minimum is not None and value <= prop.exclusive_minimum:
|
350
|
-
self.errors.append(
|
363
|
+
self.errors.append(
|
364
|
+
f"{path}: value {value} must be greater than {prop.exclusive_minimum}"
|
365
|
+
)
|
351
366
|
|
352
367
|
if prop.exclusive_maximum is not None and value >= prop.exclusive_maximum:
|
353
|
-
self.errors.append(
|
368
|
+
self.errors.append(
|
369
|
+
f"{path}: value {value} must be less than {prop.exclusive_maximum}"
|
370
|
+
)
|
354
371
|
|
355
372
|
def _validate_string(self, value: str, prop: SchemaProperty, path: str):
|
356
373
|
"""Validate string constraints"""
|
357
374
|
if prop.min_length is not None and len(value) < prop.min_length:
|
358
|
-
self.errors.append(
|
375
|
+
self.errors.append(
|
376
|
+
f"{path}: length {len(value)} is below minimum {prop.min_length}"
|
377
|
+
)
|
359
378
|
|
360
379
|
if prop.max_length is not None and len(value) > prop.max_length:
|
361
|
-
self.errors.append(
|
380
|
+
self.errors.append(
|
381
|
+
f"{path}: length {len(value)} exceeds maximum {prop.max_length}"
|
382
|
+
)
|
362
383
|
|
363
384
|
if prop.pattern:
|
364
385
|
import re
|
386
|
+
|
365
387
|
if not re.match(prop.pattern, value):
|
366
388
|
self.errors.append(f"{path}: does not match pattern {prop.pattern}")
|
367
389
|
|
@@ -372,15 +394,23 @@ class SchemaValidator:
|
|
372
394
|
def _validate_array(self, value: List, prop: SchemaProperty, path: str):
|
373
395
|
"""Validate array constraints"""
|
374
396
|
if prop.min_items is not None and len(value) < prop.min_items:
|
375
|
-
self.errors.append(
|
397
|
+
self.errors.append(
|
398
|
+
f"{path}: array length {len(value)} is below minimum {prop.min_items}"
|
399
|
+
)
|
376
400
|
|
377
401
|
if prop.max_items is not None and len(value) > prop.max_items:
|
378
|
-
self.errors.append(
|
402
|
+
self.errors.append(
|
403
|
+
f"{path}: array length {len(value)} exceeds maximum {prop.max_items}"
|
404
|
+
)
|
379
405
|
|
380
406
|
if prop.unique_items:
|
381
407
|
seen = set()
|
382
408
|
for item in value:
|
383
|
-
item_key =
|
409
|
+
item_key = (
|
410
|
+
str(item)
|
411
|
+
if not isinstance(item, (dict, list))
|
412
|
+
else json.dumps(item, sort_keys=True)
|
413
|
+
)
|
384
414
|
if item_key in seen:
|
385
415
|
self.errors.append(f"{path}: duplicate items not allowed")
|
386
416
|
break
|
@@ -406,7 +436,9 @@ class SchemaValidator:
|
|
406
436
|
if prop.properties:
|
407
437
|
extra = set(value.keys()) - set(prop.properties.keys())
|
408
438
|
if extra:
|
409
|
-
self.errors.append(
|
439
|
+
self.errors.append(
|
440
|
+
f"{path}: additional properties not allowed: {extra}"
|
441
|
+
)
|
410
442
|
|
411
443
|
def _validate_dependencies(self, config: Dict, dependencies: Dict):
|
412
444
|
"""Validate field dependencies"""
|
@@ -415,20 +447,24 @@ class SchemaValidator:
|
|
415
447
|
if isinstance(deps, list):
|
416
448
|
for dep in deps:
|
417
449
|
if dep not in config:
|
418
|
-
self.errors.append(
|
450
|
+
self.errors.append(
|
451
|
+
f"Field '{field}' requires '{dep}' to be present"
|
452
|
+
)
|
419
453
|
|
420
454
|
def _validate_format(self, value: str, format: SchemaFormat) -> bool:
|
421
455
|
"""Validate string format"""
|
422
456
|
validators = {
|
423
|
-
SchemaFormat.EMAIL: lambda v:
|
424
|
-
SchemaFormat.DATE: lambda v: self._try_parse_date(v,
|
425
|
-
SchemaFormat.DATETIME: lambda v: self._try_parse_date(
|
457
|
+
SchemaFormat.EMAIL: lambda v: "@" in v and "." in v.split("@")[1],
|
458
|
+
SchemaFormat.DATE: lambda v: self._try_parse_date(v, "%Y-%m-%d"),
|
459
|
+
SchemaFormat.DATETIME: lambda v: self._try_parse_date(
|
460
|
+
v, "%Y-%m-%dT%H:%M:%S"
|
461
|
+
),
|
426
462
|
SchemaFormat.UUID: lambda v: self._validate_uuid(v),
|
427
463
|
SchemaFormat.IPV4: lambda v: self._validate_ipv4(v),
|
428
464
|
SchemaFormat.IPV6: lambda v: self._validate_ipv6(v),
|
429
|
-
SchemaFormat.URI: lambda v:
|
465
|
+
SchemaFormat.URI: lambda v: "://" in v,
|
430
466
|
SchemaFormat.PATH: lambda v: True, # Any string is valid path
|
431
|
-
SchemaFormat.SEMVER: lambda v: self._validate_semver(v)
|
467
|
+
SchemaFormat.SEMVER: lambda v: self._validate_semver(v),
|
432
468
|
}
|
433
469
|
|
434
470
|
validator = validators.get(format)
|
@@ -445,6 +481,7 @@ class SchemaValidator:
|
|
445
481
|
def _validate_uuid(self, value: str) -> bool:
|
446
482
|
"""Validate UUID format"""
|
447
483
|
import uuid
|
484
|
+
|
448
485
|
try:
|
449
486
|
uuid.UUID(value)
|
450
487
|
return True
|
@@ -454,6 +491,7 @@ class SchemaValidator:
|
|
454
491
|
def _validate_ipv4(self, value: str) -> bool:
|
455
492
|
"""Validate IPv4 address"""
|
456
493
|
import ipaddress
|
494
|
+
|
457
495
|
try:
|
458
496
|
ipaddress.IPv4Address(value)
|
459
497
|
return True
|
@@ -463,6 +501,7 @@ class SchemaValidator:
|
|
463
501
|
def _validate_ipv6(self, value: str) -> bool:
|
464
502
|
"""Validate IPv6 address"""
|
465
503
|
import ipaddress
|
504
|
+
|
466
505
|
try:
|
467
506
|
ipaddress.IPv6Address(value)
|
468
507
|
return True
|
@@ -472,7 +511,8 @@ class SchemaValidator:
|
|
472
511
|
def _validate_semver(self, value: str) -> bool:
|
473
512
|
"""Validate semantic version"""
|
474
513
|
import re
|
475
|
-
|
514
|
+
|
515
|
+
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
516
|
return bool(re.match(pattern, value))
|
477
517
|
|
478
518
|
|
@@ -521,10 +561,7 @@ class ConfigMigration:
|
|
521
561
|
self.migrations: Dict[tuple, Callable] = {}
|
522
562
|
|
523
563
|
def register_migration(
|
524
|
-
self,
|
525
|
-
from_version: str,
|
526
|
-
to_version: str,
|
527
|
-
migration_func: Callable[[Dict], Dict]
|
564
|
+
self, from_version: str, to_version: str, migration_func: Callable[[Dict], Dict]
|
528
565
|
):
|
529
566
|
"""Register a migration function"""
|
530
567
|
key = (from_version, to_version)
|
@@ -532,10 +569,7 @@ class ConfigMigration:
|
|
532
569
|
self.logger.info(f"Registered migration: {from_version} -> {to_version}")
|
533
570
|
|
534
571
|
def migrate(
|
535
|
-
self,
|
536
|
-
config: Dict[str, Any],
|
537
|
-
from_version: str,
|
538
|
-
to_version: str
|
572
|
+
self, config: Dict[str, Any], from_version: str, to_version: str
|
539
573
|
) -> Dict[str, Any]:
|
540
574
|
"""Migrate configuration between versions"""
|
541
575
|
key = (from_version, to_version)
|
@@ -560,13 +594,15 @@ class ConfigMigration:
|
|
560
594
|
|
561
595
|
return current
|
562
596
|
|
563
|
-
def _find_migration_path(
|
597
|
+
def _find_migration_path(
|
598
|
+
self, from_version: str, to_version: str
|
599
|
+
) -> Optional[List[str]]:
|
564
600
|
"""Find migration path between versions using BFS"""
|
565
601
|
from collections import deque
|
566
602
|
|
567
603
|
# Build graph of migrations
|
568
604
|
graph = {}
|
569
|
-
for
|
605
|
+
for from_v, to_v in self.migrations.keys():
|
570
606
|
if from_v not in graph:
|
571
607
|
graph[from_v] = []
|
572
608
|
graph[from_v].append(to_v)
|
@@ -633,57 +669,67 @@ class TypedConfig(Generic[T]):
|
|
633
669
|
# Predefined common schemas
|
634
670
|
def create_database_schema() -> ConfigSchema:
|
635
671
|
"""Create common database configuration schema"""
|
636
|
-
return (
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
672
|
+
return (
|
673
|
+
SchemaBuilder("Database Configuration")
|
674
|
+
.string("host", required=True, default="localhost")
|
675
|
+
.integer("port", required=True, minimum=1, maximum=65535, default=5432)
|
676
|
+
.string("database", required=True)
|
677
|
+
.string("username", required=True)
|
678
|
+
.string("password", write_only=True)
|
679
|
+
.integer("pool_size", minimum=1, maximum=100, default=10)
|
680
|
+
.integer("timeout", minimum=1, default=30)
|
681
|
+
.boolean("ssl", default=False)
|
682
|
+
.build()
|
683
|
+
)
|
646
684
|
|
647
685
|
|
648
686
|
def create_api_schema() -> ConfigSchema:
|
649
687
|
"""Create common API configuration schema"""
|
650
|
-
return (
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
688
|
+
return (
|
689
|
+
SchemaBuilder("API Configuration")
|
690
|
+
.string("base_url", required=True, format=SchemaFormat.URI)
|
691
|
+
.string("api_key", required=True, write_only=True)
|
692
|
+
.integer("timeout", minimum=1, default=30)
|
693
|
+
.integer("retry_count", minimum=0, maximum=10, default=3)
|
694
|
+
.number("retry_delay", minimum=0, default=1.0)
|
695
|
+
.array("allowed_methods", default=["GET", "POST", "PUT", "DELETE"])
|
696
|
+
.object("headers", default={})
|
697
|
+
.boolean("verify_ssl", default=True)
|
698
|
+
.build()
|
699
|
+
)
|
660
700
|
|
661
701
|
|
662
702
|
def create_logging_schema() -> ConfigSchema:
|
663
703
|
"""Create common logging configuration schema"""
|
664
|
-
return (
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
704
|
+
return (
|
705
|
+
SchemaBuilder("Logging Configuration")
|
706
|
+
.enum(
|
707
|
+
"level", ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="INFO"
|
708
|
+
)
|
709
|
+
.string(
|
710
|
+
"format", default="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
711
|
+
)
|
712
|
+
.string("file", format=SchemaFormat.PATH)
|
713
|
+
.integer("max_size", minimum=1, default=10485760) # 10MB
|
714
|
+
.integer("backup_count", minimum=0, default=5)
|
715
|
+
.boolean("console", default=True)
|
716
|
+
.boolean("file_enabled", default=False)
|
717
|
+
.build()
|
718
|
+
)
|
673
719
|
|
674
720
|
|
675
721
|
# Export main components
|
676
722
|
__all__ = [
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
]
|
723
|
+
"ConfigMigration",
|
724
|
+
"ConfigSchema",
|
725
|
+
"SchemaBuilder",
|
726
|
+
"SchemaFormat",
|
727
|
+
"SchemaProperty",
|
728
|
+
"SchemaRegistry",
|
729
|
+
"SchemaType",
|
730
|
+
"SchemaValidator",
|
731
|
+
"TypedConfig",
|
732
|
+
"create_api_schema",
|
733
|
+
"create_database_schema",
|
734
|
+
"create_logging_schema",
|
735
|
+
]
|