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