Clients

Python client

Render a PDF from Python code inside your own process or through brevier serve, pass dataclasses and pydantic models, and handle errors.

On this page

Community

The Python client is part of the brevier package. You pass HTML and data, and you get the PDF back. It needs Python 3.10 or newer.

There are two modes:

Mode Where the engine runs Use it for
In process Inside your Python process. Scripts, tests and small services
Serve mode In a running brevier serve, reached over HTTP. Production and many renders

Install

pip install brevier

This installs the engine, the brevier command and the Python client.

Get a trial key

Every render needs a licence key, also for testing. Get the key before the first render.

  1. Request a free 30-day trial key on the pricing page. It arrives by email.
  2. Set the BREVIER_LICENCE_KEY environment variable:
Linux and macOS
export BREVIER_LICENCE_KEY="<your-licence-key>"
Windows PowerShell
$env:BREVIER_LICENCE_KEY = "<your-licence-key>"

The engine reads the key from its environment. In serve mode, set the key in the shell that starts brevier serve.

How do I make a PDF from HTML in Python?

This program renders a PDF from an HTML string and a dict:

hello.py
from brevier import Pdf

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

Run it with python hello.py. The folder now has hello.pdf.

  • with_html and with_css take the layout as text or as a path.
  • with_data takes a dict, a dataclass or a pydantic model. with_json takes JSON text or a path.
  • to_bytes returns the PDF. to_file writes it to a path.

Serve mode

In serve mode the engine runs as a service, and your code asks it for a PDF by the name of a template. A template is a folder with a file of the same name:

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

Start the server in the folder that holds templates:

brevier serve --templates templates

The server listens on 127.0.0.1:8080. It has no TLS and no login, so put a gateway in front of it before you expose it. Then render the template from 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)
  • A keyword such as name="World" sets the root of that name. An object without a keyword goes to the root of its class, which the schema file of the template names.
  • Without url, the client reads the environment variable BREVIER_URL. Without that, brevier.template("hello") renders in your process from the templates folder.

How do I render many PDFs at once?

Pdf.batch() renders every HTML file of a folder, and its subfolders, into one PDF each. All files get the same CSS. The batch passes no data: each file must already be the finished page, for example from Jinja or your own template engine.

batch.py
from brevier import Pdf

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

With letters/2026-001.html and letters/2026-002.html, the program prints one line per PDF it wrote, in the order it found the files:

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

For one PDF per record of your data, call Pdf.builder() in a loop instead. A subfolder of letters comes back as the same subfolder of out. When one file fails, to_files stops and raises the error of that file.

How do I send the PDF from a web endpoint?

Render to bytes and return them with the media type application/pdf. 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 is your own data access. Set BREVIER_URL on the server, and every render goes to your running brevier serve. Without it, the render runs inside the web process.

The handler uses def, not async def. FastAPI then runs it in its own thread, and a long render does not hold up the other requests.

How do I get HTML instead of a PDF?

Html.builder() takes the same calls as Pdf.builder() and returns HTML. Use it to show the document in a web portal, or to compare it in a 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")

The first run fails and says how to write the snapshot file. The class names of this HTML stay stable between releases. Output and tests explains the snapshot rules.

Replace a JasperReports or Word call

If your service fills a JasperReports report or a Word file today, import the template first. Then replace the call in two steps.

Step 1 keeps your dict of parameters and passes the rows:

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

Step 2 passes your objects. An object without a keyword goes to the root of its class, which the schema file of the template names.

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

Errors

The client raises a subclass of BrevierError, such as DataError when the data does not fit the template. Each error has code, the error code of the engine, message, and path, the place in the data where the problem is. The message never holds a value from your data.