Skip to content

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:

python
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:

python
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

python
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

python
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

python
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

python
# 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.0

Query Parameter Aliases and Deprecation

python
@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

python
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

python
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 result

Multiple Path Parameters

python
@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

python
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

python
@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

python
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

python
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

python
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

python
@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_data

Summary

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.

Content is for learning and research only.