Sluzba prijme adresu HTML dokumentu nebo HTML v tele requestu a vrati PDF. Navrzena pro dokumenty o stovkach az tisicich stranek. Rendering: - WeasyPrint jako vychozi engine, spravne CSS Paged Media, nizka pametova narocnost, bez JavaScriptu - Chromium pres Playwright pro dokumenty dokreslovane skripty - rezim auto s detekci skriptu a fallbackem pri selhani WeasyPrintu Velke dokumenty: - deleni na casti na strukturalnich hranicich, rez nikdy uvnitr tabulky nebo odstavce - dvoupruchodovy render obsahu se skutecnymi cisly stranek, pozice nadpisu se ctou z kotev hlasenych u kazde stranky - cislovani stranek bud pres CSS countery, nebo pres cislovaci vrstvu nastampovanou na hotove PDF, rozmer stranky se cte z vysledneho souboru - Chromium se restartuje po N jobech, nikdy vsak behem beziciho renderu API: - POST /convert synchronne, POST /jobs asynchronne se sledovanim stavu, stahovanim vysledku, rusenim a volitelnym callbackem - GET /health s overenim dostupnosti obou enginu a stavem fronty - OpenAPI respektuje prefix reverse proxy pres root_path Bezpecnost a provoz: - SSRF kontrola po DNS resolvu, na kazdem presmerovani a u vsech pozadavku prohlizece - nedostupne assety render nezastavi, ale hlasi se v odpovedi i v logu - fronta s omezenym poctem workeru, rozpracovane joby se pri ukonceni oznaci jako failed, nezmizi potichu - strukturovane JSON logovani s job_id - vsechny limity vypnute ve vychozim stavu Dockerfile je dvoufazovy, obsahuje zavislosti WeasyPrintu, Chromium a fonty s ceskou diakritikou. Autentizace zamerne neni implementovana, zpusob predavani neni domluveny. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
57 lines
1.9 KiB
Python
57 lines
1.9 KiB
Python
"""Splitting of the document into chunks."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from app.pdf.chunker import split_document
|
|
from app.pdf.document import SourceDocument
|
|
from tests.conftest import build_document
|
|
|
|
|
|
def test_document_with_sections_is_split() -> None:
|
|
document = SourceDocument(build_document(sections=40, paragraphs_per_section=10))
|
|
chunks = split_document(document, pages_per_chunk=5)
|
|
|
|
assert len(chunks) > 1
|
|
for chunk in chunks:
|
|
assert chunk.startswith("<!DOCTYPE html>")
|
|
assert "<body" in chunk
|
|
assert "</html>" in chunk
|
|
|
|
|
|
def test_every_section_appears_exactly_once() -> None:
|
|
document = SourceDocument(build_document(sections=30, paragraphs_per_section=8))
|
|
chunks = split_document(document, pages_per_chunk=3)
|
|
|
|
joined = "".join(chunks)
|
|
for index in range(30):
|
|
assert joined.count(f"id=\"sekce-{index}\"") == 1
|
|
|
|
|
|
def test_head_is_repeated_in_every_chunk() -> None:
|
|
document = SourceDocument(build_document(sections=20, paragraphs_per_section=10))
|
|
chunks = split_document(document, pages_per_chunk=2)
|
|
|
|
assert len(chunks) > 1
|
|
for chunk in chunks:
|
|
assert "font-family:sans-serif" in chunk
|
|
|
|
|
|
def test_document_without_split_points_stays_whole(unsplittable_document: str) -> None:
|
|
document = SourceDocument(unsplittable_document)
|
|
chunks = split_document(document, pages_per_chunk=1)
|
|
|
|
assert len(chunks) == 1
|
|
|
|
|
|
def test_single_wrapper_is_traversed() -> None:
|
|
sections = "".join(f"<section><h1>Kapitola {i}</h1><p>Text</p></section>" for i in range(10))
|
|
html = f"<!DOCTYPE html><html><head></head><body><main class='obal'>{sections}</main></body></html>"
|
|
|
|
document = SourceDocument(html)
|
|
assert document.container.tag == "main"
|
|
|
|
chunks = split_document(document, pages_per_chunk=1)
|
|
assert len(chunks) > 1
|
|
for chunk in chunks:
|
|
assert "class=\"obal\"" in chunk
|