claude-mpm 4.1.2__py3-none-any.whl → 4.1.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/templates/engineer.json +33 -11
- claude_mpm/cli/commands/agents.py +556 -1009
- claude_mpm/cli/commands/memory.py +248 -927
- claude_mpm/cli/commands/run.py +139 -484
- claude_mpm/cli/startup_logging.py +76 -0
- claude_mpm/core/agent_registry.py +6 -10
- claude_mpm/core/framework_loader.py +114 -595
- claude_mpm/core/logging_config.py +2 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/RECORD +52 -22
- claude_mpm/cli/commands/run_config_checker.py +0 -159
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service Container with Dependency Injection
|
|
3
|
+
===========================================
|
|
4
|
+
|
|
5
|
+
WHY: This service container provides comprehensive dependency injection to manage
|
|
6
|
+
all services in the Claude MPM framework, enabling loose coupling, better testability,
|
|
7
|
+
and simplified service management.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISIONS:
|
|
10
|
+
- Thread-safe implementation for concurrent service resolution
|
|
11
|
+
- Support for singleton, transient, and factory patterns
|
|
12
|
+
- Automatic dependency resolution based on constructor parameters
|
|
13
|
+
- Circular dependency detection and prevention
|
|
14
|
+
- Lazy initialization support for expensive services
|
|
15
|
+
|
|
16
|
+
FEATURES:
|
|
17
|
+
- Service registration (interface → implementation mapping)
|
|
18
|
+
- Automatic dependency injection based on type hints
|
|
19
|
+
- Multiple lifetime management strategies
|
|
20
|
+
- Factory pattern support for complex creation logic
|
|
21
|
+
- Service resolution with automatic dependency graph traversal
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import inspect
|
|
25
|
+
import threading
|
|
26
|
+
from enum import Enum
|
|
27
|
+
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union
|
|
28
|
+
|
|
29
|
+
from claude_mpm.core.logger import get_logger
|
|
30
|
+
|
|
31
|
+
# Type variable for generic service types
|
|
32
|
+
T = TypeVar("T")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ServiceLifetime(Enum):
|
|
36
|
+
"""Service lifetime management strategies."""
|
|
37
|
+
|
|
38
|
+
SINGLETON = "singleton" # Single instance for entire application lifetime
|
|
39
|
+
TRANSIENT = "transient" # New instance for each resolution
|
|
40
|
+
SCOPED = "scoped" # Single instance per scope (e.g., request)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ServiceDescriptor:
|
|
44
|
+
"""Describes a registered service."""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
service_type: Type,
|
|
49
|
+
implementation: Optional[Type] = None,
|
|
50
|
+
instance: Optional[Any] = None,
|
|
51
|
+
factory: Optional[Callable] = None,
|
|
52
|
+
lifetime: ServiceLifetime = ServiceLifetime.SINGLETON,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize service descriptor.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
service_type: The interface or base type
|
|
59
|
+
implementation: The concrete implementation class
|
|
60
|
+
instance: Pre-created instance (for singleton registration)
|
|
61
|
+
factory: Factory function to create instances
|
|
62
|
+
lifetime: Service lifetime management strategy
|
|
63
|
+
"""
|
|
64
|
+
self.service_type = service_type
|
|
65
|
+
self.implementation = implementation
|
|
66
|
+
self.instance = instance
|
|
67
|
+
self.factory = factory
|
|
68
|
+
self.lifetime = lifetime
|
|
69
|
+
self.lock = threading.RLock() # For thread-safe singleton creation
|
|
70
|
+
|
|
71
|
+
# Validate descriptor
|
|
72
|
+
if not any([implementation, instance, factory]):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
"Service descriptor must have either implementation, instance, or factory"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CircularDependencyError(Exception):
|
|
79
|
+
"""Raised when circular dependencies are detected."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, resolution_chain: List[Type]):
|
|
82
|
+
self.resolution_chain = resolution_chain
|
|
83
|
+
chain_str = " -> ".join(t.__name__ for t in resolution_chain)
|
|
84
|
+
super().__init__(f"Circular dependency detected: {chain_str}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ServiceNotFoundError(Exception):
|
|
88
|
+
"""Raised when a required service is not registered."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, service_type: Type):
|
|
91
|
+
self.service_type = service_type
|
|
92
|
+
super().__init__(f"Service not registered: {service_type.__name__}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ServiceContainer:
|
|
96
|
+
"""
|
|
97
|
+
Dependency injection container for service management.
|
|
98
|
+
|
|
99
|
+
This container provides:
|
|
100
|
+
- Service registration with lifetime management
|
|
101
|
+
- Automatic dependency resolution
|
|
102
|
+
- Circular dependency detection
|
|
103
|
+
- Thread-safe service creation
|
|
104
|
+
- Factory pattern support
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self):
|
|
108
|
+
"""Initialize the service container."""
|
|
109
|
+
self.logger = get_logger("service_container")
|
|
110
|
+
self._services: Dict[Type, ServiceDescriptor] = {}
|
|
111
|
+
self._lock = threading.RLock()
|
|
112
|
+
self._resolution_stack: threading.local = threading.local()
|
|
113
|
+
|
|
114
|
+
# Register the container itself for self-injection scenarios
|
|
115
|
+
self.register_instance(ServiceContainer, self)
|
|
116
|
+
|
|
117
|
+
self.logger.debug("Service container initialized")
|
|
118
|
+
|
|
119
|
+
def register(
|
|
120
|
+
self,
|
|
121
|
+
service_type: Type[T],
|
|
122
|
+
implementation: Type[T],
|
|
123
|
+
lifetime: Union[ServiceLifetime, bool] = ServiceLifetime.SINGLETON,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Register a service implementation.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
service_type: The interface or base type to register
|
|
130
|
+
implementation: The concrete implementation class
|
|
131
|
+
lifetime: Service lifetime (ServiceLifetime enum or bool for backward compat)
|
|
132
|
+
If bool: True = SINGLETON, False = TRANSIENT
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
container.register(ICacheManager, CacheManager, ServiceLifetime.SINGLETON)
|
|
136
|
+
# or for backward compatibility:
|
|
137
|
+
container.register(ICacheManager, CacheManager, singleton=True)
|
|
138
|
+
"""
|
|
139
|
+
# Handle backward compatibility for singleton parameter
|
|
140
|
+
if isinstance(lifetime, bool):
|
|
141
|
+
lifetime = (
|
|
142
|
+
ServiceLifetime.SINGLETON if lifetime else ServiceLifetime.TRANSIENT
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
with self._lock:
|
|
146
|
+
# Validate implementation
|
|
147
|
+
if not self._is_valid_implementation(service_type, implementation):
|
|
148
|
+
self.logger.warning(
|
|
149
|
+
f"Implementation {implementation.__name__} may not properly implement "
|
|
150
|
+
f"{service_type.__name__}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
descriptor = ServiceDescriptor(
|
|
154
|
+
service_type=service_type,
|
|
155
|
+
implementation=implementation,
|
|
156
|
+
lifetime=lifetime,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
self._services[service_type] = descriptor
|
|
160
|
+
|
|
161
|
+
self.logger.debug(
|
|
162
|
+
f"Registered {implementation.__name__} for {service_type.__name__} "
|
|
163
|
+
f"with {lifetime.value} lifetime"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def register_instance(self, service_type: Type[T], instance: T) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Register a pre-created service instance (always singleton).
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
service_type: The interface or base type to register
|
|
172
|
+
instance: The pre-created instance
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
cache_manager = CacheManager()
|
|
176
|
+
container.register_instance(ICacheManager, cache_manager)
|
|
177
|
+
"""
|
|
178
|
+
with self._lock:
|
|
179
|
+
descriptor = ServiceDescriptor(
|
|
180
|
+
service_type=service_type,
|
|
181
|
+
instance=instance,
|
|
182
|
+
lifetime=ServiceLifetime.SINGLETON,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
self._services[service_type] = descriptor
|
|
186
|
+
|
|
187
|
+
self.logger.debug(
|
|
188
|
+
f"Registered instance of {instance.__class__.__name__} "
|
|
189
|
+
f"for {service_type.__name__}"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def register_factory(
|
|
193
|
+
self,
|
|
194
|
+
service_type: Type[T],
|
|
195
|
+
factory: Callable[[], T],
|
|
196
|
+
lifetime: ServiceLifetime = ServiceLifetime.TRANSIENT,
|
|
197
|
+
) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Register a factory function for service creation.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
service_type: The interface or base type to register
|
|
203
|
+
factory: Factory function that creates instances
|
|
204
|
+
lifetime: Service lifetime management strategy
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
def create_logger():
|
|
208
|
+
return Logger(config=get_config())
|
|
209
|
+
|
|
210
|
+
container.register_factory(ILogger, create_logger)
|
|
211
|
+
"""
|
|
212
|
+
with self._lock:
|
|
213
|
+
descriptor = ServiceDescriptor(
|
|
214
|
+
service_type=service_type, factory=factory, lifetime=lifetime
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
self._services[service_type] = descriptor
|
|
218
|
+
|
|
219
|
+
self.logger.debug(
|
|
220
|
+
f"Registered factory for {service_type.__name__} "
|
|
221
|
+
f"with {lifetime.value} lifetime"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def resolve(self, service_type: Type[T]) -> T:
|
|
225
|
+
"""
|
|
226
|
+
Resolve a service by type with automatic dependency injection.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
service_type: The interface or type to resolve
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
The resolved service instance
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
ServiceNotFoundError: If the service is not registered
|
|
236
|
+
CircularDependencyError: If circular dependencies are detected
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
cache_manager = container.resolve(ICacheManager)
|
|
240
|
+
"""
|
|
241
|
+
# Initialize resolution stack for this thread if needed
|
|
242
|
+
if not hasattr(self._resolution_stack, "stack"):
|
|
243
|
+
self._resolution_stack.stack = []
|
|
244
|
+
|
|
245
|
+
# Check for circular dependencies
|
|
246
|
+
if service_type in self._resolution_stack.stack:
|
|
247
|
+
raise CircularDependencyError(self._resolution_stack.stack + [service_type])
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
# Add to resolution stack
|
|
251
|
+
self._resolution_stack.stack.append(service_type)
|
|
252
|
+
|
|
253
|
+
with self._lock:
|
|
254
|
+
# Check if service is registered
|
|
255
|
+
if service_type not in self._services:
|
|
256
|
+
raise ServiceNotFoundError(service_type)
|
|
257
|
+
|
|
258
|
+
descriptor = self._services[service_type]
|
|
259
|
+
|
|
260
|
+
# Handle different lifetime strategies
|
|
261
|
+
if descriptor.lifetime == ServiceLifetime.SINGLETON:
|
|
262
|
+
return self._resolve_singleton(descriptor)
|
|
263
|
+
if descriptor.lifetime == ServiceLifetime.TRANSIENT:
|
|
264
|
+
return self._resolve_transient(descriptor)
|
|
265
|
+
if descriptor.lifetime == ServiceLifetime.SCOPED:
|
|
266
|
+
# TODO: Implement scoped lifetime (requires scope context)
|
|
267
|
+
return self._resolve_transient(descriptor)
|
|
268
|
+
raise ValueError(f"Unknown lifetime: {descriptor.lifetime}")
|
|
269
|
+
|
|
270
|
+
finally:
|
|
271
|
+
# Remove from resolution stack
|
|
272
|
+
self._resolution_stack.stack.pop()
|
|
273
|
+
|
|
274
|
+
def resolve_all(self, service_type: Type[T]) -> List[T]:
|
|
275
|
+
"""
|
|
276
|
+
Resolve all implementations of a service type.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
service_type: The interface or base type to resolve
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
List of all registered implementations
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
handlers = container.resolve_all(IEventHandler)
|
|
286
|
+
"""
|
|
287
|
+
results = []
|
|
288
|
+
|
|
289
|
+
with self._lock:
|
|
290
|
+
for registered_type, descriptor in self._services.items():
|
|
291
|
+
# Check if registered type is subclass of requested type
|
|
292
|
+
if self._is_assignable(registered_type, service_type):
|
|
293
|
+
try:
|
|
294
|
+
instance = self.resolve(registered_type)
|
|
295
|
+
results.append(instance)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
self.logger.warning(
|
|
298
|
+
f"Failed to resolve {registered_type.__name__}: {e}"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return results
|
|
302
|
+
|
|
303
|
+
def is_registered(self, service_type: Type) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Check if a service type is registered.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
service_type: The interface or type to check
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
True if the service is registered, False otherwise
|
|
312
|
+
|
|
313
|
+
Example:
|
|
314
|
+
if container.is_registered(ICacheManager):
|
|
315
|
+
cache = container.resolve(ICacheManager)
|
|
316
|
+
"""
|
|
317
|
+
with self._lock:
|
|
318
|
+
return service_type in self._services
|
|
319
|
+
|
|
320
|
+
def clear(self) -> None:
|
|
321
|
+
"""Clear all registered services except the container itself."""
|
|
322
|
+
with self._lock:
|
|
323
|
+
# Keep reference to self registration
|
|
324
|
+
self_descriptor = self._services.get(ServiceContainer)
|
|
325
|
+
|
|
326
|
+
# Clear all services
|
|
327
|
+
self._services.clear()
|
|
328
|
+
|
|
329
|
+
# Re-register self if it was registered
|
|
330
|
+
if self_descriptor:
|
|
331
|
+
self._services[ServiceContainer] = self_descriptor
|
|
332
|
+
|
|
333
|
+
self.logger.debug("Service container cleared")
|
|
334
|
+
|
|
335
|
+
# Private helper methods
|
|
336
|
+
|
|
337
|
+
def _resolve_singleton(self, descriptor: ServiceDescriptor) -> Any:
|
|
338
|
+
"""Resolve a singleton service."""
|
|
339
|
+
with descriptor.lock:
|
|
340
|
+
# Return existing instance if available
|
|
341
|
+
if descriptor.instance is not None:
|
|
342
|
+
return descriptor.instance
|
|
343
|
+
|
|
344
|
+
# Create new instance
|
|
345
|
+
if descriptor.factory:
|
|
346
|
+
instance = descriptor.factory()
|
|
347
|
+
elif descriptor.implementation:
|
|
348
|
+
instance = self._create_instance(descriptor.implementation)
|
|
349
|
+
else:
|
|
350
|
+
raise ValueError("No way to create service instance")
|
|
351
|
+
|
|
352
|
+
# Cache the instance
|
|
353
|
+
descriptor.instance = instance
|
|
354
|
+
|
|
355
|
+
return instance
|
|
356
|
+
|
|
357
|
+
def _resolve_transient(self, descriptor: ServiceDescriptor) -> Any:
|
|
358
|
+
"""Resolve a transient service (new instance each time)."""
|
|
359
|
+
if descriptor.instance is not None:
|
|
360
|
+
# Pre-created instances are always returned as-is
|
|
361
|
+
return descriptor.instance
|
|
362
|
+
|
|
363
|
+
if descriptor.factory:
|
|
364
|
+
return descriptor.factory()
|
|
365
|
+
if descriptor.implementation:
|
|
366
|
+
return self._create_instance(descriptor.implementation)
|
|
367
|
+
raise ValueError("No way to create service instance")
|
|
368
|
+
|
|
369
|
+
def _create_instance(self, implementation: Type) -> Any:
|
|
370
|
+
"""
|
|
371
|
+
Create an instance with automatic dependency injection.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
implementation: The class to instantiate
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
The created instance with injected dependencies
|
|
378
|
+
"""
|
|
379
|
+
# Get constructor signature
|
|
380
|
+
try:
|
|
381
|
+
sig = inspect.signature(implementation.__init__)
|
|
382
|
+
except (ValueError, TypeError):
|
|
383
|
+
# Fallback for classes without proper __init__
|
|
384
|
+
return implementation()
|
|
385
|
+
|
|
386
|
+
# Resolve constructor dependencies
|
|
387
|
+
kwargs = {}
|
|
388
|
+
for param_name, param in sig.parameters.items():
|
|
389
|
+
# Skip 'self' parameter
|
|
390
|
+
if param_name == "self":
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
# Get type hint for parameter
|
|
394
|
+
param_type = param.annotation
|
|
395
|
+
|
|
396
|
+
# Skip if no type hint or if it's not a class
|
|
397
|
+
if param_type == inspect.Parameter.empty:
|
|
398
|
+
# Use default value if available
|
|
399
|
+
if param.default != inspect.Parameter.empty:
|
|
400
|
+
kwargs[param_name] = param.default
|
|
401
|
+
continue
|
|
402
|
+
|
|
403
|
+
# Handle Optional types
|
|
404
|
+
origin = getattr(param_type, "__origin__", None)
|
|
405
|
+
if origin is Union:
|
|
406
|
+
# Get the non-None type from Optional[T]
|
|
407
|
+
args = getattr(param_type, "__args__", ())
|
|
408
|
+
non_none_types = [t for t in args if t != type(None)]
|
|
409
|
+
if non_none_types:
|
|
410
|
+
param_type = non_none_types[0]
|
|
411
|
+
else:
|
|
412
|
+
continue
|
|
413
|
+
|
|
414
|
+
# Try to resolve the dependency
|
|
415
|
+
if isinstance(param_type, type) and self.is_registered(param_type):
|
|
416
|
+
try:
|
|
417
|
+
kwargs[param_name] = self.resolve(param_type)
|
|
418
|
+
self.logger.debug(
|
|
419
|
+
f"Injected {param_type.__name__} into {implementation.__name__}.{param_name}"
|
|
420
|
+
)
|
|
421
|
+
except CircularDependencyError:
|
|
422
|
+
# Re-raise circular dependency errors to trigger proper handling
|
|
423
|
+
raise
|
|
424
|
+
except Exception as e:
|
|
425
|
+
self.logger.warning(
|
|
426
|
+
f"Failed to inject {param_type.__name__} into "
|
|
427
|
+
f"{implementation.__name__}.{param_name}: {e}"
|
|
428
|
+
)
|
|
429
|
+
# Use default value if available, otherwise fail if required
|
|
430
|
+
if param.default != inspect.Parameter.empty:
|
|
431
|
+
kwargs[param_name] = param.default
|
|
432
|
+
else:
|
|
433
|
+
# Required parameter with no default - can't create instance
|
|
434
|
+
raise
|
|
435
|
+
elif param.default != inspect.Parameter.empty:
|
|
436
|
+
# Use default value if available
|
|
437
|
+
kwargs[param_name] = param.default
|
|
438
|
+
|
|
439
|
+
# Create instance with resolved dependencies
|
|
440
|
+
return implementation(**kwargs)
|
|
441
|
+
|
|
442
|
+
def _is_valid_implementation(
|
|
443
|
+
self, service_type: Type, implementation: Type
|
|
444
|
+
) -> bool:
|
|
445
|
+
"""Check if implementation properly implements the service type."""
|
|
446
|
+
# If service_type is ABC, check if implementation has all abstract methods
|
|
447
|
+
if hasattr(service_type, "__abstractmethods__"):
|
|
448
|
+
abstract_methods = getattr(service_type, "__abstractmethods__", set())
|
|
449
|
+
for method in abstract_methods:
|
|
450
|
+
if not hasattr(implementation, method):
|
|
451
|
+
return False
|
|
452
|
+
|
|
453
|
+
# Check if implementation is subclass (for classes)
|
|
454
|
+
try:
|
|
455
|
+
if issubclass(implementation, service_type):
|
|
456
|
+
return True
|
|
457
|
+
except TypeError:
|
|
458
|
+
# Not classes, can't check subclass relationship
|
|
459
|
+
pass
|
|
460
|
+
|
|
461
|
+
return True # Assume valid if we can't determine otherwise
|
|
462
|
+
|
|
463
|
+
def _is_assignable(self, from_type: Type, to_type: Type) -> bool:
|
|
464
|
+
"""Check if from_type can be assigned to to_type."""
|
|
465
|
+
try:
|
|
466
|
+
return issubclass(from_type, to_type)
|
|
467
|
+
except TypeError:
|
|
468
|
+
return from_type == to_type
|
|
469
|
+
|
|
470
|
+
def get_registration_info(self) -> Dict[str, Any]:
|
|
471
|
+
"""
|
|
472
|
+
Get information about all registered services.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
Dictionary with registration information
|
|
476
|
+
"""
|
|
477
|
+
info = {}
|
|
478
|
+
|
|
479
|
+
with self._lock:
|
|
480
|
+
for service_type, descriptor in self._services.items():
|
|
481
|
+
type_name = service_type.__name__
|
|
482
|
+
|
|
483
|
+
info[type_name] = {
|
|
484
|
+
"lifetime": descriptor.lifetime.value,
|
|
485
|
+
"has_instance": descriptor.instance is not None,
|
|
486
|
+
"has_factory": descriptor.factory is not None,
|
|
487
|
+
"implementation": (
|
|
488
|
+
descriptor.implementation.__name__
|
|
489
|
+
if descriptor.implementation
|
|
490
|
+
else None
|
|
491
|
+
),
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return info
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# Global container instance (optional, for convenience)
|
|
498
|
+
_global_container: Optional[ServiceContainer] = None
|
|
499
|
+
_global_lock = threading.Lock()
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def get_global_container() -> ServiceContainer:
|
|
503
|
+
"""
|
|
504
|
+
Get or create the global service container.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
The global service container instance
|
|
508
|
+
|
|
509
|
+
Example:
|
|
510
|
+
container = get_global_container()
|
|
511
|
+
container.register(IMyService, MyService)
|
|
512
|
+
"""
|
|
513
|
+
global _global_container
|
|
514
|
+
|
|
515
|
+
if _global_container is None:
|
|
516
|
+
with _global_lock:
|
|
517
|
+
if _global_container is None:
|
|
518
|
+
_global_container = ServiceContainer()
|
|
519
|
+
|
|
520
|
+
return _global_container
|