from collections.abc import AsyncIterator, Iterator from typing import NewType import boto3 import jinja2 import sqlalchemy from anydi import Container, Module, Provider, provider from cachelib import BaseCache as Cache, NullCache, RedisCache, SimpleCache from confluent_kafka import Producer from fansifter_common import context from fansifter_common.adapters.db import jinja2sql_filters from fansifter_common.adapters.encrypter import Encrypter from fansifter_common.adapters.graphql_router import GraphqlRouterClient from fansifter_common.adapters.ows_account import OwsAccountClient from fansifter_common.adapters.ows_pdp import OwsPdpClient from fansifter_common.adapters.twilio import TwilioClient from fansifter_common.artist.validators import ArtistValidator from fansifter_common.auth.account import Account, AccountAccess from fansifter_common.auth.authorization import ( AccessAuthorizationBackendStub, AuthorizationBackend, PdpAuthorizationBackend, ) from fansifter_common.auth.services import AuthService from fansifter_common.auth.validators import AccountValidator from fansifter_common.encrypter import ( Encrypter as PreferenceCenterEncrypter, JWTEncrypter, ) from fansifter_common.utils.functional import lazy_proxy from jinja2sql import Jinja2SQL from jwtauth import JWTAuth from jwtauth.utils import get_default_audience, get_default_issuer, get_default_jwks_url from mypy_boto3_dynamodb.service_resource import DynamoDBServiceResource from owsclient import OwsClient from ows_text_campaigns.adapters.aws.kms import KMSClient from ows_text_campaigns.adapters.aws.s3 import S3Client from ows_text_campaigns.adapters.aws.sts import STSClient from ows_text_campaigns.adapters.db import DB, PgDB from ows_text_campaigns.adapters.dynamodb import DynamoDBClient from ows_text_campaigns.adapters.encrypter import KMSEncrypter from ows_text_campaigns.adapters.kafka import KafkaClient from ows_text_campaigns.adapters.ows_url_shortener import OwsUrlShortenerClient from ows_text_campaigns.adapters.scanner import ContentScanner from ows_text_campaigns.config import Settings, settings PreferenceCenterProfileUrlTemplate = NewType("PreferenceCenterProfileUrlTemplate", str) UrlShortenerDomain = NewType("UrlShortenerDomain", str) class AppModule(Module): @provider(scope="singleton") def aws_session(self, settings: Settings) -> boto3.Session: return boto3.Session(region_name=settings.aws_region_name) @provider(scope="singleton") def s3_client(self, settings: Settings, session: boto3.Session) -> S3Client: return S3Client(region_name=settings.aws_region_name, session=session) @provider(scope="singleton") def sts_client(self, settings: Settings, session: boto3.Session) -> STSClient: return STSClient(region_name=settings.aws_region_name, session=session) @provider(scope="singleton") def kms_client(self, settings: Settings, session: boto3.Session) -> KMSClient: return KMSClient( region_name=settings.aws_region_name, key_id=settings.kms_key_id, session=session, ) @provider(scope="singleton") def dynamodb_resource(self, settings: Settings) -> DynamoDBServiceResource: return boto3.resource("dynamodb", region_name=settings.aws_region_name) @provider(scope="singleton") def jwt_auth(self, settings: Settings) -> JWTAuth: return JWTAuth( jwks_url=get_default_jwks_url(settings.environment), audience=get_default_audience(settings.environment), issuer=get_default_issuer(settings.environment), ) @provider(scope="singleton") def cache(self, settings: Settings) -> Cache: if settings.cache_backend in ["locmem", "memory"]: return SimpleCache(default_timeout=settings.cache_default_timeout) elif settings.cache_backend == "redis": return RedisCache( host=settings.redis_host, port=settings.redis_port, db=settings.cache_redis_db, ssl=settings.redis_ssl, default_timeout=settings.cache_default_timeout, key_prefix=settings.cache_key_prefix, ) return NullCache() @provider(scope="singleton") def kafka_producer(self, settings: Settings) -> Producer: producer = Producer( { "bootstrap.servers": settings.kafka_bootstrap_servers, "security.protocol": "ssl" if settings.kafka_use_ssl else "plaintext", } ) return producer @provider(scope="singleton") def kafka_client(self, settings: Settings, kafka_producer: Producer) -> KafkaClient: return KafkaClient( producer=kafka_producer, kafka_event_topic=settings.kafka_event_topic, kafka_status_update_topic=settings.kafka_status_update_topic, ) @provider(scope="singleton") def dynamodb_client( self, settings: Settings, dynamodb_resource: DynamoDBServiceResource, ) -> DynamoDBClient: return DynamoDBClient( db=dynamodb_resource, tablename=settings.fan_states_dynamodb_tablename, ) @provider(scope="singleton") def ows_client(self, settings: Settings) -> OwsClient: return OwsClient( environment=settings.environment, service_name=settings.service_name, request_context_getter=context.get_request_context, correlation_id_getter=context.get_correlation_id, ) @provider(scope="singleton") def ows_pdp_client(self, ows_client: OwsClient) -> OwsPdpClient: return OwsPdpClient(ows_client=ows_client) @provider(scope="singleton") def ows_account_client(self, ows_client: OwsClient) -> OwsAccountClient: return OwsAccountClient(ows_client=ows_client) @provider(scope="singleton") def ows_url_shortener_client(self, ows_client: OwsClient) -> OwsUrlShortenerClient: return OwsUrlShortenerClient(ows_client=ows_client) @provider(scope="singleton") def twilio_client(self, settings: Settings) -> Iterator[TwilioClient]: with TwilioClient( account_sid=settings.twilio_account_sid, auth_token=settings.twilio_auth_token, ) as client: yield client @provider(scope="singleton") def graphql_router_client(self, ows_client: OwsClient) -> GraphqlRouterClient: return GraphqlRouterClient(ows_client=ows_client) @provider(scope="singleton") def jinja2sql(self, settings: Settings) -> Jinja2SQL: env = jinja2.Environment( loader=jinja2.FileSystemLoader(settings.jinja2sql_template_searchpath) ) jinja2sql = Jinja2SQL(env) jinja2sql.env.globals.update( # type: ignore { "settings": settings, } ) # Register filters jinja2sql.register_filter("escape_like", jinja2sql_filters.escape_like) jinja2sql.register_filter( "orderby", jinja2sql_filters.orderby_filter, bind=True ) return jinja2sql @provider(scope="singleton") def db(self, settings: Settings, jinja2sql: Jinja2SQL) -> Iterator[DB]: with DB( url=settings.snowflake_url, engine_args={ "echo": settings.snowflake_echo, "poolclass": sqlalchemy.pool.QueuePool, "pool_size": settings.snowflake_pool_size, "max_overflow": settings.snowflake_pool_max_overflow, "pool_recycle": settings.snowflake_pool_recycle, "pool_pre_ping": settings.snowflake_pool_pre_ping, "pool_reset_on_return": settings.snowflake_pool_reset_on_return, "connect_args": settings.snowflake_connect_args, }, jinja2sql=jinja2sql, ) as db: yield db @provider(scope="request") async def db_session_factory(self, db: DB) -> AsyncIterator[None]: with db.session_factory(): yield @provider(scope="singleton") def pg_db(self, settings: Settings, jinja2sql: Jinja2SQL) -> Iterator[PgDB]: with PgDB( url=settings.postgres_url, engine_args=settings.postgres_engine_args, jinja2sql=jinja2sql, ) as db: yield db @provider(scope="request") async def pg_db_session_factory(self, db: PgDB) -> AsyncIterator[None]: with db.session_factory(): yield @provider(scope="singleton") def content_scanner(self, db: DB, settings: Settings) -> ContentScanner: return ContentScanner( db=db, llm_model=settings.content_scanner_llm_model, llm_options=settings.content_scanner_llm_options, llm_timeout=settings.content_scanner_llm_timeout, ) @provider(scope="singleton") def authorization_backend( self, ows_pdp_client: OwsPdpClient, ows_account_client: OwsAccountClient, cache: Cache, settings: Settings, ) -> AuthorizationBackend: # Local/dev environment only if settings.auth_allow_full_access: return AccessAuthorizationBackendStub( AccountAccess( accounts=[ Account(vendor_id=7123, subaccount_id=0), Account(vendor_id=34514, subaccount_id=0), ] ) ) return PdpAuthorizationBackend( ows_pdp_client=ows_pdp_client, ows_account_client=ows_account_client, cache=cache, cache_timeout=settings.account_access_cache_timeout, ) @provider(scope="singleton") def auth_service(self, authorization_backend: AuthorizationBackend) -> AuthService: return AuthService(authorization_backend=authorization_backend) @provider(scope="singleton") def account_validator( self, ows_account_client: OwsAccountClient ) -> AccountValidator: return AccountValidator(ows_account_client=ows_account_client) @provider(scope="singleton") def artist_validator( self, graphql_router_client: GraphqlRouterClient ) -> ArtistValidator: return ArtistValidator(graphql_router_client=graphql_router_client) @provider(scope="singleton") def encrypter(self, kms_client: KMSClient) -> Encrypter: return KMSEncrypter(kms_client=kms_client) @provider(scope="singleton") def preference_center_encrypter( self, settings: Settings ) -> PreferenceCenterEncrypter: return JWTEncrypter( key=settings.preference_center_secret_key, ) @provider(scope="singleton") def preference_center_profile_url_template( self, settings: Settings ) -> PreferenceCenterProfileUrlTemplate: return PreferenceCenterProfileUrlTemplate( settings.preference_center_profile_url_template ) @provider(scope="singleton") def url_shortener_domain(self, settings: Settings) -> UrlShortenerDomain: return UrlShortenerDomain(settings.url_shortener_domain) def setup_container() -> Container: """Configure the application.""" return Container( providers=[ Provider(Settings, factory=lambda: settings, scope="singleton"), ], modules=[AppModule], ) # Lazy container proxy container = lazy_proxy(setup_container)