Pydantic Models

Loading concept...

📦 Pydantic Models: Your Data’s Security Guard

Imagine you’re running a fancy restaurant. Before anyone enters, a bouncer checks their ID, dress code, and reservation. That bouncer is Pydantic—it checks every piece of data before it enters your FastAPI app!


🎯 What You’ll Learn

  • Request Body Basics
  • Creating Pydantic Models
  • Pydantic Field Validation
  • Pydantic Field Defaults
  • Nested Pydantic Models
  • List and Dict Fields
  • Model Inheritance
  • Request Example Data

📨 Request Body Basics

What’s a Request Body?

When someone sends data TO your app (like filling a form), that data travels in the request body. It’s like sending a letter inside an envelope.

graph TD A[📱 User fills form] --> B[📦 Data packed in body] B --> C[🚀 Sent to server] C --> D[🔍 FastAPI reads it]

Simple Example:

# User sends this JSON:
{
    "name": "Pizza",
    "price": 9.99
}

The request body carries this JSON to your server. But how do you tell FastAPI what shape this data should have? That’s where Pydantic comes in!


🏗️ Creating Pydantic Models

Your First Model

A Pydantic model is like a blueprint for your data. It says “my data should look exactly like THIS.”

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float

That’s it! You just created a bouncer that checks:

  • name must be text (string)
  • price must be a number (float)

Using It in FastAPI

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.post("/items/")
async def create_item(item: Item):
    return {"message": f"Created {item.name}"}

What happens:

  1. User sends JSON data
  2. Pydantic checks it matches the Item shape
  3. If wrong? → Error message sent back
  4. If correct? → Your code runs!

✅ Pydantic Field Validation

Adding Rules to Fields

The bouncer can do more than check types. It can check rules!

from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    price: float = Field(gt=0)  # gt = greater than
    quantity: int = Field(ge=1, le=100)  # ge/le = greater/less or equal

Common Validation Rules

Rule Meaning Example
gt Greater than gt=0 (must be > 0)
ge Greater or equal ge=1 (must be ≥ 1)
lt Less than lt=100
le Less or equal le=99
min_length Min string length min_length=3
max_length Max string length max_length=50

Example with all rules:

class Product(BaseModel):
    name: str = Field(
        min_length=2,
        max_length=100
    )
    price: float = Field(gt=0, le=10000)
    stock: int = Field(ge=0)

If someone sends {"name": "", "price": -5, "stock": -1}, Pydantic catches ALL the errors!


🎁 Pydantic Field Defaults

Making Fields Optional

Sometimes data is optional. Like toppings on pizza—nice to have, not required!

from pydantic import BaseModel
from typing import Optional

class Item(BaseModel):
    name: str                    # Required!
    price: float                 # Required!
    description: Optional[str] = None  # Optional
    tax: float = 0.0            # Default value

Three ways to set defaults:

class Order(BaseModel):
    # 1. Direct default value
    quantity: int = 1

    # 2. Optional (can be None)
    notes: Optional[str] = None

    # 3. Using Field with default
    priority: int = Field(default=5, ge=1, le=10)

What users can send:

# Minimal (uses all defaults):
{"name": "Pizza", "price": 9.99}

# Full (overrides defaults):
{
    "name": "Pizza",
    "price": 9.99,
    "description": "Cheesy goodness",
    "tax": 1.5
}

🪆 Nested Pydantic Models

Models Inside Models

Real data is often like Russian nesting dolls—boxes inside boxes!

class Address(BaseModel):
    street: str
    city: str
    country: str

class User(BaseModel):
    name: str
    email: str
    address: Address  # Nested model!

The JSON would look like:

{
    "name": "Alice",
    "email": "alice@example.com",
    "address": {
        "street": "123 Main St",
        "city": "Boston",
        "country": "USA"
    }
}
graph TD A[User Model] --> B[name: str] A --> C[email: str] A --> D[address: Address] D --> E[street: str] D --> F[city: str] D --> G[country: str]

Accessing Nested Data

@app.post("/users/")
async def create_user(user: User):
    # Access nested fields easily!
    city = user.address.city
    return {"user_city": city}

📋 List and Dict Fields

Lists: Multiple Items

What if a user wants to order multiple pizzas?

from typing import List

class Order(BaseModel):
    customer: str
    items: List[str]  # List of strings

JSON:

