# services/external/app/main.py """ External Service Main Application """ from fastapi import FastAPI from sqlalchemy import text from app.core.config import settings from app.core.database import database_manager from app.services.messaging import setup_messaging, cleanup_messaging from shared.service_base import StandardFastAPIService # Include routers from app.api import weather_data, traffic_data, external_operations class ExternalService(StandardFastAPIService): """External Data Service with standardized setup""" expected_migration_version = "00001" async def on_startup(self, app): """Custom startup logic including migration verification""" await self.verify_migrations() await super().on_startup(app) async def verify_migrations(self): """Verify database schema matches the latest migrations.""" try: async with self.database_manager.get_session() as session: result = await session.execute(text("SELECT version_num FROM alembic_version")) version = result.scalar() if version != self.expected_migration_version: self.logger.error(f"Migration version mismatch: expected {self.expected_migration_version}, got {version}") raise RuntimeError(f"Migration version mismatch: expected {self.expected_migration_version}, got {version}") self.logger.info(f"Migration verification successful: {version}") except Exception as e: self.logger.error(f"Migration verification failed: {e}") raise def __init__(self): # Define expected database tables for health checks external_expected_tables = [ 'weather_data', 'weather_forecasts', 'traffic_data', 'traffic_measurement_points', 'traffic_background_jobs' ] # Define custom API checks async def check_weather_api(): """Check weather API configuration""" try: return bool(settings.AEMET_API_KEY) except Exception as e: self.logger.error("Weather API check failed", error=str(e)) return False async def check_traffic_api(): """Check traffic API configuration""" try: return bool(settings.MADRID_OPENDATA_API_KEY) except Exception as e: self.logger.error("Traffic API check failed", error=str(e)) return False # Define custom metrics for external service external_custom_metrics = { "weather_api_calls_total": { "type": "counter", "description": "Total weather API calls" }, "weather_api_success_total": { "type": "counter", "description": "Successful weather API calls" }, "weather_api_failures_total": { "type": "counter", "description": "Failed weather API calls" }, "traffic_api_calls_total": { "type": "counter", "description": "Total traffic API calls" }, "traffic_api_success_total": { "type": "counter", "description": "Successful traffic API calls" }, "traffic_api_failures_total": { "type": "counter", "description": "Failed traffic API calls" }, "data_collection_jobs_total": { "type": "counter", "description": "Data collection jobs" }, "data_records_stored_total": { "type": "counter", "description": "Data records stored" }, "data_quality_issues_total": { "type": "counter", "description": "Data quality issues detected" }, "weather_api_duration_seconds": { "type": "histogram", "description": "Weather API call duration" }, "traffic_api_duration_seconds": { "type": "histogram", "description": "Traffic API call duration" }, "data_collection_duration_seconds": { "type": "histogram", "description": "Data collection job duration" }, "data_processing_duration_seconds": { "type": "histogram", "description": "Data processing duration" } } super().__init__( service_name="external-service", app_name="Bakery External Data Service", description="External data collection service for weather, traffic, and events data", version="1.0.0", log_level=settings.LOG_LEVEL, cors_origins=settings.CORS_ORIGINS, api_prefix="", # Empty because RouteBuilder already includes /api/v1 database_manager=database_manager, expected_tables=external_expected_tables, custom_health_checks={ "weather_api": check_weather_api, "traffic_api": check_traffic_api }, custom_metrics=external_custom_metrics, enable_messaging=True ) async def _setup_messaging(self): """Setup messaging for external service""" await setup_messaging() self.logger.info("External service messaging initialized") async def _cleanup_messaging(self): """Cleanup messaging for external service""" await cleanup_messaging() async def on_startup(self, app: FastAPI): """Custom startup logic for external service""" pass async def on_shutdown(self, app: FastAPI): """Custom shutdown logic for external service""" # Database cleanup is handled by the base class pass def get_service_features(self): """Return external-specific features""" return [ "weather_data_collection", "traffic_data_collection", "aemet_integration", "madrid_opendata_integration", "data_quality_monitoring", "scheduled_collection_jobs", "external_api_monitoring" ] # Create service instance service = ExternalService() # Create FastAPI app with standardized setup app = service.create_app() # Setup standard endpoints service.setup_standard_endpoints() # Include routers service.add_router(weather_data.router) service.add_router(traffic_data.router) service.add_router(external_operations.router)