Generare PDF dinamici in Python su AWS Lambda: semplice, veloce, portabile

Guida alla generazione di PDF dinamici e serverless da template HTML, utilizzando Python e WeasyPrint su AWS Lambda.

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

Il cuore del layout è un file HTML progettato per essere stampabile e leggibile, sia su carta che su schermo. Questo è un esempio semplificato del template utilizzato:

<!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 automatico Page 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

<img src="static/logo.png" width="150" />

Inline con base64 (dinamico)

with open("chart.png", "rb") as f:
    image_b64 = b64encode(f.read()).decode("utf-8")

Template:

<img src="data:image/png;base64,{{ image_b64 }}" width="400" />

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 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
Contenuti simili
DIGITAL ENTERPRISE
lug 29, 2025

L'Observability è un approccio che promette di offrire una visione completa e robusta del proprio ecosistema dati. Ma cosa significa esattamente e perché sta diventando così cruciale per le aziende?

DIGITAL ENTERPRISE
EC2 in Auto Scaling e deployment con CodeDeploy – Analisi, Diagnosi e Soluzione
lug 23, 2025

EC2 in Auto Scaling e deployment con CodeDeploy – Analisi, Diagnosi e Soluzione