π‘οΈ Applying Field-Level Validation with Pydantic
In this phase, we will apply specific validation rules to each field inside the BookRequest model.
π Instead of just checking types, we will now enforce constraints such as:
Minimum string length
Maximum string length
Numeric boundaries
To achieve this, we import and use Field from Pydantic.
π¦ Step 1 β Importing Field
from fastapi import FastAPI
from pydantic import BaseModel, Fieldπ§ Backend Understanding
Remember:
Bookis your internal domain modelIt assumes the data is already trusted
Validation must happen before creating this object
That validation will now happen inside BookRequest.
π§± Step 2 β Defining Validation Rules in BookRequest
Now we enhance the request model:
class BookRequest(BaseModel):
id: int
title: str = Field(min_length=3)
author: str
description: str
rating: intπ What Field(min_length=3) Does
title: str = Field(min_length=3)π This ensures the title:
Must be at least 3 characters long
Cannot be empty or too short
π§ Important:
This validation happens before your endpoint function runs.
<aside> π‘
FastAPI β Pydantic β Validation β THEN your function executes.
</aside>
π Step 3 β Testing Validation in the Browser
Try creating a book with:
Title =
"Hi"(less than 3 characters)
You will receive:
HTTP 422 β Unprocessable Entityπ§ Why HTTP 422?
422 means:
The request structure was correct
The data failed validation rules
This is professional API behavior.
π Step 4 β Expanding Validation Rules
Now letβs apply constraints to multiple fields:
class BookRequest(BaseModel):
id: int
title: str = Field(min_length=3)
author: str = Field(min_length=1)
description: str = Field(min_length=1, max_length=100)
rating: int = Field(gt=0, lt=6)π Field Rule Breakdown
Field | Rule | Meaning |
|---|---|---|
|
| Must be at least 3 characters |
|
| Cannot be empty |
|
| Between 1 and 100 characters |
|
| Must be between 1 and 5 |
π Testing Rating Validation
Try creating a book with:
rating = 0Result:
HTTP 422 β Unprocessable Entityπ’ Step 5 β Ensuring Unique Book IDs
Right now, users must manually provide an id.
Thatβs not ideal.
Instead, weβll automatically generate unique IDs.
π Create an ID Generator Function
def find_book_id(book: Book):
if len(BOOKS) > 0:
book.id = BOOKS[-1].id + 1
else:
book.id = 1
return bookπ§ What This Does
If books exist β increment last ID
If list is empty β start at 1
This ensures:
No duplicate IDs
Predictable sequential assignment
π Step 6 β Apply ID Generation Before Saving
Update your endpoint:
@app.post("/create_book")
async def create_book(book_request: BookRequest):
new_book = Book(**book_request.model_dump())
BOOKS.append(find_book_id(new_book))Code Explanation
Any
idprovided in the request is ignoredThe
find_book_id()function automatically assigns a correct IDIf the list is empty, the ID starts from 1
Otherwise, the ID is set to the last bookβs ID + 1
The updated book is then added to the
BOOKSlist
π‘ Cleaner Version
def find_book_id(book: Book):
book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
return bookπ Step 7 β Verifying ID Assignment
Create a new book in the browser.
You should see:
ID automatically assigned
ID incremented properly
π§Ύ Step 8 β Making id Optional
Now we improve usability.
Users should NOT have to supply an ID.
π Import Optional
from typing import Optionalπ Update the Model
class BookRequest(BaseModel):
id: Optional[int] = None
title: str = Field(min_length=3)
author: str = Field(min_length=1)
description: str = Field(min_length=1, max_length=100)
rating: int = Field(gt=1, lt=6)π§ What This Means
id: Optional[int] = NoneThe field may be missing
If missing β default value =
NoneOur ID generator will assign the correct value
Now the system controls ID generation β not the user.
π Final Browser Test
Create a book without providing an ID.
It should:
Pass validation
Auto-generate ID
Store correctly
ποΈ books2.py
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Book:
id: int
title: str
author: str
description: str
rating: int
def __init__(self, id, title, author, description, rating):
self.id = id
self.title = title
self.author = author
self.description = description
self.rating = rating
class BookRequest(BaseModel):
id: Optional[int] = None
title: str = Field(min_length=3)
author: str = Field(min_length=1)
description: str = Field(min_length=1, max_length=100)
rating: int = Field(gt=1, lt=6)
BOOKS = [
Book(1, 'Computer Science Pro', 'codingwithrobby', 'A very nice book', 3),
Book(2, 'Be Fast with FastAPI', 'codingwithrobby', 'A great book', 5),
Book(3, 'Master Endpoints', 'codingwithrobby', 'An awesome book', 5),
Book(4, 'HP1', 'Author 1', 'Book Description', 2),
Book(5, 'HP2', 'Author 2', 'Book Description', 3),
Book(6, 'HP3', 'Author 3', 'Book Description', 1)
]
@app.get("/books")
async def read_all_books():
return BOOKS
@app.post("/create_book")
async def create_book(book_request: BookRequest):
new_book = Book(**book_request.model_dump())
BOOKS.append(find_book_id(new_book))
def find_book_id(book: Book):
book.id = 1 if len(BOOKS) == 0 else BOOKS[-1].id + 1
return book