From 7115c1325edd43594a25f8d61d20fd8a264b58d6 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Mon, 14 Apr 2025 10:42:22 +0200 Subject: [PATCH] Updated docs --- docs/en/docs/tutorial/handling-errors.md | 31 ++++++++++++++++++ docs_src/handling_errors/tutorial007.py | 39 +++++++++++++++++++++++ docs_src/handling_errors/tutorial008.py | 40 ++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 docs_src/handling_errors/tutorial007.py create mode 100644 docs_src/handling_errors/tutorial008.py diff --git a/docs/en/docs/tutorial/handling-errors.md b/docs/en/docs/tutorial/handling-errors.md index 4d969747f..40b00ddf3 100644 --- a/docs/en/docs/tutorial/handling-errors.md +++ b/docs/en/docs/tutorial/handling-errors.md @@ -109,6 +109,37 @@ You could also use `from starlette.requests import Request` and `from starlette. /// +### Handle multiple exceptions or status codes + +You can register the same handler for multiple exceptions or multiple status codes at once. Just pass a list or tuple of them to `@app.exception_handler(...)`. + +This is useful when you want to group related errors together and respond with the same logic. + +For example, if you want to treat 401 Unauthorized and 403 Forbidden as access-related issues: + +{* ../../docs_src/handling_errors/tutorial007.py hl[15:20,33,37] *} + +Raising an `HTTPException` with either a `401` or `403` status code will result in the same response detail: + +```JSON +{"detail": "Access denied. Check your credentials or permissions."} +``` + +Or you can handle multiple exception classes like this: + +{* ../../docs_src/handling_errors/tutorial008.py hl[10:12,15:17,20:25,32,38] *} + +Here, if your request causes either a `FileTooLargeError` or an `UnsupportedFileTypeError`, the `custom_exception_handler` will be used to handle the exception and add a `hint` field to the response: + +```JSON +{ + "error": "The uploaded file is too large.", + "hint": "Need help? Contact support@example.com" +} +``` + +This allows for simpler, more maintainable error handling when several conditions should result in the same kind of response. + ## Override the default exception handlers **FastAPI** has some default exception handlers. diff --git a/docs_src/handling_errors/tutorial007.py b/docs_src/handling_errors/tutorial007.py new file mode 100644 index 000000000..47d59466b --- /dev/null +++ b/docs_src/handling_errors/tutorial007.py @@ -0,0 +1,39 @@ +from typing import Union + +from fastapi import FastAPI, HTTPException, Request +from fastapi.responses import JSONResponse + +app = FastAPI() + +FAKE_DB = { + 0: {"name": "Admin", "role": "ADMIN"}, + 1: {"name": "User 1", "role": "USER"}, + 2: {"name": "User 2", "role": "USER"}, +} + + +@app.exception_handler([401, 403]) +async def handle_auth_errors(request: Request, exc: Exception): + return JSONResponse( + status_code=exc.status_code if isinstance(exc, HTTPException) else 403, + content={"detail": "Access denied. Check your credentials or permissions."}, + ) + + +@app.get("/secrets/") +async def get_secrets(auth_user_id: Union[int, None] = None): + # Get authenticated user info (not a production-ready code) + if auth_user_id is not None: + auth_user_info = FAKE_DB.get(auth_user_id) + else: + auth_user_info = None + + # Return 401 status code if user not authenticated + if auth_user_info is None: + raise HTTPException(status_code=401) # Not authenticated + + # Return 403 status code if user is not authorized to get secret information + if auth_user_info["role"] != "ADMIN": + raise HTTPException(status_code=403) # Not authorized + + return {"data": "Secret information"} diff --git a/docs_src/handling_errors/tutorial008.py b/docs_src/handling_errors/tutorial008.py new file mode 100644 index 000000000..b05652647 --- /dev/null +++ b/docs_src/handling_errors/tutorial008.py @@ -0,0 +1,40 @@ +from fastapi import FastAPI, File, HTTPException, Request, UploadFile +from fastapi.responses import JSONResponse + +MAX_FILE_SIZE_MB = 5 +ALLOWED_TYPES = {"application/pdf", "image/jpeg"} + +app = FastAPI() + + +class FileTooLargeError(HTTPException): + def __init__(self): + super().__init__(status_code=413, detail="The uploaded file is too large.") + + +class UnsupportedFileTypeError(HTTPException): + def __init__(self): + super().__init__(status_code=415, detail="Unsupported file type") + + +@app.exception_handler((FileTooLargeError, UnsupportedFileTypeError)) +def custom_exception_handler(request: Request, exc: HTTPException): + return JSONResponse( + status_code=exc.status_code, + content={"error": exc.detail, "hint": "Need help? Contact support@example.com"}, + ) + + +@app.post("/upload/") +async def upload_file(file: UploadFile = File(...)): + # Validate file type + if file.content_type not in ALLOWED_TYPES: + raise UnsupportedFileTypeError() + + # Validate file size (read contents to check size in memory) + contents = await file.read() + size_mb = len(contents) / (1024 * 1024) + if size_mb > MAX_FILE_SIZE_MB: + raise FileTooLargeError() + + return {"filename": file.filename, "message": "File uploaded successfully!"}