"""Synchronous conversion. Suitable for smaller documents. Anything that does not finish within SYNC_TIMEOUT_SECONDS is cancelled and the caller is pointed at /jobs. """ from __future__ import annotations import asyncio import logging from fastapi import APIRouter, Response from fastapi.responses import FileResponse from starlette.background import BackgroundTask from ..deps import get_container from ..errors import ConversionError, SyncTooLongError, error_from_code from ..models import ConvertRequest logger = logging.getLogger(__name__) router = APIRouter(tags=["konverze"]) CONVERT_DESCRIPTION = """ Prevede dokument a rovnou vrati `application/pdf`. Urceno pro mensi dokumenty. Konverze, ktera presahne `SYNC_TIMEOUT_SECONDS` (vychozi 60 s), se zrusi a sluzba vrati 413 s odkazem na POST /jobs. **Uchovavani souboru.** Vysledek se nikam neuklada. Vznikne v docasnem adresari, odesle se v teto odpovedi a hned po jejim odeslani se i s pracovnim adresarem smaze. Neexistuje zadna adresa, ze ktere by sel stahnout znovu, opakovane stazeni znamena novou konverzi. Pokud vysledek potrebujete pozdeji, pouzijte POST /jobs. Hlavicky odpovedi: - `X-Page-Count` pocet stranek vysledku - `X-Engine-Used` engine, ktery dokument vyrenderoval - `X-Missing-Assets` pocet assetu, ktere se nepodarilo nacist, hlavicka je jen pri nenulove hodnote. Cely seznam vraci jen asynchronni cesta. - `X-Warnings` pocet varovani, hlavicka je jen pri nenulove hodnote """ @router.post( "/convert", summary="Synchronni prevod HTML na PDF", description=CONVERT_DESCRIPTION, response_class=Response, responses={ 200: {"content": {"application/pdf": {}}, "description": "Hotove PDF."}, 413: {"description": "Konverze trvala dele nez limit, pouzijte POST /jobs."}, }, ) async def convert(request: ConvertRequest): container = get_container() settings = container.settings manager = container.jobs job = manager.submit(request) try: await asyncio.wait_for(job.done.wait(), timeout=settings.sync_timeout_seconds) except asyncio.TimeoutError as exc: manager.cancel(job.id) logger.warning( "Synchronous conversion exceeded its budget", extra={"job_id": job.id, "limit_seconds": settings.sync_timeout_seconds}, ) raise SyncTooLongError( "Konverze presahla limit pro synchronni pozadavek. Pouzijte asynchronni endpoint POST /jobs.", {"limit_seconds": settings.sync_timeout_seconds}, ) from exc if job.state.status != "done": error = job.state.error if error is not None: raise error_from_code(error.error_code, error.message, error.detail) raise ConversionError("Konverze skoncila ve stavu " + job.state.status + ".") path = container.storage.result_path(job.id) filename = request.filename or "dokument.pdf" headers = { "X-Page-Count": str(job.state.page_count or 0), "X-Engine-Used": job.state.engine_used or "", } if job.state.missing_assets: headers["X-Missing-Assets"] = str(len(job.state.missing_assets)) if job.state.warnings: headers["X-Warnings"] = str(len(job.state.warnings)) return FileResponse( path, media_type="application/pdf", filename=filename, headers=headers, background=BackgroundTask(container.storage.discard, job.id), )