claude-mpm 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,747 @@
1
+ """
2
+ Consolidated Base Service for Claude PM Framework.
3
+
4
+ Combines functionality from:
5
+ - base_service.py (basic service infrastructure)
6
+ - enhanced_base_service.py (advanced features)
7
+
8
+ Provides:
9
+ - Lifecycle management (start, stop, health checks)
10
+ - Configuration management
11
+ - Logging with optional structured context
12
+ - Metrics collection and performance monitoring
13
+ - Error handling with optional circuit breaker
14
+ - Optional dependency injection support
15
+ - Service discovery and registration
16
+ - Background task management
17
+ """
18
+
19
+ import asyncio
20
+ import logging
21
+ import signal
22
+ import sys
23
+ import os
24
+ import time
25
+ import traceback
26
+ import threading
27
+ from abc import ABC, abstractmethod
28
+ from dataclasses import dataclass, field
29
+ from datetime import datetime, timedelta
30
+ from typing import Any, Dict, List, Optional, Union, Type, Callable
31
+ from pathlib import Path
32
+ from contextlib import asynccontextmanager
33
+ import json
34
+
35
+ from .config import Config
36
+ from .logger import setup_logging
37
+ from .mixins import LoggerMixin
38
+
39
+
40
+ @dataclass
41
+ class ServiceHealth:
42
+ """Service health status information."""
43
+ status: str # healthy, degraded, unhealthy, unknown
44
+ message: str
45
+ timestamp: str
46
+ metrics: Dict[str, Any] = field(default_factory=dict)
47
+ checks: Dict[str, bool] = field(default_factory=dict)
48
+
49
+
50
+ @dataclass
51
+ class ServiceMetrics:
52
+ """Service metrics collection."""
53
+ requests_total: int = 0
54
+ requests_failed: int = 0
55
+ response_time_avg: float = 0.0
56
+ uptime_seconds: int = 0
57
+ memory_usage_mb: float = 0.0
58
+ custom_metrics: Dict[str, Any] = field(default_factory=dict)
59
+
60
+
61
+ @dataclass
62
+ class ServiceContext:
63
+ """Service execution context with request tracking (enhanced feature)."""
64
+ request_id: str
65
+ start_time: float
66
+ service_name: str
67
+ operation: str
68
+ metadata: Dict[str, Any] = field(default_factory=dict)
69
+
70
+
71
+ @dataclass
72
+ class CircuitBreakerState:
73
+ """Circuit breaker state for service resilience (enhanced feature)."""
74
+ failure_count: int = 0
75
+ last_failure_time: Optional[float] = None
76
+ state: str = "closed" # closed, open, half_open
77
+ failure_threshold: int = 5
78
+ timeout_seconds: int = 60
79
+ success_threshold: int = 3 # For half-open state
80
+
81
+
82
+ class BaseService(LoggerMixin, ABC):
83
+ """
84
+ Base class for all Claude PM services with optional enhanced features.
85
+
86
+ Basic features (always enabled):
87
+ - Lifecycle management
88
+ - Configuration
89
+ - Logging
90
+ - Metrics
91
+ - Health checks
92
+ - Background tasks
93
+
94
+ Enhanced features (optional):
95
+ - Dependency injection
96
+ - Circuit breaker pattern
97
+ - Request tracking
98
+ - Performance monitoring
99
+ - Structured logging context
100
+ """
101
+
102
+ def __init__(
103
+ self,
104
+ name: str,
105
+ config: Optional[Dict[str, Any]] = None,
106
+ config_path: Optional[Path] = None,
107
+ enable_enhanced_features: bool = False,
108
+ container: Optional[Any] = None
109
+ ):
110
+ """
111
+ Initialize the base service.
112
+
113
+ Args:
114
+ name: Service name for identification
115
+ config: Optional configuration dictionary
116
+ config_path: Optional path to configuration file
117
+ enable_enhanced_features: Enable circuit breaker, request tracking, etc.
118
+ container: Optional service container for dependency injection
119
+ """
120
+ self.name = name
121
+ self.config = Config(config or {}, config_path)
122
+ self._enable_enhanced = enable_enhanced_features
123
+ self._container = container
124
+
125
+ # Set custom logger name based on service name
126
+ self._logger_name = f"{self.__class__.__module__}.{self.__class__.__name__}.{name}"
127
+
128
+ # Service state
129
+ self._running = False
130
+ self._start_time: Optional[datetime] = None
131
+ self._stop_event = asyncio.Event()
132
+ self._shutdown_timeout = timedelta(seconds=30)
133
+
134
+ # Health and metrics
135
+ self._health = ServiceHealth(
136
+ status="unknown", message="Service not started", timestamp=datetime.now().isoformat()
137
+ )
138
+ self._metrics = ServiceMetrics()
139
+ self._last_health_check: Optional[float] = None
140
+
141
+ # Background tasks
142
+ self._background_tasks: List[asyncio.Task] = []
143
+
144
+ # Enhanced features (only initialized if enabled)
145
+ if self._enable_enhanced:
146
+ self._init_enhanced_features()
147
+
148
+ # Check for quiet mode
149
+ default_log_level = "INFO"
150
+ if os.getenv('CLAUDE_PM_QUIET_MODE') == 'true':
151
+ default_log_level = "WARNING"
152
+ self.logger.setLevel(logging.WARNING)
153
+
154
+ # Only log if not in quiet mode
155
+ if not os.environ.get('CLAUDE_PM_QUIET_MODE', '').lower() == 'true':
156
+ self.logger.info(f"Initialized {self.name} service")
157
+
158
+ def _init_enhanced_features(self):
159
+ """Initialize enhanced features when enabled."""
160
+ # Circuit breaker for resilience
161
+ self._circuit_breaker = CircuitBreakerState()
162
+
163
+ # Error tracking
164
+ self._error_counts: Dict[str, int] = {}
165
+
166
+ # Request tracking
167
+ self._request_contexts: Dict[str, ServiceContext] = {}
168
+
169
+ # Performance metrics
170
+ self._performance_metrics: Dict[str, List[float]] = {}
171
+
172
+ # Thread safety
173
+ self._lock = threading.RLock()
174
+
175
+ # Optional service dependencies
176
+ self._health_monitor: Optional[Any] = None
177
+ self._error_handler: Optional[Any] = None
178
+ self._cache: Optional[Any] = None
179
+
180
+ # Register dependencies if container provided
181
+ if self._container:
182
+ self._register_service_dependencies()
183
+
184
+ @property
185
+ def running(self) -> bool:
186
+ """Check if service is currently running."""
187
+ return self._running
188
+
189
+ @property
190
+ def uptime(self) -> Optional[float]:
191
+ """Get service uptime in seconds."""
192
+ if self._start_time and self._running:
193
+ return (datetime.now() - self._start_time).total_seconds()
194
+ return None
195
+
196
+ @property
197
+ def health(self) -> ServiceHealth:
198
+ """Get current service health status."""
199
+ return self._health
200
+
201
+ @property
202
+ def metrics(self) -> ServiceMetrics:
203
+ """Get current service metrics."""
204
+ if self.uptime:
205
+ self._metrics.uptime_seconds = int(self.uptime)
206
+ return self._metrics
207
+
208
+ async def start(self) -> None:
209
+ """Start the service."""
210
+ if self._running:
211
+ self.logger.warning(f"Service {self.name} is already running")
212
+ return
213
+
214
+ self.logger.info(f"Starting {self.name} service...")
215
+
216
+ try:
217
+ # Use context manager if enhanced features enabled
218
+ if self._enable_enhanced:
219
+ async with self._service_operation("start"):
220
+ await self._start_internal()
221
+ else:
222
+ await self._start_internal()
223
+
224
+ self.logger.info(f"Service {self.name} started successfully")
225
+
226
+ except Exception as e:
227
+ self.logger.error(f"Failed to start service {self.name}: {e}")
228
+ self._health = ServiceHealth(
229
+ status="unhealthy",
230
+ message=f"Startup failed: {str(e)}",
231
+ timestamp=datetime.now().isoformat(),
232
+ checks={"startup": False},
233
+ )
234
+
235
+ if self._enable_enhanced and hasattr(self, '_handle_error'):
236
+ await self._handle_error(e, {"operation": "start", "service": self.name})
237
+ raise
238
+
239
+ async def _start_internal(self):
240
+ """Internal start logic."""
241
+ # Setup signal handlers
242
+ self._setup_signal_handlers()
243
+
244
+ # Initialize dependencies if enhanced
245
+ if self._enable_enhanced and hasattr(self, '_initialize_dependencies'):
246
+ await self._initialize_dependencies()
247
+
248
+ # Initialize service
249
+ await self._initialize()
250
+
251
+ # Start background tasks
252
+ await self._start_background_tasks()
253
+
254
+ # Register with health monitor if available
255
+ if self._enable_enhanced and hasattr(self, '_register_with_health_monitor'):
256
+ await self._register_with_health_monitor()
257
+
258
+ # Mark as running
259
+ self._running = True
260
+ self._start_time = datetime.now()
261
+
262
+ # Update health status
263
+ self._health = ServiceHealth(
264
+ status="healthy",
265
+ message="Service started successfully",
266
+ timestamp=datetime.now().isoformat(),
267
+ checks={"startup": True},
268
+ metrics=self._get_health_metrics() if self._enable_enhanced else {}
269
+ )
270
+
271
+ async def stop(self) -> None:
272
+ """Stop the service gracefully."""
273
+ if not self._running:
274
+ self.logger.warning(f"Service {self.name} is not running")
275
+ return
276
+
277
+ self.logger.info(f"Stopping {self.name} service...")
278
+
279
+ try:
280
+ # Apply timeout if enhanced features enabled
281
+ if self._enable_enhanced:
282
+ async with asyncio.timeout(self._shutdown_timeout.total_seconds()):
283
+ await self._stop_internal()
284
+ else:
285
+ await self._stop_internal()
286
+
287
+ self.logger.info(f"Service {self.name} stopped successfully")
288
+
289
+ except asyncio.TimeoutError:
290
+ self.logger.error(f"Service {self.name} shutdown timeout exceeded")
291
+ # Force stop background tasks
292
+ for task in self._background_tasks:
293
+ if not task.done():
294
+ task.cancel()
295
+ raise
296
+ except Exception as e:
297
+ self.logger.error(f"Error stopping service {self.name}: {e}")
298
+ if self._enable_enhanced and hasattr(self, '_handle_error'):
299
+ await self._handle_error(e, {"operation": "stop", "service": self.name})
300
+ raise
301
+
302
+ async def _stop_internal(self):
303
+ """Internal stop logic."""
304
+ # Signal stop to background tasks
305
+ self._stop_event.set()
306
+
307
+ # Unregister from health monitor if available
308
+ if self._enable_enhanced and hasattr(self, '_unregister_from_health_monitor'):
309
+ await self._unregister_from_health_monitor()
310
+
311
+ # Cancel background tasks
312
+ for task in self._background_tasks:
313
+ if not task.done():
314
+ task.cancel()
315
+
316
+ # Wait for tasks to complete
317
+ if self._background_tasks:
318
+ await asyncio.gather(*self._background_tasks, return_exceptions=True)
319
+
320
+ # Cleanup service
321
+ await self._cleanup()
322
+
323
+ # Mark as stopped
324
+ self._running = False
325
+
326
+ # Update health status
327
+ self._health = ServiceHealth(
328
+ status="unknown",
329
+ message="Service stopped",
330
+ timestamp=datetime.now().isoformat(),
331
+ checks={"running": False},
332
+ )
333
+
334
+ async def restart(self) -> None:
335
+ """Restart the service."""
336
+ self.logger.info(f"Restarting {self.name} service...")
337
+ await self.stop()
338
+ await self.start()
339
+
340
+ async def health_check(self) -> ServiceHealth:
341
+ """
342
+ Perform comprehensive health check.
343
+
344
+ Returns:
345
+ ServiceHealth object with current status
346
+ """
347
+ try:
348
+ # Use enhanced health check if enabled
349
+ if self._enable_enhanced and hasattr(self, '_enhanced_health_check'):
350
+ return await self._enhanced_health_check()
351
+
352
+ # Basic health check
353
+ checks = {}
354
+
355
+ # Basic running check
356
+ checks["running"] = self._running
357
+
358
+ # Custom health checks
359
+ custom_checks = await self._health_check()
360
+ checks.update(custom_checks)
361
+
362
+ # Determine overall status
363
+ if not checks["running"]:
364
+ status = "unhealthy"
365
+ message = "Service is not running"
366
+ elif all(checks.values()):
367
+ status = "healthy"
368
+ message = "All health checks passed"
369
+ elif any(checks.values()):
370
+ status = "degraded"
371
+ message = "Some health checks failed"
372
+ else:
373
+ status = "unhealthy"
374
+ message = "Multiple health checks failed"
375
+
376
+ # Update health status
377
+ self._health = ServiceHealth(
378
+ status=status,
379
+ message=message,
380
+ timestamp=datetime.now().isoformat(),
381
+ checks=checks,
382
+ metrics={
383
+ "uptime": self.uptime,
384
+ "requests_total": self._metrics.requests_total,
385
+ "requests_failed": self._metrics.requests_failed,
386
+ },
387
+ )
388
+
389
+ return self._health
390
+
391
+ except Exception as e:
392
+ self.logger.error(f"Health check failed for {self.name}: {e}")
393
+ self._health = ServiceHealth(
394
+ status="unhealthy",
395
+ message=f"Health check error: {str(e)}",
396
+ timestamp=datetime.now().isoformat(),
397
+ checks={"health_check_error": True},
398
+ )
399
+ return self._health
400
+
401
+ def update_metrics(self, **kwargs) -> None:
402
+ """Update service metrics."""
403
+ for key, value in kwargs.items():
404
+ if hasattr(self._metrics, key):
405
+ setattr(self._metrics, key, value)
406
+ else:
407
+ self._metrics.custom_metrics[key] = value
408
+
409
+ def get_config(self, key: str, default: Any = None) -> Any:
410
+ """Get configuration value."""
411
+ return self.config.get(key, default)
412
+
413
+ def _setup_signal_handlers(self) -> None:
414
+ """Setup signal handlers for graceful shutdown."""
415
+ def signal_handler(signum, frame):
416
+ self.logger.info(f"Received signal {signum}, initiating graceful shutdown...")
417
+ asyncio.create_task(self.stop())
418
+
419
+ signal.signal(signal.SIGINT, signal_handler)
420
+ signal.signal(signal.SIGTERM, signal_handler)
421
+
422
+ async def _start_background_tasks(self) -> None:
423
+ """Start background tasks."""
424
+ # Health check task
425
+ if self.get_config("enable_health_monitoring", True):
426
+ interval = self.get_config("health_check_interval", 30)
427
+ task = asyncio.create_task(self._health_monitor_task(interval))
428
+ self._background_tasks.append(task)
429
+
430
+ # Metrics collection task
431
+ if self.get_config("enable_metrics", True):
432
+ interval = self.get_config("metrics_interval", 60)
433
+ task = asyncio.create_task(self._metrics_task(interval))
434
+ self._background_tasks.append(task)
435
+
436
+ # Custom background tasks
437
+ custom_tasks = await self._start_custom_tasks()
438
+ if custom_tasks:
439
+ self._background_tasks.extend(custom_tasks)
440
+
441
+ async def _health_monitor_task(self, interval: int) -> None:
442
+ """Background task for periodic health monitoring."""
443
+ while not self._stop_event.is_set():
444
+ try:
445
+ await self.health_check()
446
+ await asyncio.sleep(interval)
447
+ except asyncio.CancelledError:
448
+ break
449
+ except Exception as e:
450
+ self.logger.error(f"Health monitor task error: {e}")
451
+ await asyncio.sleep(interval)
452
+
453
+ async def _metrics_task(self, interval: int) -> None:
454
+ """Background task for metrics collection."""
455
+ while not self._stop_event.is_set():
456
+ try:
457
+ await self._collect_metrics()
458
+ await asyncio.sleep(interval)
459
+ except asyncio.CancelledError:
460
+ break
461
+ except Exception as e:
462
+ self.logger.error(f"Metrics task error: {e}")
463
+ await asyncio.sleep(interval)
464
+
465
+ async def _collect_metrics(self) -> None:
466
+ """Collect service metrics."""
467
+ try:
468
+ # Update uptime
469
+ if self.uptime:
470
+ self._metrics.uptime_seconds = int(self.uptime)
471
+
472
+ # Memory usage (basic implementation)
473
+ try:
474
+ import psutil
475
+ process = psutil.Process()
476
+ self._metrics.memory_usage_mb = process.memory_info().rss / 1024 / 1024
477
+ except ImportError:
478
+ pass
479
+
480
+ # Custom metrics collection
481
+ await self._collect_custom_metrics()
482
+
483
+ except Exception as e:
484
+ self.logger.warning(f"Failed to collect metrics: {e}")
485
+
486
+ # Enhanced features (conditionally included)
487
+
488
+ @asynccontextmanager
489
+ async def _service_operation(self, operation: str):
490
+ """Context manager for tracking service operations (enhanced feature)."""
491
+ if not self._enable_enhanced:
492
+ yield
493
+ return
494
+
495
+ request_id = f"{self.name}_{operation}_{time.time()}"
496
+ context = ServiceContext(
497
+ request_id=request_id,
498
+ start_time=time.time(),
499
+ service_name=self.name,
500
+ operation=operation
501
+ )
502
+
503
+ self._request_contexts[request_id] = context
504
+
505
+ try:
506
+ yield context
507
+
508
+ # Record success metrics
509
+ duration = time.time() - context.start_time
510
+ self._record_operation_metrics(operation, duration, success=True)
511
+
512
+ except Exception as e:
513
+ # Record failure metrics
514
+ duration = time.time() - context.start_time
515
+ self._record_operation_metrics(operation, duration, success=False)
516
+
517
+ # Update error counts
518
+ error_type = type(e).__name__
519
+ self._error_counts[error_type] = self._error_counts.get(error_type, 0) + 1
520
+
521
+ raise
522
+
523
+ finally:
524
+ # Cleanup context
525
+ self._request_contexts.pop(request_id, None)
526
+
527
+ def _record_operation_metrics(self, operation: str, duration: float, success: bool) -> None:
528
+ """Record operation performance metrics (enhanced feature)."""
529
+ if not self._enable_enhanced:
530
+ return
531
+
532
+ with self._lock:
533
+ # Update general metrics
534
+ self._metrics.requests_total += 1
535
+ if not success:
536
+ self._metrics.requests_failed += 1
537
+
538
+ # Update performance tracking
539
+ if operation not in self._performance_metrics:
540
+ self._performance_metrics[operation] = []
541
+
542
+ self._performance_metrics[operation].append(duration)
543
+
544
+ # Keep only recent metrics (last 100 operations)
545
+ if len(self._performance_metrics[operation]) > 100:
546
+ self._performance_metrics[operation] = self._performance_metrics[operation][-100:]
547
+
548
+ # Calculate average
549
+ all_times = []
550
+ for times in self._performance_metrics.values():
551
+ all_times.extend(times)
552
+
553
+ if all_times:
554
+ self._metrics.response_time_avg = sum(all_times) / len(all_times)
555
+
556
+ async def _enhanced_health_check(self) -> ServiceHealth:
557
+ """Enhanced health check with circuit breaker (enhanced feature)."""
558
+ async with self._service_operation("health_check"):
559
+ # Check circuit breaker state
560
+ if self._circuit_breaker.state == "open":
561
+ if self._should_attempt_circuit_recovery():
562
+ self._circuit_breaker.state = "half_open"
563
+ self.logger.info(f"Circuit breaker for {self.name} moved to half-open")
564
+ else:
565
+ return ServiceHealth(
566
+ status="degraded",
567
+ message="Service circuit breaker is open",
568
+ timestamp=datetime.now().isoformat(),
569
+ checks={"circuit_breaker": False},
570
+ metrics=self._get_health_metrics()
571
+ )
572
+
573
+ # Perform standard health check
574
+ health = await self.health_check()
575
+
576
+ # Update circuit breaker
577
+ if health.status in ["healthy", "degraded"]:
578
+ self._record_circuit_success()
579
+ else:
580
+ self._record_circuit_failure()
581
+
582
+ return health
583
+
584
+ def _should_attempt_circuit_recovery(self) -> bool:
585
+ """Check if circuit breaker should attempt recovery."""
586
+ if not self._enable_enhanced or self._circuit_breaker.last_failure_time is None:
587
+ return True
588
+
589
+ time_since_failure = time.time() - self._circuit_breaker.last_failure_time
590
+ return time_since_failure >= self._circuit_breaker.timeout_seconds
591
+
592
+ def _record_circuit_failure(self) -> None:
593
+ """Record circuit breaker failure."""
594
+ if not self._enable_enhanced:
595
+ return
596
+
597
+ self._circuit_breaker.failure_count += 1
598
+ self._circuit_breaker.last_failure_time = time.time()
599
+
600
+ if self._circuit_breaker.failure_count >= self._circuit_breaker.failure_threshold:
601
+ if self._circuit_breaker.state != "open":
602
+ self._circuit_breaker.state = "open"
603
+ self.logger.warning(f"Circuit breaker opened for service {self.name}")
604
+
605
+ def _record_circuit_success(self) -> None:
606
+ """Record circuit breaker success."""
607
+ if not self._enable_enhanced:
608
+ return
609
+
610
+ if self._circuit_breaker.state == "half_open":
611
+ self._circuit_breaker.failure_count = max(0, self._circuit_breaker.failure_count - 1)
612
+ if self._circuit_breaker.failure_count == 0:
613
+ self._circuit_breaker.state = "closed"
614
+ self.logger.info(f"Circuit breaker closed for service {self.name}")
615
+ elif self._circuit_breaker.state == "closed":
616
+ self._circuit_breaker.failure_count = max(0, self._circuit_breaker.failure_count - 1)
617
+
618
+ def _get_health_metrics(self) -> Dict[str, Any]:
619
+ """Get metrics for health status."""
620
+ base_metrics = {
621
+ "uptime_seconds": int(self.uptime) if self.uptime else 0,
622
+ "requests_total": self._metrics.requests_total,
623
+ "requests_failed": self._metrics.requests_failed,
624
+ "response_time_avg": self._metrics.response_time_avg,
625
+ }
626
+
627
+ if self._enable_enhanced:
628
+ base_metrics.update({
629
+ "circuit_breaker_state": self._circuit_breaker.state,
630
+ "error_counts": self._error_counts.copy()
631
+ })
632
+
633
+ return base_metrics
634
+
635
+ async def _handle_error(self, error: Exception, context: Dict[str, Any]) -> None:
636
+ """Handle service errors with context (enhanced feature)."""
637
+ if not self._enable_enhanced:
638
+ return
639
+
640
+ try:
641
+ if hasattr(self, '_error_handler') and self._error_handler:
642
+ await self._error_handler.handle_error(error, context)
643
+ else:
644
+ # Default error handling
645
+ self.logger.error(
646
+ f"Service error in {self.name}: {error}",
647
+ extra={
648
+ "service": self.name,
649
+ "context": context,
650
+ "traceback": traceback.format_exc()
651
+ }
652
+ )
653
+ except Exception as handler_error:
654
+ self.logger.error(f"Error handler failed: {handler_error}")
655
+
656
+ def _register_service_dependencies(self) -> None:
657
+ """Register service dependencies (enhanced feature)."""
658
+ if not self._container:
659
+ return
660
+
661
+ try:
662
+ # Get optional dependencies from container
663
+ # This is a simplified version - real implementation would use interfaces
664
+ if hasattr(self._container, 'get_service'):
665
+ self._health_monitor = self._container.get_service('IHealthMonitor', None)
666
+ self._error_handler = self._container.get_service('IErrorHandler', None)
667
+ self._cache = self._container.get_service('ICacheService', None)
668
+ except Exception as e:
669
+ self.logger.warning(f"Failed to register dependencies for {self.name}: {e}")
670
+
671
+ async def _initialize_dependencies(self) -> None:
672
+ """Initialize service dependencies (enhanced feature)."""
673
+ # Override in subclasses for specific dependency setup
674
+ pass
675
+
676
+ async def _register_with_health_monitor(self) -> None:
677
+ """Register service with health monitor (enhanced feature)."""
678
+ if self._enable_enhanced and self._health_monitor:
679
+ try:
680
+ await self._health_monitor.register_service(self)
681
+ except Exception as e:
682
+ self.logger.warning(f"Failed to register with health monitor: {e}")
683
+
684
+ async def _unregister_from_health_monitor(self) -> None:
685
+ """Unregister service from health monitor (enhanced feature)."""
686
+ if self._enable_enhanced and self._health_monitor:
687
+ try:
688
+ await self._health_monitor.unregister_service(self.name)
689
+ except Exception as e:
690
+ self.logger.warning(f"Failed to unregister from health monitor: {e}")
691
+
692
+ # Abstract methods to be implemented by subclasses
693
+
694
+ @abstractmethod
695
+ async def _initialize(self) -> None:
696
+ """Initialize the service. Must be implemented by subclasses."""
697
+ pass
698
+
699
+ @abstractmethod
700
+ async def _cleanup(self) -> None:
701
+ """Cleanup service resources. Must be implemented by subclasses."""
702
+ pass
703
+
704
+ async def _health_check(self) -> Dict[str, bool]:
705
+ """
706
+ Perform custom health checks. Override in subclasses.
707
+
708
+ Returns:
709
+ Dictionary of check name -> success boolean
710
+ """
711
+ return {}
712
+
713
+ async def _start_custom_tasks(self) -> Optional[List[asyncio.Task]]:
714
+ """
715
+ Start custom background tasks. Override in subclasses.
716
+
717
+ Returns:
718
+ List of asyncio tasks or None
719
+ """
720
+ return None
721
+
722
+ async def _collect_custom_metrics(self) -> None:
723
+ """Collect custom metrics. Override in subclasses."""
724
+ pass
725
+
726
+ # Utility methods
727
+
728
+ async def run_forever(self) -> None:
729
+ """Run the service until stopped."""
730
+ await self.start()
731
+ try:
732
+ # Wait for stop signal
733
+ while self._running:
734
+ await asyncio.sleep(1)
735
+ except KeyboardInterrupt:
736
+ self.logger.info("Received keyboard interrupt")
737
+ finally:
738
+ await self.stop()
739
+
740
+ def __repr__(self) -> str:
741
+ """String representation of the service."""
742
+ enhanced_str = " (enhanced)" if self._enable_enhanced else ""
743
+ return f"<{self.__class__.__name__}(name='{self.name}', running={self._running}{enhanced_str})>"
744
+
745
+
746
+ # For backwards compatibility, create an alias
747
+ EnhancedBaseService = BaseService