from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
# --- Constants --- #
templates = Jinja2Templates(directory="./templates")
# --- Error handler --- #
def lost_page(request, exception):
headers = {"Content-Type": "text/html"}
if isinstance(exception, HTTPException):
status_code = exception.status_code
detail = exception.detail
elif isinstance(exception, Exception):
status_code = 500
detail = "Server Error"
headers["X-Error-Message"] = exception.__class__.__name__
headers["X-Error-Line"] = str(exception.__traceback__.tb_lineno)
else:
status_code = 500
detail = f"Server Error\n\nDetails: {exception}"
return templates.TemplateResponse(
"404.html",
{"request": request, "status_code": status_code, "detail": detail},
status_code=status_code,
headers=headers,
)
exception_handlers = {num: lost_page for num in range(400, 599)}
app = FastAPI(exception_handlers=exception_handlers)
This is a snippet I've used across a few projects, it's essentially a catch-all for all 400 and 500 status codes.
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
# --- Constants --- #
templates = Jinja2Templates(directory="./templates")
This block imports relevant libraries and initializes Jinja2Templates, which allows us to render HTML using FastAPI. Docs.
Let's dissect
def lost_page(request, exception):
headers = {"Content-Type": "text/html"}
if isinstance(exception, HTTPException):
status_code = exception.status_code
detail = exception.detail
elif isinstance(exception, Exception):
status_code = 500
detail = "Server Error"
headers["X-Error-Message"] = exception.__class__.__name__
headers["X-Error-Line"] = str(exception.__traceback__.tb_lineno)
else:
status_code = 500
detail = f"Server Error\n\nDetails: {exception}"
return templates.TemplateResponse(
"404.html",
{"request": request, "status_code": status_code, "detail": detail},
status_code=status_code,
headers=headers,
)
FastAPI's exception handler provides two parameters, the request object that caused the exception, and the exception that was raised.
def lost_page(request, exception):
^^ Our function takes these two parameters.
headers = {"Content-Type": "text/html"}
These are the headers we're going to send back along with the request.
if isinstance(exception, HTTPException):
status_code = exception.status_code
detail = exception.detail
elif isinstance(exception, Exception):
status_code = 500
detail = "Server Error"
headers["X-Error-Name"] = exception.__class__.__name__
else:
status_code = 500
detail = f"Server Error\n\nDetails: {exception}"
If the exception
parameter is a HTTPException (raised by Starlette/FastAPI), then we're going to set the status_code and detail appropriately. An example of an HTTPException is a 404 error, if you try accessing an endpoint that doesn't exist, a HTTPException is raised and handled automatically by FastAPI.
Then, we check if it's an instance of Exception
, which is one of Python's in-built exception classes. This covers exceptions such as ZeroDivisionError
, FileNotFoundError
, etc. This usually means that it's an issue with our code, such as trying to open a file that doesn't exist, dividing by zero, using an unknown attribute, or something else that raised an exception which wasn't handled inside of the endpoint function.
The else
block shouldn't trigger in any case, and can be removed, it's just something I keep to appease my conscience.
After the status_code
, detail
and headers are set,
return templates.TemplateResponse(
"404.html",
{"request": request, "status_code": status_code, "detail": detail},
status_code=status_code,
headers=headers,
)
We return our 404 template, the TemplateResponse function takes in a few parameters, "404.html"
being the file we want to return, {"request": request, "status_code": status_code, "detail": detail}
being the request object and the values for embeds we want to fill (embeds are a way to pass information between jinja2 and Python). Then we define the status code of the response, along with its headers.
This is a 404 html template I use alongside the error handler.
exception_handlers = {num: lost_page for num in range(400, 599)}
app = FastAPI(exception_handlers=exception_handlers)
Exception handlers uses dict comprehension to create a dictionary of status codes, and the functions that should be called,
exception_handlers = {400: lost_page, 401: lost_page, 402: lost_page, ...}
Is how it'll look after the comprehension, until 599.
FastAPI Allows us to pass this dict as a parameter of the FastAPI
class,
app = FastAPI(exception_handlers=exception_handlers)
This tells FastAPI to run the following functions when the endpoint function returns a particular status code.
To conclude, the snippet above and this error template should help you handle all FastAPI errors in a nice, user-friendly and clean way.