Clients

Python-Client

Erzeugen Sie ein PDF aus Python-Code im eigenen Prozess oder über brevier serve, übergeben Sie Dataclasses und pydantic-Modelle, und behandeln Sie Fehler.

Auf dieser Seite

Community

Der Python-Client gehört zum Paket brevier. Sie übergeben HTML und Daten und bekommen das PDF zurück. Er braucht Python 3.10 oder neuer.

Es gibt zwei Betriebsarten:

Betriebsart Wo die Engine läuft Wofür
Im Prozess In Ihrem Python-Prozess. Skripte, Tests und kleine Dienste
Serve-Modus In einem laufenden brevier serve, erreicht über HTTP. Produktion und viele Erzeugungen

Installieren

pip install brevier

Das installiert die Engine, den Befehl brevier und den Python-Client.

Testschlüssel holen

Jede Erzeugung braucht einen Lizenzschlüssel, auch zum Testen. Holen Sie den Schlüssel vor der ersten Erzeugung.

  1. Fordern Sie auf der Preisseite einen kostenlosen Testschlüssel für 30 Tage an. Er kommt per E-Mail.
  2. Setzen Sie die Umgebungsvariable BREVIER_LICENCE_KEY:
Linux und macOS
export BREVIER_LICENCE_KEY="<ihr-lizenzschluessel>"
Windows PowerShell
$env:BREVIER_LICENCE_KEY = "<ihr-lizenzschluessel>"

Die Engine liest den Schlüssel aus ihrer Umgebung. Im Serve-Modus setzen Sie den Schlüssel in der Shell, die brevier serve startet.

Im Prozess

Dieses Programm erzeugt ein PDF aus einem HTML-Text und einem Dict:

hello.py
from brevier import Pdf

Pdf.builder().with_html("<h1>Hello {{ name }}</h1>").with_data({"name": "World"}).to_file("hello.pdf")

Starten Sie es mit python hello.py. Im Ordner liegt jetzt hello.pdf.

  • with_html und with_css nehmen das Layout als Text oder als Pfad.
  • with_data nimmt ein Dict, eine Dataclass oder ein pydantic-Modell. with_json nimmt JSON-Text oder einen Pfad.
  • to_bytes gibt das PDF zurück. to_file schreibt es in einen Pfad.

Serve-Modus

Im Serve-Modus läuft die Engine als Dienst, und Ihr Code fragt sie mit dem Namen einer Vorlage nach einem PDF. Eine Vorlage ist ein Ordner mit einer Datei gleichen Namens:

templates/hello/hello.html
<!DOCTYPE html>
<html lang="en">
<body>
  <h1>Hello {{ name }}</h1>
</body>
</html>

Starten Sie den Server in dem Ordner, der templates enthält:

brevier serve --templates templates

Der Server lauscht auf 127.0.0.1:8080. Er hat weder TLS noch eine Anmeldung. Stellen Sie ein Gateway davor, bevor Sie ihn nach außen öffnen. Dann erzeugen Sie die Vorlage aus Python:

hello_serve.py
import brevier

pdf = brevier.template("hello", url="http://127.0.0.1:8080").render(name="World")

with open("hello.pdf", "wb") as file:
    file.write(pdf)
  • Ein Schlüsselwort wie name="World" setzt die Wurzel dieses Namens. Ein Objekt ohne Schlüsselwort geht an die Wurzel seiner Klasse, die die Schemadatei der Vorlage nennt.
  • Ohne url liest der Client die Umgebungsvariable BREVIER_URL. Ohne sie erzeugt brevier.template("hello") in Ihrem Prozess aus dem Ordner templates.

Wie erzeuge ich viele PDFs auf einmal?

Pdf.batch() macht aus jeder HTML-Datei eines Ordners und seiner Unterordner ein PDF. Alle Dateien bekommen dasselbe CSS. Der Batch übergibt keine Daten: Jede Datei muss schon die fertige Seite sein, zum Beispiel aus Jinja oder Ihrer eigenen Template-Engine.

batch.py
from brevier import Pdf

written = (
    Pdf.batch()
    .with_input("briefe", "*.html")
    .with_css("firma.css")
    .with_output("out")
    .to_files()
)
for path in written:
    print(path)

Mit briefe/2026-001.html und briefe/2026-002.html gibt das Programm eine Zeile je geschriebenem PDF aus, in der Reihenfolge, in der es die Dateien findet:

Ausgabe
out/2026-001.pdf
out/2026-002.pdf

Für ein PDF je Datensatz rufen Sie stattdessen Pdf.builder() in einer Schleife auf. Ein Unterordner von briefe erscheint als derselbe Unterordner von out. Scheitert eine Datei, bricht to_files mit dem Fehler dieser Datei ab.

Wie liefere ich das PDF aus einem Web-Endpunkt aus?

Erzeugen Sie die Bytes und geben Sie sie mit dem Medientyp application/pdf zurück. In FastAPI:

app.py
from fastapi import FastAPI, Response
import brevier

app = FastAPI()


@app.get("/invoices/{invoice_id}/pdf")
def invoice(invoice_id: str) -> Response:
    pdf = brevier.template("rechnung").render(invoices.find(invoice_id))
    return Response(content=pdf, media_type="application/pdf")

invoices steht für Ihren eigenen Datenzugriff. Setzen Sie BREVIER_URL auf dem Server, dann geht jeder Aufruf an Ihr laufendes brevier serve. Ohne die Variable läuft die Erzeugung im Prozess des Webservers.

Der Handler ist mit def geschrieben, nicht mit async def. So führt FastAPI ihn in einem eigenen Thread aus, und eine lange Erzeugung hält die anderen Anfragen nicht auf.

Wie bekomme ich HTML statt eines PDFs?

Html.builder() nimmt dieselben Aufrufe wie Pdf.builder() und gibt HTML zurück. Damit zeigen Sie das Dokument in einem Webportal, oder Sie vergleichen es in einem Snapshot-Test:

test_hello.py
from brevier import Html
from brevier.testing import assert_matches_snapshot


def test_hello_snapshot():
    html = Html.builder().with_html("<h1>Hello {{ name }}</h1>").with_data({"name": "World"})
    assert_matches_snapshot(html.to_bytes().decode("utf-8"), "snapshots/hello.html")

Der erste Lauf scheitert und sagt, wie Sie die Snapshot-Datei schreiben. Die Klassennamen dieses HTML bleiben von Version zu Version gleich. Ausgabe und Tests erklärt die Regeln für Snapshots.

Einen JasperReports- oder Word-Aufruf ersetzen

Wenn Ihr Dienst heute einen JasperReports-Bericht oder eine Word-Datei füllt, importieren Sie zuerst die Vorlage. Ersetzen Sie dann den Aufruf in zwei Schritten.

Schritt 1 behält Ihr Dictionary der Parameter und übergibt die Zeilen:

return brevier.template("kontoauszug").render(**context, rows=buchungen)

Schritt 2 übergibt Ihre Objekte. Ein Objekt ohne Schlüsselwort geht an die Wurzel seiner Klasse, die die Schema-Datei der Vorlage nennt.

return brevier.template("kontoauszug").render(konto, kunde, buchungen)

Fehler

Der Client wirft eine Unterklasse von BrevierError, zum Beispiel DataError, wenn die Daten nicht zur Vorlage passen. Jeder Fehler hat code, den Fehlercode der Engine, message und path, die Stelle in den Daten, an der das Problem liegt. Die Meldung enthält nie einen Wert aus Ihren Daten.