Browse Source

Improve error messages for incorrect Depends() usage

- Add validation in Depends.__init__ to catch common mistakes early
- Provide clear, actionable error messages for:
  - Nested Depends() calls: Depends(Depends(...))
  - Called functions: Depends(func()) instead of Depends(func)
  - Non-callable arguments: Depends('string') or Depends(123)
- Include helpful hints in error messages (✓/✗ examples)
- Add comprehensive test coverage for validation logic

Fixes: Cryptic TypeError messages when Depends() is misused
Helps: New users understand and fix dependency injection errors quickly
pull/14281/head
Adeniran John 9 months ago
parent
commit
f340860b41
  1. 51
      fastapi/params.py
  2. 150
      tests/test_depends_validation.py

51
fastapi/params.py

@ -3,15 +3,12 @@ from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
from fastapi.openapi.models import Example
from pydantic.fields import FieldInfo
from typing_extensions import Annotated, deprecated
from ._compat import (
PYDANTIC_V2,
PYDANTIC_VERSION_MINOR_TUPLE,
Undefined,
)
from fastapi.openapi.models import Example
from ._compat import PYDANTIC_V2, PYDANTIC_VERSION_MINOR_TUPLE, Undefined
_Unset: Any = Undefined
@ -767,6 +764,48 @@ class Depends:
dependency: Optional[Callable[..., Any]] = None
use_cache: bool = True
def __init__(
self, dependency: Optional[Callable[..., Any]] = None, *, use_cache: bool = True
):
# Validate that dependency is used correctly
if dependency is not None:
# Check for nested Depends
if isinstance(dependency, Depends):
raise TypeError(
"Invalid usage of Depends(). "
"You have nested Depends() calls: Depends(Depends(...)). "
"Each dependency should only be wrapped once. "
"Remove the inner Depends() and pass the function directly."
)
# Check if dependency is callable
if not callable(dependency):
# Provide helpful error based on the type
dep_type = type(dependency).__name__
error_msg = f"Depends() expects a callable (function or class), but received {dep_type}. "
# Add specific hints for common mistakes
if isinstance(dependency, (dict, list, str, int, float, bool)):
error_msg += (
"\n\nIt looks like you may have called the dependency function instead of passing it. "
"\n✓ Correct: Depends(my_function)"
"\n✗ Wrong: Depends(my_function())"
)
elif hasattr(dependency, "__class__") and hasattr(
dependency.__class__, "__call__"
):
# Might be an instance of a callable class
error_msg += "\n\nIf you're using a callable class, make sure to pass the class itself or a callable instance."
raise TypeError(error_msg)
self.dependency = dependency
self.use_cache = use_cache
def __repr__(self) -> str:
attr = getattr(self.dependency, "__name__", type(self.dependency).__name__)
cache = "" if self.use_cache else ", use_cache=False"
return f"{self.__class__.__name__}({attr}{cache})"
@dataclass
class Security(Depends):

150
tests/test_depends_validation.py

@ -0,0 +1,150 @@
import pytest
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
def test_depends_with_called_function():
"""Test that calling a function in Depends() raises a clear error."""
def get_value():
return "value"
with pytest.raises(TypeError) as exc_info:
app = FastAPI()
@app.get("/")
def route(val=Depends(get_value())): # Wrong: calling the function
return val
error_message = str(exc_info.value)
assert "callable" in error_message.lower()
assert (
"Depends(my_function())" in error_message
or "called the dependency" in error_message
)
def test_depends_with_nested_depends():
"""Test that nesting Depends raises a clear error."""
def get_value():
return "value"
with pytest.raises(TypeError) as exc_info:
app = FastAPI()
@app.get("/")
def route(val=Depends(Depends(get_value))): # Wrong: nested Depends
return val
error_message = str(exc_info.value)
assert "nested" in error_message.lower()
assert "Depends(Depends(" in error_message
def test_depends_with_string():
"""Test that passing a string to Depends raises a clear error."""
with pytest.raises(TypeError) as exc_info:
app = FastAPI()
@app.get("/")
def route(val=Depends("not_a_function")): # Wrong: string
return val
error_message = str(exc_info.value)
assert "callable" in error_message.lower()
assert "str" in error_message
def test_depends_with_dict():
"""Test that passing a dict to Depends raises a clear error."""
with pytest.raises(TypeError) as exc_info:
app = FastAPI()
@app.get("/")
def route(val=Depends({"key": "value"})): # Wrong: dict
return val
error_message = str(exc_info.value)
assert "callable" in error_message.lower()
assert "dict" in error_message
def test_depends_with_correct_usage():
"""Test that correct usage of Depends works fine."""
def get_value():
return "correct_value"
app = FastAPI()
@app.get("/")
def route(val: str = Depends(get_value)): # Correct usage
return {"value": val}
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"value": "correct_value"}
def test_depends_with_callable_class():
"""Test that Depends works with callable classes."""
class CallableClass:
def __call__(self):
return "from_callable_class"
app = FastAPI()
@app.get("/")
def route(val: str = Depends(CallableClass())): # Correct: callable instance
return {"value": val}
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"value": "from_callable_class"}
def test_depends_with_none():
"""Test that Depends with None works (for sub-dependencies)."""
app = FastAPI()
@app.get("/")
def route(val=Depends(None)): # Valid for sub-dependencies
return {"value": "none"}
# This should not raise an error during app definition
assert app is not None
def test_error_message_quality():
"""Test that error messages are helpful and specific."""
def returns_dict():
return {"data": "value"}
with pytest.raises(TypeError) as exc_info:
app = FastAPI()
@app.get("/")
def route(val=Depends(returns_dict())):
return val
error_message = str(exc_info.value)
# Check that the error message contains helpful hints
assert any(
hint in error_message
for hint in [
"✓ Correct",
"✗ Wrong",
"called the dependency",
"function instead of passing it",
]
)
Loading…
Cancel
Save