Full lesson
Explore the full explanation, examples, and visuals at your own pace.
Part 1
Where does an order request get checked? At the API boundary: the payload enters OrderCreate, and only after validation succeeds does the handler receive its parsed fields. That keeps input validation ahead of application logic.
The request contract
This model defines what the handler can accept: sku is required and nonempty, and quantity is required, positive, and strictly an integer. Coupon is optional, defaulting to None. Unexpected fields are forbidden, so an unknown key rejects the request.
from pydantic import BaseModel, ConfigDict, Field
class OrderCreate(BaseModel):
model_config = ConfigDict(extra='forbid')
sku: str = Field(min_length=1)
quantity: int = Field(gt=0, strict=True)
coupon: str | None = NoneA valid order
With SKU BOOK-1 and integer quantity 2, model_validate builds an OrderCreate instance. Calling model_dump turns that validated model back into a dictionary. Coupon appears as None because the field was omitted and its default supplies that value.
payload = {'sku': 'BOOK-1', 'quantity': 2}
order = OrderCreate.model_validate(payload)
print(order.model_dump())
# {'sku': 'BOOK-1', 'quantity': 2, 'coupon': None}A string quantity is rejected
Because quantity uses strict integer validation, the string '2' isn't converted to the number two. Pydantic raises a ValidationError, and int_type tells us the field's type failed—not that the SKU or order violated a business rule.
from pydantic import ValidationError
try:
OrderCreate.model_validate(
{'sku': 'BOOK-1', 'quantity': '2'}
)
except ValidationError as exc:
print(exc.errors()[0]['type'])
# int_typeStrictness changes the outcome
A Pydantic model validates declared input constraints, not whether an order is true: here, the default integer field coerces the string “2” to 2 and passes the positive-value check, while OrderCreate’s strict field rejects that string.
class LooseOrder(BaseModel):
quantity: int = Field(gt=0)
print(LooseOrder.model_validate(
{'quantity': '2'}
).quantity)
# 2OrderCreate receives quantity as the string '2'. What happens?
Let's think this through. OrderCreate receives quantity as the string '2'. What happens? A: It becomes the integer 2. B: Validation rejects its type. C: It fails the positive-number check. Choose an answer, or just think it through. I'll explain in a moment.
- It becomes the integer 2
- Validation rejects its type
- It fails the positive-number check
OrderCreate receives quantity as the string '2'. What happens?
The answer is B: Validation rejects its type. The quantity field is marked strict, so a numeric string is not converted to an integer. Without strict validation, Pydantic would normally convert this particular string to 2; positivity is not the issue.
- It becomes the integer 2
- Validation rejects its type
- It fails the positive-number check
Nullable does not mean omittable
For the order’s coupon, both declarations accept an explicit None. The first still requires the field to be present; in Pydantic v2, adding a default of None makes it omittable. A missing coupon is valid only with that default.
class RequiredCoupon(BaseModel):
coupon: str | None
class OmittableCoupon(BaseModel):
coupon: str | None = NoneValidation stops short of business truth
OrderCreate can confirm that the payload has valid declared fields, like a string SKU and an integer quantity. That doesn't prove BOOK-1 exists or that stock is available. The Handler must check the DB separately before accepting the order. Pydantic validates input shape, not business truth.
Three boundary gotchas
For OrderCreate, strict quantity validation rejects the string “2” instead of coercing it. Give coupon a default of None if omission is allowed, and set extra to “forbid” to reject unknown payload keys.
- Coercion depends on strictness
- Nullable fields still need defaults
- Unknown keys need an explicit policy
Check the payload, then check the order
Check the payload, then check the order. Pydantic validates declared fields before your handler uses them, but valid input doesn’t prove the SKU exists or that inventory can fulfill it.

