import json import os import pathlib import logging.config import logging.handlers import aioboto3 import contextlib from contextlib import asynccontextmanager from collections import defaultdict from typing import List from http import HTTPStatus import httpx import whisper from fastapi import FastAPI, Request from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from fastapi.middleware import Middleware from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import nltk from dotenv import load_dotenv from starlette import status from app.api import router from app.configs import config_di from app.exceptions import CustomException from app.middlewares import AuthenticationMiddleware, AuthBackend load_dotenv() @asynccontextmanager async def lifespan(_app: FastAPI): """ Startup and Shutdown logic is in this lifespan method https://fastapi.tiangolo.com/advanced/events/ """ # Whisper model whisper_model = whisper.load_model("base") # NLTK required datasets download nltk.download('words') nltk.download("punkt") # AWS Polly client instantiation context_stack = contextlib.AsyncExitStack() session = aioboto3.Session() polly_client = await context_stack.enter_async_context( session.client( 'polly', region_name='eu-west-1', aws_secret_access_key=os.getenv("AWS_ACCESS_KEY_ID"), aws_access_key_id=os.getenv("AWS_SECRET_ACCESS_KEY") ) ) # HTTP Client http_client = httpx.AsyncClient() config_di( polly_client=polly_client, http_client=http_client, whisper_model=whisper_model ) # Setup logging config_file = pathlib.Path("./app/configs/logging/logging_config.json") with open(config_file) as f_in: config = json.load(f_in) logging.config.dictConfig(config) yield await http_client.aclose() await polly_client.close() await context_stack.aclose() def setup_listeners(_app: FastAPI) -> None: @_app.exception_handler(RequestValidationError) async def custom_form_validation_error(request, exc): """ Don't delete request param """ reformatted_message = defaultdict(list) for pydantic_error in exc.errors(): loc, msg = pydantic_error["loc"], pydantic_error["msg"] filtered_loc = loc[1:] if loc[0] in ("body", "query", "path") else loc field_string = ".".join(filtered_loc) if field_string == "cookie.refresh_token": return JSONResponse( status_code=401, content={"error_code": 401, "message": HTTPStatus.UNAUTHORIZED.description}, ) reformatted_message[field_string].append(msg) return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, content=jsonable_encoder( {"details": "Invalid request!", "errors": reformatted_message} ), ) @_app.exception_handler(CustomException) async def custom_exception_handler(request: Request, exc: CustomException): """ Don't delete request param """ return JSONResponse( status_code=exc.code, content={"error_code": exc.error_code, "message": exc.message}, ) @_app.exception_handler(Exception) async def default_exception_handler(request: Request, exc: Exception): """ Don't delete request param """ return JSONResponse( status_code=500, content=str(exc), ) def setup_middleware() -> List[Middleware]: middleware = [ Middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ), Middleware( AuthenticationMiddleware, backend=AuthBackend() ) ] return middleware def create_app() -> FastAPI: env = os.getenv("ENV") _app = FastAPI( docs_url="/docs" if env != "prod" else None, redoc_url="/redoc" if env != "prod" else None, middleware=setup_middleware(), lifespan=lifespan ) _app.include_router(router) setup_listeners(_app) return _app app = create_app()