AssertionError: Multiple dependencies with the same key
Encountering
AssertionError: Multiple dependencies with the same keymeans FastAPI found ambiguity in your dependency declarations, typically when the same callable is registered multiple times under different wrapping types; this guide explains how to fix it.
What This Error Means
This AssertionError is a strong signal from FastAPI's dependency injection system that it has detected an internal inconsistency. Specifically, it means that while FastAPI was building its internal map of dependencies for a particular path operation, it found two or more entries attempting to register the same underlying callable function (the "key" for the dependency) as a dependency, but in a way that creates an ambiguity.
FastAPI is designed to be smart about dependencies: if Depends(get_db) is used in multiple places within the same request's dependency tree, get_db will usually only be called once, and its result cached and reused. However, this specific AssertionError doesn't relate to the result of the dependency, but to its registration. It indicates that the dependency resolver encountered a situation where it expected a unique registration for a given callable, but found a duplicate.
Why It Happens
FastAPI's power comes from its robust dependency injection system. When a request comes in, FastAPI analyzes the path operation function's signature and recursively builds a graph of all required dependencies. Each Depends(...) or Security(...) declaration tells FastAPI to inject a certain value, obtained by calling the function passed to Depends or Security. Internally, FastAPI uses the callable function itself (e.g., get_current_user) as a key to manage these dependencies.
The AssertionError: Multiple dependencies with the same key is triggered when FastAPI tries to add a dependency to its internal mapping, but finds that a dependency with the exact same callable key has already been added in a way that is deemed conflicting or ambiguous for the current context. This usually happens when the same underlying callable is specified through different dependency wrappers (like Depends and Security) within the same path operation signature, leading to the system attempting to register the same fundamental dependency twice but via different "paths" or interpretations, which it considers an assertion failure.
In my experience, this usually points to a misconfiguration in how dependencies are declared, rather than an actual bug in FastAPI itself. It's the system's way of saying, "Hey, I can't figure out how to uniquely map this dependency because you've told me to register it multiple times using the same underlying function, but perhaps in conflicting ways."
Common Causes
While the error message is quite specific about "multiple dependencies with the same key," the scenarios leading to it can sometimes be subtle. Here are the most common causes I've encountered:
- Mixing
Depends()andSecurity()for the Same Callable: This is, by far, the most frequent culprit. If you define a dependency likedef get_current_user(...)and then try to use it in a path operation's signature both withuser: User = Depends(get_current_user)andsecure_user: User = Security(get_current_user), FastAPI will throw this error. BothDepends()andSecurity()map to the same underlying callable (get_current_user), butSecurity()adds extra processing (like checking security scopes). When both are present for the same callable in the same function signature, FastAPI's internal mechanism attempts to register the dependency twice with what it considers the same key, but perhaps with conflicting expectations on how it should be processed. - Redundant Global or Router-Level Dependencies: Less common, but possible, is a scenario where you define a dependency globally (e.g., with
app.dependency_overridesor anAPIRouteoverride) and then attempt to define it again, perhaps with a slight variation or using a different wrapper, directly in a path operation. This can lead to the same callable being added to the dependency map twice for the same route. - Complex Custom Routing or Dependency Registration: If you're building highly custom routing mechanisms, perhaps by directly manipulating
fastapi.routing.APIRouteobjects or employing meta-programming to dynamically generate path operations and their dependencies, it's possible to accidentally register the same callable as a dependency multiple times for a single endpoint. This requires a deeper understanding of FastAPI's internal routing and dependency graph construction.
Step-by-Step Fix
Rectifying this error usually involves a careful review of your path operation's signature and any related dependency declarations.
-
Locate the Problematic Path Operation:
The traceback for theAssertionErrorwill typically point to the specific path operation (e.g.,@app.get("/my-endpoint")) where the dependency conflict was detected during startup. Isolate this endpoint. -
Examine the Path Operation Signature for Duplicates:
Carefully inspect every parameter in the function signature of your path operation. Look for instances where the same callable function is being passed toDepends()orSecurity().```python
Example problematic signature
@app.get("/items/")
async def read_items(
user_info: dict = Depends(get_current_active_user),
# This line is the problem if get_current_active_user is the same callable
security_user: dict = Security(get_current_active_user, scopes=["admin"])
):
# ...
pass
``` -
Identify and Refactor Conflicting
Depends()vs.Security():
If you've found a callable (likeget_current_active_userin the example above) being used with bothDepends()andSecurity()in the same signature, this is almost certainly the cause.- Choose one: Decide whether you need the
Securityfeatures (likescopes) or if a plainDependssuffices.- If
Securityis required, remove theDependsdeclaration for that same callable. - If only the basic dependency injection is needed, remove the
Securitydeclaration and stick withDepends.
- If
- Encapsulate if needed: If you truly need different aspects or results derived from the same base callable, consider wrapping one of them in another dependency function.
Example Refactoring (choosing
Security):
```pythonBefore (causing error):
user_info: dict = Depends(get_current_active_user),
security_user: dict = Security(get_current_active_user, scopes=["admin"])
After (fixed):
@app.get("/items/")
async def read_items(
user_info: dict = Security(get_current_active_user, scopes=["admin"]) # Use only Security
):
# The 'user_info' variable now holds the result from get_current_active_user,
# and security checks are applied.
pass
`` If you neededuser_infofor one purpose andsecurity_userfor another, both deriving fromget_current_active_user, simply usinguser_info: dict = Security(get_current_active_user, scopes=["admin"])and then assigningsecurity_user = user_infoinside your path operation or in a subsequent dependency would be the correct approach. FastAPI calls the underlying callable (get_current_active_user`) only once and reuses the result. - Choose one: Decide whether you need the
-
Review Global/Router Dependency Overrides (if applicable):
If the error persists or your code doesn't have theDependsvsSecurityclash, check any places where you define globalapp.dependency_overridesor router-specific dependency overrides. Ensure that a specific callable isn't being targeted for override multiple times in a conflicting manner. This is more common in advanced testing setups or plug-in architectures.```python
Example of checking overrides (especially in test files)
Ensure get_db is not overridden multiple times or in conflicting ways.
app.dependency_overrides[get_db] = override_get_db_1
If later, in the same scope or test context, you have:
app.dependency_overrides[get_db] = override_get_db_2
This might cause issues if not managed correctly.
```
-
Restart your application:
After making changes, always perform a clean restart of your FastAPI application. Since this error occurs during startup (when the dependency graph is being built), a simple code reload might not always clear the internal state if you're using a development server.
Code Examples
Here are concise, copy-paste ready examples demonstrating the error and its fix.
Problematic Code (Causing AssertionError)
This example demonstrates the classic Depends() vs Security() conflict using the same underlying callable.
from fastapi import FastAPI, Depends, Security, HTTPException, status
from typing import Annotated
app = FastAPI()
# A simple mock user database
fake_users_db = {
"john.doe": {"username": "john.doe", "full_name": "John Doe", "scopes": ["user"]},
"jane.smith": {"username": "jane.smith", "full_name": "Jane Smith", "scopes": ["user", "admin"]},
}
# A dependency to simulate getting the current user based on an API key
async def get_current_user(api_key: Annotated[str, Depends(lambda: "some_api_key")]):
"""Mocks fetching a user from a database."""
if api_key != "valid-api-key":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid API Key"
)
return fake_users_db["john.doe"]
# A dependency to check active status and scopes (reusing get_current_user)
async def get_current_active_user(
current_user: Annotated[dict, Depends(get_current_user)]
):
"""Mocks checking if user is active and has basic user scopes."""
if "user" not in current_user.get("scopes", []):
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
@app.get("/users/me/")
async def read_users_me(
# First usage of get_current_active_user as a standard dependency
user_info: Annotated[dict, Depends(get_current_active_user)],
# Second usage of the *same callable* but wrapped with Security
# This will cause: AssertionError: Multiple dependencies with the same key
admin_user: Annotated[dict, Security(get_current_active_user, scopes=["admin"])]
):
return {"user_info": user_info, "admin_user": admin_user}
# To run this:
# 1. Save as `main.py`
# 2. Run `uvicorn main:app --reload`
# 3. You will see the AssertionError on startup.
Corrected Code (Resolves AssertionError)
Here, we've removed the redundant Depends() and used only Security() for the get_current_active_user dependency, ensuring it's registered only once.
from fastapi import FastAPI, Depends, Security, HTTPException, status
from typing import Annotated
app = FastAPI()
fake_users_db = {
"john.doe": {"username": "john.doe", "full_name": "John Doe", "scopes": ["user"]},
"jane.smith": {"username": "jane.smith", "full_name": "Jane Smith", "scopes": ["user", "admin"]},
}
async def get_current_user(api_key: Annotated[str, Depends(lambda: "valid-api-key")]):
if api_key != "valid-api-key":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid API Key"
)
return fake_users_db["john.doe"]
async def get_current_active_user(
current_user: Annotated[dict, Depends(get_current_user)]
):
if "user" not in current_user.get("scopes", []):
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
@app.get("/users/me/")
async def read_users_me(
# CORRECTED: Use only Security to define the dependency.
# The result of get_current_active_user will be passed to 'user_info'.
# If 'admin_user' was truly a separate concept, it would need a different dependency function.
user_info: Annotated[dict, Security(get_current_active_user, scopes=["admin"])]
):
# Now, 'user_info' contains the result, and 'admin' scope has been checked.
# If you need to refer to it as 'admin_user' as well, you can just re-assign:
admin_user = user_info
return {"user_info": user_info, "admin_user": admin_user}
# To run this:
# 1. Save as `main_fixed.py`
# 2. Run `uvicorn main_fixed:app --reload`
# 3. The application will start successfully.
# 4. Access http://127.0.0.1:8000/users/me/
In the corrected example, Security(get_current_active_user, scopes=["admin"]) ensures that get_current_active_user is registered as a dependency for user_info and that the admin scope is checked. FastAPI will call get_current_active_user once, and its result will be available through the user_info parameter.
Environment-Specific Notes
This AssertionError is typically a code-level configuration issue, meaning its manifestation is largely consistent across different deployment environments. However, certain aspects can make it easier or harder to diagnose.
-
Local Development:
During local development withuvicorn --reload, the application restarts on file changes. If you introduce this error, it will immediately manifest as a crash on startup. This is the ideal environment to debug it, as the feedback loop is fast. The traceback will be printed directly to your console. -
Docker Containers:
When running your FastAPI application inside a Docker container, the error will cause the container to fail to start or crash shortly after startup. TheAssertionErrorwill be visible in the container logs.```bash
Example of checking logs for a Docker container
docker logs
```Ensuring your
CMDorENTRYPOINTin yourDockerfileusesuvicornin a production-ready way (e.g.,uvicorn main:app --host 0.0.0.0 --port 80) means the application will attempt to fully initialize, leading to the error if present. You might need to examine the logs carefully, especially if the container exits immediately. -
Cloud Deployments (Serverless, VMs, Kubernetes):
In cloud environments like AWS Lambda (via Mangum), Google Cloud Run, Azure Container Apps, or Kubernetes, the error will prevent your application instance from becoming healthy or starting up correctly.- Serverless: For serverless functions, the initial invocation might fail, or the function instance itself might crash during initialization. You'll need to check the logs of your serverless platform (e.g., CloudWatch for AWS Lambda, Cloud Logging for Google Cloud Functions/Run) for the full traceback.
- VMs/Kubernetes: On virtual machines or Kubernetes, the application process will fail to start. In Kubernetes, this would manifest as a failing pod with crash loop back-offs (
CrashLoopBackOffstatus). The logs of the failing pod or VM instance will contain theAssertionError. Monitoring and logging tools are crucial here.
The key takeaway is that the error is a startup-time issue. If your CI/CD pipeline includes a health check or a simple uvicorn startup command, this error should be caught before reaching production traffic.
Frequently Asked Questions
Q: Why does FastAPI throw an AssertionError instead of handling it gracefully?
A: FastAPI's dependency injection system relies on a consistent internal state. An AssertionError here indicates a fundamental conflict in how dependencies are registered, which could lead to unpredictable behavior if not caught immediately. By failing loudly at startup, FastAPI forces the developer to resolve the ambiguity, preventing harder-to-debug runtime issues. It's a design choice for robustness and correctness.
Q: Can I have two different parameters using Depends(some_function) in the same path operation?
A: Yes, you can. For example, param1: str = Depends(get_str), param2: str = Depends(get_str) would typically work without this AssertionError. FastAPI would call get_str only once and use the cached result for both param1 and param2. The AssertionError specifically targets conflicts where the same callable is registered as a dependency via different wrapping types (like Depends and Security), leading to ambiguity in how it should be processed.
Q: What if I need Security features for one parameter but plain Depends for another, both using the same underlying logic?
A: If the underlying callable is the same, you should typically use Security if you need its features (like scopes). The result will be available. If you need the same data but without the security checks for a different parameter, you can just assign the result: user: User = Security(get_user, scopes=["read"]); user_data = user. If the "underlying logic" truly needs to be processed differently, then it implies you should have two distinct callable functions for your dependencies.
Q: Does this error occur with global dependencies or router-level dependencies?
A: While less common than the Depends vs Security clash, it can occur if you're dynamically registering dependencies or using app.dependency_overrides in a conflicting way. For instance, attempting to override the same callable twice within the same application startup context could potentially trigger this. Always double-check your override definitions if the issue isn't in a path operation's signature directly.
Related Errors
(none)