Once your API is deployed, it is open to the world. Some users will not understand what to fill in a form. Some are malicious and will happily type a SQL query into your signup box just to see what breaks. 😈 Rule number one of web development: never trust request data.
Back in the type hints lesson I told you hints are a promise, not a bouncer at the door. Today you meet the bouncer.
What is Pydantic?
Pydantic is a library for data validation using type hints. You declare what the data should look like, Pydantic makes sure it actually does. FastAPI is built on top of it, which is why we are here.
Install it first (virtual environment activated, please):
Our Blog, upgraded
Remember the plain Blog class with its __init__ boilerplate? Watch it slim down:
from pydantic import BaseModelclass Blog(BaseModel): title: str tags: list[str] published: bool = Falseblog = Blog(title="My First Blog", tags=["python"])print(blog)# title='My First Blog' tags=['python'] published=FalseLet's understand the code:
Inherit from
BaseModeland declare fields with type hints. No__init__needed, Pydantic writes it for you.published: bool = Falsegives a default, so we can skip it when creating a blog.This looks like the type hints from lesson one. The difference: these are now enforced.
The bouncer in action
Let's send in some garbage:
Blog(title="Second One", tags=["python"], published="yup!")Pydantic throws a ValidationError:
1 validation error for Blogpublished Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='yup!', input_type=str]published was declared as bool, and "yup!" is not something Pydantic can read as a boolean. Entry denied. The error tells you the field, the problem, and the input it received. Debugging gold when a request fails at 2am.ðŸ˜
Optional fields
Want a description, but not compulsory? Reach for the str | None hint from lesson one:
class Blog(BaseModel): title: str description: str | None = None tags: list[str] published: bool = Falsestr | Nonesays the value can be a string or nothing.= Nonemakes it truly optional. Skip it and the blog is still valid.
Models inside models
Blogs need comments. Comments can be a Pydantic model too:
class Comment(BaseModel): text: strclass Blog(BaseModel): title: str tags: list[str] comments: list[Comment] = [] published: bool = Falseblog = Blog( title="My First Blog", tags=["python"], comments=[{"text": "Nice post"}, {"text": "Needs more emojis"}],)print(blog.comments[0].text) # Nice postLet's review this nested model code:
comments: list[Comment]nests one model inside another.We passed plain dicts, and Pydantic converted each one into a proper
Commentobject, validating along the way.This is exactly how JSON request bodies with nested data will work in FastAPI.
Getting data back out
Models also know how to turn themselves back into plain data:
blog = Blog(title="My First Blog", tags=["python"])print(blog.model_dump())# {'title': 'My First Blog', 'tags': ['python'], 'comments': [], 'published': False}print(blog.model_dump_json())# {"title":"My First Blog","tags":["python"],"comments":[],"published":false} #this is proper json :)model_dump()gives you a dict,model_dump_json()gives you a JSON string.FastAPI uses this under the hood to turn your models into API responses.
This was just a minimal basic guide that i wrote, actually pydantic is much much more powerful.
