python
47 lines · 8 steps
Streaming a generated PDF from a Flask route
A Flask endpoint renders an invoice to a PDF in memory and sends it as a downloadable file.
Explained by
highlit
1from io import BytesIO
2from flask import Blueprint, abort, send_file
3from reportlab.lib.pagesizes import LETTER
4from reportlab.lib.units import inch
5from reportlab.pdfgen import canvas
6
7from .models import Invoice
8
9bp = Blueprint("invoices", __name__)
10
11
12@bp.route("/invoices/<int:invoice_id>/download")
13def download_invoice(invoice_id):
14 invoice = Invoice.query.get_or_404(invoice_id)
15 if not invoice.is_finalized:
16 abort(409, description="Invoice is still a draft")
17
18 buffer = BytesIO()
19 pdf = canvas.Canvas(buffer, pagesize=LETTER)
20 width, height = LETTER
21
22 pdf.setFont("Helvetica-Bold", 20)
23 pdf.drawString(inch, height - inch, f"Invoice #{invoice.number}")
24
25 pdf.setFont("Helvetica", 11)
26 pdf.drawString(inch, height - 1.4 * inch, f"Billed to: {invoice.customer.name}")
27 pdf.drawString(inch, height - 1.65 * inch, f"Date: {invoice.issued_at:%B %d, %Y}")
28
29 y = height - 2.3 * inch
30 for item in invoice.line_items:
31 pdf.drawString(inch, y, item.description)
32 pdf.drawRightString(width - inch, y, f"${item.amount:,.2f}")
33 y -= 0.3 * inch
34
35 pdf.setFont("Helvetica-Bold", 12)
36 pdf.drawRightString(width - inch, y - 0.2 * inch, f"Total: ${invoice.total:,.2f}")
37
38 pdf.showPage()
39 pdf.save()
40 buffer.seek(0)
41
42 return send_file(
43 buffer,
44 mimetype="application/pdf",
45 as_attachment=True,
46 download_name=f"invoice-{invoice.number}.pdf",
47 )
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Building files in a BytesIO buffer avoids touching disk and keeps the request self-contained.
- 2Validate resource state before doing expensive rendering work, returning a precise HTTP status.
- 3send_file with as_attachment and a download_name turns a byte stream into a browser download.
Related explainers
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
python
import random from typing import Iterator, List
How reservoir sampling picks k items
reservoir-sampling
streaming
randomness
Intermediate
5 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/streaming-a-generated-pdf-from-a-flask-route-explained-python-385f/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.