FastAPI Path Parameters
Overview
In FastAPI, parameters are key components for building flexible APIs. This chapter will深入探讨各种参数类型, including path parameters, query parameters, request body parameters, and how to perform validation, transformation, and documentation. Mastering this knowledge will help you build more robust and user-friendly APIs.
🎯 Path Parameters Details
Basic Path Parameters
Path parameters are variable parts in URL paths. FastAPI automatically performs type conversion and validation:
from fastapi import FastAPI, Path, HTTPException
from typing import Optional
from enum import Enum
app = FastAPI()
# Basic integer path parameter
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
# String path parameter
@app.get("/users/{username}")
async def read_user(username: str):
return {"username": username}
# Float path parameter
@app.get("/prices/{price}")
async def read_price(price: float):
return {"price": price, "formatted": f"${price:.2f}"}Path Parameter Validation
Use Path class to add validation and documentation:
from datetime import datetime
from uuid import UUID
# Path parameters with validation
@app.get("/items/{item_id}")
async def read_item_validated(
item_id: int = Path(
..., # Required parameter
title="Item ID",
description="Unique identifier of item to retrieve",
ge=1, # Greater than or equal to 1
le=1000, # Less than or equal to 1000
example=42
)
):
return {"item_id": item_id}
# String validation
@app.get("/users/{username}")
async def read_user_validated(
username: str = Path(
...,
title="Username",
description="User's unique username",
min_length=3,
max_length=20,
regex="^[a-zA-Z0-9_]+$",
example="john_doe"
)
):
return {"username": username}
# UUID parameter
@app.get("/orders/{order_id}")
async def read_order(
order_id: UUID = Path(
...,
title="Order ID",
description="UUID identifier of order",
example="123e4567-e89b-12d3-a456-426614174000"
)
):
return {"order_id": str(order_id)}Enum Path Parameters
class ItemType(str, Enum):
ELECTRONICS = "electronics"
CLOTHING = "clothing"
BOOKS = "books"
FOOD = "food"
class Priority(int, Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
URGENT = 4
@app.get("/items/category/{category}")
async def get_items_by_category(
category: ItemType = Path(
...,
title="Item category",
description="Category type of item"
)
):
return {
"category": category,
"message": f"Get items of {category.value} category",
"available_categories": [item.value for item in ItemType]
}
@app.get("/tasks/{priority}")
async def get_tasks_by_priority(priority: Priority):
priority_names = {
Priority.LOW: "Low priority",
Priority.MEDIUM: "Medium priority",
Priority.HIGH: "High priority",
Priority.URGENT: "Urgent"
}
return {
"priority": priority.value,
"name": priority_names[priority],
"tasks": f"Get {priority_names[priority]} tasks"
}🔍 Query Parameters Details
Basic Query Parameters
from typing import Optional, List, Set
# Basic query parameters
@app.get("/items/")
async def read_items(
skip: int = 0,
limit: int = 10,
q: Optional[str] = None
):
items = {"skip": skip, "limit": limit}
if q:
items["q"] = q
return items
# Required query parameters
@app.get("/search/")
async def search_items(q: str):
return {"query": q, "results": []}
# Boolean query parameters
@app.get("/items/")
async def filter_items(
available: bool = True,
featured: Optional[bool] = None
):
return {
"available": available,
"featured": featured,
"message": "Filters applied"
}Query Parameter Validation
from fastapi import Query
@app.get("/items/")
async def read_items_with_validation(
skip: int = Query(
0,
title="Skip records",
description="Number of records to skip, for pagination",
ge=0,
example=0
),
limit: int = Query(
10,
title="Limit records",
description="Maximum number of records to return",
ge=1,
le=100,
example=10
),
q: Optional[str] = Query(
None,
title="Search query",
description="Search query string",
min_length=3,
max_length=50,
example="laptop"
),
sort_by: str = Query(
"created_at",
title="Sort field",
description="Field name for sorting",
regex="^(name|price|created_at|updated_at)$"
)
):
return {
"skip": skip,
"limit": limit,
"q": q,
"sort_by": sort_by
}List Query Parameters
# List parameters
@app.get("/items/")
async def read_items_with_lists(
tags: List[str] = Query(
[],
title="Tag list",
description="List of item tags",
example=["electronics", "mobile"]
),
categories: Set[str] = Query(
set(),
title="Category set",
description="Set of item categories"
),
prices: List[float] = Query(
[],
title="Price list",
description="List of price ranges"
)
):
return {
"tags": tags,
"categories": list(categories),
"prices": prices,
"filters_applied": len(tags) + len(categories) + len(prices)
}
# Example call:
# GET /items/?tags=electronics&tags=mobile&categories=tech&prices=100.0&prices=200.0Query Parameter Aliases and Deprecation
@app.get("/items/")
async def read_items_with_alias(
# Use alias
item_query: Optional[str] = Query(
None,
alias="item-query", # Use hyphen in URL
title="Item query",
description="Search query string for items"
),
# Deprecated parameter
old_param: Optional[str] = Query(
None,
deprecated=True,
title="Old parameter",
description="Deprecated parameter, please use item-query"
),
# Hidden parameter (not shown in documentation)
internal_param: Optional[str] = Query(
None,
include_in_schema=False,
description="Internal use parameter"
)
):
# Compatibility handling
search_query = item_query or old_param
return {
"search_query": search_query,
"internal_param": internal_param
}📅 Date and Time Parameters
Date Time Processing
from datetime import datetime, date, time
from typing import Optional
@app.get("/events/")
async def get_events(
start_date: Optional[date] = Query(
None,
title="Start date",
description="Event start date (YYYY-MM-DD)",
example="2023-12-01"
),
end_date: Optional[date] = Query(
None,
title="End date",
description="Event end date (YYYY-MM-DD)",
example="2023-12-31"
),
created_after: Optional[datetime] = Query(
None,
title="Created after",
description="Get events created after this time",
example="2023-12-01T10:00:00"
),
time_slot: Optional[time] = Query(
None,
title="Time slot",
description="Event time slot (HH:MM:SS)",
example="14:30:00"
)
):
# Date validation
if start_date and end_date and start_date > end_date:
raise HTTPException(
status_code=400,
detail="Start date cannot be later than end date"
)
return {
"start_date": start_date,
"end_date": end_date,
"created_after": created_after,
"time_slot": time_slot,
"date_range_days": (end_date - start_date).days if start_date and end_date else None
}🎭 Mixed Parameter Types
Path + Query + Request Body Parameters
from pydantic import BaseModel
class ItemUpdate(BaseModel):
name: Optional[str] = None
price: Optional[float] = None
description: Optional[str] = None
@app.put("/items/{item_id}")
async def update_item(
# Path parameters
item_id: int = Path(
...,
title="Item ID",
ge=1
),
# Query parameters
notify_users: bool = Query(
False,
title="Notify users",
description="Whether to notify related users"
),
# Request body parameters
item: ItemUpdate = ...,
# Optional query parameters
reason: Optional[str] = Query(
None,
title="Update reason",
max_length=200
)
):
update_data = item.dict(exclude_unset=True)
result = {
"item_id": item_id,
"updated_fields": list(update_data.keys()),
"notify_users": notify_users,
"update_data": update_data
}
if reason:
result["reason"] = reason
return resultMultiple Path Parameters
@app.get("/users/{user_id}/orders/{order_id}/items/{item_id}")
async def get_user_order_item(
user_id: int = Path(..., title="User ID", ge=1),
order_id: int = Path(..., title="Order ID", ge=1),
item_id: int = Path(..., title="Item ID", ge=1),
include_details: bool = Query(False, title="Include details")
):
return {
"user_id": user_id,
"order_id": order_id,
"item_id": item_id,
"include_details": include_details,
"resource_path": f"/users/{user_id}/orders/{order_id}/items/{item_id}"
}🔧 Parameter Validation and Error Handling
Custom Validation
from pydantic import validator, ValidationError
class SearchParams(BaseModel):
query: str
category: Optional[str] = None
min_price: Optional[float] = None
max_price: Optional[float] = None
@validator('query')
def query_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError('Query string cannot be empty')
return v.strip()
@validator('max_price')
def max_price_must_be_greater_than_min(cls, v, values):
if 'min_price' in values and values['min_price'] is not None and v is not None:
if v <= values['min_price']:
raise ValueError('Max price must be greater than min price')
return v
@app.get("/search/")
async def search_with_validation(
query: str = Query(..., min_length=1),
category: Optional[str] = Query(None),
min_price: Optional[float] = Query(None, ge=0),
max_price: Optional[float] = Query(None, ge=0)
):
try:
# Use Pydantic model for validation
params = SearchParams(
query=query,
category=category,
min_price=min_price,
max_price=max_price
)
return {
"search_params": params.dict(),
"message": "Search parameters validated successfully"
}
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())Conditional Parameter Validation
@app.get("/reports/")
async def generate_report(
report_type: str = Query(
...,
regex="^(daily|weekly|monthly|yearly)$",
title="Report type"
),
start_date: Optional[date] = Query(None, title="Start date"),
end_date: Optional[date] = Query(None, title="End date"),
format: str = Query(
"json",
regex="^(json|csv|pdf)$",
title="Output format"
)
):
# Validate date requirements based on report type
if report_type in ["daily", "weekly"] and not start_date:
raise HTTPException(
status_code=400,
detail=f"{report_type} report requires start date"
)
if start_date and end_date:
if start_date > end_date:
raise HTTPException(
status_code=400,
detail="Start date cannot be later than end date"
)
# Check date range
date_diff = (end_date - start_date).days
max_days = {"daily": 31, "weekly": 90, "monthly": 365, "yearly": 1095}
if date_diff > max_days.get(report_type, 365):
raise HTTPException(
status_code=400,
detail=f"{report_type} report date range cannot exceed {max_days[report_type]} days"
)
return {
"report_type": report_type,
"start_date": start_date,
"end_date": end_date,
"format": format,
"estimated_records": date_diff if start_date and end_date else "unknown"
}📊 Parameter Transformation and Processing
Custom Parameter Converters
def parse_coordinates(coord_str: str) -> tuple:
"""Parse coordinate string '123.456,789.012' to tuple"""
try:
lat, lng = map(float, coord_str.split(','))
if not (-90 <= lat <= 90):
raise ValueError("Latitude must be between -90 and 90")
if not (-180 <= lng <= 180):
raise ValueError("Longitude must be between -180 and 180")
return (lat, lng)
except (ValueError, TypeError) as e:
raise HTTPException(status_code=400, detail=f"Coordinate format error: {str(e)}")
@app.get("/locations/nearby/")
async def find_nearby_locations(
coordinates: str = Query(
...,
title="Coordinates",
description="Format: 'latitude,longitude' e.g., '39.9042,116.4074'",
example="39.9042,116.4074"
),
radius: float = Query(
1.0,
title="Search radius",
description="Search radius (kilometers)",
ge=0.1,
le=100.0
)
):
lat, lng = parse_coordinates(coordinates)
return {
"center": {"latitude": lat, "longitude": lng},
"radius_km": radius,
"search_area": f"Area centered at ({lat}, {lng}) with {radius}km radius"
}Complex Query Parameter Processing
from urllib.parse import unquote
@app.get("/advanced-search/")
async def advanced_search(
# JSON string parameter
filters: Optional[str] = Query(
None,
title="Filter conditions",
description="JSON format filter conditions",
example='{"category": "electronics", "brand": "apple"}'
),
# Sort parameter
sort: str = Query(
"created_at:desc",
title="Sorting",
description="Sort field and direction, format: field:direction",
example="price:asc"
),
# Pagination parameters
page: int = Query(1, title="Page number", ge=1),
per_page: int = Query(20, title="Records per page", ge=1, le=100)
):
import json
# Parse filter conditions
parsed_filters = {}
if filters:
try:
parsed_filters = json.loads(unquote(filters))
except json.JSONDecodeError:
raise HTTPException(
status_code=400,
detail="Filter conditions JSON format error"
)
# Parse sorting
try:
sort_field, sort_direction = sort.split(':')
if sort_direction not in ['asc', 'desc']:
raise ValueError("Sort direction must be asc or desc")
except ValueError:
raise HTTPException(
status_code=400,
detail="Sort format error, should be 'field:direction'"
)
# Calculate offset
offset = (page - 1) * per_page
return {
"filters": parsed_filters,
"sort": {"field": sort_field, "direction": sort_direction},
"pagination": {
"page": page,
"per_page": per_page,
"offset": offset
},
"query_summary": f"Page {page}, {per_page} per page, sorted by {sort_field}"
}🎯 Best Practices and Tips
Parameter Organization and Reuse
from fastapi import Depends
# Reusable parameter dependencies
class PaginationParams:
def __init__(
self,
page: int = Query(1, ge=1, title="Page number"),
per_page: int = Query(20, ge=1, le=100, title="Records per page")
):
self.page = page
self.per_page = per_page
self.offset = (page - 1) * per_page
class SortParams:
def __init__(
self,
sort_by: str = Query("created_at", title="Sort field"),
sort_order: str = Query("desc", regex="^(asc|desc)$", title="Sort direction")
):
self.sort_by = sort_by
self.sort_order = sort_order
# Use parameter dependencies
@app.get("/items/")
async def list_items(
pagination: PaginationParams = Depends(),
sorting: SortParams = Depends(),
search: Optional[str] = Query(None, title="Search keyword")
):
return {
"pagination": {
"page": pagination.page,
"per_page": pagination.per_page,
"offset": pagination.offset
},
"sorting": {
"sort_by": sorting.sort_by,
"sort_order": sorting.sort_order
},
"search": search
}Parameter Documentation
@app.get(
"/products/{product_id}",
summary="Get product details",
description="Get detailed product information by product ID",
response_description="Product detail information"
)
async def get_product(
product_id: int = Path(
...,
title="Product ID",
description="Unique identifier of product to retrieve",
example=123,
ge=1
),
include_reviews: bool = Query(
False,
title="Include reviews",
description="Whether to include product reviews in response"
),
review_limit: Optional[int] = Query(
5,
title="Review count limit",
description="Limit on number of reviews to return (only effective when include_reviews=true)",
ge=1,
le=50
)
):
"""
Get product details
This endpoint returns detailed information about specified product, including:
- Product basic information (name, price, description)
- Product specifications and attributes
- Optional user reviews (if enabled)
**Parameter Description:**
- **product_id**: Database ID of product
- **include_reviews**: Set to true to include user reviews
- **review_limit**: Limit on number of reviews returned
**Example Request:**
```
GET /products/123?include_reviews=true&review_limit=10
```
"""
product_data = {
"product_id": product_id,
"name": "Sample product",
"price": 99.99,
"description": "This is a sample product"
}
if include_reviews:
product_data["reviews"] = [
{"id": i, "rating": 5, "comment": f"Review {i}"}
for i in range(1, min(review_limit + 1, 6))
]
return product_dataSummary
This chapter detailed various parameter types in FastAPI:
- ✅ Path Parameters: Basic usage, validation, enum types
- ✅ Query Parameters: Validation, list parameters, aliases and deprecation
- ✅ Date Time Parameters: Date, time, datetime processing
- ✅ Mixed Parameters: Combination of path + query + request body
- ✅ Parameter Validation: Custom validation, conditional validation, error handling
- ✅ Parameter Transformation: Custom converters, complex parameter processing
- ✅ Best Practices: Parameter organization, reuse, documentation
Mastering these parameter handling skills will help you build more flexible, robust, and user-friendly API interfaces.
Parameter Design Recommendations
- Use clear parameter names and descriptions
- Add appropriate validation and constraints
- Provide meaningful example values
- Consider backward compatibility of parameters
- Use reasonable default values
- Write detailed API documentation
In the next chapter, we will learn FastAPI's request and response processing, including request body, response models, and status code management.