python
44 lines · 8 steps
Serving cacheable static assets in Flask
A Flask blueprint that streams files safely while using ETags and cache headers to skip redundant transfers.
Explained by
highlit
1import hashlib
2import os
3from datetime import timedelta
4
5from flask import Blueprint, abort, current_app, request, send_file
6from werkzeug.utils import safe_join
7
8assets = Blueprint("assets", __name__)
9
10
11def _compute_etag(path, stat):
12 digest = hashlib.blake2b(digest_size=16)
13 digest.update(str(stat.st_mtime_ns).encode())
14 digest.update(str(stat.st_size).encode())
15 digest.update(os.fsencode(path))
16 return f'"{digest.hexdigest()}"'
17
18
19@assets.route("/assets/<path:filename>")
20def serve_asset(filename):
21 root = current_app.config["ASSET_ROOT"]
22 full_path = safe_join(root, filename)
23 if full_path is None or not os.path.isfile(full_path):
24 abort(404)
25
26 stat = os.stat(full_path)
27 etag = _compute_etag(full_path, stat)
28
29 if etag in request.if_none_match:
30 response = current_app.response_class(status=304)
31 response.set_etag(etag.strip('"'))
32 return response
33
34 response = send_file(
35 full_path,
36 conditional=True,
37 last_modified=stat.st_mtime,
38 max_age=timedelta(days=30),
39 )
40 response.set_etag(etag.strip('"'))
41 response.cache_control.public = True
42 response.cache_control.immutable = True
43 response.cache_control.max_age = int(timedelta(days=30).total_seconds())
44 return response
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1An ETag derived from file metadata lets clients revalidate and receive a cheap 304 instead of the full body.
- 2safe_join prevents path traversal by rejecting filenames that escape the configured root directory.
- 3Setting cache-control to public and immutable tells browsers they can reuse the asset without ever revalidating.
Related explainers
python
import pytest from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker
Testing FastAPI routes with dependency overrides
testing
fixtures
dependency-injection
Intermediate
7 steps
python
import re from collections import Counter from pathlib import Path
Ranking the top words in a PDF
text-processing
regex
counting
Intermediate
6 steps
python
import django_filters from django import forms from django.utils import timezone
Building a date-range FilterSet in Django
filtering
querysets
form-widgets
Intermediate
6 steps
python
import json import hashlib from datetime import timedelta
Idempotent payment endpoints in FastAPI
idempotency
redis
distributed-locking
Advanced
9 steps
python
from flask import Blueprint, jsonify, request, abort v1 = Blueprint("users_v1", __name__) v2 = Blueprint("users_v2", __name__)
Versioning a Flask API with Blueprints
api versioning
blueprints
rest
Intermediate
7 steps
python
import time import threading from enum import Enum from functools import wraps
Building a circuit breaker in Python
circuit-breaker
resilience
decorators
Advanced
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/serving-cacheable-static-assets-in-flask-explained-python-af45/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.