Schema Validation with Pydantic
Why do we need schema?
It's a pain to get all the values from the body
The client can send whatever data they want
The data isn't getting validated
We ultimately want to force the client to send data in a schema that we expect
Pydantic
This is already installed if you have used the
[all]
installation flag
pip install fastapi[all]
It is it's own separate module and has nothing to do with FastAPI (can be used separately)
FastAPI uses it so that it can define a schema
So how do you validate the data using Pydantic?
First you import the
BaseModel
frompydantic
from pydantic import BaseModel
Then you define a class that inherits from the
BaseModel
and set up the schema
class Post(BaseModel):
title: str
content: str
Once you have defined the class correctly, you can pass this to the function for the Path Operation
@app.post("/createpost")
def create_posts(post: Post):
print(post)
return {"data": "new post"}
We are referencing the Post class and assigning it to a variable on line 2
After which we are printing the post to the console
The good thing about doing things in this way is that our data gets validated automatically and our API does not accept integers for example
It only accepts strings as input, otherwise it throws an error
If you check the console you can see the following output with the data validated:
INFO: Waiting for application startup.
INFO: Application startup complete.
title='top gun' content='check out my top gun'
INFO: 127.0.0.1:48886 - "POST /createpost HTTP/1.1" 200 OK
Extracting the data:
This is made easy by defining the schema as mentioned above
All you have to do is add the field you want it to print
@app.post("/createpost")
def create_posts(post: Post):
print(post.title) # Note that we added the title here
return {"data": "new post"}
Output:
INFO: Application startup complete.
top gun
INFO: 127.0.0.1:33096 - "POST /createpost HTTP/1.1" 200 OK
Data validation:
What happens if we remove the title field?
If we remove the title from the HTTP Packet in postman and have it as such:
{
"content": "check out my top gun"
}
Once you send the packet you will get the following error message:
{
"detail": [
{
"loc": [
"body",
"title"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
It is throwing an error as it is expecting both a title and content to process
It let's us know that the value is missing:
"type": "value_error.missing"
What happens if we provide a string as input for title?
We are sending the HTTP Packet via Postman as such:
{
"title": 1,
"content": "check out my top gun"
}
It will not throw an error as it will try to convert whatever data we are trying to give it to the type specified (string)
INFO: Application startup complete.
title='1' content='check out my top gun'
INFO: 127.0.0.1:56232 - "POST /createpost HTTP/1.1" 200 OK
Letting the user define data
We will have to extend our class with to look as follows:
class Post(BaseModel):
title: str
content: str
published: bool = True
# The above line decides if a Post gets published
# Also defaults to True
Now you have 3 options on sending the HTTP Packet via Postman
// Setting it to True
{
"title": "top gun",
"content": "check out my top gun",
"published": true
}
// Setting it to False
{
"title": "top gun",
"content": "check out my top gun",
"published": false
}
// Not specifying it as it defaults to True
{
"title": "top gun",
"content": "check out my top gun"
}
Setting an optional field that defaults to None
You will need to import
Optional
from thetyping
library
from typing import Optional
Then we will extend the class for post ratings
class Post(BaseModel):
title: str
content: str
published: bool = True
rating: Optional[int] = None
# Note that the above line is Optional
# Requires an integer value (makes sense for ratings)
# Defaults to None
Now we can send HTTP Packets like so:
// Not Specifed as it is optional, returns None
{
"title": "top gun",
"content": "check out my top gun"
}
// Specified with number, returns 4
{
"title": "top gun",
"content": "check out my top gun"
"rating": 4
}
// Specifing but with a string, returns error - see below
{
"title": "top gun",
"content": "check out my top gun"
"rating": "hello"
}
// Error Recived:
{
"detail": [
{
"loc": [
"body",
"rating"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
The error raised is that the value provided is not integer:
"type": "type_error.integer"
Note that all these values that you provide via HTTP Packet are stored in a pydantic model - which is a specific type of storage
All these models have a
.dict
method to convert the data to a dictionarySo if you ever need to convert the data, use as follows:
@app.post("/createpost")
def create_posts(post: Post):
post.dict() # This will convert to dictionary data structure
Last updated