{
    "customer": "Bob",
    "items": ["Pizza", "Pasta", "Salad"]
}

Lists of Models

Even more powerful—lists of complex objects!

class Item(BaseModel):
    name: str
    price: float

class Order(BaseModel):
    customer: str
    items: List[Item]  # List of Item models!

JSON:

{
    "customer": "Bob",
    "items": [
        {"name": "Pizza", "price": 12.99},
        {"name": "Pasta", "price": 8.99}
    ]
}

Dict Fields

For key-value data:

from typing import Dict

class Inventory(BaseModel):
    products: Dict[str, int]  # product name → quantity

JSON:

{
    "products": {
        "apple": 50,
        "banana": 30,
        "orange": 45
    }
}

👨‍👩‍👧 Model Inheritance

Don’t Repeat Yourself!

If many models share fields, use inheritance!

class BaseItem(BaseModel):
    name: str
    description: Optional[str] = None

class Item(BaseItem):
    price: float  # Adds to base fields

class ItemWithTax(BaseItem):
    price: float
    tax: float    # Different addition

Both Item and ItemWithTax have:

  • name (from BaseItem)
  • description (from BaseItem)
  • Plus their own unique fields!

Real Example

class PersonBase(BaseModel):
    name: str
    email: str

class PersonCreate(PersonBase):
    password: str  # For creating accounts

class PersonPublic(PersonBase):
    id: int  # For returning data (no password!)
graph TD A[PersonBase] --> B[name: str] A --> C[email: str] D[PersonCreate] --> A D --> E[password: str] F[PersonPublic] --> A F --> G[id: int]

📝 Request Example Data

Show Examples in API Docs

FastAPI auto-generates beautiful docs. Make them even better with examples!

Method 1: Field Examples

class Item(BaseModel):
    name: str = Field(example="Super Widget")
    price: float = Field(example=29.99)

Method 2: model_config

class Item(BaseModel):
    name: str
    price: float

    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "name": "Awesome Gadget",
                    "price": 49.99
                }
            ]
        }
    }

Method 3: Multiple Examples

class Item(BaseModel):
    name: str
    price: float

    model_config = {
        "json_schema_extra": {
            "examples": [
                {"name": "Budget Phone", "price": 199.99},
                {"name": "Premium Phone", "price": 999.99}
            ]
        }
    }

When you visit /docs, you’ll see these examples ready to test!


🎉 Putting It All Together

Here’s a complete, real-world example:

from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import List, Optional

app = FastAPI()

class Address(BaseModel):
    street: str = Field(min_length=5)
    city: str
    zip_code: str = Field(pattern=r"^\d{5}quot;)

class OrderItem(BaseModel):
    product: str = Field(example="Pizza")
    quantity: int = Field(ge=1, le=10, default=1)
    price: float = Field(gt=0, example=9.99)

class Order(BaseModel):
    customer_name: str = Field(min_length=2)
    email: str
    shipping: Address
    items: List[OrderItem]
    notes: Optional[str] = None

    model_config = {
        "json_schema_extra": {
            "examples": [{
                "customer_name": "Alice",
                "email": "alice@email.com",
                "shipping": {
                    "street": "123 Main Street",
                    "city": "Boston",
                    "zip_code": "02101"
                },
                "items": [
                    {"product": "Pizza", "quantity": 2, "price": 12.99}
                ],
                "notes": "Ring doorbell"
            }]
        }
    }

@app.post("/orders/")
async def create_order(order: Order):
    total = sum(item.price * item.quantity for item in order.items)
    return {
        "message": f"Order received for {order.customer_name}",
        "total": total
    }

🚀 Key Takeaways

Concept What It Does
BaseModel Creates data blueprints
Field() Adds validation rules
Optional/defaults Makes fields not required
Nested models Models inside models
List/Dict Multiple items or key-value pairs
Inheritance Share fields between models
Examples Show sample data in docs

Remember: Pydantic is your data’s security guard. It checks everything at the door so your code only deals with clean, validated data! 🛡️

Loading story...

No Story Available

This concept doesn't have a story yet.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

Interactive Preview

Interactive - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Interactive Content

This concept doesn't have interactive content yet.

Cheatsheet Preview

Cheatsheet - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Cheatsheet Available

This concept doesn't have a cheatsheet yet.

Quiz Preview

Quiz - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Quiz Available

This concept doesn't have a quiz yet.