claude-mpm 4.3.22__py3-none-any.whl → 4.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/WORKFLOW.md +2 -14
  3. claude_mpm/cli/commands/configure.py +2 -29
  4. claude_mpm/cli/commands/doctor.py +2 -2
  5. claude_mpm/cli/commands/mpm_init.py +3 -3
  6. claude_mpm/cli/parsers/configure_parser.py +4 -15
  7. claude_mpm/core/framework/__init__.py +38 -0
  8. claude_mpm/core/framework/formatters/__init__.py +11 -0
  9. claude_mpm/core/framework/formatters/capability_generator.py +356 -0
  10. claude_mpm/core/framework/formatters/content_formatter.py +283 -0
  11. claude_mpm/core/framework/formatters/context_generator.py +180 -0
  12. claude_mpm/core/framework/loaders/__init__.py +13 -0
  13. claude_mpm/core/framework/loaders/agent_loader.py +202 -0
  14. claude_mpm/core/framework/loaders/file_loader.py +213 -0
  15. claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
  16. claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
  17. claude_mpm/core/framework/processors/__init__.py +11 -0
  18. claude_mpm/core/framework/processors/memory_processor.py +222 -0
  19. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  20. claude_mpm/core/framework/processors/template_processor.py +238 -0
  21. claude_mpm/core/framework_loader.py +277 -1798
  22. claude_mpm/hooks/__init__.py +9 -1
  23. claude_mpm/hooks/kuzu_memory_hook.py +352 -0
  24. claude_mpm/hooks/memory_integration_hook.py +1 -1
  25. claude_mpm/services/agents/memory/content_manager.py +5 -2
  26. claude_mpm/services/agents/memory/memory_file_service.py +1 -0
  27. claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
  28. claude_mpm/services/core/path_resolver.py +1 -0
  29. claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
  30. claude_mpm/services/mcp_config_manager.py +67 -4
  31. claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
  32. claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
  33. claude_mpm/services/mcp_gateway/main.py +3 -13
  34. claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
  35. claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
  36. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
  37. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
  38. claude_mpm/services/shared/__init__.py +2 -1
  39. claude_mpm/services/shared/service_factory.py +8 -5
  40. claude_mpm/services/unified/__init__.py +65 -0
  41. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  42. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
  43. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
  44. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
  45. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
  46. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
  47. claude_mpm/services/unified/config_strategies/__init__.py +190 -0
  48. claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
  49. claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
  50. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
  51. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
  52. claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
  53. claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
  54. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  55. claude_mpm/services/unified/deployment_strategies/base.py +557 -0
  56. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
  57. claude_mpm/services/unified/deployment_strategies/local.py +594 -0
  58. claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
  59. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  60. claude_mpm/services/unified/interfaces.py +499 -0
  61. claude_mpm/services/unified/migration.py +532 -0
  62. claude_mpm/services/unified/strategies.py +551 -0
  63. claude_mpm/services/unified/unified_analyzer.py +534 -0
  64. claude_mpm/services/unified/unified_config.py +688 -0
  65. claude_mpm/services/unified/unified_deployment.py +470 -0
  66. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
  67. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
  68. claude_mpm/cli/commands/configure_tui.py +0 -1927
  69. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
  70. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
  71. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
  72. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
  73. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
  74. {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,748 @@
1
+ """
2
+ Context Strategy - Context-based lifecycle management for configurations
3
+ Part of Phase 3 Configuration Consolidation
4
+ """
5
+
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any, Dict, List, Optional, Union, Callable, Set
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ import threading
12
+ from datetime import datetime, timedelta
13
+ from contextlib import contextmanager
14
+ import weakref
15
+ from collections import OrderedDict
16
+
17
+ from claude_mpm.core.logging_utils import get_logger
18
+ from .unified_config_service import IConfigStrategy, ConfigContext
19
+
20
+
21
+ class ContextScope(Enum):
22
+ """Configuration context scopes"""
23
+ GLOBAL = "global" # Application-wide
24
+ SESSION = "session" # User session
25
+ PROJECT = "project" # Project-specific
26
+ AGENT = "agent" # Agent-specific
27
+ SERVICE = "service" # Service-specific
28
+ TRANSACTION = "transaction" # Transaction-specific
29
+ REQUEST = "request" # Request-specific
30
+ THREAD = "thread" # Thread-local
31
+ TEMPORARY = "temporary" # Temporary context
32
+
33
+
34
+ class ContextLifecycle(Enum):
35
+ """Context lifecycle states"""
36
+ CREATED = "created"
37
+ INITIALIZING = "initializing"
38
+ ACTIVE = "active"
39
+ SUSPENDED = "suspended"
40
+ CLOSING = "closing"
41
+ CLOSED = "closed"
42
+
43
+
44
+ @dataclass
45
+ class ContextMetadata:
46
+ """Metadata for context tracking"""
47
+ id: str
48
+ scope: ContextScope
49
+ lifecycle: ContextLifecycle
50
+ created_at: datetime
51
+ updated_at: datetime
52
+ parent_id: Optional[str] = None
53
+ children: List[str] = field(default_factory=list)
54
+ attributes: Dict[str, Any] = field(default_factory=dict)
55
+ ttl: Optional[timedelta] = None
56
+ expires_at: Optional[datetime] = None
57
+
58
+
59
+ @dataclass
60
+ class ContextConfig:
61
+ """Configuration within a context"""
62
+ context_id: str
63
+ data: Dict[str, Any]
64
+ overrides: Dict[str, Any] = field(default_factory=dict)
65
+ defaults: Dict[str, Any] = field(default_factory=dict)
66
+ locked_keys: Set[str] = field(default_factory=set)
67
+ watchers: Dict[str, List[Callable]] = field(default_factory=dict)
68
+
69
+
70
+ class BaseContextManager(ABC):
71
+ """Base class for context managers"""
72
+
73
+ def __init__(self):
74
+ self.logger = get_logger(self.__class__.__name__)
75
+ self.contexts: Dict[str, ContextMetadata] = {}
76
+ self.configs: Dict[str, ContextConfig] = {}
77
+ self._lock = threading.RLock()
78
+
79
+ @abstractmethod
80
+ def create_context(self, scope: ContextScope, **kwargs) -> str:
81
+ """Create a new context"""
82
+ pass
83
+
84
+ @abstractmethod
85
+ def get_context(self, context_id: str) -> Optional[ContextMetadata]:
86
+ """Get context metadata"""
87
+ pass
88
+
89
+ @abstractmethod
90
+ def close_context(self, context_id: str):
91
+ """Close a context"""
92
+ pass
93
+
94
+
95
+ class HierarchicalContextManager(BaseContextManager):
96
+ """Manages hierarchical context relationships"""
97
+
98
+ def __init__(self):
99
+ super().__init__()
100
+ self.context_stack = threading.local()
101
+ self.context_hierarchy: Dict[str, List[str]] = {} # parent -> children
102
+
103
+ def create_context(
104
+ self,
105
+ scope: ContextScope,
106
+ parent_id: Optional[str] = None,
107
+ ttl: Optional[timedelta] = None,
108
+ **kwargs
109
+ ) -> str:
110
+ """Create a new hierarchical context"""
111
+ with self._lock:
112
+ # Generate context ID
113
+ context_id = self._generate_context_id(scope)
114
+
115
+ # Use current context as parent if not specified
116
+ if parent_id is None and hasattr(self.context_stack, 'stack'):
117
+ if self.context_stack.stack:
118
+ parent_id = self.context_stack.stack[-1]
119
+
120
+ # Create metadata
121
+ metadata = ContextMetadata(
122
+ id=context_id,
123
+ scope=scope,
124
+ lifecycle=ContextLifecycle.CREATED,
125
+ created_at=datetime.now(),
126
+ updated_at=datetime.now(),
127
+ parent_id=parent_id,
128
+ ttl=ttl,
129
+ expires_at=datetime.now() + ttl if ttl else None,
130
+ attributes=kwargs
131
+ )
132
+
133
+ # Store context
134
+ self.contexts[context_id] = metadata
135
+
136
+ # Update hierarchy
137
+ if parent_id:
138
+ if parent_id in self.contexts:
139
+ self.contexts[parent_id].children.append(context_id)
140
+ if parent_id not in self.context_hierarchy:
141
+ self.context_hierarchy[parent_id] = []
142
+ self.context_hierarchy[parent_id].append(context_id)
143
+
144
+ # Initialize context config
145
+ self.configs[context_id] = ContextConfig(
146
+ context_id=context_id,
147
+ data={}
148
+ )
149
+
150
+ # Set lifecycle to active
151
+ metadata.lifecycle = ContextLifecycle.ACTIVE
152
+
153
+ self.logger.debug(f"Created context: {context_id} (scope={scope.value})")
154
+ return context_id
155
+
156
+ def get_context(self, context_id: str) -> Optional[ContextMetadata]:
157
+ """Get context metadata"""
158
+ with self._lock:
159
+ context = self.contexts.get(context_id)
160
+
161
+ # Check expiration
162
+ if context and context.expires_at:
163
+ if datetime.now() > context.expires_at:
164
+ self.close_context(context_id)
165
+ return None
166
+
167
+ return context
168
+
169
+ def close_context(self, context_id: str):
170
+ """Close a context and its children"""
171
+ with self._lock:
172
+ if context_id not in self.contexts:
173
+ return
174
+
175
+ context = self.contexts[context_id]
176
+ context.lifecycle = ContextLifecycle.CLOSING
177
+
178
+ # Close all children first
179
+ for child_id in context.children[:]:
180
+ self.close_context(child_id)
181
+
182
+ # Remove from parent's children
183
+ if context.parent_id and context.parent_id in self.contexts:
184
+ parent = self.contexts[context.parent_id]
185
+ if context_id in parent.children:
186
+ parent.children.remove(context_id)
187
+
188
+ # Clean up hierarchy
189
+ if context_id in self.context_hierarchy:
190
+ del self.context_hierarchy[context_id]
191
+
192
+ # Remove configs
193
+ if context_id in self.configs:
194
+ del self.configs[context_id]
195
+
196
+ # Remove context
197
+ del self.contexts[context_id]
198
+
199
+ context.lifecycle = ContextLifecycle.CLOSED
200
+ self.logger.debug(f"Closed context: {context_id}")
201
+
202
+ def get_context_chain(self, context_id: str) -> List[str]:
203
+ """Get the chain of contexts from root to specified context"""
204
+ chain = []
205
+ current_id = context_id
206
+
207
+ while current_id:
208
+ if current_id in self.contexts:
209
+ chain.insert(0, current_id)
210
+ current_id = self.contexts[current_id].parent_id
211
+ else:
212
+ break
213
+
214
+ return chain
215
+
216
+ def _generate_context_id(self, scope: ContextScope) -> str:
217
+ """Generate unique context ID"""
218
+ import uuid
219
+ return f"{scope.value}_{uuid.uuid4().hex[:8]}"
220
+
221
+ @contextmanager
222
+ def context(self, scope: ContextScope, **kwargs):
223
+ """Context manager for automatic context lifecycle"""
224
+ context_id = self.create_context(scope, **kwargs)
225
+
226
+ # Push to thread-local stack
227
+ if not hasattr(self.context_stack, 'stack'):
228
+ self.context_stack.stack = []
229
+ self.context_stack.stack.append(context_id)
230
+
231
+ try:
232
+ yield context_id
233
+ finally:
234
+ # Pop from stack
235
+ if self.context_stack.stack and self.context_stack.stack[-1] == context_id:
236
+ self.context_stack.stack.pop()
237
+
238
+ # Close context
239
+ self.close_context(context_id)
240
+
241
+
242
+ class ScopedConfigManager:
243
+ """Manages configuration within scoped contexts"""
244
+
245
+ def __init__(self, context_manager: HierarchicalContextManager):
246
+ self.logger = get_logger(self.__class__.__name__)
247
+ self.context_manager = context_manager
248
+ self._lock = threading.RLock()
249
+
250
+ def get_config(
251
+ self,
252
+ context_id: str,
253
+ key: Optional[str] = None,
254
+ inherit: bool = True
255
+ ) -> Any:
256
+ """Get configuration value from context"""
257
+ with self._lock:
258
+ if context_id not in self.context_manager.configs:
259
+ return None
260
+
261
+ if inherit:
262
+ # Get merged config from context chain
263
+ config = self._get_inherited_config(context_id)
264
+ else:
265
+ # Get config from this context only
266
+ config = self.context_manager.configs[context_id].data
267
+
268
+ if key:
269
+ return self._get_nested_value(config, key)
270
+ return config
271
+
272
+ def set_config(
273
+ self,
274
+ context_id: str,
275
+ key: str,
276
+ value: Any,
277
+ override: bool = False,
278
+ lock: bool = False
279
+ ):
280
+ """Set configuration value in context"""
281
+ with self._lock:
282
+ if context_id not in self.context_manager.configs:
283
+ raise ValueError(f"Context not found: {context_id}")
284
+
285
+ config = self.context_manager.configs[context_id]
286
+
287
+ # Check if key is locked
288
+ if key in config.locked_keys and not override:
289
+ raise ValueError(f"Configuration key is locked: {key}")
290
+
291
+ # Set value
292
+ if override:
293
+ config.overrides[key] = value
294
+ else:
295
+ self._set_nested_value(config.data, key, value)
296
+
297
+ # Lock key if requested
298
+ if lock:
299
+ config.locked_keys.add(key)
300
+
301
+ # Trigger watchers
302
+ self._trigger_watchers(context_id, key, value)
303
+
304
+ # Update context metadata
305
+ if context_id in self.context_manager.contexts:
306
+ self.context_manager.contexts[context_id].updated_at = datetime.now()
307
+
308
+ def _get_inherited_config(self, context_id: str) -> Dict[str, Any]:
309
+ """Get merged configuration from context hierarchy"""
310
+ chain = self.context_manager.get_context_chain(context_id)
311
+ merged = {}
312
+
313
+ for ctx_id in chain:
314
+ if ctx_id in self.context_manager.configs:
315
+ config = self.context_manager.configs[ctx_id]
316
+
317
+ # Apply defaults first
318
+ merged = self._deep_merge(config.defaults, merged)
319
+
320
+ # Apply data
321
+ merged = self._deep_merge(merged, config.data)
322
+
323
+ # Apply overrides last
324
+ merged = self._deep_merge(merged, config.overrides)
325
+
326
+ return merged
327
+
328
+ def _deep_merge(self, base: Dict, override: Dict) -> Dict:
329
+ """Deep merge two dictionaries"""
330
+ result = base.copy()
331
+
332
+ for key, value in override.items():
333
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
334
+ result[key] = self._deep_merge(result[key], value)
335
+ else:
336
+ result[key] = value
337
+
338
+ return result
339
+
340
+ def _get_nested_value(self, config: Dict, key: str) -> Any:
341
+ """Get nested value using dot notation"""
342
+ parts = key.split('.')
343
+ current = config
344
+
345
+ for part in parts:
346
+ if isinstance(current, dict) and part in current:
347
+ current = current[part]
348
+ else:
349
+ return None
350
+
351
+ return current
352
+
353
+ def _set_nested_value(self, config: Dict, key: str, value: Any):
354
+ """Set nested value using dot notation"""
355
+ parts = key.split('.')
356
+ current = config
357
+
358
+ for part in parts[:-1]:
359
+ if part not in current:
360
+ current[part] = {}
361
+ current = current[part]
362
+
363
+ current[parts[-1]] = value
364
+
365
+ def _trigger_watchers(self, context_id: str, key: str, value: Any):
366
+ """Trigger configuration watchers"""
367
+ if context_id in self.context_manager.configs:
368
+ config = self.context_manager.configs[context_id]
369
+
370
+ # Trigger exact match watchers
371
+ if key in config.watchers:
372
+ for watcher in config.watchers[key]:
373
+ try:
374
+ watcher(key, value, context_id)
375
+ except Exception as e:
376
+ self.logger.error(f"Watcher failed: {e}")
377
+
378
+ # Trigger pattern watchers
379
+ for pattern, watchers in config.watchers.items():
380
+ if '*' in pattern or '?' in pattern:
381
+ import fnmatch
382
+ if fnmatch.fnmatch(key, pattern):
383
+ for watcher in watchers:
384
+ try:
385
+ watcher(key, value, context_id)
386
+ except Exception as e:
387
+ self.logger.error(f"Pattern watcher failed: {e}")
388
+
389
+
390
+ class IsolatedContextManager:
391
+ """Manages isolated configuration contexts"""
392
+
393
+ def __init__(self):
394
+ self.logger = get_logger(self.__class__.__name__)
395
+ self.isolated_contexts: Dict[str, Dict[str, Any]] = {}
396
+ self._lock = threading.RLock()
397
+
398
+ def create_isolated_context(
399
+ self,
400
+ base_config: Optional[Dict[str, Any]] = None
401
+ ) -> str:
402
+ """Create an isolated context with no inheritance"""
403
+ import uuid
404
+ context_id = f"isolated_{uuid.uuid4().hex[:8]}"
405
+
406
+ with self._lock:
407
+ self.isolated_contexts[context_id] = base_config or {}
408
+ self.logger.debug(f"Created isolated context: {context_id}")
409
+
410
+ return context_id
411
+
412
+ def get_isolated_config(self, context_id: str) -> Dict[str, Any]:
413
+ """Get configuration from isolated context"""
414
+ with self._lock:
415
+ return self.isolated_contexts.get(context_id, {}).copy()
416
+
417
+ def update_isolated_config(self, context_id: str, updates: Dict[str, Any]):
418
+ """Update isolated context configuration"""
419
+ with self._lock:
420
+ if context_id in self.isolated_contexts:
421
+ self.isolated_contexts[context_id].update(updates)
422
+
423
+ def close_isolated_context(self, context_id: str):
424
+ """Close isolated context"""
425
+ with self._lock:
426
+ if context_id in self.isolated_contexts:
427
+ del self.isolated_contexts[context_id]
428
+ self.logger.debug(f"Closed isolated context: {context_id}")
429
+
430
+
431
+ class ThreadLocalContextManager:
432
+ """Manages thread-local configuration contexts"""
433
+
434
+ def __init__(self):
435
+ self.logger = get_logger(self.__class__.__name__)
436
+ self.thread_contexts = threading.local()
437
+ self._global_registry: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
438
+
439
+ def get_thread_context(self) -> Optional[Dict[str, Any]]:
440
+ """Get current thread's context"""
441
+ if hasattr(self.thread_contexts, 'config'):
442
+ return self.thread_contexts.config
443
+ return None
444
+
445
+ def set_thread_context(self, config: Dict[str, Any]):
446
+ """Set current thread's context"""
447
+ self.thread_contexts.config = config
448
+ self._global_registry[threading.current_thread().ident] = config
449
+
450
+ def clear_thread_context(self):
451
+ """Clear current thread's context"""
452
+ if hasattr(self.thread_contexts, 'config'):
453
+ delattr(self.thread_contexts, 'config')
454
+
455
+ @contextmanager
456
+ def thread_context(self, config: Dict[str, Any]):
457
+ """Context manager for thread-local configuration"""
458
+ old_config = self.get_thread_context()
459
+ self.set_thread_context(config)
460
+
461
+ try:
462
+ yield config
463
+ finally:
464
+ if old_config is not None:
465
+ self.set_thread_context(old_config)
466
+ else:
467
+ self.clear_thread_context()
468
+
469
+
470
+ class CachingContextManager:
471
+ """Manages context-aware configuration caching"""
472
+
473
+ def __init__(self, max_size: int = 1000):
474
+ self.logger = get_logger(self.__class__.__name__)
475
+ self.cache: OrderedDict = OrderedDict()
476
+ self.max_size = max_size
477
+ self._lock = threading.RLock()
478
+ self.hit_count = 0
479
+ self.miss_count = 0
480
+
481
+ def get_cached(self, context_id: str, key: str) -> Optional[Any]:
482
+ """Get cached value for context"""
483
+ cache_key = f"{context_id}:{key}"
484
+
485
+ with self._lock:
486
+ if cache_key in self.cache:
487
+ # Move to end (LRU)
488
+ self.cache.move_to_end(cache_key)
489
+ self.hit_count += 1
490
+ return self.cache[cache_key]
491
+
492
+ self.miss_count += 1
493
+ return None
494
+
495
+ def set_cached(self, context_id: str, key: str, value: Any):
496
+ """Cache value for context"""
497
+ cache_key = f"{context_id}:{key}"
498
+
499
+ with self._lock:
500
+ # Remove oldest if at capacity
501
+ if len(self.cache) >= self.max_size:
502
+ self.cache.popitem(last=False)
503
+
504
+ self.cache[cache_key] = value
505
+
506
+ def invalidate_context(self, context_id: str):
507
+ """Invalidate all cached values for context"""
508
+ with self._lock:
509
+ keys_to_remove = [
510
+ k for k in self.cache.keys()
511
+ if k.startswith(f"{context_id}:")
512
+ ]
513
+
514
+ for key in keys_to_remove:
515
+ del self.cache[key]
516
+
517
+ def get_statistics(self) -> Dict[str, Any]:
518
+ """Get cache statistics"""
519
+ total_requests = self.hit_count + self.miss_count
520
+
521
+ return {
522
+ 'size': len(self.cache),
523
+ 'max_size': self.max_size,
524
+ 'hit_count': self.hit_count,
525
+ 'miss_count': self.miss_count,
526
+ 'hit_rate': (self.hit_count / total_requests * 100) if total_requests > 0 else 0,
527
+ 'utilization': (len(self.cache) / self.max_size * 100) if self.max_size > 0 else 0
528
+ }
529
+
530
+
531
+ class ContextStrategy(IConfigStrategy):
532
+ """
533
+ Main context strategy for configuration lifecycle management
534
+ Provides context-based configuration with hierarchy, isolation, and caching
535
+ """
536
+
537
+ def __init__(self):
538
+ self.logger = get_logger(self.__class__.__name__)
539
+ self.hierarchy_manager = HierarchicalContextManager()
540
+ self.scoped_manager = ScopedConfigManager(self.hierarchy_manager)
541
+ self.isolated_manager = IsolatedContextManager()
542
+ self.thread_manager = ThreadLocalContextManager()
543
+ self.cache_manager = CachingContextManager()
544
+
545
+ def can_handle(self, source: Union[str, Path, Dict]) -> bool:
546
+ """Context strategy handles dictionary configurations"""
547
+ return isinstance(source, dict)
548
+
549
+ def load(self, source: Any, **kwargs) -> Dict[str, Any]:
550
+ """Load configuration into context"""
551
+ context_scope = kwargs.get('context_scope', ContextScope.TEMPORARY)
552
+ context_id = kwargs.get('context_id')
553
+
554
+ if not context_id:
555
+ # Create new context
556
+ context_id = self.hierarchy_manager.create_context(
557
+ context_scope,
558
+ ttl=kwargs.get('ttl')
559
+ )
560
+
561
+ # Load config into context
562
+ if isinstance(source, dict):
563
+ for key, value in source.items():
564
+ self.scoped_manager.set_config(context_id, key, value)
565
+
566
+ return self.scoped_manager.get_config(context_id)
567
+
568
+ def validate(self, config: Dict[str, Any], schema: Optional[Dict] = None) -> bool:
569
+ """Validate configuration in context"""
570
+ return True
571
+
572
+ def transform(self, config: Dict[str, Any]) -> Dict[str, Any]:
573
+ """Transform configuration based on context"""
574
+ # Apply context-specific transformations
575
+ context = self.get_current_context()
576
+
577
+ if context:
578
+ # Apply context-specific overrides
579
+ overrides = self.scoped_manager.get_config(
580
+ context,
581
+ 'transformations.overrides',
582
+ inherit=True
583
+ )
584
+
585
+ if overrides:
586
+ config = self._apply_overrides(config, overrides)
587
+
588
+ return config
589
+
590
+ def create_context(
591
+ self,
592
+ scope: ContextScope = ContextScope.TEMPORARY,
593
+ parent: Optional[str] = None,
594
+ isolated: bool = False,
595
+ **kwargs
596
+ ) -> str:
597
+ """Create a new configuration context"""
598
+ if isolated:
599
+ return self.isolated_manager.create_isolated_context(
600
+ kwargs.get('base_config')
601
+ )
602
+ else:
603
+ return self.hierarchy_manager.create_context(
604
+ scope,
605
+ parent_id=parent,
606
+ **kwargs
607
+ )
608
+
609
+ def get_current_context(self) -> Optional[str]:
610
+ """Get current active context"""
611
+ # Check thread-local stack
612
+ if hasattr(self.hierarchy_manager.context_stack, 'stack'):
613
+ stack = self.hierarchy_manager.context_stack.stack
614
+ if stack:
615
+ return stack[-1]
616
+
617
+ # Check for global context
618
+ for ctx_id, metadata in self.hierarchy_manager.contexts.items():
619
+ if metadata.scope == ContextScope.GLOBAL and metadata.lifecycle == ContextLifecycle.ACTIVE:
620
+ return ctx_id
621
+
622
+ return None
623
+
624
+ def with_context(self, context_id: str, operation: Callable) -> Any:
625
+ """Execute operation within specified context"""
626
+ # Push context
627
+ if not hasattr(self.hierarchy_manager.context_stack, 'stack'):
628
+ self.hierarchy_manager.context_stack.stack = []
629
+
630
+ self.hierarchy_manager.context_stack.stack.append(context_id)
631
+
632
+ try:
633
+ return operation()
634
+ finally:
635
+ # Pop context
636
+ if self.hierarchy_manager.context_stack.stack:
637
+ self.hierarchy_manager.context_stack.stack.pop()
638
+
639
+ @contextmanager
640
+ def context(self, scope: ContextScope = ContextScope.TEMPORARY, **kwargs):
641
+ """Context manager for configuration contexts"""
642
+ with self.hierarchy_manager.context(scope, **kwargs) as context_id:
643
+ yield context_id
644
+
645
+ def get_config(
646
+ self,
647
+ key: Optional[str] = None,
648
+ context: Optional[str] = None,
649
+ default: Any = None
650
+ ) -> Any:
651
+ """Get configuration value from context"""
652
+ context_id = context or self.get_current_context()
653
+
654
+ if not context_id:
655
+ return default
656
+
657
+ # Check cache first
658
+ if key:
659
+ cached = self.cache_manager.get_cached(context_id, key)
660
+ if cached is not None:
661
+ return cached
662
+
663
+ # Get from context
664
+ value = self.scoped_manager.get_config(context_id, key)
665
+
666
+ # Cache result
667
+ if key and value is not None:
668
+ self.cache_manager.set_cached(context_id, key, value)
669
+
670
+ return value if value is not None else default
671
+
672
+ def set_config(
673
+ self,
674
+ key: str,
675
+ value: Any,
676
+ context: Optional[str] = None,
677
+ **kwargs
678
+ ):
679
+ """Set configuration value in context"""
680
+ context_id = context or self.get_current_context()
681
+
682
+ if not context_id:
683
+ # Create temporary context
684
+ context_id = self.create_context(ContextScope.TEMPORARY)
685
+
686
+ self.scoped_manager.set_config(context_id, key, value, **kwargs)
687
+
688
+ # Invalidate cache
689
+ self.cache_manager.invalidate_context(context_id)
690
+
691
+ def close_context(self, context_id: str):
692
+ """Close a configuration context"""
693
+ # Invalidate cache
694
+ self.cache_manager.invalidate_context(context_id)
695
+
696
+ # Close context
697
+ if context_id.startswith('isolated_'):
698
+ self.isolated_manager.close_isolated_context(context_id)
699
+ else:
700
+ self.hierarchy_manager.close_context(context_id)
701
+
702
+ def _apply_overrides(self, config: Dict, overrides: Dict) -> Dict:
703
+ """Apply context overrides to configuration"""
704
+ result = config.copy()
705
+
706
+ for key, value in overrides.items():
707
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
708
+ result[key] = self._apply_overrides(result[key], value)
709
+ else:
710
+ result[key] = value
711
+
712
+ return result
713
+
714
+ def get_statistics(self) -> Dict[str, Any]:
715
+ """Get context strategy statistics"""
716
+ return {
717
+ 'active_contexts': len(self.hierarchy_manager.contexts),
718
+ 'isolated_contexts': len(self.isolated_manager.isolated_contexts),
719
+ 'cache_stats': self.cache_manager.get_statistics(),
720
+ 'contexts_by_scope': self._count_by_scope(),
721
+ 'contexts_by_lifecycle': self._count_by_lifecycle()
722
+ }
723
+
724
+ def _count_by_scope(self) -> Dict[str, int]:
725
+ """Count contexts by scope"""
726
+ counts = {}
727
+ for metadata in self.hierarchy_manager.contexts.values():
728
+ scope = metadata.scope.value
729
+ counts[scope] = counts.get(scope, 0) + 1
730
+ return counts
731
+
732
+ def _count_by_lifecycle(self) -> Dict[str, int]:
733
+ """Count contexts by lifecycle state"""
734
+ counts = {}
735
+ for metadata in self.hierarchy_manager.contexts.values():
736
+ state = metadata.lifecycle.value
737
+ counts[state] = counts.get(state, 0) + 1
738
+ return counts
739
+
740
+
741
+ # Export main components
742
+ __all__ = [
743
+ 'ContextStrategy',
744
+ 'ContextScope',
745
+ 'ContextLifecycle',
746
+ 'HierarchicalContextManager',
747
+ 'ScopedConfigManager'
748
+ ]