FastAPI Type System: Your Magic Labels 🏷️
Imagine you have a toy box. What if every toy had a special label telling you exactly what it is? A car label, a doll label, a ball label. You’d never get confused!
In FastAPI, type hints are those magic labels. They tell Python (and you!) exactly what kind of data you’re working with.
The Big Picture
Think of types like sorting bins in a classroom:
- One bin for numbers (age, price, count)
- One bin for words (names, messages)
- One bin for yes/no answers (is it raining?)
Python’s type system helps you put the right things in the right bins!
1. Python Type Hints
What Are They?
Type hints are like name tags for your data. They tell everyone what kind of information a variable holds.
name: str = "Emma"
age: int = 8
height: float = 4.2
is_happy: bool = True
Plain English:
str= words and sentences (strings)int= whole numbers (integers)float= numbers with decimalsbool= True or False only
In FastAPI
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello/{name}")
def say_hello(name: str):
return {"message": f"Hello, {name}!"}
The : str tells FastAPI: “This name must be text!”
graph TD A[User sends name] --> B{Is it text?} B -->|Yes| C[✅ Works!] B -->|No| D[❌ Error message]
2. Union and Optional Types
The “Either/Or” Label
Sometimes a toy can be more than one thing. A transformer can be a car OR a robot!
from typing import Union
price: Union[int, float] = 9.99
# price can be 10 OR 10.50
The “Maybe” Label (Optional)
What if a field might be empty? Like a nickname - not everyone has one!
from typing import Optional
nickname: Optional[str] = None
# Same as: Union[str, None]
Real FastAPI Example
from typing import Union, Optional
@app.get("/items/{item_id}")
def get_item(
item_id: int,
name: Optional[str] = None,
price: Union[int, float] = 0
):
return {
"id": item_id,
"name": name,
"price": price
}
What happens:
item_id: MUST be a numbername: Can be text OR nothingprice: Can be whole number OR decimal
3. Annotated Type Hints
Super-Powered Labels!
Annotated lets you add extra instructions to your labels. Like a toy label that also says “batteries included!”
from typing import Annotated
from fastapi import Query
@app.get("/search")
def search(
q: Annotated[str, Query(min_length=3)]
):
return {"query": q}
What this means:
qmust be text (str)- AND it must be at least 3 characters long!
More Examples
from typing import Annotated
from fastapi import Path, Query
@app.get("/users/{user_id}")
def get_user(
user_id: Annotated[int, Path(gt=0)],
limit: Annotated[int, Query(le=100)] = 10
):
return {"user_id": user_id, "limit": limit}
| Parameter | Type | Extra Rule |
|---|---|---|
user_id |
int | Must be > 0 |
limit |
int | Must be ≤ 100 |
graph TD A[Annotated] --> B[Base Type] A --> C[Extra Rules] B --> D[str, int, etc.] C --> E[min_length] C --> F[gt, lt, ge, le] C --> G[regex patterns]
4. Date and Time Types
Clock Labels!
Python has special types for dates and times. Like having a calendar bin and a clock bin!
from datetime import date, time, datetime
from datetime import timedelta
birthday: date = date(2016, 5, 15)
# Just the day: 2016-05-15
alarm: time = time(7, 30, 0)
# Just the clock: 07:30:00
party: datetime = datetime(2024, 12, 25, 18, 0)
# Day AND clock: 2024-12-25 18:00:00
wait: timedelta = timedelta(days=7)
# How long to wait: 7 days
In FastAPI
from datetime import date, datetime
@app.post("/events")
def create_event(
name: str,
event_date: date,
created_at: datetime
):
return {
"name": name,
"date": event_date,
"created": created_at
}
FastAPI automatically converts text like "2024-12-25" into a proper date!
| Type | Example | What It Holds |
|---|---|---|
date |
2024-12-25 | Year, month, day |
time |
14:30:00 | Hour, minute, second |
datetime |
2024-12-25T14:30:00 | Both! |
timedelta |
7 days | Duration |
5. Special Data Types
Fancy Labels for Fancy Data!
FastAPI has special types for common things:
UUID - Unique ID
from uuid import UUID
@app.get("/orders/{order_id}")
def get_order(order_id: UUID):
return {"order": str(order_id)}
# Example: 550e8400-e29b-41d4-a716-446655440000
Like a fingerprint - every UUID is unique!
EmailStr - Valid Email
from pydantic import EmailStr
@app.post("/signup")
def signup(email: EmailStr):
return {"email": email}
# âś… "emma@school.com" - Works!
# ❌ "not-an-email" - Error!
HttpUrl - Valid Web Address
from pydantic import HttpUrl
@app.post("/bookmark")
def save_bookmark(url: HttpUrl):
return {"saved": str(url)}
# âś… "https://example.com" - Works!
# ❌ "not-a-url" - Error!
graph TD A[Special Types] --> B[UUID] A --> C[EmailStr] A --> D[HttpUrl] B --> E[Unique IDs] C --> F[Valid Emails] D --> G[Valid URLs]
6. Enum Types
The “Pick One” Label!
Sometimes you can only choose from a fixed list. Like picking your favorite color from red, blue, or green - nothing else allowed!
from enum import Enum
class Color(str, Enum):
red = "red"
blue = "blue"
green = "green"
favorite: Color = Color.red
In FastAPI
from enum import Enum
class Size(str, Enum):
small = "small"
medium = "medium"
large = "large"
@app.get("/shirts/{size}")
def get_shirt(size: Size):
return {"size": size}
# âś… /shirts/small - Works!
# âś… /shirts/medium - Works!
# ❌ /shirts/huge - Error!
Why Use Enums?
| Without Enum | With Enum |
|---|---|
| User types “smol” | Only “small” allowed |
| Typos cause bugs | No typos possible |
| Hard to remember options | Clear list of choices |
graph TD A[User picks size] --> B{Is it in the list?} B -->|small/medium/large| C[✅ Accepted] B -->|anything else| D[❌ Rejected]
Integer Enums
Numbers can be enums too!
from enum import IntEnum
class Priority(IntEnum):
low = 1
medium = 2
high = 3
@app.post("/tasks")
def create_task(priority: Priority):
return {"priority": priority}
Putting It All Together
Here’s a complete example using everything we learned:
from fastapi import FastAPI, Query
from typing import Annotated, Optional
from datetime import datetime
from enum import Enum
from pydantic import EmailStr
from uuid import UUID
app = FastAPI()
class Status(str, Enum):
pending = "pending"
done = "done"
@app.post("/tasks")
def create_task(
title: str,
email: EmailStr,
status: Status = Status.pending,
due_date: Optional[datetime] = None,
priority: Annotated[
int, Query(ge=1, le=5)
] = 3
):
return {
"title": title,
"email": email,
"status": status,
"due": due_date,
"priority": priority
}
Quick Reference
| Type | Use For | Example |
|---|---|---|
str |
Text | “Hello” |
int |
Whole numbers | 42 |
float |
Decimals | 3.14 |
bool |
Yes/No | True |
Optional[X] |
Maybe empty | None or value |
Union[A, B] |
Either type | int or str |
Annotated |
Extra rules | min_length, gt |
date |
Calendar day | 2024-12-25 |
datetime |
Day + time | 2024-12-25T10:00 |
Enum |
Fixed choices | small/medium/large |
UUID |
Unique ID | 550e8400-… |
EmailStr |
Valid email | a@b.com |
You Did It! 🎉
You now understand FastAPI’s type system! Remember:
- Type hints = Labels for your data
- Optional/Union = Flexible labels
- Annotated = Labels with extra rules
- Date/Time = Calendar and clock labels
- Enums = Pick-from-a-list labels
These labels make your API safer, clearer, and easier to use!