import argparse import asyncio import logging import os import sys from collections.abc import AsyncIterator from contextlib import asynccontextmanager from pathlib import Path from typing import Final import firebase_admin # type: ignore[import-untyped] import uvicorn from dishka import AsyncContainer from dishka.integrations.fastapi import setup_dishka from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from firebase_admin import credentials from prometheus_fastapi_instrumentator import Instrumentator from template_project.web_api.configuration import Configuration, load_configuration from template_project.web_api.ioc.make import make_ioc from template_project.web_api.routes import auth, healthcheck, notification, profile, storage LOG_CONFIG: Final = { "version": 1, "disable_existing_loggers": False, "formatters": { "default": { "format": "%(asctime)s [%(levelname)s] [%(name)s] %(message)s", }, }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "default", }, }, "root": { "level": "DEBUG", "handlers": ["console"], }, } @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: configuration: Configuration = await app.state.dishka_container.get(Configuration) cred = credentials.Certificate(configuration.firebase.certificate_path) firebase_admin.initialize_app(cred) yield await app.state.dishka_container.close() def make_asgi_application( ioc: AsyncContainer, ) -> FastAPI: app = FastAPI( lifespan=lifespan, docs_url="/docs", title="Template project", description="Template project API", version="1.0.0", openapi_url="/openapi.json", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(auth.router) app.include_router(healthcheck.router) app.include_router(profile.router) app.include_router(notification.router) app.include_router(storage.router) Instrumentator().instrument(app).expose(app) setup_dishka(container=ioc, app=app) return app async def _main( configuration_path: Path, ) -> None: logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpcore").setLevel(logging.WARNING) configuration = load_configuration(configuration_path) ioc = make_ioc(configuration) asgi_application = make_asgi_application(ioc) config = uvicorn.Config( app=asgi_application, host=configuration.server.host, port=configuration.server.port, log_config=LOG_CONFIG, access_log=configuration.server.access_log, ) server = uvicorn.Server(config) await server.serve() def main() -> None: if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) arg_parser = argparse.ArgumentParser() arg_parser.add_argument("configuration", default=None) args = arg_parser.parse_args() configuration_path = args.configuration or os.getenv("CONFIGURATION_PATH") if configuration_path is None: raise RuntimeError( "pass the path to the config or specify it in the environment variables `CONFIGURATION_PATH`", ) asyncio.run(_main(Path(configuration_path))) if __name__ == "__main__": main()