FastAPI Path Parameters
Introduction
Path parameters are a crucial part of building dynamic APIs. They allow you to capture values from the URL path and use them in your API functions. In FastAPI, path parameters are easy to define and come with built-in validation, documentation, and type conversion capabilities.
When developing web applications, you'll often need to retrieve specific resources based on identifiers in the URL. For example, you might want to fetch a user's profile by their ID (/users/123
) or get details about a product using its slug (/products/wireless-headphones
). This is where path parameters shine.
In this tutorial, we'll explore how to work with path parameters in FastAPI, from basic usage to more advanced scenarios.
Basic Path Parameters
Defining a Path Parameter
To define a path parameter in FastAPI, you use curly braces {}
in your route path and include the parameter with the same name in your function:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(user_id):
return {"user_id": user_id}
When you make a request to /users/123
, FastAPI automatically captures 123
as the user_id
parameter and passes it to your function.
Type Validation
One of FastAPI's most powerful features is automatic type validation. By adding type hints to your path parameters, FastAPI will:
- Convert the parameter to the specified type
- Validate the data
- Document the parameter type in the OpenAPI schema
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id, "user_id_type": type(user_id).__name__}
Now, if you access /users/123
, FastAPI will convert 123
to an integer. However, if you try /users/abc
, you'll get a validation error since abc
cannot be converted to an integer.
Sample output for a valid request:
{
"user_id": 123,
"user_id_type": "int"
}
Multiple Path Parameters
You can define multiple path parameters in a single route:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}/posts/{post_id}")
async def get_user_post(user_id: int, post_id: int):
return {
"user_id": user_id,
"post_id": post_id
}
This route would match URLs like /users/1/posts/42
.
Path Parameters with Predefined Values
Sometimes you want to restrict path parameters to a set of predefined values. You can use Python's Enum
class for this:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
resnet = "resnet"
alexnet = "alexnet"
yolo = "yolo"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.resnet:
return {"model_name": model_name, "message": "Deep ResNet model"}
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "AlexNet model"}
return {"model_name": model_name, "message": "YOLO object detection model"}
Now, the model_name
parameter will only accept values defined in the ModelName
enum, and your API documentation will show the available options.
Path Parameter Validation
FastAPI provides more advanced validation through Pydantic. For example, you can set constraints on integers:
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(title="The ID of the item to get", ge=1, le=1000)
):
return {"item_id": item_id}
In this example:
title
provides a description for the documentationge=1
specifies thatitem_id
must be greater than or equal to 1le=1000
ensuresitem_id
is less than or equal to 1000
Path Parameters with Regular Expressions
You can use regular expressions to define specific patterns for path parameters:
from fastapi import FastAPI
app = FastAPI()
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
The :path
suffix allows the parameter to match paths that contain forward slashes, which is useful for file paths.
Similarly, you can use custom regex patterns:
@app.get("/users/{username:str}")
async def read_username(username: str):
return {"username": username}
@app.get("/license-plates/{license:regexp([A-Z]{2}-[0-9]{3}-[A-Z]{2})}")
async def read_license_plate(license: str):
return {"license": license}
Order Matters
When you have multiple routes with path parameters, FastAPI evaluates them in the order they're defined. This is important when you have routes that could potentially match the same URL pattern:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/me")
async def read_current_user():
return {"user_id": "the current user"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
In this example, if someone accesses /users/me
, the first route will match because it's defined first. If the order was reversed, the second route would match /users/me
and interpret me
as the user_id
.
Real-world Example: Building a Blog API
Let's create a simple blog API with path parameters:
from fastapi import FastAPI, HTTPException, Path
from typing import Dict, List
from enum import Enum
app = FastAPI()
# Sample data
blogs = {
1: {"id": 1, "title": "FastAPI Introduction", "content": "FastAPI is awesome!"},
2: {"id": 2, "title": "Path Parameters", "content": "Learn about path parameters in FastAPI"}
}
class SortOptions(str, Enum):
id = "id"
title = "title"
newest = "newest"
@app.get("/blogs/")
async def list_blogs(skip: int = 0, limit: int = 10):
"""List all blogs with pagination."""
return list(blogs.values())[skip:skip+limit]
@app.get("/blogs/{blog_id}")
async def get_blog(blog_id: int = Path(..., title="The ID of the blog to retrieve", ge=1)):
"""Get a specific blog by ID."""
if blog_id not in blogs:
raise HTTPException(status_code=404, detail="Blog not found")
return blogs[blog_id]
@app.get("/blogs/sort/{sort_by}")
async def sort_blogs(sort_by: SortOptions):
"""Sort blogs by different criteria."""
blog_list = list(blogs.values())
if sort_by is SortOptions.id:
return sorted(blog_list, key=lambda x: x["id"])
elif sort_by is SortOptions.title:
return sorted(blog_list, key=lambda x: x["title"])
elif sort_by is SortOptions.newest:
# Assuming we had a 'created_at' field
# return sorted(blog_list, key=lambda x: x["created_at"], reverse=True)
return blog_list
@app.get("/categories/{category}/blogs/{blog_id}")
async def get_blog_by_category(category: str, blog_id: int):
"""Get a blog by category and ID."""
# In a real application, we would filter by category
if blog_id not in blogs:
raise HTTPException(status_code=404, detail="Blog not found")
return {"category": category, "blog": blogs[blog_id]}
In this example, we've created several endpoints that demonstrate different aspects of path parameters:
- A basic blog retrieval by ID
- Path parameters with validation
- Enum-based path parameters for sorting
- Multiple path parameters for filtering by category and ID
Summary
Path parameters in FastAPI provide a powerful way to create dynamic API endpoints. By leveraging FastAPI's type hints and validation capabilities, you can build robust APIs that are both self-documenting and secure.
Key points to remember:
- Use curly braces
{}
to define path parameters in route paths - Add type hints to enable automatic conversion and validation
- Use Pydantic's
Path()
for additional validation - Consider the order of route definitions to avoid conflicts
- Path parameters can be combined with query parameters for flexible APIs
Practice Exercises
- Create an API endpoint that accepts a user ID and returns user information
- Build a "calculator" API with endpoints for basic operations (add, subtract, etc.) that take numerical parameters
- Implement an API for a library that lets you fetch books by ID, ISBN, or title
- Create an API for a file system that accepts file paths as parameters
- Design a RESTful API for a blog with proper path parameters for posts, categories, and authors
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)