1. Advanced input validation and error handling
Let's learn how we can safely increase the complexity of our APIs with advanced input validation and error handling.
2. Why we need advanced input
First of all, why do we need advanced input in the first place? Let's go back to our "APIs are like restaurants" analogy. In fact, let's make an API for a restaurant! We want an API to accept restaurant orders and suggest additional items based on the customer's preferences, as predicted by an AI model.
Any order could have a variable number of items.
With a flat input schema, we can only support a fixed number of items, and we can only define each item as a string. We need more structure!
3. Nested Pydantic models
Pydantic's BaseModel supports nested structure out of the box. As usual, it does this using Python's standard type hint syntax. Here we have model Bar, which has model Foo nested inside.
Pydantic supports more complex nested structures with help from the typing library. In this case we will use the List class.
Here we have a simple OrderItem class with a name and a quantity.
Now we can create a RestaurantOrder model with a customer name and variable-length List of OrderItems, so OrderItems is now nested into RestaurantOrder.
4. Custom model validators
Complex models often benefit from custom validation. FastAPI supports custom validation using the RequestValidationError exception and Pydantic's model_validator annotation.
Let's use the RestaurantOrder model again as an example. What if we wanted to add a custom validator to ensure every order has at least one item?
We start by adding the model_validator annotation with parameter mode as "after". This tells the server to run the validator right after the default validations.
We then add a method called validate_after, which accepts self as the only argument and returns self. If the list of items in the order is empty, the method will raise a RequestValidationError with a specific message. RequestValidationError is the standard exception for Pydantic input validation failures, and it comes with its own error handler.
This ensures the validation error message will be relayed to the user just like when we use HTTPException.
5. Global exception handlers
FastAPI even lets us add our own exception handlers specific to any exception type. Perhaps we have built an AI chatbot API that returns structured data, but we want to make our API more friendly to human users. To this end, we want to return a PlainTextResponse for any RequestValidationError that directs the user to our API documentation.
We start by adding an @app.exception_handler annotation specific to RequestValidationError. This will override the default handler, which normally returns a JSONResponse.
Now we can add the function validation_exception_handler, which returns the PlainTextResponse.
6. Let's practice!
Now that we've learned advanced input validation and error handling, let's practice what we have learned!