claude-mpm 0.3.0__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""Configuration management utility for Claude MPM.
|
|
2
|
+
|
|
3
|
+
This module provides a unified interface for loading, saving, and managing
|
|
4
|
+
configurations across different file formats (JSON, YAML, TOML).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional, Union
|
|
12
|
+
from functools import lru_cache
|
|
13
|
+
|
|
14
|
+
# Handle optional imports
|
|
15
|
+
try:
|
|
16
|
+
import yaml
|
|
17
|
+
HAS_YAML = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
HAS_YAML = False
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import toml
|
|
23
|
+
HAS_TOML = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
HAS_TOML = False
|
|
26
|
+
|
|
27
|
+
from ..core.logger import get_logger
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConfigurationManager:
|
|
33
|
+
"""Unified configuration management with support for multiple formats."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, cache_enabled: bool = True):
|
|
36
|
+
"""Initialize the configuration manager.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
cache_enabled: Whether to enable configuration caching
|
|
40
|
+
"""
|
|
41
|
+
self.cache_enabled = cache_enabled
|
|
42
|
+
self._cache: Dict[str, Any] = {}
|
|
43
|
+
|
|
44
|
+
def _get_cache_key(self, file_path: Union[str, Path]) -> str:
|
|
45
|
+
"""Generate a cache key for a configuration file."""
|
|
46
|
+
path = Path(file_path)
|
|
47
|
+
stat = path.stat()
|
|
48
|
+
return f"{path.absolute()}:{stat.st_mtime}:{stat.st_size}"
|
|
49
|
+
|
|
50
|
+
def _check_cache(self, file_path: Union[str, Path]) -> Optional[Dict[str, Any]]:
|
|
51
|
+
"""Check if configuration is cached and still valid."""
|
|
52
|
+
if not self.cache_enabled:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
cache_key = self._get_cache_key(file_path)
|
|
57
|
+
return self._cache.get(cache_key)
|
|
58
|
+
except (OSError, IOError):
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def _update_cache(self, file_path: Union[str, Path], config: Dict[str, Any]):
|
|
62
|
+
"""Update the configuration cache."""
|
|
63
|
+
if not self.cache_enabled:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
cache_key = self._get_cache_key(file_path)
|
|
68
|
+
self._cache[cache_key] = config
|
|
69
|
+
except (OSError, IOError):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def clear_cache(self):
|
|
73
|
+
"""Clear the configuration cache."""
|
|
74
|
+
self._cache.clear()
|
|
75
|
+
|
|
76
|
+
def load_json(self, file_path: Union[str, Path]) -> Dict[str, Any]:
|
|
77
|
+
"""Load JSON configuration file.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
file_path: Path to JSON file
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Configuration dictionary
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
FileNotFoundError: If file doesn't exist
|
|
87
|
+
json.JSONDecodeError: If JSON is invalid
|
|
88
|
+
"""
|
|
89
|
+
file_path = Path(file_path)
|
|
90
|
+
|
|
91
|
+
# Check cache
|
|
92
|
+
cached = self._check_cache(file_path)
|
|
93
|
+
if cached is not None:
|
|
94
|
+
logger.debug(f"Using cached configuration for {file_path}")
|
|
95
|
+
return cached
|
|
96
|
+
|
|
97
|
+
if not file_path.exists():
|
|
98
|
+
raise FileNotFoundError(f"Configuration file not found: {file_path}")
|
|
99
|
+
|
|
100
|
+
logger.debug(f"Loading JSON configuration from {file_path}")
|
|
101
|
+
try:
|
|
102
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
103
|
+
config = json.load(f)
|
|
104
|
+
self._update_cache(file_path, config)
|
|
105
|
+
return config
|
|
106
|
+
except json.JSONDecodeError as e:
|
|
107
|
+
logger.error(f"Invalid JSON in {file_path}: {e}")
|
|
108
|
+
raise
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"Error loading JSON from {file_path}: {e}")
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
def load_yaml(self, file_path: Union[str, Path]) -> Dict[str, Any]:
|
|
114
|
+
"""Load YAML configuration file.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
file_path: Path to YAML file
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Configuration dictionary
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
ImportError: If PyYAML is not installed
|
|
124
|
+
FileNotFoundError: If file doesn't exist
|
|
125
|
+
yaml.YAMLError: If YAML is invalid
|
|
126
|
+
"""
|
|
127
|
+
if not HAS_YAML:
|
|
128
|
+
raise ImportError("PyYAML is required for YAML support. Install with: pip install pyyaml")
|
|
129
|
+
|
|
130
|
+
file_path = Path(file_path)
|
|
131
|
+
|
|
132
|
+
# Check cache
|
|
133
|
+
cached = self._check_cache(file_path)
|
|
134
|
+
if cached is not None:
|
|
135
|
+
logger.debug(f"Using cached configuration for {file_path}")
|
|
136
|
+
return cached
|
|
137
|
+
|
|
138
|
+
if not file_path.exists():
|
|
139
|
+
raise FileNotFoundError(f"Configuration file not found: {file_path}")
|
|
140
|
+
|
|
141
|
+
logger.debug(f"Loading YAML configuration from {file_path}")
|
|
142
|
+
try:
|
|
143
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
144
|
+
config = yaml.safe_load(f) or {}
|
|
145
|
+
self._update_cache(file_path, config)
|
|
146
|
+
return config
|
|
147
|
+
except yaml.YAMLError as e:
|
|
148
|
+
logger.error(f"Invalid YAML in {file_path}: {e}")
|
|
149
|
+
raise
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Error loading YAML from {file_path}: {e}")
|
|
152
|
+
raise
|
|
153
|
+
|
|
154
|
+
def load_toml(self, file_path: Union[str, Path]) -> Dict[str, Any]:
|
|
155
|
+
"""Load TOML configuration file.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
file_path: Path to TOML file
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Configuration dictionary
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
ImportError: If toml is not installed
|
|
165
|
+
FileNotFoundError: If file doesn't exist
|
|
166
|
+
toml.TomlDecodeError: If TOML is invalid
|
|
167
|
+
"""
|
|
168
|
+
if not HAS_TOML:
|
|
169
|
+
raise ImportError("toml is required for TOML support. Install with: pip install toml")
|
|
170
|
+
|
|
171
|
+
file_path = Path(file_path)
|
|
172
|
+
|
|
173
|
+
# Check cache
|
|
174
|
+
cached = self._check_cache(file_path)
|
|
175
|
+
if cached is not None:
|
|
176
|
+
logger.debug(f"Using cached configuration for {file_path}")
|
|
177
|
+
return cached
|
|
178
|
+
|
|
179
|
+
if not file_path.exists():
|
|
180
|
+
raise FileNotFoundError(f"Configuration file not found: {file_path}")
|
|
181
|
+
|
|
182
|
+
logger.debug(f"Loading TOML configuration from {file_path}")
|
|
183
|
+
try:
|
|
184
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
185
|
+
config = toml.load(f)
|
|
186
|
+
self._update_cache(file_path, config)
|
|
187
|
+
return config
|
|
188
|
+
except toml.TomlDecodeError as e:
|
|
189
|
+
logger.error(f"Invalid TOML in {file_path}: {e}")
|
|
190
|
+
raise
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Error loading TOML from {file_path}: {e}")
|
|
193
|
+
raise
|
|
194
|
+
|
|
195
|
+
def load_auto(self, file_path: Union[str, Path]) -> Dict[str, Any]:
|
|
196
|
+
"""Auto-detect format and load configuration file.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
file_path: Path to configuration file
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Configuration dictionary
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ValueError: If file format is not supported
|
|
206
|
+
FileNotFoundError: If file doesn't exist
|
|
207
|
+
"""
|
|
208
|
+
file_path = Path(file_path)
|
|
209
|
+
suffix = file_path.suffix.lower()
|
|
210
|
+
|
|
211
|
+
if suffix in ['.json']:
|
|
212
|
+
return self.load_json(file_path)
|
|
213
|
+
elif suffix in ['.yaml', '.yml']:
|
|
214
|
+
return self.load_yaml(file_path)
|
|
215
|
+
elif suffix in ['.toml']:
|
|
216
|
+
return self.load_toml(file_path)
|
|
217
|
+
else:
|
|
218
|
+
raise ValueError(f"Unsupported configuration format: {suffix}")
|
|
219
|
+
|
|
220
|
+
def save_json(self, config: Dict[str, Any], file_path: Union[str, Path],
|
|
221
|
+
indent: int = 2, sort_keys: bool = True):
|
|
222
|
+
"""Save configuration as JSON.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
config: Configuration dictionary
|
|
226
|
+
file_path: Path to save JSON file
|
|
227
|
+
indent: JSON indentation (default: 2)
|
|
228
|
+
sort_keys: Whether to sort keys (default: True)
|
|
229
|
+
"""
|
|
230
|
+
file_path = Path(file_path)
|
|
231
|
+
logger.debug(f"Saving JSON configuration to {file_path}")
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
# Create parent directories if needed
|
|
235
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
236
|
+
|
|
237
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
238
|
+
json.dump(config, f, indent=indent, sort_keys=sort_keys)
|
|
239
|
+
logger.info(f"Configuration saved to {file_path}")
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"Error saving JSON to {file_path}: {e}")
|
|
242
|
+
raise
|
|
243
|
+
|
|
244
|
+
def save_yaml(self, config: Dict[str, Any], file_path: Union[str, Path],
|
|
245
|
+
default_flow_style: bool = False, sort_keys: bool = True):
|
|
246
|
+
"""Save configuration as YAML.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
config: Configuration dictionary
|
|
250
|
+
file_path: Path to save YAML file
|
|
251
|
+
default_flow_style: Use flow style (default: False)
|
|
252
|
+
sort_keys: Whether to sort keys (default: True)
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
ImportError: If PyYAML is not installed
|
|
256
|
+
"""
|
|
257
|
+
if not HAS_YAML:
|
|
258
|
+
raise ImportError("PyYAML is required for YAML support. Install with: pip install pyyaml")
|
|
259
|
+
|
|
260
|
+
file_path = Path(file_path)
|
|
261
|
+
logger.debug(f"Saving YAML configuration to {file_path}")
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Create parent directories if needed
|
|
265
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
266
|
+
|
|
267
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
268
|
+
yaml.dump(config, f, default_flow_style=default_flow_style,
|
|
269
|
+
sort_keys=sort_keys)
|
|
270
|
+
logger.info(f"Configuration saved to {file_path}")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.error(f"Error saving YAML to {file_path}: {e}")
|
|
273
|
+
raise
|
|
274
|
+
|
|
275
|
+
def save_toml(self, config: Dict[str, Any], file_path: Union[str, Path]):
|
|
276
|
+
"""Save configuration as TOML.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
config: Configuration dictionary
|
|
280
|
+
file_path: Path to save TOML file
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
ImportError: If toml is not installed
|
|
284
|
+
"""
|
|
285
|
+
if not HAS_TOML:
|
|
286
|
+
raise ImportError("toml is required for TOML support. Install with: pip install toml")
|
|
287
|
+
|
|
288
|
+
file_path = Path(file_path)
|
|
289
|
+
logger.debug(f"Saving TOML configuration to {file_path}")
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
# Create parent directories if needed
|
|
293
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
294
|
+
|
|
295
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
296
|
+
toml.dump(config, f)
|
|
297
|
+
logger.info(f"Configuration saved to {file_path}")
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Error saving TOML to {file_path}: {e}")
|
|
300
|
+
raise
|
|
301
|
+
|
|
302
|
+
def merge_configs(self, *configs: Dict[str, Any]) -> Dict[str, Any]:
|
|
303
|
+
"""Merge multiple configurations.
|
|
304
|
+
|
|
305
|
+
Later configurations override earlier ones. Nested dictionaries
|
|
306
|
+
are merged recursively.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
*configs: Configuration dictionaries to merge
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Merged configuration dictionary
|
|
313
|
+
"""
|
|
314
|
+
result = {}
|
|
315
|
+
|
|
316
|
+
for config in configs:
|
|
317
|
+
self._deep_merge(result, config)
|
|
318
|
+
|
|
319
|
+
return result
|
|
320
|
+
|
|
321
|
+
def _deep_merge(self, target: Dict[str, Any], source: Dict[str, Any]):
|
|
322
|
+
"""Deep merge source into target dictionary."""
|
|
323
|
+
for key, value in source.items():
|
|
324
|
+
if key in target and isinstance(target[key], dict) and isinstance(value, dict):
|
|
325
|
+
self._deep_merge(target[key], value)
|
|
326
|
+
else:
|
|
327
|
+
target[key] = value
|
|
328
|
+
|
|
329
|
+
def validate_schema(self, config: Dict[str, Any], schema: Dict[str, Any]) -> List[str]:
|
|
330
|
+
"""Validate configuration against a schema.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
config: Configuration to validate
|
|
334
|
+
schema: Schema dictionary defining required fields and types
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
List of validation errors (empty if valid)
|
|
338
|
+
|
|
339
|
+
Example schema:
|
|
340
|
+
{
|
|
341
|
+
"required": ["field1", "field2"],
|
|
342
|
+
"types": {
|
|
343
|
+
"field1": str,
|
|
344
|
+
"field2": int,
|
|
345
|
+
"field3": list
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
"""
|
|
349
|
+
errors = []
|
|
350
|
+
|
|
351
|
+
# Check required fields
|
|
352
|
+
required = schema.get("required", [])
|
|
353
|
+
for field in required:
|
|
354
|
+
if field not in config:
|
|
355
|
+
errors.append(f"Required field missing: {field}")
|
|
356
|
+
|
|
357
|
+
# Check types
|
|
358
|
+
types = schema.get("types", {})
|
|
359
|
+
for field, expected_type in types.items():
|
|
360
|
+
if field in config:
|
|
361
|
+
if not isinstance(config[field], expected_type):
|
|
362
|
+
actual_type = type(config[field]).__name__
|
|
363
|
+
expected_name = expected_type.__name__
|
|
364
|
+
errors.append(f"Invalid type for {field}: expected {expected_name}, got {actual_type}")
|
|
365
|
+
|
|
366
|
+
return errors
|
|
367
|
+
|
|
368
|
+
def get_with_default(self, config: Dict[str, Any], key: str,
|
|
369
|
+
default: Any = None, separator: str = ".") -> Any:
|
|
370
|
+
"""Get configuration value with default fallback.
|
|
371
|
+
|
|
372
|
+
Supports nested keys using dot notation.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
config: Configuration dictionary
|
|
376
|
+
key: Key to retrieve (supports dot notation for nested keys)
|
|
377
|
+
default: Default value if key not found
|
|
378
|
+
separator: Key separator for nested access (default: ".")
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
Configuration value or default
|
|
382
|
+
|
|
383
|
+
Example:
|
|
384
|
+
get_with_default(config, "database.host", "localhost")
|
|
385
|
+
"""
|
|
386
|
+
keys = key.split(separator)
|
|
387
|
+
value = config
|
|
388
|
+
|
|
389
|
+
for k in keys:
|
|
390
|
+
if isinstance(value, dict) and k in value:
|
|
391
|
+
value = value[k]
|
|
392
|
+
else:
|
|
393
|
+
return default
|
|
394
|
+
|
|
395
|
+
return value
|
|
396
|
+
|
|
397
|
+
def interpolate_env(self, config: Dict[str, Any],
|
|
398
|
+
pattern: str = "${%s}") -> Dict[str, Any]:
|
|
399
|
+
"""Interpolate environment variables in configuration.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
config: Configuration dictionary
|
|
403
|
+
pattern: Pattern for environment variables (default: "${VAR}")
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Configuration with environment variables interpolated
|
|
407
|
+
|
|
408
|
+
Example:
|
|
409
|
+
Input: {"host": "${DB_HOST}", "port": "${DB_PORT}"}
|
|
410
|
+
Output: {"host": "localhost", "port": "5432"}
|
|
411
|
+
"""
|
|
412
|
+
import re
|
|
413
|
+
|
|
414
|
+
def _interpolate_value(value: Any) -> Any:
|
|
415
|
+
if isinstance(value, str):
|
|
416
|
+
# Find all environment variable references
|
|
417
|
+
env_pattern = pattern.replace("%s", r"([A-Z_][A-Z0-9_]*)")
|
|
418
|
+
matches = re.findall(env_pattern, value)
|
|
419
|
+
|
|
420
|
+
result = value
|
|
421
|
+
for var_name in matches:
|
|
422
|
+
env_value = os.environ.get(var_name, "")
|
|
423
|
+
placeholder = pattern % var_name
|
|
424
|
+
result = result.replace(placeholder, env_value)
|
|
425
|
+
|
|
426
|
+
return result
|
|
427
|
+
elif isinstance(value, dict):
|
|
428
|
+
return {k: _interpolate_value(v) for k, v in value.items()}
|
|
429
|
+
elif isinstance(value, list):
|
|
430
|
+
return [_interpolate_value(item) for item in value]
|
|
431
|
+
else:
|
|
432
|
+
return value
|
|
433
|
+
|
|
434
|
+
return _interpolate_value(config)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# Convenience functions
|
|
438
|
+
_default_manager = ConfigurationManager()
|
|
439
|
+
|
|
440
|
+
def load_config(file_path: Union[str, Path]) -> Dict[str, Any]:
|
|
441
|
+
"""Load configuration file with auto-detection.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
file_path: Path to configuration file
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
Configuration dictionary
|
|
448
|
+
"""
|
|
449
|
+
return _default_manager.load_auto(file_path)
|
|
450
|
+
|
|
451
|
+
def save_config(config: Dict[str, Any], file_path: Union[str, Path]):
|
|
452
|
+
"""Save configuration file with auto-detection.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
config: Configuration dictionary
|
|
456
|
+
file_path: Path to save configuration
|
|
457
|
+
"""
|
|
458
|
+
file_path = Path(file_path)
|
|
459
|
+
suffix = file_path.suffix.lower()
|
|
460
|
+
|
|
461
|
+
if suffix in ['.json']:
|
|
462
|
+
_default_manager.save_json(config, file_path)
|
|
463
|
+
elif suffix in ['.yaml', '.yml']:
|
|
464
|
+
_default_manager.save_yaml(config, file_path)
|
|
465
|
+
elif suffix in ['.toml']:
|
|
466
|
+
_default_manager.save_toml(config, file_path)
|
|
467
|
+
else:
|
|
468
|
+
raise ValueError(f"Unsupported configuration format: {suffix}")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Example of how to migrate duplicate import patterns to use safe_import.
|
|
2
|
+
|
|
3
|
+
This file demonstrates how to replace the common try/except ImportError
|
|
4
|
+
pattern with the new safe_import utility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# BEFORE: The old pattern used throughout the codebase
|
|
8
|
+
# -----------------------------------------------------
|
|
9
|
+
# try:
|
|
10
|
+
# from ..core.logger import get_logger
|
|
11
|
+
# except ImportError:
|
|
12
|
+
# from utils.logger import get_logger
|
|
13
|
+
#
|
|
14
|
+
# try:
|
|
15
|
+
# from ..core.agent_registry import AgentRegistryAdapter
|
|
16
|
+
# except ImportError:
|
|
17
|
+
# from core.agent_registry import AgentRegistryAdapter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# AFTER: Using safe_import utility
|
|
21
|
+
# --------------------------------
|
|
22
|
+
from claude_mpm.utils.imports import safe_import, safe_import_multiple
|
|
23
|
+
|
|
24
|
+
# Method 1: Individual imports
|
|
25
|
+
get_logger = safe_import('..utils.logger', 'utils.logger', from_list=['get_logger'])
|
|
26
|
+
AgentRegistryAdapter = safe_import('..core.agent_registry', 'core.agent_registry',
|
|
27
|
+
from_list=['AgentRegistryAdapter'])
|
|
28
|
+
|
|
29
|
+
# Method 2: Batch imports (recommended for multiple imports)
|
|
30
|
+
imports = safe_import_multiple([
|
|
31
|
+
('..utils.logger', 'utils.logger', ['get_logger']),
|
|
32
|
+
('..core.agent_registry', 'core.agent_registry', ['AgentRegistryAdapter']),
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
get_logger = imports.get('get_logger')
|
|
36
|
+
AgentRegistryAdapter = imports.get('AgentRegistryAdapter')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# MIGRATION GUIDE
|
|
40
|
+
# ---------------
|
|
41
|
+
# 1. Add import: from claude_mpm.utils.imports import safe_import
|
|
42
|
+
#
|
|
43
|
+
# 2. Replace try/except blocks:
|
|
44
|
+
# FROM:
|
|
45
|
+
# try:
|
|
46
|
+
# from ..module import something
|
|
47
|
+
# except ImportError:
|
|
48
|
+
# from module import something
|
|
49
|
+
#
|
|
50
|
+
# TO:
|
|
51
|
+
# something = safe_import('..module', 'module', from_list=['something'])
|
|
52
|
+
#
|
|
53
|
+
# 3. For multiple imports from same module:
|
|
54
|
+
# FROM:
|
|
55
|
+
# try:
|
|
56
|
+
# from ..module import foo, bar, baz
|
|
57
|
+
# except ImportError:
|
|
58
|
+
# from module import foo, bar, baz
|
|
59
|
+
#
|
|
60
|
+
# TO:
|
|
61
|
+
# foo, bar, baz = safe_import('..module', 'module', from_list=['foo', 'bar', 'baz'])
|
|
62
|
+
#
|
|
63
|
+
# 4. For whole module imports:
|
|
64
|
+
# FROM:
|
|
65
|
+
# try:
|
|
66
|
+
# from .. import module
|
|
67
|
+
# except ImportError:
|
|
68
|
+
# import module
|
|
69
|
+
#
|
|
70
|
+
# TO:
|
|
71
|
+
# module = safe_import('..module', 'module')
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# BENEFITS
|
|
75
|
+
# --------
|
|
76
|
+
# 1. Reduces code duplication (97 files can be simplified)
|
|
77
|
+
# 2. Centralizes import error handling logic
|
|
78
|
+
# 3. Provides optional logging for debugging import issues
|
|
79
|
+
# 4. Maintains the same functionality with cleaner code
|
|
80
|
+
# 5. Makes the codebase more maintainable
|