Nel contesto di molte applicazioni enterprise, può emergere la necessità di generare documenti PDF partendo da template HTML personalizzabili. Dai report periodici alle ricevute, dai certificati ai documenti riepilogativi, un output in formato PDF rappresenta spesso uno standard utile e professionale.
Ma come automatizzare questo processo in ambienti serverless, ad esempio con una funzione AWS Lambda? E come farlo in modo semplice, mantenendo flessibilità nell’impaginazione e nei contenuti? In questo articolo analizziamo un approccio adottato in Miriade: una funzione Lambda scritta in Python che genera PDF a partire da template HTML con dati dinamici, sfruttando Jinja2 per il rendering e WeasyPrint per la conversione finale.
Il Contesto
Un nostro cliente necessitava di automatizzare la generazione di report mensili, distribuiti in formato PDF. Ogni documento includeva tabelle di dati, intestazioni, loghi aziendali e un layout coerente.
La soluzione doveva soddisfare tre vincoli:
- essere serverless, per ridurre costi e manutenzione;
- adattabile, per aggiornare facilmente layout e dati;
- rapida da integrare, con un endpoint HTTP con risposta immediata.
Il team ha quindi scelto di usare una funzione AWS Lambda scritta in Python, integrata con Jinja2 e WeasyPrint, due librerie mature e ben supportate.
Il Problema
Generare PDF in ambiente serverless comporta alcune sfide tecniche.
- Rendering HTML: serviva un motore di templating flessibile e leggibile per trasformare i dati in un layout stampabile.
- Conversione in PDF: occorreva una libreria che supportasse CSS avanzato e fosse compatibile con le dipendenze di Linux su Lambda.
- Restituzione dell’output: il PDF doveva essere generato in memoria, codificato e restituito direttamente via HTTP.
La Soluzione
Il codice sviluppato è conciso ma potente. I due componenti chiave sono:
- Jinja2: per compilare un template HTML con dati dinamici (es. lista prodotti, date, intestazioni).
- WeasyPrint: per convertire l’HTML in un PDF ben formattato, gestendo margini, tabelle, font e stili CSS.
Ecco una panoramica del funzionamento.
from jinja2 import Environment, FileSystemLoader
from weasyprint import HTML
from base64 import b64encode
def lambda_handler(event, context):
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('template.html')
data = event.get("body", {})
#{
# "title": "Monthly Sales Report",
# "header": "Company XYZ",
# "date": "January 2025",
# "records": [{"product": "Product A", "quantity": 30, "price": 100}, ...],
# "total_sales": 950,
#}
html_out = template.render(data)
pdf_bytes = HTML(string=html_out).write_pdf()
return {
"statusCode": 200,
"headers": {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=report.pdf",
},
"isBase64Encoded": True,
"body": b64encode(pdf_bytes).decode('utf-8'),
}
Struttura e stile del template HTML
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<style>
@page {
size: A4;
margin: 20mm;
@top-center {
content: '{{ header }}';
}
@bottom-right {
content: 'Page ' counter(page) ' of ' counter(pages);
}
}
body {
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<h1>{{ title }}</h1>
<p>Date: {{ date }}</p>
<table>
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr>
<td>{{ record.product }}</td>
<td>{{ record.quantity }}</td>
<td>${{ record.price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h3>Total Sales: ${{ total_sales }}</h3>
</body>
</html>
Alcuni punti chiave da tenere bene a mente.
- La regola CSS
@page
definisce margini, intestazione e footer direttamente nel CSS per ciascuna pagina PDF. - La variabile
{{ header }}
viene stampata in alto su ogni pagina, mentre il conteggio automaticoPage X of Y
appare in basso a destra. - Il layout è pensato per tabelle e dati ripetitivi, con il corpo del documento costruito tramite un ciclo for di Jinja2.
Aggiungere immagini dinamiche
È possibile inserire immagini nel PDF in due modi.
Da file statici
Inline con base64 (dinamico)
image_b64 = b64encode(f.read()).decode("utf-8")
Template:
Questo approccio funziona perfettamente anche su Lambda.
I Risultati
Questa soluzione ha permesso di:
- automatizzare la creazione di report PDF personalizzati;
- generare PDF in pochi secondi, direttamente da una funzione Lambda;
- non avere alcuna dipendenza da browser headless esterni;
- evitare lo storage su disco, elaborando i dati interamente in memoria;
- integrare facilmente layout complessi con intestazioni e paginazione;
- riutilizzare lo stesso template HTML senza toccare il codice Python.
Il confronto in sintesi
Il Confronto in Sintesi
Analisi comparativa dei principali metodi per la generazione di documenti PDF in ambiente serverless.
Approccio | Vantaggi | Svantaggi |
---|---|---|
WeasyPrint + Jinja2 | Leggero, HTML-native, portabile | Richiede configurazione dei font |
Puppeteer / Headless Chrome | Molto potente | Pesante, lento, difficile su Lambda |
Servizi esterni (es. PDFMonkey) | Nessuna infrastruttura da gestire | Costo per richiesta, lock-in |
Conclusioni
Jinja2 e WeasyPrint formano una coppia vincente per chi vuole generare PDF di qualità partendo da HTML. La possibilità di separare contenuto e layout, abbinata all’esecuzione serverless tramite AWS Lambda, consente di sviluppare soluzioni rapide, scalabili e a basso costo.
In Miriade adottiamo questo pattern per automatizzare reportistica e documentazione, offrendo ai clienti un risultato professionale e pronto all’uso.
Hai bisogno di generare PDF nei tuoi sistemi cloud? Contattaci: possiamo aiutarti a progettare una soluzione elegante e robusta, perfettamente integrata nel tuo flusso di lavoro.
Ti è piaciuto quanto hai letto? Iscriviti a MISPECIAL, la nostra newsletter, per ricevere altri interessanti contenuti.
Iscriviti a MISPECIAL