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.
Search across all documentation pages
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.
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:
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:
BytesIO for HTTP responsesload_workbook for edits; data_only=True reads cached formula values.| 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 |
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)load_workbook returns formulas by default. Fix: data_only=True for displayed values (stale until Excel opens).write_only=True mode.
| 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 |
openpyxl reads and writes; xlsxwriter is write-only with rich formatting - pick by read need.
Response(content=bytes, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") with Content-Disposition attachment header.
Yes - document.add_picture(path, width=Inches(2)) for logos in generated docs.
table = doc.add_table(rows=1, cols=3) then populate cells - style with built-in table styles.
reportlab is code-first; ReportBro is designer UI - choose based on who maintains layouts.
openpyxl has limited support - decrypt before load or reject encrypted uploads.
Assert PDF starts with %PDF and contains expected text extracted via pypdf in tests.
Avoid server-side - macros are a security risk; accept xlsx only.
Register a TTF that covers your glyph set - built-in Helvetica lacks CJK.
Write to object storage (S3) and return signed URL - not multi-MB email attachments.
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+.
Reviewed by Chris St. John·Last updated Jul 19, 2026