FastAPI x GenAI

Pydantic Basics

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=False

Let's understand the code:

  1. Inherit from BaseModel and declare fields with type hints. No __init__ needed, Pydantic writes it for you.

  2. published: bool = False gives a default, so we can skip it when creating a blog.

  3. 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 = False
  1. str | None says the value can be a string or nothing.

  2. = None makes 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 post

Let's review this nested model code:

  1. comments: list[Comment] nests one model inside another.

  2. We passed plain dicts, and Pydantic converted each one into a proper Comment object, validating along the way.

  3. 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 :)
  1. model_dump() gives you a dict, model_dump_json() gives you a JSON string.

  2. 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.