claude-mpm 4.0.23__py3-none-any.whl → 4.0.28__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/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +4 -1
- claude_mpm/agents/BASE_PM.md +3 -0
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/cli/commands/agents.py +453 -113
- claude_mpm/cli/commands/aggregate.py +107 -15
- claude_mpm/cli/commands/cleanup.py +142 -10
- claude_mpm/cli/commands/config.py +358 -224
- claude_mpm/cli/commands/info.py +184 -75
- claude_mpm/cli/commands/mcp_command_router.py +5 -76
- claude_mpm/cli/commands/mcp_install_commands.py +68 -36
- claude_mpm/cli/commands/mcp_server_commands.py +30 -37
- claude_mpm/cli/commands/memory.py +331 -61
- claude_mpm/cli/commands/monitor.py +101 -7
- claude_mpm/cli/commands/run.py +368 -8
- claude_mpm/cli/commands/tickets.py +206 -24
- claude_mpm/cli/parsers/mcp_parser.py +3 -0
- claude_mpm/cli/shared/__init__.py +40 -0
- claude_mpm/cli/shared/argument_patterns.py +212 -0
- claude_mpm/cli/shared/command_base.py +234 -0
- claude_mpm/cli/shared/error_handling.py +238 -0
- claude_mpm/cli/shared/output_formatters.py +231 -0
- claude_mpm/config/agent_config.py +29 -8
- claude_mpm/core/container.py +6 -4
- claude_mpm/core/service_registry.py +4 -2
- claude_mpm/core/shared/__init__.py +17 -0
- claude_mpm/core/shared/config_loader.py +320 -0
- claude_mpm/core/shared/path_resolver.py +277 -0
- claude_mpm/core/shared/singleton_manager.py +208 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +14 -3
- claude_mpm/hooks/memory_integration_hook.py +11 -2
- claude_mpm/services/agents/deployment/agent_deployment.py +49 -23
- claude_mpm/services/agents/deployment/deployment_wrapper.py +71 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +1 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +43 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +4 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +1 -1
- claude_mpm/services/agents/loading/base_agent_manager.py +11 -3
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +14 -5
- claude_mpm/services/event_aggregator.py +4 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +89 -28
- claude_mpm/services/mcp_gateway/config/configuration.py +29 -0
- claude_mpm/services/mcp_gateway/registry/service_registry.py +22 -5
- claude_mpm/services/memory/builder.py +6 -1
- claude_mpm/services/response_tracker.py +3 -1
- claude_mpm/services/runner_configuration_service.py +15 -6
- claude_mpm/services/shared/__init__.py +20 -0
- claude_mpm/services/shared/async_service_base.py +219 -0
- claude_mpm/services/shared/config_service_base.py +292 -0
- claude_mpm/services/shared/lifecycle_service_base.py +317 -0
- claude_mpm/services/shared/manager_base.py +303 -0
- claude_mpm/services/shared/service_factory.py +308 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/METADATA +19 -13
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/RECORD +60 -44
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.23.dist-info → claude_mpm-4.0.28.dist-info}/top_level.txt +0 -0
|
@@ -10,6 +10,8 @@ This module handles:
|
|
|
10
10
|
- Environment variable support for agent paths
|
|
11
11
|
- Project-specific agent overrides
|
|
12
12
|
- Tier precedence configuration
|
|
13
|
+
|
|
14
|
+
UPDATED: Migrated to use shared ConfigLoader pattern (TSK-0141)
|
|
13
15
|
"""
|
|
14
16
|
|
|
15
17
|
import json
|
|
@@ -21,6 +23,7 @@ from pathlib import Path
|
|
|
21
23
|
from typing import Any, Dict, List, Optional
|
|
22
24
|
|
|
23
25
|
from claude_mpm.core.unified_paths import get_path_manager
|
|
26
|
+
from claude_mpm.core.shared.config_loader import ConfigLoader, ConfigPattern
|
|
24
27
|
|
|
25
28
|
logger = logging.getLogger(__name__)
|
|
26
29
|
|
|
@@ -62,6 +65,9 @@ class AgentConfig:
|
|
|
62
65
|
validate_on_load: bool = True
|
|
63
66
|
strict_validation: bool = False
|
|
64
67
|
|
|
68
|
+
# ConfigLoader instance for consistent configuration loading
|
|
69
|
+
_config_loader: ConfigLoader = field(default_factory=ConfigLoader, init=False)
|
|
70
|
+
|
|
65
71
|
@classmethod
|
|
66
72
|
def from_environment(cls) -> "AgentConfig":
|
|
67
73
|
"""
|
|
@@ -147,14 +153,29 @@ class AgentConfig:
|
|
|
147
153
|
return cls()
|
|
148
154
|
|
|
149
155
|
try:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
# Use ConfigLoader for consistent file loading
|
|
157
|
+
config_loader = ConfigLoader()
|
|
158
|
+
|
|
159
|
+
# Create a pattern for agent configuration
|
|
160
|
+
pattern = ConfigPattern(
|
|
161
|
+
filenames=[config_file.name],
|
|
162
|
+
search_paths=[str(config_file.parent)],
|
|
163
|
+
env_prefix="CLAUDE_MPM_AGENT_",
|
|
164
|
+
defaults={
|
|
165
|
+
"enable_project_agents": True,
|
|
166
|
+
"enable_user_agents": True,
|
|
167
|
+
"enable_system_agents": True,
|
|
168
|
+
"enable_hot_reload": True,
|
|
169
|
+
"cache_ttl_seconds": 3600,
|
|
170
|
+
"enable_caching": True,
|
|
171
|
+
"validate_on_load": True,
|
|
172
|
+
"strict_validation": False,
|
|
173
|
+
"precedence_mode": "override"
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
loaded_config = config_loader.load_config(pattern, cache_key=f"agent_{config_file}")
|
|
178
|
+
data = loaded_config.to_dict()
|
|
158
179
|
|
|
159
180
|
config = cls()
|
|
160
181
|
|
claude_mpm/core/container.py
CHANGED
|
@@ -250,8 +250,9 @@ class DIContainer(IServiceContainer):
|
|
|
250
250
|
instance: Pre-created instance to register as singleton
|
|
251
251
|
|
|
252
252
|
Examples:
|
|
253
|
-
# Register a pre-created instance
|
|
254
|
-
|
|
253
|
+
# Register a pre-created instance using ConfigLoader
|
|
254
|
+
config_loader = ConfigLoader()
|
|
255
|
+
config = config_loader.load_main_config()
|
|
255
256
|
container.register_instance(IConfig, config)
|
|
256
257
|
"""
|
|
257
258
|
self._register_internal(
|
|
@@ -307,8 +308,9 @@ class DIContainer(IServiceContainer):
|
|
|
307
308
|
# Register with implementation class
|
|
308
309
|
container.register_singleton(ILogger, ConsoleLogger)
|
|
309
310
|
|
|
310
|
-
# Register with instance
|
|
311
|
-
|
|
311
|
+
# Register with instance using ConfigLoader
|
|
312
|
+
config_loader = ConfigLoader()
|
|
313
|
+
container.register_singleton(IConfig, instance=config_loader.load_main_config())
|
|
312
314
|
|
|
313
315
|
# Register with disposal handler
|
|
314
316
|
container.register_singleton(
|
|
@@ -11,6 +11,7 @@ from .base_service import BaseService
|
|
|
11
11
|
from .config import Config
|
|
12
12
|
from .container import DIContainer, ServiceLifetime, get_container
|
|
13
13
|
from .logger import get_logger
|
|
14
|
+
from .shared.config_loader import ConfigLoader
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
16
17
|
from claude_mpm.services.agents.deployment import AgentDeploymentService
|
|
@@ -46,8 +47,9 @@ class ServiceRegistry:
|
|
|
46
47
|
from .agent_session_manager import AgentSessionManager
|
|
47
48
|
from .session_manager import SessionManager
|
|
48
49
|
|
|
49
|
-
# Register configuration as singleton with name
|
|
50
|
-
|
|
50
|
+
# Register configuration as singleton with name using ConfigLoader
|
|
51
|
+
config_loader = ConfigLoader()
|
|
52
|
+
config = config_loader.load_main_config()
|
|
51
53
|
self.container.register_singleton(Config, instance=config, name="main_config")
|
|
52
54
|
|
|
53
55
|
# Register core services with proper lifetime management
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared core utilities to reduce code duplication.
|
|
3
|
+
|
|
4
|
+
This module provides common utilities that can be used across
|
|
5
|
+
different parts of the application.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .config_loader import ConfigLoader, ConfigPattern
|
|
9
|
+
from .path_resolver import PathResolver
|
|
10
|
+
from .singleton_manager import SingletonManager
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ConfigLoader",
|
|
14
|
+
"ConfigPattern",
|
|
15
|
+
"PathResolver",
|
|
16
|
+
"SingletonManager",
|
|
17
|
+
]
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared configuration loading utilities to reduce duplication.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional, Union
|
|
9
|
+
|
|
10
|
+
from ..config import Config
|
|
11
|
+
from ..logger import get_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ConfigPattern:
|
|
16
|
+
"""Configuration loading pattern definition."""
|
|
17
|
+
|
|
18
|
+
# File patterns to search for
|
|
19
|
+
filenames: List[str]
|
|
20
|
+
|
|
21
|
+
# Search paths (relative to working directory)
|
|
22
|
+
search_paths: List[str]
|
|
23
|
+
|
|
24
|
+
# Environment variable prefix
|
|
25
|
+
env_prefix: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
# Default values
|
|
28
|
+
defaults: Optional[Dict[str, Any]] = None
|
|
29
|
+
|
|
30
|
+
# Required configuration keys
|
|
31
|
+
required_keys: Optional[List[str]] = None
|
|
32
|
+
|
|
33
|
+
# Configuration section name
|
|
34
|
+
section: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ConfigLoader:
|
|
38
|
+
"""
|
|
39
|
+
Centralized configuration loading utility.
|
|
40
|
+
|
|
41
|
+
Reduces duplication by providing standard patterns for:
|
|
42
|
+
- Configuration file discovery
|
|
43
|
+
- Environment variable loading
|
|
44
|
+
- Default value management
|
|
45
|
+
- Configuration validation
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# Standard configuration patterns
|
|
49
|
+
AGENT_CONFIG = ConfigPattern(
|
|
50
|
+
filenames=[".agent.yaml", ".agent.yml", "agent.yaml", "agent.yml"],
|
|
51
|
+
search_paths=[".", ".claude-mpm", "agents"],
|
|
52
|
+
env_prefix="CLAUDE_MPM_AGENT_",
|
|
53
|
+
defaults={
|
|
54
|
+
"timeout": 30,
|
|
55
|
+
"max_retries": 3,
|
|
56
|
+
"log_level": "INFO"
|
|
57
|
+
},
|
|
58
|
+
required_keys=["name", "model"]
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
MEMORY_CONFIG = ConfigPattern(
|
|
62
|
+
filenames=[".memory.yaml", ".memory.yml", "memory.yaml", "memory.yml"],
|
|
63
|
+
search_paths=[".", ".claude-mpm", "memories"],
|
|
64
|
+
env_prefix="CLAUDE_MPM_MEMORY_",
|
|
65
|
+
defaults={
|
|
66
|
+
"max_entries": 1000,
|
|
67
|
+
"cleanup_interval": 3600,
|
|
68
|
+
"compression": True
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
SERVICE_CONFIG = ConfigPattern(
|
|
73
|
+
filenames=[".service.yaml", ".service.yml", "service.yaml", "service.yml"],
|
|
74
|
+
search_paths=[".", ".claude-mpm", "config"],
|
|
75
|
+
env_prefix="CLAUDE_MPM_SERVICE_",
|
|
76
|
+
defaults={
|
|
77
|
+
"enabled": True,
|
|
78
|
+
"auto_start": False,
|
|
79
|
+
"health_check_interval": 60
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def __init__(self, working_dir: Union[str, Path] = None):
|
|
84
|
+
"""
|
|
85
|
+
Initialize config loader.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
working_dir: Working directory for relative paths
|
|
89
|
+
"""
|
|
90
|
+
self.working_dir = Path(working_dir) if working_dir else Path.cwd()
|
|
91
|
+
self.logger = get_logger("config_loader")
|
|
92
|
+
self._cache: Dict[str, Config] = {}
|
|
93
|
+
|
|
94
|
+
def load_config(self,
|
|
95
|
+
pattern: ConfigPattern,
|
|
96
|
+
cache_key: str = None,
|
|
97
|
+
force_reload: bool = False) -> Config:
|
|
98
|
+
"""
|
|
99
|
+
Load configuration using a pattern.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
pattern: Configuration pattern to use
|
|
103
|
+
cache_key: Optional cache key (defaults to pattern hash)
|
|
104
|
+
force_reload: Force reload even if cached
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Loaded configuration
|
|
108
|
+
"""
|
|
109
|
+
# Generate cache key if not provided
|
|
110
|
+
if cache_key is None:
|
|
111
|
+
cache_key = self._generate_cache_key(pattern)
|
|
112
|
+
|
|
113
|
+
# Check cache
|
|
114
|
+
if not force_reload and cache_key in self._cache:
|
|
115
|
+
self.logger.debug(f"Using cached config: {cache_key}")
|
|
116
|
+
return self._cache[cache_key]
|
|
117
|
+
|
|
118
|
+
# Start with defaults
|
|
119
|
+
config_data = pattern.defaults.copy() if pattern.defaults else {}
|
|
120
|
+
|
|
121
|
+
# Load from files
|
|
122
|
+
config_file = self._find_config_file(pattern)
|
|
123
|
+
if config_file:
|
|
124
|
+
file_config = self._load_config_file(config_file)
|
|
125
|
+
if pattern.section:
|
|
126
|
+
file_config = file_config.get(pattern.section, {})
|
|
127
|
+
config_data.update(file_config)
|
|
128
|
+
self.logger.info(f"Loaded config from: {config_file}")
|
|
129
|
+
|
|
130
|
+
# Load from environment
|
|
131
|
+
if pattern.env_prefix:
|
|
132
|
+
env_config = self._load_env_config(pattern.env_prefix)
|
|
133
|
+
config_data.update(env_config)
|
|
134
|
+
if env_config:
|
|
135
|
+
self.logger.debug(f"Loaded {len(env_config)} env vars with prefix {pattern.env_prefix}")
|
|
136
|
+
|
|
137
|
+
# Create config instance
|
|
138
|
+
config = Config(config_data)
|
|
139
|
+
|
|
140
|
+
# Validate required keys
|
|
141
|
+
if pattern.required_keys:
|
|
142
|
+
self._validate_required_keys(config, pattern.required_keys)
|
|
143
|
+
|
|
144
|
+
# Cache the result
|
|
145
|
+
self._cache[cache_key] = config
|
|
146
|
+
|
|
147
|
+
return config
|
|
148
|
+
|
|
149
|
+
def load_agent_config(self, agent_dir: Union[str, Path] = None) -> Config:
|
|
150
|
+
"""Load agent configuration."""
|
|
151
|
+
pattern = self.AGENT_CONFIG
|
|
152
|
+
if agent_dir:
|
|
153
|
+
# Override search paths for specific agent directory
|
|
154
|
+
pattern = ConfigPattern(
|
|
155
|
+
filenames=pattern.filenames,
|
|
156
|
+
search_paths=[str(agent_dir)],
|
|
157
|
+
env_prefix=pattern.env_prefix,
|
|
158
|
+
defaults=pattern.defaults,
|
|
159
|
+
required_keys=pattern.required_keys
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return self.load_config(pattern, cache_key=f"agent_{agent_dir}")
|
|
163
|
+
|
|
164
|
+
def load_main_config(self) -> Config:
|
|
165
|
+
"""Load main application configuration."""
|
|
166
|
+
pattern = ConfigPattern(
|
|
167
|
+
filenames=["claude-mpm.yaml", "claude-mpm.yml", ".claude-mpm.yaml", ".claude-mpm.yml", "config.yaml", "config.yml"],
|
|
168
|
+
search_paths=["~/.config/claude-mpm", ".", "./config", "/etc/claude-mpm"],
|
|
169
|
+
env_prefix="CLAUDE_MPM_",
|
|
170
|
+
defaults={}
|
|
171
|
+
)
|
|
172
|
+
return self.load_config(pattern, cache_key="main_config")
|
|
173
|
+
|
|
174
|
+
def load_memory_config(self, memory_dir: Union[str, Path] = None) -> Config:
|
|
175
|
+
"""Load memory configuration."""
|
|
176
|
+
pattern = self.MEMORY_CONFIG
|
|
177
|
+
if memory_dir:
|
|
178
|
+
pattern = ConfigPattern(
|
|
179
|
+
filenames=pattern.filenames,
|
|
180
|
+
search_paths=[str(memory_dir)],
|
|
181
|
+
env_prefix=pattern.env_prefix,
|
|
182
|
+
defaults=pattern.defaults
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return self.load_config(pattern, cache_key=f"memory_{memory_dir}")
|
|
186
|
+
|
|
187
|
+
def load_service_config(self, service_name: str, config_dir: Union[str, Path] = None) -> Config:
|
|
188
|
+
"""Load service configuration."""
|
|
189
|
+
pattern = self.SERVICE_CONFIG
|
|
190
|
+
|
|
191
|
+
# Add service-specific filenames
|
|
192
|
+
service_filenames = [
|
|
193
|
+
f".{service_name}.yaml",
|
|
194
|
+
f".{service_name}.yml",
|
|
195
|
+
f"{service_name}.yaml",
|
|
196
|
+
f"{service_name}.yml"
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
pattern = ConfigPattern(
|
|
200
|
+
filenames=service_filenames + pattern.filenames,
|
|
201
|
+
search_paths=[str(config_dir)] if config_dir else pattern.search_paths,
|
|
202
|
+
env_prefix=f"CLAUDE_MPM_{service_name.upper()}_",
|
|
203
|
+
defaults=pattern.defaults,
|
|
204
|
+
section=service_name
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return self.load_config(pattern, cache_key=f"service_{service_name}")
|
|
208
|
+
|
|
209
|
+
def _find_config_file(self, pattern: ConfigPattern) -> Optional[Path]:
|
|
210
|
+
"""Find configuration file using pattern."""
|
|
211
|
+
for search_path in pattern.search_paths:
|
|
212
|
+
search_dir = self.working_dir / search_path
|
|
213
|
+
if not search_dir.exists():
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
for filename in pattern.filenames:
|
|
217
|
+
config_file = search_dir / filename
|
|
218
|
+
if config_file.exists() and config_file.is_file():
|
|
219
|
+
return config_file
|
|
220
|
+
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
def _load_config_file(self, config_file: Path) -> Dict[str, Any]:
|
|
224
|
+
"""Load configuration from file."""
|
|
225
|
+
try:
|
|
226
|
+
import yaml
|
|
227
|
+
|
|
228
|
+
with open(config_file, 'r') as f:
|
|
229
|
+
if config_file.suffix.lower() in ('.yaml', '.yml'):
|
|
230
|
+
return yaml.safe_load(f) or {}
|
|
231
|
+
else:
|
|
232
|
+
# Try JSON as fallback
|
|
233
|
+
import json
|
|
234
|
+
f.seek(0)
|
|
235
|
+
return json.load(f)
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self.logger.error(f"Failed to load config file {config_file}: {e}")
|
|
239
|
+
return {}
|
|
240
|
+
|
|
241
|
+
def _load_env_config(self, prefix: str) -> Dict[str, Any]:
|
|
242
|
+
"""Load configuration from environment variables."""
|
|
243
|
+
config = {}
|
|
244
|
+
|
|
245
|
+
for key, value in os.environ.items():
|
|
246
|
+
if key.startswith(prefix):
|
|
247
|
+
# Convert env var name to config key
|
|
248
|
+
config_key = key[len(prefix):].lower().replace('_', '.')
|
|
249
|
+
|
|
250
|
+
# Try to parse value
|
|
251
|
+
parsed_value = self._parse_env_value(value)
|
|
252
|
+
config[config_key] = parsed_value
|
|
253
|
+
|
|
254
|
+
return config
|
|
255
|
+
|
|
256
|
+
def _parse_env_value(self, value: str) -> Any:
|
|
257
|
+
"""Parse environment variable value."""
|
|
258
|
+
# Try boolean
|
|
259
|
+
if value.lower() in ('true', 'false'):
|
|
260
|
+
return value.lower() == 'true'
|
|
261
|
+
|
|
262
|
+
# Try integer
|
|
263
|
+
try:
|
|
264
|
+
return int(value)
|
|
265
|
+
except ValueError:
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
# Try float
|
|
269
|
+
try:
|
|
270
|
+
return float(value)
|
|
271
|
+
except ValueError:
|
|
272
|
+
pass
|
|
273
|
+
|
|
274
|
+
# Try JSON
|
|
275
|
+
try:
|
|
276
|
+
import json
|
|
277
|
+
return json.loads(value)
|
|
278
|
+
except (json.JSONDecodeError, ValueError):
|
|
279
|
+
pass
|
|
280
|
+
|
|
281
|
+
# Return as string
|
|
282
|
+
return value
|
|
283
|
+
|
|
284
|
+
def _validate_required_keys(self, config: Config, required_keys: List[str]) -> None:
|
|
285
|
+
"""Validate that required keys are present."""
|
|
286
|
+
missing_keys = []
|
|
287
|
+
|
|
288
|
+
for key in required_keys:
|
|
289
|
+
if config.get(key) is None:
|
|
290
|
+
missing_keys.append(key)
|
|
291
|
+
|
|
292
|
+
if missing_keys:
|
|
293
|
+
raise ValueError(f"Missing required configuration keys: {missing_keys}")
|
|
294
|
+
|
|
295
|
+
def _generate_cache_key(self, pattern: ConfigPattern) -> str:
|
|
296
|
+
"""Generate cache key for pattern."""
|
|
297
|
+
import hashlib
|
|
298
|
+
|
|
299
|
+
# Create a string representation of the pattern
|
|
300
|
+
pattern_str = f"{pattern.filenames}_{pattern.search_paths}_{pattern.env_prefix}"
|
|
301
|
+
|
|
302
|
+
# Generate hash
|
|
303
|
+
return hashlib.md5(pattern_str.encode()).hexdigest()[:8]
|
|
304
|
+
|
|
305
|
+
def clear_cache(self, cache_key: str = None) -> None:
|
|
306
|
+
"""Clear configuration cache."""
|
|
307
|
+
if cache_key:
|
|
308
|
+
self._cache.pop(cache_key, None)
|
|
309
|
+
self.logger.debug(f"Cleared cache for: {cache_key}")
|
|
310
|
+
else:
|
|
311
|
+
self._cache.clear()
|
|
312
|
+
self.logger.debug("Cleared all config cache")
|
|
313
|
+
|
|
314
|
+
def get_cache_info(self) -> Dict[str, Any]:
|
|
315
|
+
"""Get cache information."""
|
|
316
|
+
return {
|
|
317
|
+
"cached_configs": len(self._cache),
|
|
318
|
+
"cache_keys": list(self._cache.keys()),
|
|
319
|
+
"working_dir": str(self.working_dir)
|
|
320
|
+
}
|