Skip to main content

FastAPI Path Dependencies

Introduction

In FastAPI, path dependencies are a specialized form of dependency injection that allows you to apply dependencies specifically to path parameters in your route handlers. This powerful feature enables you to validate, process, and extract information from URL path parameters before your endpoint function is called.

Path dependencies help you:

  • Validate path parameters before processing the request
  • Convert path parameters to appropriate Python objects
  • Implement security checks based on path parameters
  • Keep your code DRY (Don't Repeat Yourself) by reusing validation logic
  • Improve error handling for malformed path parameters

Let's dive into how path dependencies work and how they can enhance your FastAPI applications.

Basic Path Dependencies

What Are Path Dependencies?

Path dependencies in FastAPI are dependencies that are specifically tied to path parameters in your route paths. They allow you to process these parameters separately before your endpoint function executes.

Here's a simple example of a path dependency in action:

python
from fastapi import FastAPI, Path, HTTPException, Depends

app = FastAPI()

async def validate_item_id(item_id: int = Path(..., title="The ID of the item")):
if item_id <= 0:
raise HTTPException(status_code=400, detail="Item ID must be positive")
return item_id

@app.get("/items/{item_id}")
async def read_item(item_id: int = Depends(validate_item_id)):
return {"item_id": item_id, "message": "Item found"}

In this example, validate_item_id is a dependency function that validates the item_id path parameter. If the ID is not positive, it raises an HTTP exception. Otherwise, it returns the validated ID for use in the endpoint function.

Path Dependencies vs. Regular Dependencies

While regular dependencies can be used for various purposes, path dependencies are specifically designed to work with path parameters. Here's how they differ:

python
# Regular dependency
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

# Path dependency
async def validate_item_id(item_id: int = Path(..., gt=0)):
return item_id

@app.get("/items/{item_id}")
async def read_item(
commons: dict = Depends(common_parameters),
item_id: int = Depends(validate_item_id)
):
return {"item_id": item_id, **commons}

The key difference is that path dependencies typically process and validate path parameters, while regular dependencies can handle any type of request data.

Advanced Path Dependencies

Type Conversion with Path Dependencies

Path dependencies can automatically convert path parameters to appropriate Python types:

python
from fastapi import FastAPI, Path, Depends
from uuid import UUID

app = FastAPI()

async def validate_user_id(user_id: UUID = Path(...)):
return user_id

@app.get("/users/{user_id}")
async def get_user(user_id: UUID = Depends(validate_user_id)):
return {"user_id": str(user_id)}

If someone tries to access /users/not-a-uuid, FastAPI will automatically return a validation error before the dependency function is even called.

Path Dependencies for Parameter Validation

You can use path dependencies to implement complex validation rules for your path parameters:

python
from fastapi import FastAPI, Path, HTTPException, Depends
import re

app = FastAPI()

async def validate_product_code(
product_code: str = Path(..., min_length=8, max_length=12)
):
pattern = r'^[A-Z]{2}-\d{4}-[A-Z]{2}$'
if not re.match(pattern, product_code):
raise HTTPException(
status_code=400,
detail="Invalid product code format. Expected format: XX-0000-XX"
)
return product_code

@app.get("/products/{product_code}")
async def get_product(product_code: str = Depends(validate_product_code)):
return {"product_code": product_code, "is_valid": True}

This example ensures that product codes follow a specific format (e.g., AB-1234-CD).

Practical Applications of Path Dependencies

Resource Existence Checking

One common use case for path dependencies is to check if a requested resource exists:

python
from fastapi import FastAPI, Path, HTTPException, Depends
from typing import Dict

app = FastAPI()

# Simulated database
fake_db: Dict[int, Dict] = {
1: {"name": "Laptop", "price": 999.99},
2: {"name": "Smartphone", "price": 599.99},
3: {"name": "Tablet", "price": 299.99},
}

async def get_item_or_404(item_id: int = Path(..., gt=0)):
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
return fake_db[item_id]

@app.get("/items/{item_id}")
async def read_item(item: dict = Depends(get_item_or_404)):
return item

@app.put("/items/{item_id}")
async def update_item(
item_data: dict,
current_item: dict = Depends(get_item_or_404)
):
# Update the item
for key, value in item_data.items():
current_item[key] = value
return current_item

This pattern prevents code duplication by centralizing the existence check for items across different endpoints.

Hierarchical Resource Access

Path dependencies are especially useful for handling hierarchical resources:

python
from fastapi import FastAPI, Path, HTTPException, Depends
from typing import Dict, List

app = FastAPI()

# Simulated database
fake_users = {
1: {"name": "Alice"},
2: {"name": "Bob"},
}

fake_user_items = {
1: [{"item_id": 1, "name": "Laptop"}, {"item_id": 2, "name": "Phone"}],
2: [{"item_id": 1, "name": "Tablet"}],
}

async def get_user_or_404(user_id: int = Path(..., gt=0)):
if user_id not in fake_users:
raise HTTPException(status_code=404, detail="User not found")
return {"user_id": user_id, **fake_users[user_id]}

async def get_user_item_or_404(
user_id: int = Path(..., gt=0),
item_id: int = Path(..., gt=0),
user: dict = Depends(get_user_or_404)
):
if user_id not in fake_user_items:
raise HTTPException(status_code=404, detail="User has no items")

for item in fake_user_items[user_id]:
if item["item_id"] == item_id:
return item

raise HTTPException(status_code=404, detail="Item not found for this user")

@app.get("/users/{user_id}")
async def read_user(user: dict = Depends(get_user_or_404)):
return user

@app.get("/users/{user_id}/items")
async def read_user_items(user: dict = Depends(get_user_or_404)):
if user["user_id"] not in fake_user_items:
return {"items": []}
return {"items": fake_user_items[user["user_id"]]}

@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(item: dict = Depends(get_user_item_or_404)):
return item

This example demonstrates how path dependencies can be used to validate nested resources like user items.

Authorization Based on Path Parameters

Path dependencies can also implement authorization logic based on path parameters:

python
from fastapi import FastAPI, Path, HTTPException, Depends, Header
from typing import Optional

app = FastAPI()

async def verify_project_access(
project_id: int = Path(...),
x_api_key: Optional[str] = Header(None)
):
# In a real app, you would check against a database
allowed_projects = {
"user1_api_key": [1, 2, 3],
"user2_api_key": [2, 4, 6]
}

if not x_api_key:
raise HTTPException(status_code=401, detail="API key is required")

if x_api_key not in allowed_projects:
raise HTTPException(status_code=403, detail="Invalid API key")

if project_id not in allowed_projects[x_api_key]:
raise HTTPException(
status_code=403,
detail="You don't have access to this project"
)

return project_id

@app.get("/projects/{project_id}")
async def get_project(project_id: int = Depends(verify_project_access)):
# Process the request only if the user has access to the project
return {"project_id": project_id, "data": "Project data here"}

This example checks if the user has access to a specific project based on their API key before allowing access to the endpoint.

Path Dependencies with Dependency Classes

For more complex validation logic, you can use classes with the __call__ method:

python
from fastapi import FastAPI, Path, HTTPException, Depends
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

class ItemValidator:
def __init__(self, min_id: int = 1, include_price: bool = True):
self.min_id = min_id
self.include_price = include_price

async def __call__(self, item_id: int = Path(...)):
if item_id < self.min_id:
raise HTTPException(status_code=400, detail=f"Item ID must be >= {self.min_id}")

# Simulate database lookup
item = {"id": item_id, "name": f"Item {item_id}"}

if self.include_price:
item["price"] = 19.99 * item_id

return item

# Create different validators with different configurations
standard_validator = ItemValidator(min_id=1)
premium_validator = ItemValidator(min_id=100)
basic_validator = ItemValidator(min_id=1, include_price=False)

@app.get("/items/{item_id}")
async def get_item(item: dict = Depends(standard_validator)):
return item

@app.get("/premium-items/{item_id}")
async def get_premium_item(item: dict = Depends(premium_validator)):
return item

@app.get("/basic-items/{item_id}")
async def get_basic_item(item: dict = Depends(basic_validator)):
return item

This approach allows you to create reusable validation components with different configurations.

Chaining Path Dependencies

You can chain multiple dependencies to build complex validation pipelines:

python
from fastapi import FastAPI, Path, Query, HTTPException, Depends
from typing import Optional

app = FastAPI()

async def verify_positive_id(item_id: int = Path(..., gt=0)):
return item_id

async def get_item_from_db(
item_id: int = Depends(verify_positive_id),
q: Optional[str] = Query(None)
):
# Simulate database lookup
item = {"id": item_id, "name": f"Item {item_id}"}

if q:
item["query"] = q

return item

@app.get("/items/{item_id}")
async def read_item(item: dict = Depends(get_item_from_db)):
return item

In this example, get_item_from_db depends on verify_positive_id, creating a chain of dependencies.

Summary

Path dependencies in FastAPI are a powerful tool for handling path parameters in your applications. They allow you to:

  • Validate and transform path parameters before your endpoint logic runs
  • Reuse validation logic across multiple endpoints
  • Implement access control based on resource identifiers
  • Organize code in a clean, maintainable way
  • Build complex validation pipelines through dependency chaining

By leveraging path dependencies, you can create more robust, maintainable, and secure FastAPI applications.

Additional Resources

Exercises

  1. Create a path dependency that validates a username path parameter to ensure it contains only alphanumeric characters and is between 3-16 characters long.

  2. Implement a path dependency that takes a product ID and checks if the product exists in a database. If it does, return the product; if not, return a 404 error.

  3. Build a chained dependency system for a blog post application where:

    • The first dependency validates that a post ID is a valid integer
    • The second dependency checks if the post exists
    • The third dependency verifies if the current user has permission to view the post


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)