import asyncio import logging from collections.abc import AsyncIterator from contextlib import asynccontextmanager from fastapi import FastAPI from faststream.rabbit import Channel, RabbitBroker from app.api import router from app.config import settings from app.db import close_database, session_factory from app.messaging import declare_topology from app.outbox import OutboxPublisher logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) @asynccontextmanager async def lifespan(_: FastAPI) -> AsyncIterator[None]: broker = RabbitBroker( settings.rabbitmq_url, default_channel=Channel(publisher_confirms=True, on_return_raises=True), ) await broker.start() await declare_topology(broker) stop_event = asyncio.Event() publisher = OutboxPublisher( session_factory, broker, batch_size=settings.outbox_batch_size, poll_interval=settings.outbox_poll_interval, publish_timeout=settings.outbox_publish_timeout, ) publisher_task = asyncio.create_task(publisher.run(stop_event)) try: yield finally: stop_event.set() try: await asyncio.wait_for( publisher_task, timeout=settings.outbox_publish_timeout + settings.outbox_poll_interval, ) except TimeoutError: publisher_task.cancel() await asyncio.gather(publisher_task, return_exceptions=True) await broker.stop() await close_database() app = FastAPI(title=settings.app_name, version="1.0.0", lifespan=lifespan) app.include_router(router)