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
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 brevierThis 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.
- Request a free 30-day trial key on the pricing page. It arrives by email.
- Set the
BREVIER_LICENCE_KEYenvironment variable:
export BREVIER_LICENCE_KEY="<your-licence-key>"$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:
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_htmlandwith_csstake the layout as text or as a path.with_datatakes a dict, a dataclass or a pydantic model.with_jsontakes JSON text or a path.to_bytesreturns the PDF.to_filewrites 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:
<!DOCTYPE html>
<html lang="en">
<body>
<h1>Hello {{ name }}</h1>
</body>
</html>Start the server in the folder that holds templates:
brevier serve --templates templatesThe 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:
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 variableBREVIER_URL. Without that,brevier.template("hello")renders in your process from thetemplatesfolder.
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.
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:
out/2026-001.pdf
out/2026-002.pdfFor 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:
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:
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.