This commit is contained in:
JiriUhlir
2026-09-03 10:03:10 +02:00
parent 156289fe2d
commit a922117b3d
13 changed files with 583 additions and 59 deletions
+23
View File
@@ -22,9 +22,32 @@ 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."},
+54 -2
View File
@@ -21,11 +21,29 @@ def _result_url(job_id: str) -> str:
return f"{root}/jobs/{job_id}/result"
CREATE_DESCRIPTION = """
Zaradi konverzi do fronty a hned vrati 202 s identifikatorem jobu. Telo je
stejne jako u POST /convert.
Postup sledujte pres GET /jobs/{job_id}, hotove PDF stahnete z
GET /jobs/{job_id}/result. Volitelny `callback_url` dostane po dokonceni POST
se stejnym telem, jake vraci GET /jobs/{job_id}.
**Uchovavani souboru.** Hotove PDF zustava na disku sluzby, aby slo stahnout.
Meziprodukty renderu se po dokonceni smazou, zustava jen vysledek. Dostupny je
`JOB_RESULT_TTL_SECONDS` (vychozi 1 hodina) od dokonceni jobu, presny cas je
v poli `expires_at`. Pak se soubor i zaznam jobu smazou a dalsi dotaz vraci 404.
Stahovat lze opakovane. Neuspesny job se maze hned. Uloziste je docasny adresar
procesu, restart sluzby vysledky ztrati.
"""
@router.post(
"",
response_model=JobAccepted,
status_code=status.HTTP_202_ACCEPTED,
summary="Zaradi konverzi do fronty",
description=CREATE_DESCRIPTION,
)
async def create_job(request: ConvertRequest) -> JobAccepted:
job = get_container().jobs.submit(request)
@@ -37,7 +55,26 @@ async def create_job(request: ConvertRequest) -> JobAccepted:
)
@router.get("/{job_id}", response_model=JobState, summary="Stav jobu")
STATE_DESCRIPTION = """
Stav jobu, postup renderu a vysledek kontrol.
Stavy: `queued`, `running`, `done`, `failed`, `cancelled`, `expired`.
`progress.pass_number` rozlisuje prvni a druhy pruchod. Druhy nastava jen
u dokumentu s generovanym obsahem, takze ukazatel postupu probehne dvakrat.
`expires_at` je cas, do ktereho je vysledek ke stazeni. `missing_assets` je
seznam assetu, ktere se nepodarilo nacist, `warnings` obsahuje veci, na ktere
sluzba upozornuje, aniz by kvuli nim konverze selhala.
"""
@router.get(
"/{job_id}",
response_model=JobState,
summary="Stav jobu",
description=STATE_DESCRIPTION,
)
async def job_state(job_id: str) -> JobState:
job = get_container().jobs.get(job_id)
state = job.state.model_copy()
@@ -49,6 +86,12 @@ async def job_state(job_id: str) -> JobState:
@router.get(
"/{job_id}/result",
summary="Stahne hotove PDF",
description=(
"Stahne vysledek jobu. Soubor se streamuje, nenacita se cely do pameti. "
"Stahovat lze opakovane, dokud vysledek nevyprsi. Doba dostupnosti je "
"JOB_RESULT_TTL_SECONDS od dokonceni jobu, presny cas je v poli expires_at "
"u GET /jobs/{job_id}."
),
response_class=Response,
responses={
200: {"content": {"application/pdf": {}}, "description": "Hotove PDF."},
@@ -85,6 +128,15 @@ async def job_result(job_id: str):
)
@router.delete("/{job_id}", response_model=JobState, summary="Zrusi job nebo smaze jeho vysledek")
@router.delete(
"/{job_id}",
response_model=JobState,
summary="Zrusi job nebo smaze jeho vysledek",
description=(
"Zrusi bezici job nebo smaze hotovy vysledek hned, bez cekani na expiraci. "
"Soubor se z uloziste odstrani okamzite, zaznam jobu zustava jeste po dobu "
"TTL, aby bylo videt, co se s nim stalo."
),
)
async def delete_job(job_id: str) -> JobState:
return get_container().jobs.cancel(job_id).state