Extra Data Types
Explore FastAPI's support for advanced Python data types including UUID, datetime, timedelta, bytes, Decimal, and more with automatic validation and conversion.
🎯 What You'll Learn
- •Use UUID for universally unique identifiers in path parameters
- •Handle datetime, date, time, and timedelta types with automatic ISO 8601 conversion
- •Work with bytes, Decimal, and frozenset data types
- •Understand automatic data conversion and validation for complex types
- •Perform date/time calculations within FastAPI endpoints
Extra Data Types
🎯 What You'll Learn
This lesson covers the official FastAPI "Extra Data Types" tutorial. You'll discover FastAPI's support for advanced Python data types beyond the basic int, float, str, and bool, with automatic validation, conversion, and documentation.
By the end of this lesson, you'll understand how to:
- Use UUID for universally unique identifiers in path parameters
- Handle datetime, date, time, and timedelta types with automatic ISO 8601 conversion
- Work with bytes, Decimal, and frozenset data types
- Understand automatic data conversion and validation for complex types
- Perform date/time calculations within FastAPI endpoints
📚 Supported Extra Data Types
FastAPI supports many additional Python data types with the same great features:
- Great editor support
- Data conversion from incoming requests
- Data conversion for response data
- Data validation
- Automatic annotation and documentation
🆔 UUID
Universally Unique Identifier, common as an ID in databases and systems.
- In requests/responses: Represented as a
str - Example:
"550e8400-e29b-41d4-a716-446655440000"
📅 datetime.datetime
Python datetime.datetime objects.
- In requests/responses: Represented as ISO 8601 format string
- Example:
"2008-09-15T15:53:00+05:00"
📆 datetime.date
Python datetime.date objects.
- In requests/responses: Represented as ISO 8601 date string
- Example:
"2008-09-15"
⏰ datetime.time
Python datetime.time objects.
- In requests/responses: Represented as ISO 8601 time string
- Example:
"14:23:55.003"
⏱️ datetime.timedelta
Python datetime.timedelta objects.
- In requests/responses: Represented as
floatof total seconds - Example:
3600(for 1 hour) - Alternative: ISO 8601 time diff encoding (see Pydantic docs)
🧊 frozenset
Immutable set type.
- In requests: List is read, duplicates eliminated, converted to
set - In responses: Set is converted to
list - Schema: Specifies unique items using JSON Schema's
uniqueItems
📄 bytes
Standard Python bytes.
- In requests/responses: Treated as
str - Schema: Specified as
strwithbinaryformat
💰 Decimal
Standard Python Decimal for precise decimal arithmetic.
- In requests/responses: Handled the same as
float - Use case: Financial calculations requiring precision
🎯 Complete Example
Following the official FastAPI tutorial with modern Annotated syntax:
from datetime import datetime, time, timedelta
from typing import Annotated, Union
from uuid import UUID
from fastapi import Body, FastAPI
app = FastAPI()
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: Annotated[datetime, Body()],
end_datetime: Annotated[datetime, Body()],
process_after: Annotated[timedelta, Body()],
repeat_at: Annotated[Union[time, None], Body()] = None,
):
# Perform normal date manipulations
start_process = start_datetime + process_after
duration = end_datetime - start_process
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"process_after": process_after,
"repeat_at": repeat_at,
"start_process": start_process,
"duration": duration,
}
📝 Request Example
URL:
PUT /items/550e8400-e29b-41d4-a716-446655440000
Request Body:
{
"start_datetime": "2024-01-15T10:30:00",
"end_datetime": "2024-01-15T15:30:00",
"process_after": 3600,
"repeat_at": "14:30:00"
}
Response:
{
"item_id": "550e8400-e29b-41d4-a716-446655440000",
"start_datetime": "2024-01-15T10:30:00",
"end_datetime": "2024-01-15T15:30:00",
"process_after": 3600.0,
"repeat_at": "14:30:00",
"start_process": "2024-01-15T11:30:00",
"duration": 14400.0
}
🔄 Automatic Conversions
UUID Conversion
# Path parameter: /items/550e8400-e29b-41d4-a716-446655440000
# Automatically converted to UUID object
item_id: UUID # <UUID('550e8400-e29b-41d4-a716-446655440000')>
DateTime Conversion
# JSON: "2024-01-15T10:30:00"
# Automatically converted to datetime object
start_datetime: datetime # datetime(2024, 1, 15, 10, 30)
Timedelta Conversion
# JSON: 3600 (seconds)
# Automatically converted to timedelta object
process_after: timedelta # timedelta(seconds=3600)
Time Conversion
# JSON: "14:30:00"
# Automatically converted to time object
repeat_at: time # time(14, 30)
🔧 Modern Annotated Syntax
The official FastAPI documentation now recommends using Annotated for better type hints and IDE support:
from typing import Annotated
# Modern approach (recommended)
start_datetime: Annotated[datetime, Body()]
# Older approach (still works)
start_datetime: datetime = Body()
Benefits of Annotated:
- Better IDE Support: More precise type information
- Cleaner Separation: Type and metadata are clearly separated
- Future-Proof: Aligns with Python's type annotation evolution
- Explicit: Makes the relationship between type and validation clear
🌟 Key Benefits
Type Safety
- Full IDE support with autocomplete
- Type checking catches errors at development time
- Clear function signatures show expected types
Automatic Validation
- Invalid UUIDs return HTTP 422 with clear error messages
- Invalid datetime formats are automatically rejected
- Type conversion happens transparently
Documentation Generation
- OpenAPI schema includes proper type information
- Interactive docs show expected formats
- Examples are automatically generated
Natural Python Operations
# You can perform normal Python operations
start_process = start_datetime + process_after # datetime + timedelta
duration = end_datetime - start_process # datetime - datetime
is_same_day = start_datetime.date() == end_datetime.date()
💡 Best Practices
UUID Usage
from uuid import UUID, uuid4
# Path parameter
@app.get("/users/{user_id}")
async def get_user(user_id: UUID):
# user_id is automatically validated as UUID
return {"user_id": user_id}
# Generate new UUIDs
new_id = uuid4() # Generate random UUID
DateTime Handling
from datetime import datetime, timezone
# Always use timezone-aware datetimes for APIs
@app.post("/events/")
async def create_event(
start_time: datetime,
end_time: datetime
):
# Validate time range
if end_time <= start_time:
raise HTTPException(400, "End time must be after start time")
return {"duration": end_time - start_time}
Decimal for Financial Data
from decimal import Decimal
class Product(BaseModel):
name: str
price: Decimal # Use Decimal for money to avoid floating-point errors
tax_rate: Decimal = Decimal('0.08') # 8% tax
def total_price(self) -> Decimal:
return self.price * (1 + self.tax_rate)
Bytes for Binary Data
@app.post("/upload/")
async def upload_file(
file_content: bytes = Body(),
filename: str = Body()
):
# file_content is automatically decoded from base64 string
return {"size": len(file_content), "filename": filename}
🔍 Validation Examples
Valid Requests
UUID:
"550e8400-e29b-41d4-a716-446655440000" ✅
DateTime:
"2024-01-15T10:30:00" ✅ ISO format
"2024-01-15T10:30:00Z" ✅ UTC timezone
"2024-01-15T10:30:00+05:00" ✅ With timezone
Timedelta:
3600 ✅ Seconds as number
"01:00:00" ✅ ISO duration format (if supported)
Invalid Requests
Invalid UUID:
"not-a-uuid" ❌ HTTP 422: "value is not a valid uuid"
Invalid DateTime:
"2024-13-45" ❌ HTTP 422: "invalid datetime format"
"not-a-date" ❌ HTTP 422: "value is not a valid datetime"
Invalid Time:
"25:30:00" ❌ HTTP 422: "hour must be in 0..23"
🔗 Advanced Usage
Combining Types in Models
from datetime import datetime
from uuid import UUID
from decimal import Decimal
class Order(BaseModel):
id: UUID
created_at: datetime
amount: Decimal
items: List[str]
metadata: Dict[str, Union[str, int]]
Custom Validation
from pydantic import validator
class Event(BaseModel):
id: UUID
start_time: datetime
end_time: datetime
@validator('end_time')
def end_after_start(cls, v, values):
if 'start_time' in values and v <= values['start_time']:
raise ValueError('end_time must be after start_time')
return v
🔗 What's Next?
In the next lesson, you'll learn about Cookie Parameters, where you'll discover how to read and validate cookies using FastAPI's Cookie() function.
📖 Additional Resources
💡 Hint
Import datetime types and UUID from their standard Python modules. FastAPI automatically converts between JSON strings and Python objects for these types.
Ready to Practice?
Now that you understand the theory, let's put it into practice with hands-on coding!
Start Interactive Lesson