Sanic and Data Validation using Pydantic
Sanic and Data Validation using Pydantic
In the previous article, we implemented simple CRUD. But from requests, data doesn’t arrive as planned. Like we want our student’s name to be string, not an integer or float type.
For this purpose, we are using a Python package named Pydantic.
pip install pydantic
After installing pydantic import BaseModel.
from pydantic import BaseModel
Now, we have to create our Pydantic model for students inheriting BaseModel.
class Student(BaseModel):
name: str
grade: str
roll: int
email: str
phone: int
subjects: List[str]
friends: List[str]
Now, we create an instance of Student [this is for demonstration purposes only].
student = Student(
name=in_memory_student_db[0]["name"],
grade=in_memory_student_db[0]["grade"],
roll=in_memory_student_db[0]["roll"],
email=in_memory_student_db[0]["email"],
phone=in_memory_student_db[0]["phone"],
subjects=in_memory_student_db[0]["subjects"],
friends=in_memory_student_db[0]["friends"]
)
or
student = Student(**in_memory_student_db[0])
Both ways we can create Student objects.
What does the pydantic class do?
- pydantic class allows custom data types to be defined or we can extend validation with methods on a model decorated with the validator decorator
- Here, The datatype of name, grade, roll, email, phone, subjects, friends must be as mentioned in pydantic class else it will raise an error (pydantic validation error)
But if you pass roll = “16” then it won’t show any error as it is type-converted to an integer.
Now, Since we made Student class we can make a Student model and then work on that model as it validates data as we want in our system.
For getting an error message in JSON format:
app.config.FALLBACK_ERROR_FORMAT = "json"
Since the Student object is not JSON serializable, we use the dict method and make the Student object JSON serializable.
- Since we don’t want to save Student objects in our real database, rather we want to map and insert data into rows in case of SQL or as JSON objects in NoSQL, So we convert the Student object into a python dictionary.
Why make Student object JSON serializable?
- In Python “serialization” does nothing else than just convert the given data structure (e.g. a
dict
) into its valid JSON object. JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object). - When transmitting a student in a file, the student is required to be byte strings, but Pydntic(here Student) objects are seldom in this format. Serialization can convert these Pydantic objects into byte strings for such use. After the byte strings are transmitted, the receiver will have to recover the original object from the byte string. ref
- We need JSON Serializable objects to provide HTTP Response.
We now modify our code according to the Student object.
For the POST Method,
@app.post("/")
async def post_student(request):
student = Student(**request.json)
in_memory_student_db.append(student.dict())
return response.json("message":"inserted","data":student.dict())
- We will request data in JSON format and pass the data to the Student class, Which will Return the Student object and store it in the student variable.
- before inserting the student object into DB, we converted the Student object to a python dictionary and then inserted it into the database.
- for the response that we just inserted, we again converted the Student object to a python dictionary.
For the PUT method:
@app.put("/<id_:int>")
async def update_student(request, id_):
student = Student(**request.json)
if id_ in range(len(in_memory_student_db)):
in_memory_student_db[id_] = student.dict()
return response.json({"message":"updated Successfully"})
return response.json({"error": "No Student with given id"})
- We pass id and mention that id_ is of type integer,
- We will request data to be updated in JSON format and pass the data to the Student class, Which will Return the Student object and store it in the student variable.
- before inserting student objects into DB, we check whether the provided id exists or not.
- if id_ exists, we converted the Student object to python dictionary and then inserted it into the database and provide Success message.
- else we provide the error message.
For the GET Method:
Since getting data from the database doesn’t need to be validated but the data to be inserted in the database needs to be validated, So we won’t use Student Model while fetching data.
@app.get("/")
async def get_student(request):
id_ = int(request.args.get("id", -1))
if id_ != -1:
if id_ in range(len(in_memory_student_db)):
return response.json(in_memory_student_db[id_])
else:
return response.json(
{
"status": "error",
"message": f"data with id {id_} not found. Try with different id!!!",
})
return response.json(in_memory_student_db)
- We pass query parameter id to get data of particular id. if the query parameter is not passed default value if id_ is -1
- if the value if id_ is not -1 then we check whether or not id_ exists in the database, if exists we provide a response of that data else we provide data with a given id not found.
- else we provide all data that is in the database. i.e if id value is -1 (the default one)
Now you will get a response like this:
For the DELETE Method
@app.delete("/<id_:int>")
async def delete_student(request, id_):
if id_ in range(len(in_memory_student_db)):
del in_memory_student_db[id_]
return response.json({"message": "Deleted student successfully"})
return response.json({"error": "No Student with given id"})
- We pass id and mention that id_ is of type integer,
- we check whether or not id_ exists in the database, if exists, delete the data and provide a response that the data is deleted
- else we provide the error message.
In this way we can validate our data, we can use custom validators in the pydantic model too, like if we require to accept the name that must contain space then you can specify a validator for that,
from pydantic import validator
@validator("name")
def name_must_contain_space(cls, name):
if " " not in name:
raise ValueError("must contain a space")
return name.title()
This completes CRUD operations and we learned to validate data using pydantic models. In the next series, instead of using an in-memory database, we will use RDBMS to store our data.
Join the conversation