openpyxl / python-docx / reportlab
Three libraries cover most Python document automation: openpyxl for Excel, python-docx for Word, and reportlab for PDF generation - common in reporting, exports, and compliance workflows.
Recipe
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "Summary"
ws.append(["sku", "qty", "revenue"])
ws.append(["ABC", 10, 199.90])
wb.save("report.xlsx")When to reach for this:
- Exporting query results to Excel for finance/ops
- Mail-merge style Word docs from CRM data
- Invoices, packing slips, and certificates as PDF
- Scheduled report jobs in Celery/cron
Working Example
from __future__ import annotations
from datetime import datetime, timezone
from io import BytesIO
from openpyxl import Workbook
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
def build_sales_xlsx(rows: list[tuple[str, int, float]]) -> bytes:
wb = Workbook()
ws = wb.active
ws.title = "Sales"
ws.append(["product", "units", "revenue"])
for product, units, revenue in rows:
ws.append([product, units, revenue])
buf = BytesIO()
wb.save(buf)
return buf.getvalue()
def build_invoice_pdf(invoice_id: str, total: float) -> bytes:
buf = BytesIO()
c = canvas.Canvas(buf, pagesize=letter)
c.setFont("Helvetica", 12)
c.drawString(72, 720, f"Invoice {invoice_id}")
c.drawString(72, 700, f"Total: ${total:,.2f}")
c.drawString(72, 680, datetime.now(timezone.utc).isoformat())
c.showPage()
c.save()
return buf.getvalue()
if __name__ == "__main__":
xlsx = build_sales_xlsx([("widget", 3, 59.97)])
pdf = build_invoice_pdf("INV-1001", 59.97)
print(len(xlsx), len(pdf))What this demonstrates:
- In-memory workbooks via
BytesIOfor HTTP responses - Simple tabular xlsx without pandas overhead
- Minimal PDF with reportlab canvas API
- UTC timestamp on PDF for audit trail
Deep Dive
How It Works
- openpyxl - OOXML xlsx;
load_workbookfor edits;data_only=Truereads cached formula values. - python-docx - Manipulates WordprocessingML; paragraphs, tables, styles; template merge by replacing placeholders.
- reportlab - Low-level PDF canvas plus Platypus flowables for complex layouts.
Library Picker
| Output | Library | Notes |
|---|---|---|
| xlsx export | openpyxl | Or pandas to_excel (uses openpyxl/xlsxwriter) |
| Word memo | python-docx | Keep template .docx in repo |
| PDF invoice | reportlab | Or WeasyPrint from HTML if team knows CSS |
| PDF from Word | docx2pdf (Win/Mac) | Not for Linux servers |
Python Notes
from docx import Document
doc = Document("template.docx")
for p in doc.paragraphs:
if "{{NAME}}" in p.text:
p.text = p.text.replace("{{NAME}}", "Ada Lovelace")
buf = BytesIO()
doc.save(buf)Gotchas
- Loading untrusted xlsx - XML bombs and billion-laughs variants exist. Fix: size limits, virus scan, parse in sandbox worker.
- Formulas vs values -
load_workbookreturns formulas by default. Fix:data_only=Truefor displayed values (stale until Excel opens). - python-docx unsupported features - Headers/footers in complex templates may break. Fix: simplify template or use docx templates maintained in Word.
- reportlab font licensing - Embed only licensed fonts for commercial PDFs. Fix: use built-in Helvetica or licensed TTF.
- Memory on huge exports - Building million-row xlsx in RAM OOMs. Fix: CSV for big data, or chunked writes with
write_only=Truemode. - Locale and dates in Excel - Excel displays local format; store ISO in source data. Fix: explicit number formats in openpyxl.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
pandas to_excel | DataFrame already in memory | Simple 3-column export (openpyxl lighter) |
| WeasyPrint / wkhtmltopdf | Team prefers HTML/CSS layouts | Minimal deps on constrained servers |
| Google Sheets API | Collaborative live sheets | Offline/air-gapped reporting |
| LaTeX | Scientific PDF quality | Business users edit templates in Word |
FAQs
openpyxl or xlsxwriter?
openpyxl reads and writes; xlsxwriter is write-only with rich formatting - pick by read need.
How do I return xlsx from FastAPI?
Response(content=bytes, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") with Content-Disposition attachment header.
Can python-docx insert images?
Yes - document.add_picture(path, width=Inches(2)) for logos in generated docs.
How do I add tables in Word?
table = doc.add_table(rows=1, cols=3) then populate cells - style with built-in table styles.
reportlab vs ReportBro?
reportlab is code-first; ReportBro is designer UI - choose based on who maintains layouts.
Password-protected Excel?
openpyxl has limited support - decrypt before load or reject encrypted uploads.
How do I unit test PDF output?
Assert PDF starts with %PDF and contains expected text extracted via pypdf in tests.
Macro-enabled xlsm?
Avoid server-side - macros are a security risk; accept xlsx only.
Unicode in PDF?
Register a TTF that covers your glyph set - built-in Helvetica lacks CJK.
Celery task generating reports?
Write to object storage (S3) and return signed URL - not multi-MB email attachments.
Related
- pandas Basics - DataFrame to Excel
- celery - async report generation
- S3 - storing generated documents
- pendulum / arrow - dates in reports
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.