Délégation du stockage vers des compartiments compatibles S3
Diffusez directement les téléversements vers S3/MinIO avec des URL pré-signées afin de garder l’API sans état et évolutive.
Délégation du stockage vers des compartiments compatibles S3 est une leçon FastAPI Backend Development Bootcamp gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage FastAPI Backend Development Bootcamp, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours FastAPI Backend Development Bootcamp comprend 4 leçons au total.
Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.
Why Offload Storage to S3?
Storing uploaded files on your API server's local disk does not scale. Every replica would need its own copy, disks fill up, and containers are ephemeral — restart the pod and the files vanish.
The fix is to push media to an external object store and keep your API stateless. Any S3-compatible service works:
- Amazon S3 — the original, fully managed.
- MinIO — self-hosted, S3 API-compatible, great for dev and on-prem.
- Cloudflare R2, Backblaze B2, DigitalOcean Spaces — cheaper egress, same API.
Because they all speak the S3 protocol, the same boto3 client code targets any of them just by changing the endpoint URL.
Configuring a boto3 S3 Client
The boto3 library is the standard AWS SDK for Python. To target a non-AWS provider like MinIO or R2, pass an explicit endpoint_url.
Keep credentials and the endpoint in settings, never hard-coded. Use config=Config(signature_version="s3v4") so presigned URLs are generated with the modern SigV4 algorithm that all providers accept.
import boto3
from botocore.config import Config
def build_s3_client():
return boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.amazonaws.com",
aws_access_key_id="AKIA...",
aws_secret_access_key="secret...",
region_name="eu-central-1",
config=Config(signature_version="s3v4"),
)
client = build_s3_client()
print(type(client).__name__)The Naive Approach (and Why It Hurts)
The obvious first attempt is to read the whole upload into memory, then hand it to S3:
data = await file.read()loads the entire file into RAM.- A 2 GB video upload becomes 2 GB of process memory — multiply by concurrent requests and your workers OOM-crash.
This works for tiny avatars but is dangerous for media. We want to stream bytes through the API (or skip the API entirely with presigned URLs). The next scenes build up both techniques.
from fastapi import FastAPI, UploadFile
app = FastAPI()
@app.post("/upload-naive")
async def upload_naive(file: UploadFile):
data = await file.read() # whole file in RAM - avoid for large media!
return {"size": len(data)}Streaming Uploads with upload_fileobj
FastAPI's UploadFile wraps a SpooledTemporaryFile: small uploads stay in memory, large ones spill to disk automatically. Its .file attribute is a standard file-like object.
boto3's upload_fileobj reads that stream in chunks and performs a multipart upload under the hood — so memory stays bounded regardless of file size.
from fastapi import FastAPI, UploadFile
app = FastAPI()
BUCKET = "user-media"
@app.post("/upload")
async def upload(file: UploadFile):
client.upload_fileobj(
Fileobj=file.file, # streams in chunks, no full read
Bucket=BUCKET,
Key=f"uploads/{file.filename}",
ExtraArgs={"ContentType": file.content_type},
)
return {"key": f"uploads/{file.filename}"}Don't Block the Event Loop
boto3 is synchronous. Calling upload_fileobj directly inside an async def endpoint blocks the event loop while bytes travel to S3, stalling every other request on that worker.
Offload the blocking call to a thread pool with run_in_threadpool (Starlette) or asyncio.to_thread. Now the event loop stays free to serve other connections.
from fastapi import FastAPI, UploadFile
from fastapi.concurrency import run_in_threadpool
app = FastAPI()
BUCKET = "user-media"
@app.post("/upload")
async def upload(file: UploadFile):
key = f"uploads/{file.filename}"
await run_in_threadpool(
client.upload_fileobj, file.file, BUCKET, key,
{"ContentType": file.content_type},
)
return {"key": key}Presigned URLs: Let Clients Talk to S3 Directly
Streaming through the API still spends your bandwidth and CPU twice (client→API, API→S3). The most scalable pattern removes the API from the data path entirely using a presigned URL.
A presigned URL is a temporary, signed link that grants permission for one specific operation (PUT or GET) on one object, expiring after N seconds. The client uploads directly to S3; your API only signs the request.
- API stays stateless and tiny — it never touches the bytes.
- Credentials never leave the server; the signature encodes the grant.
Generating a Presigned PUT URL
Use generate_presigned_url with the put_object client method to mint an upload link. The endpoint returns the URL plus the final object key; the browser then issues a plain HTTP PUT to that URL with the file body.
Set a short ExpiresIn (e.g. 300–900 seconds) — just long enough to start the upload.
import uuid
from fastapi import FastAPI
app = FastAPI()
BUCKET = "user-media"
@app.post("/uploads/presign")
def presign_put(filename: str, content_type: str):
key = f"uploads/{uuid.uuid4()}-{filename}"
url = client.generate_presigned_url(
ClientMethod="put_object",
Params={"Bucket": BUCKET, "Key": key, "ContentType": content_type},
ExpiresIn=600,
)
return {"upload_url": url, "key": key}The Client-Side Upload Flow
With a presigned PUT URL, the browser uploads with a single request — no multipart form, just the raw body. The flow is:
- 1. Client asks your API for a presigned URL (sends filename + content type).
- 2. API returns
upload_urland the finalkey. - 3. Client does
PUT upload_urlwith the file bytes and the matchingContent-Typeheader. - 4. Client notifies your API of the
keyso you can persist it in the DB.
The Content-Type on the PUT must match the one you signed, or S3 returns 403.
// Browser-side (illustrative)
const { upload_url, key } = await api.presign(file.name, file.type);
await fetch(upload_url, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
});
await api.confirm(key);Serving Private Files with Presigned GET URLs
Make buckets private by default. To let a user download or view a file, generate a short-lived presigned get_object URL on demand instead of making the object public.
This keeps access controlled by your API's auth: check the user owns the file, then sign a URL valid for a few minutes. Embed it in an <img> src or return it as a redirect.
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
BUCKET = "user-media"
@app.get("/files/{key:path}")
def download(key: str):
url = client.generate_presigned_url(
ClientMethod="get_object",
Params={"Bucket": BUCKET, "Key": key},
ExpiresIn=300,
)
return RedirectResponse(url)Constraining Uploads with Presigned POST
A presigned PUT URL cannot limit file size — a malicious client could upload a 50 GB file. When you need server-enforced limits, use generate_presigned_post instead.
It returns a URL plus form fields, and lets you attach conditions like content-length-range and an exact content type. S3 rejects the upload server-side if the bytes violate the policy.
from fastapi import FastAPI
app = FastAPI()
BUCKET = "user-media"
@app.post("/uploads/presign-post")
def presign_post(key: str, content_type: str):
return client.generate_presigned_post(
Bucket=BUCKET,
Key=key,
Fields={"Content-Type": content_type},
Conditions=[
{"Content-Type": content_type},
["content-length-range", 1, 10 * 1024 * 1024], # max 10 MB
],
ExpiresIn=600,
)A Reusable Key Builder
Object keys should be collision-proof, organized, and never trust the client's raw filename (which can contain ../ or odd characters). A small helper centralizes this logic and is pure Python — easy to unit test.
A good key includes a logical prefix (owner, category), a UUID for uniqueness, and a sanitized extension.
import re
import uuid
def build_key(user_id: int, filename: str) -> str:
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "bin"
ext = re.sub(r"[^a-z0-9]", "", ext)[:8] or "bin"
return f"users/{user_id}/{uuid.uuid4().hex}.{ext}"
print(build_key(42, "My Vacation.JPG"))
print(build_key(7, "../../etc/passwd"))
print(build_key(1, "noext"))Quick Check: Choosing the Scalable Pattern
You are building an endpoint that lets users upload large videos (up to 2 GB). You want the FastAPI service to stay stateless and to avoid routing the file bytes through the API server at all. Which approach best fits?
Recap: Stateless Media at Scale
You now have a full toolkit for offloading storage to S3-compatible buckets:
- One client, many providers —
boto3with anendpoint_urltargets S3, MinIO, R2, Spaces. - Never buffer whole files — if bytes must pass through the API, use
upload_fileobjand offload it withrun_in_threadpoolso the event loop stays free. - Prefer presigned URLs — clients PUT/GET directly against S3; the API only signs, staying stateless.
- Enforce limits with
generate_presigned_postand acontent-length-rangecondition. - Keep buckets private and serve files via short-lived presigned GET links gated by your auth.
- Sanitize keys — UUID-based, prefixed, never trusting raw filenames.
The result is an API that handles 2 GB or 2 KB uploads with the same bounded footprint.
Questions Fréquemment Posées
La leçon « Délégation du stockage vers des compartiments compatibles S3 » est-elle gratuite ?
Oui — le texte complet de « Délégation du stockage vers des compartiments compatibles S3 » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours FastAPI Backend Development Bootcamp, passe à CoddyKit PRO. Le cours FastAPI Backend Development Bootcamp comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Délégation du stockage vers des compartiments compatibles S3 » ?
Diffusez directement les téléversements vers S3/MinIO avec des URL pré-signées afin de garder l’API sans état et évolutive. Tu pratiques FastAPI Backend Development Bootcamp avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer FastAPI Backend Development Bootcamp ?
Aucune expérience préalable n'est requise. FastAPI Backend Development Bootcamp sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « Délégation du stockage vers des compartiments compatibles S3 » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon FastAPI Backend Development Bootcamp ?
Oui. Chaque leçon FastAPI Backend Development Bootcamp inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Téléversements multiparties et validation du contenu
- Réponses en flux continu et requêtes par plages
- Délégation du stockage vers des compartiments compatibles S3
- Transformation asynchrone d’images et de documents