python
44 lines · 8 steps
Handling secure file uploads in Flask
A Flask blueprint that validates, renames, and stores uploaded files while rejecting bad or oversized requests.
Explained by
highlit
1import os
2import uuid
3
4from flask import Blueprint, current_app, jsonify, request
5from werkzeug.exceptions import RequestEntityTooLarge
6from werkzeug.utils import secure_filename
7
8uploads = Blueprint("uploads", __name__)
9
10ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "pdf"}
11MAX_CONTENT_LENGTH = 8 * 1024 * 1024
12
13
14def _is_allowed(filename):
15 _, _, ext = filename.rpartition(".")
16 return "." in filename and ext.lower() in ALLOWED_EXTENSIONS
17
18
19@uploads.route("/documents", methods=["POST"])
20def upload_document():
21 if "file" not in request.files:
22 return jsonify(error="no file part in request"), 400
23
24 upload = request.files["file"]
25 if not upload.filename:
26 return jsonify(error="no file selected"), 400
27
28 if not _is_allowed(upload.filename):
29 return jsonify(error="unsupported file type"), 415
30
31 safe_name = secure_filename(upload.filename)
32 stored_name = f"{uuid.uuid4().hex}_{safe_name}"
33 dest = os.path.join(current_app.config["UPLOAD_DIR"], stored_name)
34
35 upload.save(dest)
36 size = os.path.getsize(dest)
37
38 return jsonify(filename=stored_name, original=safe_name, size=size), 201
39
40
41@uploads.errorhandler(RequestEntityTooLarge)
42def handle_too_large(err):
43 limit_mb = MAX_CONTENT_LENGTH // (1024 * 1024)
44 return jsonify(error=f"file exceeds {limit_mb}MB limit"), 413
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Validate presence, filename, and type before touching the filesystem to fail fast with meaningful status codes.
- 2Sanitize and randomize stored filenames to avoid path traversal and name collisions.
- 3Register an error handler so framework-level exceptions like oversized bodies return clean JSON.
Related explainers
python
import ijson from decimal import Decimal
Streaming JSON aggregation with ijson
streaming
json-parsing
generators
Intermediate
7 steps
php
<?php namespace App\Http\Requests;
Normalizing input in a Laravel FormRequest
validation
authorization
input-normalization
Intermediate
6 steps
java
package com.example.orders.messaging; import com.example.orders.dto.OrderEvent; import com.example.orders.service.OrderProcessingService;
Manual RabbitMQ acks in a Spring listener
message-queue
error-handling
acknowledgement
Intermediate
6 steps
rust
use axum::{extract::{Query, State}, http::StatusCode, Json}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use serde::{Deserialize, Serialize}; use sqlx::PgPool;
Cursor pagination in an Axum handler
cursor-pagination
keyset-pagination
base64
Advanced
10 steps
rust
use std::num::ParseIntError; fn parse_line(line: &str) -> Result<Vec<i64>, ParseIntError> { line.split(',')
Collecting Results in Rust iterators
iterators
error-handling
result
Intermediate
7 steps
python
from django.core.cache import cache from django.db.models import Count, Sum, Q from django.utils import timezone
Caching a Django dashboard aggregate query
caching
aggregation
conditional-queries
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/handling-secure-file-uploads-in-flask-explained-python-5f94/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.