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.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base service class for Claude PM Framework services.
|
|
3
|
+
|
|
4
|
+
Provides common functionality including:
|
|
5
|
+
- Lifecycle management (start, stop, health checks)
|
|
6
|
+
- Configuration management
|
|
7
|
+
- Logging
|
|
8
|
+
- Metrics collection
|
|
9
|
+
- Error handling and retry logic
|
|
10
|
+
- Service discovery and registration
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import logging
|
|
15
|
+
import signal
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from typing import Any, Dict, List, Optional, Union
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
import json
|
|
24
|
+
|
|
25
|
+
from .config import Config
|
|
26
|
+
from .logger import setup_logging
|
|
27
|
+
from .mixins import LoggerMixin
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ServiceHealth:
|
|
32
|
+
"""Service health status information."""
|
|
33
|
+
|
|
34
|
+
status: str # healthy, degraded, unhealthy, unknown
|
|
35
|
+
message: str
|
|
36
|
+
timestamp: str
|
|
37
|
+
metrics: Dict[str, Any] = field(default_factory=dict)
|
|
38
|
+
checks: Dict[str, bool] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ServiceMetrics:
|
|
43
|
+
"""Service metrics collection."""
|
|
44
|
+
|
|
45
|
+
requests_total: int = 0
|
|
46
|
+
requests_failed: int = 0
|
|
47
|
+
response_time_avg: float = 0.0
|
|
48
|
+
uptime_seconds: int = 0
|
|
49
|
+
memory_usage_mb: float = 0.0
|
|
50
|
+
custom_metrics: Dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BaseService(LoggerMixin, ABC):
|
|
54
|
+
"""
|
|
55
|
+
Abstract base class for all Claude PM services.
|
|
56
|
+
|
|
57
|
+
Provides common infrastructure for service lifecycle management,
|
|
58
|
+
health monitoring, configuration, logging, and error handling.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self, name: str, config: Optional[Dict[str, Any]] = None, config_path: Optional[Path] = None
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Initialize the base service.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: Service name for identification
|
|
69
|
+
config: Optional configuration dictionary
|
|
70
|
+
config_path: Optional path to configuration file
|
|
71
|
+
"""
|
|
72
|
+
self.name = name
|
|
73
|
+
self.config = Config(config or {}, config_path)
|
|
74
|
+
|
|
75
|
+
# Set custom logger name based on service name
|
|
76
|
+
self._logger_name = f"{self.__class__.__module__}.{self.__class__.__name__}.{name}"
|
|
77
|
+
|
|
78
|
+
# Service state
|
|
79
|
+
self._running = False
|
|
80
|
+
self._start_time: Optional[datetime] = None
|
|
81
|
+
self._stop_event = asyncio.Event()
|
|
82
|
+
|
|
83
|
+
# Health and metrics
|
|
84
|
+
self._health = ServiceHealth(
|
|
85
|
+
status="unknown", message="Service not started", timestamp=datetime.now().isoformat()
|
|
86
|
+
)
|
|
87
|
+
self._metrics = ServiceMetrics()
|
|
88
|
+
|
|
89
|
+
# Background tasks
|
|
90
|
+
self._background_tasks: List[asyncio.Task] = []
|
|
91
|
+
|
|
92
|
+
# Check for quiet mode environment variable
|
|
93
|
+
default_log_level = "INFO"
|
|
94
|
+
if os.getenv('CLAUDE_PM_QUIET_MODE') == 'true':
|
|
95
|
+
default_log_level = "WARNING"
|
|
96
|
+
# Set logger level if needed
|
|
97
|
+
self.logger.setLevel(logging.WARNING)
|
|
98
|
+
|
|
99
|
+
# Only log if not in quiet mode
|
|
100
|
+
if not os.environ.get('CLAUDE_PM_QUIET_MODE', '').lower() == 'true':
|
|
101
|
+
self.logger.info(f"Initialized {self.name} service")
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def running(self) -> bool:
|
|
105
|
+
"""Check if service is currently running."""
|
|
106
|
+
return self._running
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def uptime(self) -> Optional[float]:
|
|
110
|
+
"""Get service uptime in seconds."""
|
|
111
|
+
if self._start_time and self._running:
|
|
112
|
+
return (datetime.now() - self._start_time).total_seconds()
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def health(self) -> ServiceHealth:
|
|
117
|
+
"""Get current service health status."""
|
|
118
|
+
return self._health
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def metrics(self) -> ServiceMetrics:
|
|
122
|
+
"""Get current service metrics."""
|
|
123
|
+
if self.uptime:
|
|
124
|
+
self._metrics.uptime_seconds = int(self.uptime)
|
|
125
|
+
return self._metrics
|
|
126
|
+
|
|
127
|
+
async def start(self) -> None:
|
|
128
|
+
"""Start the service."""
|
|
129
|
+
if self._running:
|
|
130
|
+
self.logger.warning(f"Service {self.name} is already running")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
self.logger.info(f"Starting {self.name} service...")
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
# Setup signal handlers
|
|
137
|
+
self._setup_signal_handlers()
|
|
138
|
+
|
|
139
|
+
# Initialize service
|
|
140
|
+
await self._initialize()
|
|
141
|
+
|
|
142
|
+
# Start background tasks
|
|
143
|
+
await self._start_background_tasks()
|
|
144
|
+
|
|
145
|
+
# Mark as running
|
|
146
|
+
self._running = True
|
|
147
|
+
self._start_time = datetime.now()
|
|
148
|
+
|
|
149
|
+
# Update health status
|
|
150
|
+
self._health = ServiceHealth(
|
|
151
|
+
status="healthy",
|
|
152
|
+
message="Service started successfully",
|
|
153
|
+
timestamp=datetime.now().isoformat(),
|
|
154
|
+
checks={"startup": True},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
self.logger.info(f"Service {self.name} started successfully")
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
self.logger.error(f"Failed to start service {self.name}: {e}")
|
|
161
|
+
self._health = ServiceHealth(
|
|
162
|
+
status="unhealthy",
|
|
163
|
+
message=f"Startup failed: {str(e)}",
|
|
164
|
+
timestamp=datetime.now().isoformat(),
|
|
165
|
+
checks={"startup": False},
|
|
166
|
+
)
|
|
167
|
+
raise
|
|
168
|
+
|
|
169
|
+
async def stop(self) -> None:
|
|
170
|
+
"""Stop the service gracefully."""
|
|
171
|
+
if not self._running:
|
|
172
|
+
self.logger.warning(f"Service {self.name} is not running")
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
self.logger.info(f"Stopping {self.name} service...")
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
# Signal stop to background tasks
|
|
179
|
+
self._stop_event.set()
|
|
180
|
+
|
|
181
|
+
# Cancel background tasks
|
|
182
|
+
for task in self._background_tasks:
|
|
183
|
+
if not task.done():
|
|
184
|
+
task.cancel()
|
|
185
|
+
|
|
186
|
+
# Wait for tasks to complete
|
|
187
|
+
if self._background_tasks:
|
|
188
|
+
await asyncio.gather(*self._background_tasks, return_exceptions=True)
|
|
189
|
+
|
|
190
|
+
# Cleanup service
|
|
191
|
+
await self._cleanup()
|
|
192
|
+
|
|
193
|
+
# Mark as stopped
|
|
194
|
+
self._running = False
|
|
195
|
+
|
|
196
|
+
# Update health status
|
|
197
|
+
self._health = ServiceHealth(
|
|
198
|
+
status="unknown",
|
|
199
|
+
message="Service stopped",
|
|
200
|
+
timestamp=datetime.now().isoformat(),
|
|
201
|
+
checks={"running": False},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self.logger.info(f"Service {self.name} stopped successfully")
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
self.logger.error(f"Error stopping service {self.name}: {e}")
|
|
208
|
+
raise
|
|
209
|
+
|
|
210
|
+
async def restart(self) -> None:
|
|
211
|
+
"""Restart the service."""
|
|
212
|
+
self.logger.info(f"Restarting {self.name} service...")
|
|
213
|
+
await self.stop()
|
|
214
|
+
await self.start()
|
|
215
|
+
|
|
216
|
+
async def health_check(self) -> ServiceHealth:
|
|
217
|
+
"""
|
|
218
|
+
Perform comprehensive health check.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
ServiceHealth object with current status
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
checks = {}
|
|
225
|
+
|
|
226
|
+
# Basic running check
|
|
227
|
+
checks["running"] = self._running
|
|
228
|
+
|
|
229
|
+
# Custom health checks
|
|
230
|
+
custom_checks = await self._health_check()
|
|
231
|
+
checks.update(custom_checks)
|
|
232
|
+
|
|
233
|
+
# Determine overall status
|
|
234
|
+
if not checks["running"]:
|
|
235
|
+
status = "unhealthy"
|
|
236
|
+
message = "Service is not running"
|
|
237
|
+
elif all(checks.values()):
|
|
238
|
+
status = "healthy"
|
|
239
|
+
message = "All health checks passed"
|
|
240
|
+
elif any(checks.values()):
|
|
241
|
+
status = "degraded"
|
|
242
|
+
message = "Some health checks failed"
|
|
243
|
+
else:
|
|
244
|
+
status = "unhealthy"
|
|
245
|
+
message = "Multiple health checks failed"
|
|
246
|
+
|
|
247
|
+
# Update health status
|
|
248
|
+
self._health = ServiceHealth(
|
|
249
|
+
status=status,
|
|
250
|
+
message=message,
|
|
251
|
+
timestamp=datetime.now().isoformat(),
|
|
252
|
+
checks=checks,
|
|
253
|
+
metrics={
|
|
254
|
+
"uptime": self.uptime,
|
|
255
|
+
"requests_total": self._metrics.requests_total,
|
|
256
|
+
"requests_failed": self._metrics.requests_failed,
|
|
257
|
+
},
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return self._health
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
self.logger.error(f"Health check failed for {self.name}: {e}")
|
|
264
|
+
self._health = ServiceHealth(
|
|
265
|
+
status="unhealthy",
|
|
266
|
+
message=f"Health check error: {str(e)}",
|
|
267
|
+
timestamp=datetime.now().isoformat(),
|
|
268
|
+
checks={"health_check_error": True},
|
|
269
|
+
)
|
|
270
|
+
return self._health
|
|
271
|
+
|
|
272
|
+
def update_metrics(self, **kwargs) -> None:
|
|
273
|
+
"""Update service metrics."""
|
|
274
|
+
for key, value in kwargs.items():
|
|
275
|
+
if hasattr(self._metrics, key):
|
|
276
|
+
setattr(self._metrics, key, value)
|
|
277
|
+
else:
|
|
278
|
+
self._metrics.custom_metrics[key] = value
|
|
279
|
+
|
|
280
|
+
def get_config(self, key: str, default: Any = None) -> Any:
|
|
281
|
+
"""Get configuration value."""
|
|
282
|
+
return self.config.get(key, default)
|
|
283
|
+
|
|
284
|
+
def _setup_signal_handlers(self) -> None:
|
|
285
|
+
"""Setup signal handlers for graceful shutdown."""
|
|
286
|
+
|
|
287
|
+
def signal_handler(signum, frame):
|
|
288
|
+
self.logger.info(f"Received signal {signum}, initiating graceful shutdown...")
|
|
289
|
+
asyncio.create_task(self.stop())
|
|
290
|
+
|
|
291
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
292
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
293
|
+
|
|
294
|
+
async def _start_background_tasks(self) -> None:
|
|
295
|
+
"""Start background tasks."""
|
|
296
|
+
# Health check task
|
|
297
|
+
if self.get_config("enable_health_monitoring", True):
|
|
298
|
+
interval = self.get_config("health_check_interval", 30)
|
|
299
|
+
task = asyncio.create_task(self._health_monitor_task(interval))
|
|
300
|
+
self._background_tasks.append(task)
|
|
301
|
+
|
|
302
|
+
# Metrics collection task
|
|
303
|
+
if self.get_config("enable_metrics", True):
|
|
304
|
+
interval = self.get_config("metrics_interval", 60)
|
|
305
|
+
task = asyncio.create_task(self._metrics_task(interval))
|
|
306
|
+
self._background_tasks.append(task)
|
|
307
|
+
|
|
308
|
+
# Custom background tasks
|
|
309
|
+
custom_tasks = await self._start_custom_tasks()
|
|
310
|
+
if custom_tasks:
|
|
311
|
+
self._background_tasks.extend(custom_tasks)
|
|
312
|
+
|
|
313
|
+
async def _health_monitor_task(self, interval: int) -> None:
|
|
314
|
+
"""Background task for periodic health monitoring."""
|
|
315
|
+
while not self._stop_event.is_set():
|
|
316
|
+
try:
|
|
317
|
+
await self.health_check()
|
|
318
|
+
await asyncio.sleep(interval)
|
|
319
|
+
except asyncio.CancelledError:
|
|
320
|
+
break
|
|
321
|
+
except Exception as e:
|
|
322
|
+
self.logger.error(f"Health monitor task error: {e}")
|
|
323
|
+
await asyncio.sleep(interval)
|
|
324
|
+
|
|
325
|
+
async def _metrics_task(self, interval: int) -> None:
|
|
326
|
+
"""Background task for metrics collection."""
|
|
327
|
+
while not self._stop_event.is_set():
|
|
328
|
+
try:
|
|
329
|
+
await self._collect_metrics()
|
|
330
|
+
await asyncio.sleep(interval)
|
|
331
|
+
except asyncio.CancelledError:
|
|
332
|
+
break
|
|
333
|
+
except Exception as e:
|
|
334
|
+
self.logger.error(f"Metrics task error: {e}")
|
|
335
|
+
await asyncio.sleep(interval)
|
|
336
|
+
|
|
337
|
+
async def _collect_metrics(self) -> None:
|
|
338
|
+
"""Collect service metrics."""
|
|
339
|
+
try:
|
|
340
|
+
# Update uptime
|
|
341
|
+
if self.uptime:
|
|
342
|
+
self._metrics.uptime_seconds = int(self.uptime)
|
|
343
|
+
|
|
344
|
+
# Memory usage (basic implementation)
|
|
345
|
+
import psutil
|
|
346
|
+
|
|
347
|
+
process = psutil.Process()
|
|
348
|
+
self._metrics.memory_usage_mb = process.memory_info().rss / 1024 / 1024
|
|
349
|
+
|
|
350
|
+
# Custom metrics collection
|
|
351
|
+
await self._collect_custom_metrics()
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
self.logger.warning(f"Failed to collect metrics: {e}")
|
|
355
|
+
|
|
356
|
+
# Abstract methods to be implemented by subclasses
|
|
357
|
+
|
|
358
|
+
@abstractmethod
|
|
359
|
+
async def _initialize(self) -> None:
|
|
360
|
+
"""Initialize the service. Must be implemented by subclasses."""
|
|
361
|
+
pass
|
|
362
|
+
|
|
363
|
+
@abstractmethod
|
|
364
|
+
async def _cleanup(self) -> None:
|
|
365
|
+
"""Cleanup service resources. Must be implemented by subclasses."""
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
async def _health_check(self) -> Dict[str, bool]:
|
|
369
|
+
"""
|
|
370
|
+
Perform custom health checks. Override in subclasses.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Dictionary of check name -> success boolean
|
|
374
|
+
"""
|
|
375
|
+
return {}
|
|
376
|
+
|
|
377
|
+
async def _start_custom_tasks(self) -> Optional[List[asyncio.Task]]:
|
|
378
|
+
"""
|
|
379
|
+
Start custom background tasks. Override in subclasses.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
List of asyncio tasks or None
|
|
383
|
+
"""
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
async def _collect_custom_metrics(self) -> None:
|
|
387
|
+
"""Collect custom metrics. Override in subclasses."""
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
# Utility methods
|
|
391
|
+
|
|
392
|
+
async def run_forever(self) -> None:
|
|
393
|
+
"""Run the service until stopped."""
|
|
394
|
+
await self.start()
|
|
395
|
+
try:
|
|
396
|
+
# Wait for stop signal
|
|
397
|
+
while self._running:
|
|
398
|
+
await asyncio.sleep(1)
|
|
399
|
+
except KeyboardInterrupt:
|
|
400
|
+
self.logger.info("Received keyboard interrupt")
|
|
401
|
+
finally:
|
|
402
|
+
await self.stop()
|
|
403
|
+
|
|
404
|
+
def __repr__(self) -> str:
|
|
405
|
+
"""String representation of the service."""
|
|
406
|
+
return f"<{self.__class__.__name__}(name='{self.name}', running={self._running})>"
|