claude-mpm 3.7.8__py3-none-any.whl → 3.9.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.
Files changed (100) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -96
  4. claude_mpm/agents/MEMORY.md +94 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/templates/code_analyzer.json +2 -2
  7. claude_mpm/agents/templates/data_engineer.json +1 -1
  8. claude_mpm/agents/templates/documentation.json +1 -1
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/ops.json +1 -1
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/agents/templates/security.json +1 -1
  14. claude_mpm/agents/templates/ticketing.json +3 -8
  15. claude_mpm/agents/templates/version_control.json +1 -1
  16. claude_mpm/agents/templates/web_qa.json +2 -2
  17. claude_mpm/agents/templates/web_ui.json +2 -2
  18. claude_mpm/cli/__init__.py +2 -2
  19. claude_mpm/cli/commands/__init__.py +2 -1
  20. claude_mpm/cli/commands/agents.py +8 -3
  21. claude_mpm/cli/commands/tickets.py +596 -19
  22. claude_mpm/cli/parser.py +217 -5
  23. claude_mpm/config/__init__.py +30 -39
  24. claude_mpm/config/socketio_config.py +8 -5
  25. claude_mpm/constants.py +13 -0
  26. claude_mpm/core/__init__.py +8 -18
  27. claude_mpm/core/cache.py +596 -0
  28. claude_mpm/core/claude_runner.py +166 -622
  29. claude_mpm/core/config.py +7 -3
  30. claude_mpm/core/constants.py +339 -0
  31. claude_mpm/core/container.py +548 -38
  32. claude_mpm/core/exceptions.py +392 -0
  33. claude_mpm/core/framework_loader.py +249 -93
  34. claude_mpm/core/interactive_session.py +479 -0
  35. claude_mpm/core/interfaces.py +424 -0
  36. claude_mpm/core/lazy.py +467 -0
  37. claude_mpm/core/logging_config.py +444 -0
  38. claude_mpm/core/oneshot_session.py +465 -0
  39. claude_mpm/core/optimized_agent_loader.py +485 -0
  40. claude_mpm/core/optimized_startup.py +490 -0
  41. claude_mpm/core/service_registry.py +52 -26
  42. claude_mpm/core/socketio_pool.py +162 -5
  43. claude_mpm/core/types.py +292 -0
  44. claude_mpm/core/typing_utils.py +477 -0
  45. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  46. claude_mpm/init.py +2 -1
  47. claude_mpm/services/__init__.py +78 -14
  48. claude_mpm/services/agent/__init__.py +24 -0
  49. claude_mpm/services/agent/deployment.py +2548 -0
  50. claude_mpm/services/agent/management.py +598 -0
  51. claude_mpm/services/agent/registry.py +813 -0
  52. claude_mpm/services/agents/deployment/agent_deployment.py +728 -308
  53. claude_mpm/services/agents/memory/agent_memory_manager.py +160 -4
  54. claude_mpm/services/async_session_logger.py +8 -3
  55. claude_mpm/services/communication/__init__.py +21 -0
  56. claude_mpm/services/communication/socketio.py +1933 -0
  57. claude_mpm/services/communication/websocket.py +479 -0
  58. claude_mpm/services/core/__init__.py +123 -0
  59. claude_mpm/services/core/base.py +247 -0
  60. claude_mpm/services/core/interfaces.py +951 -0
  61. claude_mpm/services/framework_claude_md_generator/__init__.py +10 -3
  62. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +14 -11
  63. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  64. claude_mpm/services/framework_claude_md_generator.py +3 -2
  65. claude_mpm/services/health_monitor.py +4 -3
  66. claude_mpm/services/hook_service.py +64 -4
  67. claude_mpm/services/infrastructure/__init__.py +21 -0
  68. claude_mpm/services/infrastructure/logging.py +202 -0
  69. claude_mpm/services/infrastructure/monitoring.py +893 -0
  70. claude_mpm/services/memory/indexed_memory.py +648 -0
  71. claude_mpm/services/project/__init__.py +21 -0
  72. claude_mpm/services/project/analyzer.py +864 -0
  73. claude_mpm/services/project/registry.py +608 -0
  74. claude_mpm/services/project_analyzer.py +95 -2
  75. claude_mpm/services/recovery_manager.py +15 -9
  76. claude_mpm/services/response_tracker.py +3 -5
  77. claude_mpm/services/socketio/__init__.py +25 -0
  78. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  79. claude_mpm/services/socketio/handlers/base.py +121 -0
  80. claude_mpm/services/socketio/handlers/connection.py +198 -0
  81. claude_mpm/services/socketio/handlers/file.py +213 -0
  82. claude_mpm/services/socketio/handlers/git.py +723 -0
  83. claude_mpm/services/socketio/handlers/memory.py +27 -0
  84. claude_mpm/services/socketio/handlers/project.py +25 -0
  85. claude_mpm/services/socketio/handlers/registry.py +145 -0
  86. claude_mpm/services/socketio_client_manager.py +12 -7
  87. claude_mpm/services/socketio_server.py +156 -30
  88. claude_mpm/services/ticket_manager.py +172 -9
  89. claude_mpm/services/ticket_manager_di.py +1 -1
  90. claude_mpm/services/version_control/semantic_versioning.py +80 -7
  91. claude_mpm/services/version_control/version_parser.py +528 -0
  92. claude_mpm/utils/error_handler.py +1 -1
  93. claude_mpm/validation/agent_validator.py +27 -14
  94. claude_mpm/validation/frontmatter_validator.py +231 -0
  95. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/METADATA +38 -128
  96. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/RECORD +100 -59
  97. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/WHEEL +0 -0
  98. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/entry_points.txt +0 -0
  99. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/licenses/LICENSE +0 -0
  100. {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,7 @@ from enum import Enum
18
18
  from typing import Any, Callable, Dict, List, Optional, Set, Type, TypeVar, Union
19
19
 
20
20
  from .logger import get_logger
21
+ from claude_mpm.services.core.interfaces import IServiceContainer
21
22
 
22
23
  logger = get_logger(__name__)
23
24
 
@@ -28,7 +29,7 @@ class ServiceLifetime(Enum):
28
29
  """Service lifetime options."""
29
30
  SINGLETON = "singleton" # One instance per container
30
31
  TRANSIENT = "transient" # New instance per request
31
- SCOPED = "scoped" # One instance per scope (future enhancement)
32
+ SCOPED = "scoped" # One instance per scope
32
33
 
33
34
 
34
35
  class ServiceRegistration:
@@ -88,21 +89,123 @@ class ServiceNotFoundError(Exception):
88
89
  pass
89
90
 
90
91
 
91
- class DIContainer:
92
+ class ServiceScope:
92
93
  """
93
- Lightweight Dependency Injection Container.
94
+ Represents a service scope for scoped lifetime services.
94
95
 
95
- Provides service registration, resolution, and lifecycle management.
96
+ Scoped services live for the duration of the scope and are shared
97
+ within that scope.
98
+ """
99
+
100
+ def __init__(self, container: 'DIContainer'):
101
+ """Initialize service scope."""
102
+ self._container = container
103
+ self._scoped_instances: Dict[Type, Any] = {}
104
+ self._disposed = False
105
+ self._lock = threading.Lock()
106
+
107
+ def get_scoped_instance(self, service_type: Type) -> Optional[Any]:
108
+ """Get scoped instance if exists."""
109
+ with self._lock:
110
+ return self._scoped_instances.get(service_type)
111
+
112
+ def set_scoped_instance(self, service_type: Type, instance: Any) -> None:
113
+ """Store scoped instance."""
114
+ with self._lock:
115
+ if not self._disposed:
116
+ self._scoped_instances[service_type] = instance
117
+
118
+ def dispose(self) -> None:
119
+ """
120
+ Dispose of the scope and all scoped instances.
121
+
122
+ Calls dispose() on instances that implement IDisposable.
123
+ """
124
+ with self._lock:
125
+ if self._disposed:
126
+ return
127
+
128
+ for service_type, instance in self._scoped_instances.items():
129
+ # Call dispose if available
130
+ if hasattr(instance, 'dispose'):
131
+ try:
132
+ instance.dispose()
133
+ except Exception as e:
134
+ logger.error(f"Error disposing scoped service {service_type}: {e}")
135
+
136
+ self._scoped_instances.clear()
137
+ self._disposed = True
138
+
139
+ def __enter__(self):
140
+ """Context manager entry."""
141
+ return self
142
+
143
+ def __exit__(self, exc_type, exc_val, exc_tb):
144
+ """Context manager exit - dispose scope."""
145
+ self.dispose()
146
+
147
+
148
+ class DIContainer(IServiceContainer):
149
+ """
150
+ Enhanced Dependency Injection Container.
151
+
152
+ Implements IServiceContainer interface to provide a complete
153
+ dependency injection solution.
154
+
155
+ Provides:
156
+ - Service registration with multiple lifetime options
157
+ - Automatic constructor injection
158
+ - Interface to implementation mapping
159
+ - Circular dependency detection
160
+ - Lazy loading support
161
+ - Scoped service support
162
+ - Service disposal lifecycle
163
+ - Named registrations
164
+ - Configuration injection
96
165
  """
97
166
 
98
167
  def __init__(self):
99
168
  """Initialize the DI container."""
100
169
  self._registrations: Dict[Type, ServiceRegistration] = {}
170
+ self._named_registrations: Dict[str, ServiceRegistration] = {}
171
+ self._factories: Dict[Type, Callable] = {}
101
172
  self._singletons: Dict[Type, Any] = {}
173
+ self._scopes: List[ServiceScope] = []
174
+ self._initialization_order: List[Type] = []
175
+ self._disposal_handlers: Dict[Type, Callable] = {}
102
176
  self._lock = threading.RLock()
103
177
  self._resolving: Set[Type] = set()
178
+ self._current_scope: Optional[ServiceScope] = None
104
179
 
105
180
  def register(
181
+ self,
182
+ service_type: type,
183
+ implementation: type,
184
+ singleton: bool = True
185
+ ) -> None:
186
+ """
187
+ Register a service implementation (IServiceContainer interface method).
188
+
189
+ Args:
190
+ service_type: The interface/base type to register
191
+ implementation: The concrete implementation class
192
+ singleton: Whether to use singleton lifetime (default True)
193
+
194
+ Examples:
195
+ # Register interface with implementation
196
+ container.register(ILogger, ConsoleLogger)
197
+
198
+ # Register as transient (new instance each time)
199
+ container.register(IService, ServiceImpl, singleton=False)
200
+ """
201
+ lifetime = ServiceLifetime.SINGLETON if singleton else ServiceLifetime.TRANSIENT
202
+ self._register_internal(
203
+ service_type=service_type,
204
+ implementation=implementation,
205
+ lifetime=lifetime
206
+ )
207
+
208
+ def _register_internal(
106
209
  self,
107
210
  service_type: Type[T],
108
211
  implementation: Optional[Union[Type[T], Callable[..., T]]] = None,
@@ -112,7 +215,7 @@ class DIContainer:
112
215
  dependencies: Optional[Dict[str, Type]] = None
113
216
  ) -> None:
114
217
  """
115
- Register a service in the container.
218
+ Internal registration method with full flexibility.
116
219
 
117
220
  Args:
118
221
  service_type: The interface/base type to register
@@ -121,19 +224,6 @@ class DIContainer:
121
224
  factory: Optional factory function
122
225
  instance: Pre-created instance (for singleton)
123
226
  dependencies: Explicit dependency mapping for constructor params
124
-
125
- Examples:
126
- # Register interface with implementation
127
- container.register(ILogger, ConsoleLogger)
128
-
129
- # Register with factory
130
- container.register(IDatabase, factory=lambda c: Database(c.resolve(IConfig)))
131
-
132
- # Register singleton instance
133
- container.register(IConfig, instance=Config())
134
-
135
- # Register with explicit dependencies
136
- container.register(IService, ServiceImpl, dependencies={'logger': ILogger})
137
227
  """
138
228
  with self._lock:
139
229
  registration = ServiceRegistration(
@@ -149,23 +239,155 @@ class DIContainer:
149
239
  # If instance provided, store as singleton
150
240
  if instance is not None:
151
241
  self._singletons[service_type] = instance
242
+
243
+ def register_instance(self, service_type: type, instance: Any) -> None:
244
+ """
245
+ Register a service instance (IServiceContainer interface method).
246
+
247
+ Args:
248
+ service_type: The interface/base type to register
249
+ instance: Pre-created instance to register as singleton
250
+
251
+ Examples:
252
+ # Register a pre-created instance
253
+ config = Config()
254
+ container.register_instance(IConfig, config)
255
+ """
256
+ self._register_internal(
257
+ service_type=service_type,
258
+ instance=instance,
259
+ lifetime=ServiceLifetime.SINGLETON
260
+ )
261
+
262
+ def resolve_all(self, service_type: type) -> List[Any]:
263
+ """
264
+ Resolve all implementations of a service type (IServiceContainer interface method).
265
+
266
+ Args:
267
+ service_type: The type to resolve
268
+
269
+ Returns:
270
+ List of all registered implementations for the service type
271
+
272
+ Examples:
273
+ # Register multiple implementations
274
+ container.register(IPlugin, PluginA)
275
+ container.register(IPlugin, PluginB)
276
+
277
+ # Get all implementations
278
+ plugins = container.resolve_all(IPlugin)
279
+ """
280
+ with self._lock:
281
+ # For now, return a list with the single registered implementation
282
+ # In the future, we could support multiple registrations for the same type
283
+ if service_type in self._registrations:
284
+ return [self._resolve_internal(service_type)]
285
+ return []
152
286
 
153
287
  def register_singleton(
154
288
  self,
155
- service_type: Type[T],
156
- implementation: Optional[Union[Type[T], T]] = None
289
+ interface: Type[T],
290
+ implementation: Optional[Union[Type[T], T]] = None,
291
+ instance: Optional[T] = None,
292
+ name: Optional[str] = None,
293
+ dispose_handler: Optional[Callable[[T], None]] = None
157
294
  ) -> None:
158
295
  """
159
296
  Register a singleton service.
160
297
 
161
- Convenience method for registering singletons.
298
+ Args:
299
+ interface: The interface/base type to register
300
+ implementation: The concrete implementation class
301
+ instance: Pre-created instance (alternative to implementation)
302
+ name: Optional name for named registration
303
+ dispose_handler: Optional handler called when disposing service
304
+
305
+ Examples:
306
+ # Register with implementation class
307
+ container.register_singleton(ILogger, ConsoleLogger)
308
+
309
+ # Register with instance
310
+ container.register_singleton(IConfig, instance=Config())
311
+
312
+ # Register with disposal handler
313
+ container.register_singleton(
314
+ IDatabase,
315
+ DatabaseConnection,
316
+ dispose_handler=lambda db: db.close()
317
+ )
162
318
  """
163
- if implementation is not None and not inspect.isclass(implementation):
164
- # It's an instance
165
- self.register(service_type, instance=implementation)
319
+ # For named registrations, create a unique registration
320
+ if name:
321
+ # Create named registration directly
322
+ registration = ServiceRegistration(
323
+ service_type=interface,
324
+ implementation=implementation,
325
+ instance=instance,
326
+ lifetime=ServiceLifetime.SINGLETON
327
+ )
328
+ self._named_registrations[name] = registration
329
+
330
+ # Also store the instance if provided
331
+ if instance is not None:
332
+ # Store with a composite key for retrieval
333
+ named_key = (interface, name)
334
+ if not hasattr(self, '_named_singletons'):
335
+ self._named_singletons = {}
336
+ self._named_singletons[named_key] = instance
166
337
  else:
167
- self.register(service_type, implementation, lifetime=ServiceLifetime.SINGLETON)
338
+ # Normal registration without name
339
+ if instance is not None:
340
+ self._register_internal(interface, instance=instance)
341
+ elif implementation is not None and not inspect.isclass(implementation):
342
+ # It's an instance passed as implementation (backward compatibility)
343
+ self._register_internal(interface, instance=implementation)
344
+ else:
345
+ self._register_internal(interface, implementation, lifetime=ServiceLifetime.SINGLETON)
346
+
347
+ # Handle disposal handler
348
+ if dispose_handler:
349
+ if name:
350
+ # Store with composite key for named services
351
+ if not hasattr(self, '_named_disposal_handlers'):
352
+ self._named_disposal_handlers = {}
353
+ self._named_disposal_handlers[(interface, name)] = dispose_handler
354
+ else:
355
+ self._disposal_handlers[interface] = dispose_handler
356
+
357
+ # Track initialization order for proper disposal
358
+ key = (interface, name) if name else interface
359
+ if key not in self._initialization_order:
360
+ self._initialization_order.append(key)
168
361
 
362
+ def register_scoped(
363
+ self,
364
+ interface: Type[T],
365
+ implementation: Optional[Type[T]] = None,
366
+ name: Optional[str] = None
367
+ ) -> None:
368
+ """
369
+ Register a scoped service.
370
+
371
+ Scoped services are created once per scope and shared within that scope.
372
+
373
+ Args:
374
+ interface: The interface/base type to register
375
+ implementation: The concrete implementation
376
+ name: Optional name for named registration
377
+
378
+ Examples:
379
+ # Register scoped service
380
+ container.register_scoped(IRequestContext, RequestContext)
381
+
382
+ # Use in scope
383
+ with container.create_scope() as scope:
384
+ context = container.get(IRequestContext) # Created
385
+ context2 = container.get(IRequestContext) # Same instance
386
+ """
387
+ self._register_internal(interface, implementation, lifetime=ServiceLifetime.SCOPED)
388
+ if name:
389
+ self._named_registrations[name] = self._registrations[interface]
390
+
169
391
  def register_transient(
170
392
  self,
171
393
  service_type: Type[T],
@@ -176,24 +398,94 @@ class DIContainer:
176
398
 
177
399
  Convenience method for registering transient services.
178
400
  """
179
- self.register(service_type, implementation, lifetime=ServiceLifetime.TRANSIENT)
401
+ self._register_internal(service_type, implementation, lifetime=ServiceLifetime.TRANSIENT)
180
402
 
181
403
  def register_factory(
182
404
  self,
183
- service_type: Type[T],
405
+ interface: Type[T],
184
406
  factory: Callable[['DIContainer'], T],
185
- lifetime: ServiceLifetime = ServiceLifetime.SINGLETON
407
+ lifetime: ServiceLifetime = ServiceLifetime.TRANSIENT,
408
+ name: Optional[str] = None
186
409
  ) -> None:
187
410
  """
188
411
  Register a service with a factory function.
189
412
 
190
413
  The factory receives the container as parameter for resolving dependencies.
414
+
415
+ Args:
416
+ interface: The interface/base type to register
417
+ factory: Factory function that creates instances
418
+ lifetime: Service lifetime (default: TRANSIENT for factories)
419
+ name: Optional name for named registration
420
+
421
+ Examples:
422
+ # Register with factory
423
+ container.register_factory(
424
+ IService,
425
+ lambda c: Service(c.get(ILogger), c.get(IConfig)),
426
+ lifetime=ServiceLifetime.SINGLETON
427
+ )
428
+ """
429
+ self._register_internal(interface, factory=factory, lifetime=lifetime)
430
+ self._factories[interface] = factory
431
+ if name:
432
+ self._named_registrations[name] = self._registrations[interface]
433
+
434
+ def get(self, interface: Type[T], name: Optional[str] = None) -> T:
435
+ """
436
+ Get service instance with dependency resolution.
437
+
438
+ This is the primary method for retrieving services from the container.
439
+ It handles all lifetime management and dependency resolution.
440
+
441
+ Args:
442
+ interface: The type to resolve
443
+ name: Optional name for named registration lookup
444
+
445
+ Returns:
446
+ Instance of the requested service
447
+
448
+ Raises:
449
+ ServiceNotFoundError: If service is not registered
450
+ CircularDependencyError: If circular dependencies detected
451
+
452
+ Examples:
453
+ # Get by interface
454
+ logger = container.get(ILogger)
455
+
456
+ # Get named service
457
+ primary_db = container.get(IDatabase, name="primary")
191
458
  """
192
- self.register(service_type, factory=factory, lifetime=lifetime)
459
+ if name:
460
+ if name not in self._named_registrations:
461
+ suggestions = self._get_similar_names(name)
462
+ raise ServiceNotFoundError(
463
+ f"Named service '{name}' is not registered. "
464
+ f"Did you mean: {', '.join(suggestions)}?" if suggestions else ""
465
+ )
466
+
467
+ # Check if we have a pre-stored instance for this named service
468
+ named_key = (interface, name)
469
+ if hasattr(self, '_named_singletons') and named_key in self._named_singletons:
470
+ return self._named_singletons[named_key]
471
+
472
+ # Otherwise resolve from named registration
473
+ registration = self._named_registrations[name]
474
+ if registration.instance is not None:
475
+ return registration.instance
476
+ elif registration.factory:
477
+ return registration.factory(self)
478
+ else:
479
+ return self.create_instance(registration.implementation, registration.dependencies)
480
+
481
+ return self._resolve_internal(interface)
193
482
 
194
483
  def resolve(self, service_type: Type[T]) -> T:
195
484
  """
196
- Resolve a service from the container.
485
+ Resolve a service from the container (legacy method).
486
+
487
+ This method is maintained for backward compatibility.
488
+ New code should use get() instead.
197
489
 
198
490
  Args:
199
491
  service_type: The type to resolve
@@ -205,23 +497,43 @@ class DIContainer:
205
497
  ServiceNotFoundError: If service is not registered
206
498
  CircularDependencyError: If circular dependencies detected
207
499
  """
500
+ return self.get(service_type)
501
+
502
+ def _resolve_internal(self, service_type: Type[T]) -> T:
503
+ """
504
+ Internal method to resolve a service.
505
+
506
+ Handles the actual resolution logic with proper locking and lifecycle management.
507
+ """
208
508
  with self._lock:
209
509
  # Check for circular dependencies
210
510
  if service_type in self._resolving:
211
- cycle = " -> ".join(str(t) for t in self._resolving) + f" -> {service_type}"
511
+ cycle = " -> ".join(str(t.__name__) for t in self._resolving) + f" -> {service_type.__name__}"
212
512
  raise CircularDependencyError(f"Circular dependency detected: {cycle}")
213
513
 
214
514
  # Check if registered
215
515
  if service_type not in self._registrations:
216
- raise ServiceNotFoundError(f"Service {service_type} is not registered")
516
+ suggestions = self._get_similar_types(service_type)
517
+ error_msg = f"Service {service_type.__name__} is not registered."
518
+ if suggestions:
519
+ error_msg += f" Did you mean: {', '.join(suggestions)}?"
520
+ raise ServiceNotFoundError(error_msg)
217
521
 
218
522
  registration = self._registrations[service_type]
219
523
 
220
- # Return existing singleton if available
524
+ # Handle different lifetimes
221
525
  if registration.lifetime == ServiceLifetime.SINGLETON:
526
+ # Return existing singleton if available
222
527
  if service_type in self._singletons:
223
528
  return self._singletons[service_type]
224
529
 
530
+ elif registration.lifetime == ServiceLifetime.SCOPED:
531
+ # Check current scope
532
+ if self._current_scope:
533
+ instance = self._current_scope.get_scoped_instance(service_type)
534
+ if instance is not None:
535
+ return instance
536
+
225
537
  # Mark as resolving
226
538
  self._resolving.add(service_type)
227
539
 
@@ -229,10 +541,24 @@ class DIContainer:
229
541
  # Create instance
230
542
  instance = registration.create_instance(self)
231
543
 
232
- # Store singleton
544
+ # Store based on lifetime
233
545
  if registration.lifetime == ServiceLifetime.SINGLETON:
234
546
  self._singletons[service_type] = instance
235
-
547
+ if service_type not in self._initialization_order:
548
+ self._initialization_order.append(service_type)
549
+
550
+ elif registration.lifetime == ServiceLifetime.SCOPED:
551
+ if self._current_scope:
552
+ self._current_scope.set_scoped_instance(service_type, instance)
553
+
554
+ # Call initialization hook if available
555
+ if hasattr(instance, 'initialize'):
556
+ try:
557
+ instance.initialize()
558
+ except Exception as e:
559
+ logger.error(f"Failed to initialize service {service_type.__name__}: {e}")
560
+ raise
561
+
236
562
  return instance
237
563
 
238
564
  finally:
@@ -278,6 +604,28 @@ class DIContainer:
278
604
  if param.annotation != param.empty:
279
605
  param_type = param.annotation
280
606
 
607
+ # Handle string annotations (forward references)
608
+ if isinstance(param_type, str):
609
+ # Try to resolve forward reference
610
+ try:
611
+ # Get the module where the class is defined
612
+ import sys
613
+ frame = sys._getframe(1)
614
+ module = frame.f_globals
615
+ if param_type in module:
616
+ param_type = module[param_type]
617
+ else:
618
+ # Try looking in registrations by name
619
+ for reg_type in self._registrations:
620
+ if reg_type.__name__ == param_type:
621
+ param_type = reg_type
622
+ break
623
+ except:
624
+ # If we can't resolve, skip this parameter
625
+ if param.default != param.empty:
626
+ kwargs[param_name] = param.default
627
+ continue
628
+
281
629
  # Handle Optional types
282
630
  if hasattr(param_type, '__origin__') and param_type.__origin__ is Union:
283
631
  # Get the non-None type from Optional
@@ -285,7 +633,13 @@ class DIContainer:
285
633
  param_type = next((arg for arg in args if arg is not type(None)), None)
286
634
 
287
635
  if param_type and param_type in self._registrations:
288
- kwargs[param_name] = self.resolve(param_type)
636
+ # Check for circular dependency
637
+ if param_type in self._resolving:
638
+ # Circular dependency detected
639
+ cycle = " -> ".join(str(t.__name__) for t in self._resolving) + f" -> {param_type.__name__}"
640
+ raise CircularDependencyError(f"Circular dependency detected: {cycle}")
641
+ else:
642
+ kwargs[param_name] = self.resolve(param_type)
289
643
  elif param.default != param.empty:
290
644
  # Use default value
291
645
  kwargs[param_name] = param.default
@@ -301,25 +655,181 @@ class DIContainer:
301
655
  with self._lock:
302
656
  return self._registrations.copy()
303
657
 
658
+ def create_scope(self) -> ServiceScope:
659
+ """
660
+ Create a new service scope.
661
+
662
+ Scoped services will be created once per scope and shared within that scope.
663
+
664
+ Returns:
665
+ New ServiceScope instance
666
+
667
+ Examples:
668
+ # Use scope with context manager
669
+ with container.create_scope() as scope:
670
+ # Services with SCOPED lifetime are shared within this scope
671
+ service1 = container.get(IScopedService)
672
+ service2 = container.get(IScopedService)
673
+ assert service1 is service2 # Same instance
674
+
675
+ # Scope is disposed, scoped instances are cleaned up
676
+ """
677
+ scope = ServiceScope(self)
678
+ with self._lock:
679
+ self._scopes.append(scope)
680
+ # Set as current scope for resolution
681
+ old_scope = self._current_scope
682
+ self._current_scope = scope
683
+
684
+ # Return a context manager that restores old scope
685
+ class ScopeContext:
686
+ def __init__(self, container, new_scope, old_scope):
687
+ self.container = container
688
+ self.new_scope = new_scope
689
+ self.old_scope = old_scope
690
+
691
+ def __enter__(self):
692
+ return self.new_scope
693
+
694
+ def __exit__(self, exc_type, exc_val, exc_tb):
695
+ with self.container._lock:
696
+ self.container._current_scope = self.old_scope
697
+ if self.new_scope in self.container._scopes:
698
+ self.container._scopes.remove(self.new_scope)
699
+ self.new_scope.dispose()
700
+
701
+ return ScopeContext(self, scope, old_scope)
702
+
304
703
  def create_child_container(self) -> 'DIContainer':
305
704
  """
306
705
  Create a child container that inherits registrations.
307
706
 
308
- Useful for scoped scenarios.
707
+ Useful for isolated scenarios where you want separate singleton instances.
309
708
  """
310
709
  child = DIContainer()
311
710
  with self._lock:
312
711
  # Copy registrations but not singleton instances
313
712
  for service_type, registration in self._registrations.items():
314
713
  child._registrations[service_type] = registration
714
+ # Copy named registrations
715
+ child._named_registrations = self._named_registrations.copy()
716
+ # Copy factories
717
+ child._factories = self._factories.copy()
718
+ # Copy disposal handlers
719
+ child._disposal_handlers = self._disposal_handlers.copy()
315
720
  return child
316
721
 
722
+ def dispose(self) -> None:
723
+ """
724
+ Dispose of the container and all managed services.
725
+
726
+ Calls disposal handlers for singleton services in reverse initialization order.
727
+ Also disposes any active scopes.
728
+ """
729
+ with self._lock:
730
+ # Dispose scopes first
731
+ for scope in reversed(self._scopes):
732
+ scope.dispose()
733
+ self._scopes.clear()
734
+
735
+ # Dispose singletons in reverse initialization order
736
+ for service_type in reversed(self._initialization_order):
737
+ if service_type in self._singletons:
738
+ instance = self._singletons[service_type]
739
+
740
+ # Call disposal handler if registered
741
+ if service_type in self._disposal_handlers:
742
+ try:
743
+ self._disposal_handlers[service_type](instance)
744
+ except Exception as e:
745
+ logger.error(f"Error in disposal handler for {service_type.__name__}: {e}")
746
+
747
+ # Call dispose method if available
748
+ elif hasattr(instance, 'dispose'):
749
+ try:
750
+ instance.dispose()
751
+ except Exception as e:
752
+ logger.error(f"Error disposing service {service_type.__name__}: {e}")
753
+
754
+ # Clear everything
755
+ self._singletons.clear()
756
+ self._initialization_order.clear()
757
+ self._current_scope = None
758
+
317
759
  def clear(self) -> None:
318
760
  """Clear all registrations and instances."""
761
+ self.dispose()
319
762
  with self._lock:
320
763
  self._registrations.clear()
321
- self._singletons.clear()
764
+ self._named_registrations.clear()
765
+ self._factories.clear()
766
+ self._disposal_handlers.clear()
322
767
  self._resolving.clear()
768
+
769
+ def _get_similar_types(self, service_type: Type) -> List[str]:
770
+ """
771
+ Get similar registered type names for better error messages.
772
+
773
+ Uses simple string similarity to suggest possible alternatives.
774
+ """
775
+ if not self._registrations:
776
+ return []
777
+
778
+ type_name = service_type.__name__.lower()
779
+ similar = []
780
+
781
+ for registered_type in self._registrations.keys():
782
+ registered_name = registered_type.__name__
783
+ registered_lower = registered_name.lower()
784
+
785
+ # Check for substring match
786
+ if type_name in registered_lower or registered_lower in type_name:
787
+ similar.append(registered_name)
788
+ continue
789
+
790
+ # Check for common prefix
791
+ common_prefix_len = 0
792
+ for i, (a, b) in enumerate(zip(type_name, registered_lower)):
793
+ if a == b:
794
+ common_prefix_len = i + 1
795
+ else:
796
+ break
797
+
798
+ if common_prefix_len >= min(3, len(type_name) // 2):
799
+ similar.append(registered_name)
800
+
801
+ return similar[:3] # Return top 3 suggestions
802
+
803
+ def _get_similar_names(self, name: str) -> List[str]:
804
+ """
805
+ Get similar registered names for better error messages.
806
+ """
807
+ if not self._named_registrations:
808
+ return []
809
+
810
+ name_lower = name.lower()
811
+ similar = []
812
+
813
+ for registered_name in self._named_registrations.keys():
814
+ registered_lower = registered_name.lower()
815
+
816
+ # Check for substring match
817
+ if name_lower in registered_lower or registered_lower in name_lower:
818
+ similar.append(registered_name)
819
+ continue
820
+
821
+ # Check for common prefix
822
+ common_prefix_len = 0
823
+ for i, (a, b) in enumerate(zip(name_lower, registered_lower)):
824
+ if a == b:
825
+ common_prefix_len = i + 1
826
+ else:
827
+ break
828
+
829
+ if common_prefix_len >= min(3, len(name_lower) // 2):
830
+ similar.append(registered_name)
831
+
832
+ return similar[:3] # Return top 3 suggestions
323
833
 
324
834
 
325
835
  # Global container instance (optional, for convenience)