Générer un PDF depuis HTML et JSON avec Python

Ce guide transforme les données d’une facture fictive en PDF avec Python. Le modèle reste enregistré dans Templatr ; votre serveur transmet les données à chaque génération. Vous obtenez un fichier binaire à enregistrer ou à servir depuis votre application, sans installer Chromium sur votre serveur.

PDF statique de démonstration, produit avec les fichiers ci-dessous et Chromium. Les données sont fictives.

Prérequis

  • Python 3.10 ou ultérieur. Le script utilise uniquement la bibliothèque standard : aucun pip install requis.
  • Un compte Templatr, une clé API et du quota PDF disponible.
  • Un terminal avec curl pour envoyer le modèle une première fois. Les commandes d’environnement ci-dessous utilisent un shell POSIX.

1. Préparer un modèle HTML réutilisable

Téléchargez ce fichier sous le nom invoice-template.html. Le modèle utilise des propriétés imbriquées et une boucle items. Dans cette boucle, item représente chaque objet du tableau items ; les valeurs sont échappées en HTML. Gardez les styles dans le modèle : JavaScript est désactivé pendant le rendu.

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Invoice {{invoice.number}}</title>
  <style>
    @page { size: A4; margin: 18mm; }
    body { font: 11pt Arial, sans-serif; color: #172033; }
    h1 { font-size: 26pt; margin-bottom: 8mm; }
    table { width: 100%; border-collapse: collapse; margin: 8mm 0; }
    th, td { padding: 3mm; border-bottom: 1px solid #dbe2ea; text-align: left; }
    th:last-child, td:last-child { text-align: right; }
    thead { display: table-header-group; }
    tr, .total { break-inside: avoid; }
    .total { text-align: right; font-weight: bold; }
  </style>
</head>
<body>
  <h1>Invoice {{invoice.number}}</h1>
  <p>From: {{seller.name}}</p>
  <p>Bill to: {{customer.name}}</p>
  <p>Date: {{invoice.date}}</p>
  <table>
    <thead><tr><th>Description</th><th>Quantity</th><th>Amount</th></tr></thead>
    <tbody>{{ items loop }}<tr><td>{{item.description}}</td><td>{{item.quantity}}</td><td>{{item.amount}}</td></tr>{{ end loop }}</tbody>
  </table>
  <p class="total">Total: {{invoice.total}}</p>
  <p>Sample document with fictional data. No payment is due.</p>
</body>
</html>
Télécharger invoice-template.html

2. Préparer les valeurs JSON

Téléchargez invoice-data.json dans le même dossier. L’objet JSON constitue directement le corps de la requête, sans enveloppe data supplémentaire. Formatez dates, devises et totaux dans votre application. Templatr insère les valeurs à afficher ; il ne calcule pas le total à partir des lignes.

json
{
  "seller": { "name": "Acme Studio" },
  "customer": { "name": "Northstar Co." },
  "invoice": { "number": "DEMO-0042", "date": "2026-09-23", "total": "EUR 299.00" },
  "items": [
    { "description": "Template design", "quantity": 1, "amount": "EUR 199.00" },
    { "description": "Integration review", "quantity": 2, "amount": "EUR 100.00" }
  ]
}
Télécharger invoice-data.json

3. Envoyer le modèle et conserver son identifiant

Créez une clé API dans les paramètres, puis définissez TEMPLATR_API_KEY dans l’environnement de votre serveur. Exécutez cette commande depuis le dossier du modèle. La réponse JSON contient template_id. Affectez cette valeur à TEMPLATR_TEMPLATE_ID et conservez-la : il est inutile d’envoyer le même HTML pour chaque document.

bash
export TEMPLATR_API_KEY='YOUR_API_KEY'
curl --fail-with-body "https://api.templatr.app/upload" \
  -H "Authorization: Bearer $TEMPLATR_API_KEY" \
  -F "[email protected]" \
  -F "name=Example invoice"
export TEMPLATR_TEMPLATE_ID='TEMPLATE_ID_FROM_RESPONSE'

4. Générer et enregistrer le PDF

Enregistrez le script ci-dessous à côté de invoice-data.json. Il envoie le JSON à la route de téléchargement PDF, vérifie le statut HTTP et le type de contenu, puis écrit invoice.pdf uniquement si la réponse commence par un en-tête PDF. Conservez la clé dans les variables d’environnement du serveur, jamais dans le code du navigateur ni dans un dépôt public.

urllib sérialise le JSON en UTF-8 et transmet Authorization dans un en-tête. Les erreurs HTTP interrompent le script avant l’écriture du fichier.

python
import json
import os
import sys
from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen

api_key = os.environ.get("TEMPLATR_API_KEY")
template_id = os.environ.get("TEMPLATR_TEMPLATE_ID")
if not api_key or not template_id:
    raise SystemExit("Set TEMPLATR_API_KEY and TEMPLATR_TEMPLATE_ID first.")

input_path = Path(sys.argv[1] if len(sys.argv) > 1 else "invoice-data.json")
data = json.loads(input_path.read_text(encoding="utf-8"))
base_url = os.environ.get("TEMPLATR_API_BASE_URL", "https://api.templatr.app")
request = Request(
    f"{base_url}/pdf/{quote(template_id, safe='')}?format=pdf",
    data=json.dumps(data).encode("utf-8"),
    headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
    method="POST",
)
try:
    with urlopen(request, timeout=60) as response:
        if response.headers.get_content_type() != "application/pdf":
            raise SystemExit("Expected an application/pdf response.")
        pdf = response.read()
except HTTPError as error:
    raise SystemExit(f"PDF request failed (HTTP {error.code}). Check the API error guide.") from None

if not pdf.startswith(b"%PDF-"):
    raise SystemExit("Response does not contain a PDF.")
output_path = Path(sys.argv[2] if len(sys.argv) > 2 else "invoice.pdf")
output_path.write_bytes(pdf)
print("PDF saved.")
Télécharger generate_pdf.py

5. Exécuter le script

Le premier argument est le fichier JSON, le second est le fichier PDF de sortie. Les variables d’environnement doivent être définies dans le même terminal.

bash
python3 generate_pdf.py invoice-data.json invoice.pdf

Vérifications et erreurs

  • Ouvrez invoice.pdf et vérifiez DEMO-0042, Northstar Co., les deux lignes et le total de EUR 299.00. Le document d’exemple est en anglais.
  • Changez le nom du client et une description dans invoice-data.json, relancez le script et vérifiez les nouvelles valeurs.
  • Testez les noms et descriptions longs avant d’utiliser vos données réelles. Une variable absente produit une chaîne vide.
  • Pour une erreur 401, vérifiez la clé API. Pour 404, vérifiez que le modèle appartient au compte de la clé. Pour 429, contrôlez le quota du compte et l’en-tête Retry-After.
  • Un délai dépassé ne prouve pas l’échec de la génération côté serveur. Vérifiez l’historique avant de réessayer : plusieurs requêtes réussies peuvent consommer plusieurs crédits.
Consulter les quotas et limites de l’API

Pour continuer