"""Module to create shared external connections for product-staging endpoints.""" import logging from contextlib import asynccontextmanager from typing import Any, AsyncIterator, Dict, Optional import boto3 import spotipy from aioboto3 import Session from aiobotocore.config import AioConfig from fastapi import FastAPI, HTTPException from owsclient import OwsClient from owscontext import get_correlation_id, get_request_context from python_pdp_sdk.backends.authorization_backend import ( AuthorizationBackend, PdpAuthorizationBackend, ) from python_pdp_sdk.connectors.ows_pdp.ows_pdp import OwsPdpClient from types_aiobotocore_s3 import S3Client from product_staging import config from product_staging.connectors.features import SplitioClient, splitio_client_factory from product_staging.connectors.redis import RedisConnector from product_staging.connectors.spotify import create_spotify_client logger = logging.getLogger(__name__) S3_CLIENT_KEY = "S3_CLIENT" REDIS_CONNECTOR_KEY = "REDIS_CONNECTOR" SFN_CLIENT_KEY = "SFN_CLIENT" ECS_CLIENT_KEY = "ECS_CLIENT" EC2_CLIENT_KEY = "EC2_CLIENT" LAMBDA_CLIENT_KEY = "LAMBDA_CLIENT" SQS_CLIENT_KEY = "SQS_CLIENT" OWS_CLIENT_KEY = "OWS_CLIENT" AUTHORIZATION_BACKEND_KEY = "AUTHORIZATION_BACKEND" SPLITIO_CLIENT_KEY = "SPLITIO_CLIENT" SPOTIFY_CLIENT_KEY = "SPOTIFY_CLIENT" DATA_SOURCES: Dict[str, Any] = {} @asynccontextmanager async def datasources_lifespan( app: FastAPI | None, ) -> AsyncIterator[Dict[str, Any]]: s3_client_cm = Session().client( "s3", config=AioConfig(s3={"use_accelerate_endpoint": True}) ) s3_client = await s3_client_cm.__aenter__() logger.info("[lifespan] Initialized s3 client") DATA_SOURCES[S3_CLIENT_KEY] = s3_client redis_connector = RedisConnector( redis_host=config.REDIS_HOST, redis_port=config.REDIS_PORT, use_redis_cache=config.CACHE_USE_REDIS, ) logger.info("[lifespan] Initialized redis connector") DATA_SOURCES[REDIS_CONNECTOR_KEY] = redis_connector sfn_client = boto3.client("stepfunctions", region_name=config.AWS_REGION) logger.info("[lifespan] Initialized step functions client") DATA_SOURCES[SFN_CLIENT_KEY] = sfn_client lambda_client = boto3.client("lambda", region_name=config.AWS_REGION) logger.info("[lifespan] Initialized lambda client") DATA_SOURCES[LAMBDA_CLIENT_KEY] = lambda_client ecs_client = boto3.client("ecs", region_name=config.AWS_REGION) logger.info("[lifespan] Initialized ecs client") DATA_SOURCES[ECS_CLIENT_KEY] = ecs_client ec2_client = boto3.client("ec2", region_name=config.AWS_REGION) logger.info("[lifespan] Initialized ec2 client") DATA_SOURCES[EC2_CLIENT_KEY] = ec2_client sqs_client = boto3.client("sqs", region_name=config.AWS_REGION) logger.info("[lifespan] Initialized sqs client") DATA_SOURCES[SQS_CLIENT_KEY] = sqs_client ows_client = OwsClient( environment=config.ENVIRONMENT, service_name=config.SERVICE_NAME, correlation_id_getter=get_correlation_id, request_context_getter=get_request_context, ) logger.info("[lifespan] Initialized ows client") DATA_SOURCES[OWS_CLIENT_KEY] = ows_client authorization_backend = setup_authorization_backend(ows_client) logger.info("[lifespan] Initialized authorization backend") DATA_SOURCES[AUTHORIZATION_BACKEND_KEY] = authorization_backend splitio_client = splitio_client_factory() logger.info("[lifespan] Initialized splitio client") DATA_SOURCES[SPLITIO_CLIENT_KEY] = splitio_client if config.SPOTIFY_API_CLIENT_ID and config.SPOTIFY_API_CLIENT_SECRET: spotify_client = create_spotify_client() logger.info("[lifespan] Initialized spotify client") DATA_SOURCES[SPOTIFY_CLIENT_KEY] = spotify_client try: # Lines before 'yield' will be executed before the application starts. yield DATA_SOURCES # Lines after the 'yield' will be executed after the application has finished. await shutdown_s3_client(s3_client_cm) await shutdown_redis_connector(redis_connector=redis_connector) await shutdown_ecs_client(ecs_client) await shutdown_ec2_client(ec2_client) await shutdown_sfn_client(sfn_client) finally: DATA_SOURCES.clear() def setup_authorization_backend( ows_client: OwsClient, ) -> AuthorizationBackend: """Set up the authorization backend for the application.""" ows_pdp_client = OwsPdpClient(ows_client) return PdpAuthorizationBackend(ows_pdp_client) def get_s3_client() -> S3Client: """Dependency injector method for the s3 client.""" return DATA_SOURCES[S3_CLIENT_KEY] def get_lambda_client(): """Dependency injector method for the lambda client.""" return DATA_SOURCES[LAMBDA_CLIENT_KEY] def get_ecs_client(): """Dependency injector method for the ECS client.""" return DATA_SOURCES[ECS_CLIENT_KEY] def get_ec2_client(): """Dependency injector method for the EC2 client.""" return DATA_SOURCES[EC2_CLIENT_KEY] def get_redis_client() -> Optional[RedisConnector]: """Dependency injector method for the redis connector.""" return DATA_SOURCES.get(REDIS_CONNECTOR_KEY, None) def get_sfn_client(): """Dependency injector method for the sfn client.""" return DATA_SOURCES[SFN_CLIENT_KEY] def get_sqs_client(): """Dependency injector method for the sqs client.""" return DATA_SOURCES[SQS_CLIENT_KEY] def get_ows_client() -> OwsClient: """Dependency injector method for the ows client.""" return DATA_SOURCES[OWS_CLIENT_KEY] def get_authorization_backend() -> AuthorizationBackend: """Dependency injector method for the authorization backend.""" return DATA_SOURCES[AUTHORIZATION_BACKEND_KEY] def get_splitio_client() -> SplitioClient: """Dependency injector method for the splitio client.""" return DATA_SOURCES[SPLITIO_CLIENT_KEY] def get_spotify_client() -> spotipy.Spotify: """Dependency injector method for the spotify client.""" client = DATA_SOURCES.get(SPOTIFY_CLIENT_KEY) if client is None: raise HTTPException( status_code=503, detail="Spotify client is not configured.", ) return client async def shutdown_s3_client(s3_client_cm) -> None: """Shut down the s3 connection.""" try: await s3_client_cm.__aexit__(None, None, None) except Exception as ex: logger.warning("[lifespan] Failed to shut down s3 client: %s", str(ex)) else: logger.info("[lifespan] Closed s3 client") async def shutdown_ecs_client(ecs_client) -> None: """Shut down the ECS client.""" try: await ecs_client.close() except Exception as ex: logger.warning("[lifespan] Failed to shutdown ECS client: %s", str(ex)) else: logger.info("[lifespan] Closed ECS client") async def shutdown_ec2_client(ec2_client) -> None: """Shut down the EC2 client.""" try: await ec2_client.close() except Exception as ex: logger.warning("[lifespan] Failed to shutdown EC2 client: %s", str(ex)) else: logger.info("[lifespan] Closed EC2 client") async def shutdown_redis_connector(redis_connector: RedisConnector) -> None: """Shut down the Redis connector.""" try: await redis_connector.client.aclose() except Exception as ex: logger.warning("[lifespan] Failed to shutdown RedisConnector: %s", str(ex)) else: logger.info("[lifespan] Closed RedisConnector") async def shutdown_sfn_client(sfn_client) -> None: """Shut down the step functions client.""" try: await sfn_client.close() except Exception as ex: logger.warning( "[lifespan] Failed to shutdown stepfunctions client: %s", str(ex) ) else: logger.info("[lifespan] Closed stepfunctions client")