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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/WORKFLOW.md +2 -14
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +2 -2
- claude_mpm/cli/commands/mpm_init.py +3 -3
- claude_mpm/cli/parsers/configure_parser.py +4 -15
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +356 -0
- claude_mpm/core/framework/formatters/content_formatter.py +283 -0
- claude_mpm/core/framework/formatters/context_generator.py +180 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +202 -0
- claude_mpm/core/framework/loaders/file_loader.py +213 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +222 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +238 -0
- claude_mpm/core/framework_loader.py +277 -1798
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/kuzu_memory_hook.py +352 -0
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/services/agents/memory/content_manager.py +5 -2
- claude_mpm/services/agents/memory/memory_file_service.py +1 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
- claude_mpm/services/core/path_resolver.py +1 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
- claude_mpm/services/unified/config_strategies/__init__.py +190 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +557 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
- claude_mpm/services/unified/deployment_strategies/local.py +594 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +499 -0
- claude_mpm/services/unified/migration.py +532 -0
- claude_mpm/services/unified/strategies.py +551 -0
- claude_mpm/services/unified/unified_analyzer.py +534 -0
- claude_mpm/services/unified/unified_config.py +688 -0
- claude_mpm/services/unified/unified_deployment.py +470 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,802 @@
|
|
1
|
+
"""
|
2
|
+
Unified Configuration Service - Phase 3 Consolidation
|
3
|
+
Consolidates 15+ configuration services into a single unified service
|
4
|
+
Achieves 65-75% code reduction through strategic patterns
|
5
|
+
"""
|
6
|
+
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from typing import Any, Dict, Optional, Type, Union, List, Callable, TypeVar, Generic
|
9
|
+
from pathlib import Path
|
10
|
+
import json
|
11
|
+
import yaml
|
12
|
+
import os
|
13
|
+
from enum import Enum
|
14
|
+
from dataclasses import dataclass, field
|
15
|
+
import threading
|
16
|
+
from datetime import datetime, timedelta
|
17
|
+
from collections import defaultdict
|
18
|
+
import hashlib
|
19
|
+
import pickle
|
20
|
+
|
21
|
+
from claude_mpm.core.logging_utils import get_logger
|
22
|
+
|
23
|
+
T = TypeVar('T')
|
24
|
+
|
25
|
+
class ConfigFormat(Enum):
|
26
|
+
"""Supported configuration formats"""
|
27
|
+
JSON = "json"
|
28
|
+
YAML = "yaml"
|
29
|
+
ENV = "env"
|
30
|
+
PYTHON = "py"
|
31
|
+
TOML = "toml"
|
32
|
+
INI = "ini"
|
33
|
+
|
34
|
+
class ConfigContext(Enum):
|
35
|
+
"""Configuration contexts for lifecycle management"""
|
36
|
+
GLOBAL = "global"
|
37
|
+
PROJECT = "project"
|
38
|
+
USER = "user"
|
39
|
+
AGENT = "agent"
|
40
|
+
SERVICE = "service"
|
41
|
+
RUNTIME = "runtime"
|
42
|
+
TEST = "test"
|
43
|
+
|
44
|
+
@dataclass
|
45
|
+
class ConfigMetadata:
|
46
|
+
"""Metadata for configuration tracking"""
|
47
|
+
source: str
|
48
|
+
format: ConfigFormat
|
49
|
+
context: ConfigContext
|
50
|
+
loaded_at: datetime
|
51
|
+
version: Optional[str] = None
|
52
|
+
checksum: Optional[str] = None
|
53
|
+
dependencies: List[str] = field(default_factory=list)
|
54
|
+
hot_reload: bool = False
|
55
|
+
ttl: Optional[timedelta] = None
|
56
|
+
|
57
|
+
class IConfigStrategy(ABC):
|
58
|
+
"""Base strategy interface for configuration operations"""
|
59
|
+
|
60
|
+
@abstractmethod
|
61
|
+
def can_handle(self, source: Union[str, Path, Dict]) -> bool:
|
62
|
+
"""Check if this strategy can handle the given source"""
|
63
|
+
pass
|
64
|
+
|
65
|
+
@abstractmethod
|
66
|
+
def load(self, source: Any, **kwargs) -> Dict[str, Any]:
|
67
|
+
"""Load configuration from source"""
|
68
|
+
pass
|
69
|
+
|
70
|
+
@abstractmethod
|
71
|
+
def validate(self, config: Dict[str, Any], schema: Optional[Dict] = None) -> bool:
|
72
|
+
"""Validate configuration against schema"""
|
73
|
+
pass
|
74
|
+
|
75
|
+
@abstractmethod
|
76
|
+
def transform(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
77
|
+
"""Transform configuration to standard format"""
|
78
|
+
pass
|
79
|
+
|
80
|
+
class UnifiedConfigService:
|
81
|
+
"""
|
82
|
+
Unified Configuration Service
|
83
|
+
Consolidates all configuration management into a single service
|
84
|
+
Reduces 15+ services to 1, 215 loaders to 5, 236 validators to 15
|
85
|
+
"""
|
86
|
+
|
87
|
+
_instance = None
|
88
|
+
_lock = threading.Lock()
|
89
|
+
|
90
|
+
def __new__(cls, *args, **kwargs):
|
91
|
+
"""Singleton pattern for global configuration management"""
|
92
|
+
if not cls._instance:
|
93
|
+
with cls._lock:
|
94
|
+
if not cls._instance:
|
95
|
+
cls._instance = super().__new__(cls)
|
96
|
+
return cls._instance
|
97
|
+
|
98
|
+
def __init__(self):
|
99
|
+
"""Initialize the unified configuration service"""
|
100
|
+
if not hasattr(self, '_initialized'):
|
101
|
+
self.logger = get_logger(self.__class__.__name__)
|
102
|
+
self._strategies: Dict[str, IConfigStrategy] = {}
|
103
|
+
self._loaders: Dict[ConfigFormat, Callable] = {}
|
104
|
+
self._validators: Dict[str, Callable] = {}
|
105
|
+
self._cache: Dict[str, Any] = {}
|
106
|
+
self._metadata: Dict[str, ConfigMetadata] = {}
|
107
|
+
self._watchers: Dict[str, List[Callable]] = defaultdict(list)
|
108
|
+
self._contexts: Dict[ConfigContext, Dict[str, Any]] = defaultdict(dict)
|
109
|
+
self._transformers: List[Callable] = []
|
110
|
+
self._error_handlers: List[Callable] = []
|
111
|
+
self._lock = threading.RLock()
|
112
|
+
self._hot_reload_threads: Dict[str, threading.Thread] = {}
|
113
|
+
|
114
|
+
self._initialize_core_strategies()
|
115
|
+
self._initialize_core_loaders()
|
116
|
+
self._initialize_core_validators()
|
117
|
+
self._initialized = True
|
118
|
+
|
119
|
+
self.logger.info("Unified Configuration Service initialized")
|
120
|
+
|
121
|
+
def _initialize_core_strategies(self):
|
122
|
+
"""Initialize core configuration strategies"""
|
123
|
+
# Strategies will be loaded from separate strategy files
|
124
|
+
self.logger.debug("Initializing core strategies")
|
125
|
+
|
126
|
+
def _initialize_core_loaders(self):
|
127
|
+
"""Initialize the 5 core strategic loaders (reduced from 215)"""
|
128
|
+
self._loaders[ConfigFormat.JSON] = self._load_json
|
129
|
+
self._loaders[ConfigFormat.YAML] = self._load_yaml
|
130
|
+
self._loaders[ConfigFormat.ENV] = self._load_env
|
131
|
+
self._loaders[ConfigFormat.PYTHON] = self._load_python
|
132
|
+
self._loaders[ConfigFormat.TOML] = self._load_toml
|
133
|
+
|
134
|
+
def _initialize_core_validators(self):
|
135
|
+
"""Initialize the 15 composable validators (reduced from 236)"""
|
136
|
+
# Core validators that can be composed for complex validation
|
137
|
+
self._validators['required'] = self._validate_required
|
138
|
+
self._validators['type'] = self._validate_type
|
139
|
+
self._validators['range'] = self._validate_range
|
140
|
+
self._validators['pattern'] = self._validate_pattern
|
141
|
+
self._validators['enum'] = self._validate_enum
|
142
|
+
self._validators['schema'] = self._validate_schema
|
143
|
+
self._validators['dependency'] = self._validate_dependency
|
144
|
+
self._validators['unique'] = self._validate_unique
|
145
|
+
self._validators['format'] = self._validate_format
|
146
|
+
self._validators['length'] = self._validate_length
|
147
|
+
self._validators['custom'] = self._validate_custom
|
148
|
+
self._validators['conditional'] = self._validate_conditional
|
149
|
+
self._validators['recursive'] = self._validate_recursive
|
150
|
+
self._validators['cross_field'] = self._validate_cross_field
|
151
|
+
self._validators['composite'] = self._validate_composite
|
152
|
+
|
153
|
+
def register_strategy(self, name: str, strategy: IConfigStrategy):
|
154
|
+
"""Register a configuration strategy"""
|
155
|
+
with self._lock:
|
156
|
+
self._strategies[name] = strategy
|
157
|
+
self.logger.debug(f"Registered strategy: {name}")
|
158
|
+
|
159
|
+
def load(
|
160
|
+
self,
|
161
|
+
source: Union[str, Path, Dict],
|
162
|
+
context: ConfigContext = ConfigContext.RUNTIME,
|
163
|
+
format: Optional[ConfigFormat] = None,
|
164
|
+
schema: Optional[Dict] = None,
|
165
|
+
hot_reload: bool = False,
|
166
|
+
ttl: Optional[timedelta] = None,
|
167
|
+
**kwargs
|
168
|
+
) -> Dict[str, Any]:
|
169
|
+
"""
|
170
|
+
Universal configuration loading method
|
171
|
+
Replaces 215 individual file loading instances
|
172
|
+
"""
|
173
|
+
with self._lock:
|
174
|
+
# Generate cache key
|
175
|
+
cache_key = self._generate_cache_key(source, context)
|
176
|
+
|
177
|
+
# Check cache first
|
178
|
+
if cache_key in self._cache:
|
179
|
+
metadata = self._metadata.get(cache_key)
|
180
|
+
if metadata and self._is_cache_valid(metadata):
|
181
|
+
self.logger.debug(f"Using cached config: {cache_key}")
|
182
|
+
return self._cache[cache_key]
|
183
|
+
|
184
|
+
try:
|
185
|
+
# Detect format if not specified
|
186
|
+
if format is None:
|
187
|
+
format = self._detect_format(source)
|
188
|
+
|
189
|
+
# Load configuration
|
190
|
+
if format in self._loaders:
|
191
|
+
config = self._loaders[format](source, **kwargs)
|
192
|
+
else:
|
193
|
+
# Try strategies
|
194
|
+
for strategy in self._strategies.values():
|
195
|
+
if strategy.can_handle(source):
|
196
|
+
config = strategy.load(source, **kwargs)
|
197
|
+
break
|
198
|
+
else:
|
199
|
+
raise ValueError(f"No loader available for source: {source}")
|
200
|
+
|
201
|
+
# Apply transformations
|
202
|
+
for transformer in self._transformers:
|
203
|
+
config = transformer(config)
|
204
|
+
|
205
|
+
# Validate if schema provided
|
206
|
+
if schema:
|
207
|
+
self.validate(config, schema)
|
208
|
+
|
209
|
+
# Store metadata
|
210
|
+
self._metadata[cache_key] = ConfigMetadata(
|
211
|
+
source=str(source),
|
212
|
+
format=format,
|
213
|
+
context=context,
|
214
|
+
loaded_at=datetime.now(),
|
215
|
+
checksum=self._calculate_checksum(config),
|
216
|
+
hot_reload=hot_reload,
|
217
|
+
ttl=ttl
|
218
|
+
)
|
219
|
+
|
220
|
+
# Cache configuration
|
221
|
+
self._cache[cache_key] = config
|
222
|
+
self._contexts[context][cache_key] = config
|
223
|
+
|
224
|
+
# Setup hot reload if requested
|
225
|
+
if hot_reload:
|
226
|
+
self._setup_hot_reload(cache_key, source, format, schema, context)
|
227
|
+
|
228
|
+
self.logger.info(f"Loaded configuration: {cache_key}")
|
229
|
+
return config
|
230
|
+
|
231
|
+
except Exception as e:
|
232
|
+
return self._handle_error(e, source, context)
|
233
|
+
|
234
|
+
def validate(
|
235
|
+
self,
|
236
|
+
config: Dict[str, Any],
|
237
|
+
schema: Union[Dict, str],
|
238
|
+
validators: Optional[List[str]] = None
|
239
|
+
) -> bool:
|
240
|
+
"""
|
241
|
+
Universal validation method using composable validators
|
242
|
+
Replaces 236 individual validation functions with 15 composable ones
|
243
|
+
"""
|
244
|
+
try:
|
245
|
+
# If schema is a string, it's a reference to a registered schema
|
246
|
+
if isinstance(schema, str):
|
247
|
+
schema = self._get_schema(schema)
|
248
|
+
|
249
|
+
# Apply specified validators or use schema-defined ones
|
250
|
+
if validators:
|
251
|
+
for validator_name in validators:
|
252
|
+
if validator_name in self._validators:
|
253
|
+
if not self._validators[validator_name](config, schema):
|
254
|
+
return False
|
255
|
+
else:
|
256
|
+
# Use schema to determine validators
|
257
|
+
return self._validate_schema(config, schema)
|
258
|
+
|
259
|
+
return True
|
260
|
+
|
261
|
+
except Exception as e:
|
262
|
+
self.logger.error(f"Validation failed: {e}")
|
263
|
+
return False
|
264
|
+
|
265
|
+
def get(
|
266
|
+
self,
|
267
|
+
key: str,
|
268
|
+
context: Optional[ConfigContext] = None,
|
269
|
+
default: Any = None
|
270
|
+
) -> Any:
|
271
|
+
"""Get configuration value by key with context awareness"""
|
272
|
+
# Check specific context first
|
273
|
+
if context:
|
274
|
+
for cache_key, config in self._contexts[context].items():
|
275
|
+
if key in config:
|
276
|
+
return config[key]
|
277
|
+
|
278
|
+
# Check all cached configs
|
279
|
+
for config in self._cache.values():
|
280
|
+
if key in config:
|
281
|
+
return config[key]
|
282
|
+
|
283
|
+
return default
|
284
|
+
|
285
|
+
def set(
|
286
|
+
self,
|
287
|
+
key: str,
|
288
|
+
value: Any,
|
289
|
+
context: ConfigContext = ConfigContext.RUNTIME,
|
290
|
+
persist: bool = False
|
291
|
+
):
|
292
|
+
"""Set configuration value with optional persistence"""
|
293
|
+
with self._lock:
|
294
|
+
# Update runtime config
|
295
|
+
if context not in self._contexts:
|
296
|
+
self._contexts[context] = {}
|
297
|
+
|
298
|
+
# Find or create config for context
|
299
|
+
context_configs = self._contexts[context]
|
300
|
+
if context_configs:
|
301
|
+
# Update first config in context
|
302
|
+
first_config = next(iter(context_configs.values()))
|
303
|
+
first_config[key] = value
|
304
|
+
else:
|
305
|
+
# Create new config for context
|
306
|
+
new_config = {key: value}
|
307
|
+
cache_key = f"{context.value}_{key}"
|
308
|
+
self._contexts[context][cache_key] = new_config
|
309
|
+
self._cache[cache_key] = new_config
|
310
|
+
|
311
|
+
# Trigger watchers
|
312
|
+
self._trigger_watchers(key, value)
|
313
|
+
|
314
|
+
# Persist if requested
|
315
|
+
if persist:
|
316
|
+
self._persist_config(key, value, context)
|
317
|
+
|
318
|
+
def watch(self, key: str, callback: Callable):
|
319
|
+
"""Watch configuration key for changes"""
|
320
|
+
self._watchers[key].append(callback)
|
321
|
+
self.logger.debug(f"Added watcher for key: {key}")
|
322
|
+
|
323
|
+
def reload(self, cache_key: Optional[str] = None, context: Optional[ConfigContext] = None):
|
324
|
+
"""Reload configuration(s)"""
|
325
|
+
with self._lock:
|
326
|
+
if cache_key:
|
327
|
+
# Reload specific configuration
|
328
|
+
if cache_key in self._metadata:
|
329
|
+
metadata = self._metadata[cache_key]
|
330
|
+
self.load(
|
331
|
+
metadata.source,
|
332
|
+
metadata.context,
|
333
|
+
metadata.format,
|
334
|
+
hot_reload=metadata.hot_reload,
|
335
|
+
ttl=metadata.ttl
|
336
|
+
)
|
337
|
+
elif context:
|
338
|
+
# Reload all configurations in context
|
339
|
+
for key in list(self._contexts[context].keys()):
|
340
|
+
if key in self._metadata:
|
341
|
+
metadata = self._metadata[key]
|
342
|
+
self.load(
|
343
|
+
metadata.source,
|
344
|
+
metadata.context,
|
345
|
+
metadata.format,
|
346
|
+
hot_reload=metadata.hot_reload,
|
347
|
+
ttl=metadata.ttl
|
348
|
+
)
|
349
|
+
else:
|
350
|
+
# Reload all configurations
|
351
|
+
for key, metadata in list(self._metadata.items()):
|
352
|
+
self.load(
|
353
|
+
metadata.source,
|
354
|
+
metadata.context,
|
355
|
+
metadata.format,
|
356
|
+
hot_reload=metadata.hot_reload,
|
357
|
+
ttl=metadata.ttl
|
358
|
+
)
|
359
|
+
|
360
|
+
def merge(
|
361
|
+
self,
|
362
|
+
*configs: Dict[str, Any],
|
363
|
+
strategy: str = "deep",
|
364
|
+
context: ConfigContext = ConfigContext.RUNTIME
|
365
|
+
) -> Dict[str, Any]:
|
366
|
+
"""Merge multiple configurations with specified strategy"""
|
367
|
+
if not configs:
|
368
|
+
return {}
|
369
|
+
|
370
|
+
if strategy == "deep":
|
371
|
+
result = {}
|
372
|
+
for config in configs:
|
373
|
+
self._deep_merge(result, config)
|
374
|
+
return result
|
375
|
+
elif strategy == "shallow":
|
376
|
+
result = {}
|
377
|
+
for config in configs:
|
378
|
+
result.update(config)
|
379
|
+
return result
|
380
|
+
elif strategy == "override":
|
381
|
+
return configs[-1] if configs else {}
|
382
|
+
else:
|
383
|
+
raise ValueError(f"Unknown merge strategy: {strategy}")
|
384
|
+
|
385
|
+
def export(
|
386
|
+
self,
|
387
|
+
format: ConfigFormat,
|
388
|
+
context: Optional[ConfigContext] = None,
|
389
|
+
path: Optional[Path] = None
|
390
|
+
) -> Union[str, None]:
|
391
|
+
"""Export configuration to specified format"""
|
392
|
+
configs = []
|
393
|
+
|
394
|
+
if context:
|
395
|
+
configs = list(self._contexts[context].values())
|
396
|
+
else:
|
397
|
+
configs = list(self._cache.values())
|
398
|
+
|
399
|
+
# Merge all configs
|
400
|
+
merged = self.merge(*configs)
|
401
|
+
|
402
|
+
# Export to format
|
403
|
+
if format == ConfigFormat.JSON:
|
404
|
+
output = json.dumps(merged, indent=2)
|
405
|
+
elif format == ConfigFormat.YAML:
|
406
|
+
output = yaml.dump(merged, default_flow_style=False)
|
407
|
+
else:
|
408
|
+
output = str(merged)
|
409
|
+
|
410
|
+
if path:
|
411
|
+
path.write_text(output)
|
412
|
+
return None
|
413
|
+
return output
|
414
|
+
|
415
|
+
def clear(self, context: Optional[ConfigContext] = None):
|
416
|
+
"""Clear cached configurations"""
|
417
|
+
with self._lock:
|
418
|
+
if context:
|
419
|
+
# Clear specific context
|
420
|
+
for key in list(self._contexts[context].keys()):
|
421
|
+
self._cache.pop(key, None)
|
422
|
+
self._metadata.pop(key, None)
|
423
|
+
self._contexts[context].clear()
|
424
|
+
else:
|
425
|
+
# Clear all
|
426
|
+
self._cache.clear()
|
427
|
+
self._metadata.clear()
|
428
|
+
self._contexts.clear()
|
429
|
+
|
430
|
+
# Private helper methods
|
431
|
+
|
432
|
+
def _load_json(self, source: Union[str, Path, Dict], **kwargs) -> Dict[str, Any]:
|
433
|
+
"""Load JSON configuration"""
|
434
|
+
if isinstance(source, dict):
|
435
|
+
return source
|
436
|
+
|
437
|
+
path = Path(source)
|
438
|
+
if path.exists():
|
439
|
+
with open(path, 'r') as f:
|
440
|
+
return json.load(f)
|
441
|
+
|
442
|
+
# Try to parse as JSON string
|
443
|
+
return json.loads(str(source))
|
444
|
+
|
445
|
+
def _load_yaml(self, source: Union[str, Path, Dict], **kwargs) -> Dict[str, Any]:
|
446
|
+
"""Load YAML configuration"""
|
447
|
+
if isinstance(source, dict):
|
448
|
+
return source
|
449
|
+
|
450
|
+
path = Path(source)
|
451
|
+
if path.exists():
|
452
|
+
with open(path, 'r') as f:
|
453
|
+
return yaml.safe_load(f)
|
454
|
+
|
455
|
+
# Try to parse as YAML string
|
456
|
+
return yaml.safe_load(str(source))
|
457
|
+
|
458
|
+
def _load_env(self, source: Union[str, Path, Dict], **kwargs) -> Dict[str, Any]:
|
459
|
+
"""Load environment variables as configuration"""
|
460
|
+
prefix = kwargs.get('prefix', '')
|
461
|
+
config = {}
|
462
|
+
|
463
|
+
for key, value in os.environ.items():
|
464
|
+
if prefix and not key.startswith(prefix):
|
465
|
+
continue
|
466
|
+
|
467
|
+
# Remove prefix if present
|
468
|
+
clean_key = key[len(prefix):] if prefix else key
|
469
|
+
|
470
|
+
# Convert to lowercase and replace underscores
|
471
|
+
clean_key = clean_key.lower()
|
472
|
+
|
473
|
+
# Try to parse value
|
474
|
+
try:
|
475
|
+
config[clean_key] = json.loads(value)
|
476
|
+
except:
|
477
|
+
config[clean_key] = value
|
478
|
+
|
479
|
+
return config
|
480
|
+
|
481
|
+
def _load_python(self, source: Union[str, Path], **kwargs) -> Dict[str, Any]:
|
482
|
+
"""Load Python module as configuration"""
|
483
|
+
import importlib.util
|
484
|
+
|
485
|
+
path = Path(source)
|
486
|
+
spec = importlib.util.spec_from_file_location("config", path)
|
487
|
+
module = importlib.util.module_from_spec(spec)
|
488
|
+
spec.loader.exec_module(module)
|
489
|
+
|
490
|
+
# Extract configuration from module
|
491
|
+
config = {}
|
492
|
+
for key in dir(module):
|
493
|
+
if not key.startswith('_'):
|
494
|
+
config[key] = getattr(module, key)
|
495
|
+
|
496
|
+
return config
|
497
|
+
|
498
|
+
def _load_toml(self, source: Union[str, Path], **kwargs) -> Dict[str, Any]:
|
499
|
+
"""Load TOML configuration"""
|
500
|
+
try:
|
501
|
+
import toml
|
502
|
+
except ImportError:
|
503
|
+
self.logger.error("toml package not installed")
|
504
|
+
return {}
|
505
|
+
|
506
|
+
path = Path(source)
|
507
|
+
if path.exists():
|
508
|
+
with open(path, 'r') as f:
|
509
|
+
return toml.load(f)
|
510
|
+
|
511
|
+
return toml.loads(str(source))
|
512
|
+
|
513
|
+
def _validate_required(self, config: Dict[str, Any], schema: Dict) -> bool:
|
514
|
+
"""Validate required fields"""
|
515
|
+
required = schema.get('required', [])
|
516
|
+
for field in required:
|
517
|
+
if field not in config:
|
518
|
+
self.logger.error(f"Required field missing: {field}")
|
519
|
+
return False
|
520
|
+
return True
|
521
|
+
|
522
|
+
def _validate_type(self, config: Dict[str, Any], schema: Dict) -> bool:
|
523
|
+
"""Validate field types"""
|
524
|
+
properties = schema.get('properties', {})
|
525
|
+
for key, value in config.items():
|
526
|
+
if key in properties:
|
527
|
+
expected_type = properties[key].get('type')
|
528
|
+
if expected_type and not self._check_type(value, expected_type):
|
529
|
+
self.logger.error(f"Type mismatch for {key}: expected {expected_type}")
|
530
|
+
return False
|
531
|
+
return True
|
532
|
+
|
533
|
+
def _validate_range(self, config: Dict[str, Any], schema: Dict) -> bool:
|
534
|
+
"""Validate numeric ranges"""
|
535
|
+
properties = schema.get('properties', {})
|
536
|
+
for key, value in config.items():
|
537
|
+
if key in properties:
|
538
|
+
prop = properties[key]
|
539
|
+
if 'minimum' in prop and value < prop['minimum']:
|
540
|
+
return False
|
541
|
+
if 'maximum' in prop and value > prop['maximum']:
|
542
|
+
return False
|
543
|
+
return True
|
544
|
+
|
545
|
+
def _validate_pattern(self, config: Dict[str, Any], schema: Dict) -> bool:
|
546
|
+
"""Validate string patterns"""
|
547
|
+
import re
|
548
|
+
properties = schema.get('properties', {})
|
549
|
+
for key, value in config.items():
|
550
|
+
if key in properties and 'pattern' in properties[key]:
|
551
|
+
pattern = properties[key]['pattern']
|
552
|
+
if not re.match(pattern, str(value)):
|
553
|
+
return False
|
554
|
+
return True
|
555
|
+
|
556
|
+
def _validate_enum(self, config: Dict[str, Any], schema: Dict) -> bool:
|
557
|
+
"""Validate enum values"""
|
558
|
+
properties = schema.get('properties', {})
|
559
|
+
for key, value in config.items():
|
560
|
+
if key in properties and 'enum' in properties[key]:
|
561
|
+
if value not in properties[key]['enum']:
|
562
|
+
return False
|
563
|
+
return True
|
564
|
+
|
565
|
+
def _validate_schema(self, config: Dict[str, Any], schema: Dict) -> bool:
|
566
|
+
"""Validate against full schema"""
|
567
|
+
# Compose validators based on schema
|
568
|
+
if not self._validate_required(config, schema):
|
569
|
+
return False
|
570
|
+
if not self._validate_type(config, schema):
|
571
|
+
return False
|
572
|
+
if not self._validate_range(config, schema):
|
573
|
+
return False
|
574
|
+
if not self._validate_pattern(config, schema):
|
575
|
+
return False
|
576
|
+
if not self._validate_enum(config, schema):
|
577
|
+
return False
|
578
|
+
return True
|
579
|
+
|
580
|
+
def _validate_dependency(self, config: Dict[str, Any], schema: Dict) -> bool:
|
581
|
+
"""Validate field dependencies"""
|
582
|
+
dependencies = schema.get('dependencies', {})
|
583
|
+
for field, deps in dependencies.items():
|
584
|
+
if field in config:
|
585
|
+
for dep in deps:
|
586
|
+
if dep not in config:
|
587
|
+
self.logger.error(f"Dependency missing: {field} requires {dep}")
|
588
|
+
return False
|
589
|
+
return True
|
590
|
+
|
591
|
+
def _validate_unique(self, config: Dict[str, Any], schema: Dict) -> bool:
|
592
|
+
"""Validate unique values in arrays"""
|
593
|
+
properties = schema.get('properties', {})
|
594
|
+
for key, value in config.items():
|
595
|
+
if key in properties and properties[key].get('uniqueItems'):
|
596
|
+
if isinstance(value, list) and len(value) != len(set(map(str, value))):
|
597
|
+
return False
|
598
|
+
return True
|
599
|
+
|
600
|
+
def _validate_format(self, config: Dict[str, Any], schema: Dict) -> bool:
|
601
|
+
"""Validate format strings (email, uri, etc.)"""
|
602
|
+
# Format validation implementation
|
603
|
+
return True
|
604
|
+
|
605
|
+
def _validate_length(self, config: Dict[str, Any], schema: Dict) -> bool:
|
606
|
+
"""Validate string/array lengths"""
|
607
|
+
properties = schema.get('properties', {})
|
608
|
+
for key, value in config.items():
|
609
|
+
if key in properties:
|
610
|
+
prop = properties[key]
|
611
|
+
if 'minLength' in prop and len(value) < prop['minLength']:
|
612
|
+
return False
|
613
|
+
if 'maxLength' in prop and len(value) > prop['maxLength']:
|
614
|
+
return False
|
615
|
+
return True
|
616
|
+
|
617
|
+
def _validate_custom(self, config: Dict[str, Any], schema: Dict) -> bool:
|
618
|
+
"""Apply custom validation functions"""
|
619
|
+
if 'custom' in schema:
|
620
|
+
validator = schema['custom']
|
621
|
+
if callable(validator):
|
622
|
+
return validator(config)
|
623
|
+
return True
|
624
|
+
|
625
|
+
def _validate_conditional(self, config: Dict[str, Any], schema: Dict) -> bool:
|
626
|
+
"""Validate conditional requirements"""
|
627
|
+
if 'if' in schema:
|
628
|
+
condition = schema['if']
|
629
|
+
if self._evaluate_condition(config, condition):
|
630
|
+
if 'then' in schema:
|
631
|
+
return self._validate_schema(config, schema['then'])
|
632
|
+
elif 'else' in schema:
|
633
|
+
return self._validate_schema(config, schema['else'])
|
634
|
+
return True
|
635
|
+
|
636
|
+
def _validate_recursive(self, config: Dict[str, Any], schema: Dict) -> bool:
|
637
|
+
"""Recursively validate nested structures"""
|
638
|
+
properties = schema.get('properties', {})
|
639
|
+
for key, value in config.items():
|
640
|
+
if key in properties and isinstance(value, dict):
|
641
|
+
if 'properties' in properties[key]:
|
642
|
+
if not self._validate_schema(value, properties[key]):
|
643
|
+
return False
|
644
|
+
return True
|
645
|
+
|
646
|
+
def _validate_cross_field(self, config: Dict[str, Any], schema: Dict) -> bool:
|
647
|
+
"""Validate cross-field constraints"""
|
648
|
+
constraints = schema.get('crossField', [])
|
649
|
+
for constraint in constraints:
|
650
|
+
if not self._evaluate_constraint(config, constraint):
|
651
|
+
return False
|
652
|
+
return True
|
653
|
+
|
654
|
+
def _validate_composite(self, config: Dict[str, Any], schema: Dict) -> bool:
|
655
|
+
"""Composite validation using multiple validators"""
|
656
|
+
validators = schema.get('validators', [])
|
657
|
+
for validator_name in validators:
|
658
|
+
if validator_name in self._validators:
|
659
|
+
if not self._validators[validator_name](config, schema):
|
660
|
+
return False
|
661
|
+
return True
|
662
|
+
|
663
|
+
def _detect_format(self, source: Union[str, Path, Dict]) -> ConfigFormat:
|
664
|
+
"""Detect configuration format from source"""
|
665
|
+
if isinstance(source, dict):
|
666
|
+
return ConfigFormat.JSON
|
667
|
+
|
668
|
+
path = Path(source)
|
669
|
+
if path.exists():
|
670
|
+
suffix = path.suffix.lower()
|
671
|
+
if suffix == '.json':
|
672
|
+
return ConfigFormat.JSON
|
673
|
+
elif suffix in ['.yaml', '.yml']:
|
674
|
+
return ConfigFormat.YAML
|
675
|
+
elif suffix == '.toml':
|
676
|
+
return ConfigFormat.TOML
|
677
|
+
elif suffix in ['.py', '.python']:
|
678
|
+
return ConfigFormat.PYTHON
|
679
|
+
elif suffix in ['.env']:
|
680
|
+
return ConfigFormat.ENV
|
681
|
+
|
682
|
+
# Try to detect from content
|
683
|
+
content = str(source)
|
684
|
+
if content.startswith('{'):
|
685
|
+
return ConfigFormat.JSON
|
686
|
+
elif ':' in content and '\n' in content:
|
687
|
+
return ConfigFormat.YAML
|
688
|
+
|
689
|
+
return ConfigFormat.JSON
|
690
|
+
|
691
|
+
def _generate_cache_key(self, source: Any, context: ConfigContext) -> str:
|
692
|
+
"""Generate unique cache key"""
|
693
|
+
source_str = str(source)
|
694
|
+
return f"{context.value}:{hashlib.md5(source_str.encode()).hexdigest()}"
|
695
|
+
|
696
|
+
def _calculate_checksum(self, config: Dict[str, Any]) -> str:
|
697
|
+
"""Calculate configuration checksum"""
|
698
|
+
config_bytes = pickle.dumps(config, protocol=pickle.HIGHEST_PROTOCOL)
|
699
|
+
return hashlib.sha256(config_bytes).hexdigest()
|
700
|
+
|
701
|
+
def _is_cache_valid(self, metadata: ConfigMetadata) -> bool:
|
702
|
+
"""Check if cached configuration is still valid"""
|
703
|
+
if metadata.ttl:
|
704
|
+
expiry = metadata.loaded_at + metadata.ttl
|
705
|
+
if datetime.now() > expiry:
|
706
|
+
return False
|
707
|
+
return True
|
708
|
+
|
709
|
+
def _setup_hot_reload(
|
710
|
+
self,
|
711
|
+
cache_key: str,
|
712
|
+
source: Any,
|
713
|
+
format: ConfigFormat,
|
714
|
+
schema: Optional[Dict],
|
715
|
+
context: ConfigContext
|
716
|
+
):
|
717
|
+
"""Setup hot reload for configuration"""
|
718
|
+
# Implementation for file watching and hot reload
|
719
|
+
pass
|
720
|
+
|
721
|
+
def _trigger_watchers(self, key: str, value: Any):
|
722
|
+
"""Trigger registered watchers for key"""
|
723
|
+
for callback in self._watchers.get(key, []):
|
724
|
+
try:
|
725
|
+
callback(key, value)
|
726
|
+
except Exception as e:
|
727
|
+
self.logger.error(f"Watcher callback failed: {e}")
|
728
|
+
|
729
|
+
def _persist_config(self, key: str, value: Any, context: ConfigContext):
|
730
|
+
"""Persist configuration change"""
|
731
|
+
# Implementation for persisting configuration changes
|
732
|
+
pass
|
733
|
+
|
734
|
+
def _handle_error(self, error: Exception, source: Any, context: ConfigContext) -> Dict[str, Any]:
|
735
|
+
"""Unified error handling"""
|
736
|
+
for handler in self._error_handlers:
|
737
|
+
try:
|
738
|
+
result = handler(error, source, context)
|
739
|
+
if result is not None:
|
740
|
+
return result
|
741
|
+
except:
|
742
|
+
continue
|
743
|
+
|
744
|
+
self.logger.error(f"Failed to load config from {source}: {error}")
|
745
|
+
return {}
|
746
|
+
|
747
|
+
def _deep_merge(self, target: Dict, source: Dict):
|
748
|
+
"""Deep merge source into target"""
|
749
|
+
for key, value in source.items():
|
750
|
+
if key in target and isinstance(target[key], dict) and isinstance(value, dict):
|
751
|
+
self._deep_merge(target[key], value)
|
752
|
+
else:
|
753
|
+
target[key] = value
|
754
|
+
|
755
|
+
def _check_type(self, value: Any, expected_type: str) -> bool:
|
756
|
+
"""Check if value matches expected type"""
|
757
|
+
type_map = {
|
758
|
+
'string': str,
|
759
|
+
'number': (int, float),
|
760
|
+
'integer': int,
|
761
|
+
'boolean': bool,
|
762
|
+
'array': list,
|
763
|
+
'object': dict
|
764
|
+
}
|
765
|
+
expected = type_map.get(expected_type)
|
766
|
+
if expected:
|
767
|
+
return isinstance(value, expected)
|
768
|
+
return True
|
769
|
+
|
770
|
+
def _evaluate_condition(self, config: Dict[str, Any], condition: Dict) -> bool:
|
771
|
+
"""Evaluate conditional expression"""
|
772
|
+
# Implementation for condition evaluation
|
773
|
+
return True
|
774
|
+
|
775
|
+
def _evaluate_constraint(self, config: Dict[str, Any], constraint: Dict) -> bool:
|
776
|
+
"""Evaluate cross-field constraint"""
|
777
|
+
# Implementation for constraint evaluation
|
778
|
+
return True
|
779
|
+
|
780
|
+
def _get_schema(self, name: str) -> Dict:
|
781
|
+
"""Get registered schema by name"""
|
782
|
+
# Implementation for schema registry
|
783
|
+
return {}
|
784
|
+
|
785
|
+
def get_statistics(self) -> Dict[str, Any]:
|
786
|
+
"""Get service statistics for monitoring"""
|
787
|
+
return {
|
788
|
+
'total_configs': len(self._cache),
|
789
|
+
'contexts': {ctx.value: len(configs) for ctx, configs in self._contexts.items()},
|
790
|
+
'strategies': len(self._strategies),
|
791
|
+
'loaders': len(self._loaders),
|
792
|
+
'validators': len(self._validators),
|
793
|
+
'watchers': sum(len(w) for w in self._watchers.values()),
|
794
|
+
'cache_size': sum(len(str(c)) for c in self._cache.values()),
|
795
|
+
'metadata_entries': len(self._metadata)
|
796
|
+
}
|
797
|
+
|
798
|
+
|
799
|
+
# Backward compatibility aliases
|
800
|
+
ConfigService = UnifiedConfigService
|
801
|
+
ConfigManager = UnifiedConfigService
|
802
|
+
ConfigLoader = UnifiedConfigService
|