Skip to main content

Fastapi Depends with parameters and arguments

· 8 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

FastAPI's Dependency Injection (DI) system is remarkably flexible: dependency functions aren't just executed independently; they can be designed to accept and process arguments from the current HTTP request or the results of other dependencies.

Using parameters within a Depends function allows you to implement complex logic, dynamic configuration, and validation rules while keeping your main route handler clean.

Different varieties of Parameterized Dependency Injection

Here are five distinct ways you can leverage parameters within your dependency functions (callable in Depends(callable)).

Request Input Parameters (Path/Query/Header)

The most common method involves a dependency function accepting standard request parameters. FastAPI automatically extracts these parameters from the request and injects them into the dependency, allowing for validation or modification before the route runs.

from fastapi import Depends, Path, Header, HTTPException, Query
from typing import Annotated

# 1. Dependency: Validates an integer parameter from the URL path
def validate_item_id(
item_id: Annotated[int, Path(ge=1, le=1000)]
) -> int:
"""
Checks if the item_id is within a valid range and is an odd number.
"""
if item_id % 2 == 0:
raise HTTPException(
status_code=400,
detail="Item ID must be odd."
)
return item_id

ValidItemID = Annotated[int, Depends(validate_item_id)]

@app.get("/items/{item_id}")
def read_item(item_id: ValidItemID):
# Route only runs if item_id is odd and between 1 and 1000.
return {"item_id": item_id, "status": "validated"}

Annotation: FastAPI uses the function parameter names (item_id) to automatically map them to the corresponding path or query parameters defined in the route.


Dependencies Consuming Dependencies (Nested Chaining)

A powerful pattern is when one dependency requires the output of another dependency as its input parameter. FastAPI handles the execution order, running the sub-dependency first.

# Mocks for complex data types
class User:
def __init__(self, user_id: int, roles: list[str]):
self.id = user_id
self.roles = roles

# 2a. Sub-Dependency: Authentication (Gets the User object)
def get_authenticated_user():
# In a real app, this decodes a JWT token from a header
return User(user_id=101, roles=["contributor", "admin"])

# 2b. Main Dependency: Authorization (Consumes the User object)
def check_admin_role(
user: Annotated[User, Depends(get_authenticated_user)]
) -> User:
"""
Checks the User object provided by the sub-dependency for the 'admin' role.
"""
if "admin" not in user.roles:
raise HTTPException(
status_code=403,
detail="Requires admin privileges."
)
return user # Pass the validated User object forward

AdminUser = Annotated[User, Depends(check_admin_role)]

@app.delete("/secure/resource")
def delete_resource(user: AdminUser):
# Route only executes if both authentication AND authorization passed.
return {"message": f"Resource deleted by admin {user.id}"}

Annotation: The check_admin_role dependency relies on the return value of get_authenticated_user, demonstrating how dependencies form a resolution chain.


Dependency Factories (Parameterized Dependency Definition)

This advanced pattern uses a function that returns a dependency function. This allows you to define a single generic dependency checker and parameterize its behavior based on the route it's attached to.

from typing import Callable

# 3. Dependency Factory: Creates a parameterized checker
def role_required(required_role: str) -> Callable:
"""
Returns a dependency function tailored to check for the given role string.
"""
def check_user_role(
user: Annotated[User, Depends(get_authenticated_user)] # Nested dependency
):
if required_role not in user.roles:
raise HTTPException(
status_code=403,
detail=f"User lacks the required role: {required_role}"
)
return user
return check_user_role

@app.post("/submit/article", dependencies=[Depends(role_required("contributor"))])
def submit_article():
# The dependency ensures the user has the 'contributor' role.
return {"status": "submission successful"}

@app.post("/submit/review", dependencies=[Depends(role_required("manager"))])
def submit_review():
# The same factory is used, but parameterized for the 'manager' role.
return {"status": "review submission successful"}

Annotation: role_required("contributor") runs at startup and gives Depends() the specific inner function tailored to check for "contributor".


Direct Access to the Request Object

A dependency can request the Request object itself, allowing it to inspect raw elements not typically mapped to route parameters, such as raw headers, the client IP, or the full URL.

def check_internal_access(request: Request):
"""
Checks if the request originated from a private IP subnet.
"""
client_ip = request.client.host if request.client else "unknown"

# Simple check for internal-only access (e.g., bypassing load balancers)
if not client_ip.startswith("192.168.") and client_ip != "127.0.0.1":
raise HTTPException(
status_code=403,
detail="Access is restricted to internal network."
)

InternalOnly = Annotated[None, Depends(check_internal_access)]

@app.get("/metrics", dependencies=[Depends(check_internal_access)])
def get_metrics():
# Only executes if the client's IP passes the check
return {"metrics": "System metrics"}

Annotation: Request-aware dependencies are essential for low-level tasks like IP whitelisting or custom request logging.


Consuming Dependencies in Class Constructors

When using classes as dependencies, the class constructor (__init__) can itself consume other dependencies as parameters. This is the standard pattern for injecting database connections or global services into a class method.

# 5a. Sub-Dependency: Provides the database configuration
def get_db_settings():
return {"host": "db.prod.internal", "port": 5432}

# 5b. Class Dependency: Consumes settings in its constructor
class DatabaseService:
def __init__(self, settings: Annotated[dict, Depends(get_db_settings)]):
self.connection_string = f"postgres://{settings['host']}:{settings['port']}"
print(f"Service initialized with: {self.connection_string}")

def get_version(self):
return f"Connected to {self.connection_string}" # Use the injected dependency

# __call__ is optional but allows the class to be used as a simple dependency
def __call__(self):
return self

DBSvc = Annotated[DatabaseService, Depends(DatabaseService)]

@app.get("/service/version")
def service_version(db_service: DBSvc):
# The injected db_service instance already contains the connection string
return {"version_info": db_service.get_version()}

Annotation: When FastAPI encounters Depends(DatabaseService), it attempts to instantiate DatabaseService. To do this, it first resolves the dependencies required by its constructor (get_db_settings), executing the chain automatically.


Sources and Further Reading

  1. FastAPI Documentation - Dependencies in Path Operation Decorators
  2. FastAPI Documentation - Classes as Dependencies
  3. FastAPI Documentation - Advanced Dependencies (Dependency Factories)
  4. FastAPI Documentation - Using Request Directly