User Avatars¶
A complete example of handling user avatar uploads with image validation, UUID filenames, and per-user directories.
Full Example¶
app.py
import uuid
from pathlib import Path
from fastapi import Depends, FastAPI, HTTPException
from filestore import FileStore, LocalEngine, Store, StoreConfig, UploadContext
app = FastAPI()
def avatar_filename(ctx: UploadContext) -> str:
"""Generate a UUID filename preserving the original extension."""
ext = Path(ctx.file.filename or "").suffix.lower()
return f"{uuid.uuid4()}{ext}"
async def user_directory(ctx: UploadContext) -> Path:
"""Route uploads to per-user directories."""
user_id = ctx.request.headers.get("X-User-ID", "default")
return Path("uploads/avatars") / user_id
storage = FileStore(
"avatar",
required=True,
engine=LocalEngine(base_url="/media/avatars"),
config=StoreConfig(
destination=user_directory,
filename=avatar_filename,
allowed_extensions=[".jpg", ".jpeg", ".png", ".webp"],
allowed_content_types=["image/jpeg", "image/png", "image/webp"],
max_file_size=2 * 1024 * 1024, # 2 MB
overwrite=True, # Replace existing avatar
),
)
@app.post("/users/avatar")
async def upload_avatar(store: Store = Depends(storage)):
if not store:
raise HTTPException(status_code=422, detail=store.errors)
avatar = store.first("avatar")
return {
"url": avatar.url,
"size": avatar.size,
"content_type": avatar.content_type,
}
Key Points¶
- UUID filenames prevent collisions and information leakage
- Per-user directories via a destination callback on
UploadContext - Extension + content-type validation for defense in depth
- 2 MB limit prevents abuse
overwrite=Truereplaces the old avatar automatically