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 enum import Enum
from typing import Any, Callable, Dict, List, Optional, Sequence, Union from typing import Any, Callable, Dict, List, Optional, Sequence, Union
from fastapi.openapi.models import Example
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
from typing_extensions import Annotated, deprecated from typing_extensions import Annotated, deprecated
from ._compat import ( from fastapi.openapi.models import Example
PYDANTIC_V2,
PYDANTIC_VERSION_MINOR_TUPLE, from ._compat import PYDANTIC_V2, PYDANTIC_VERSION_MINOR_TUPLE, Undefined
Undefined,
)
_Unset: Any = Undefined _Unset: Any = Undefined
@ -767,6 +764,48 @@ class Depends:
dependency: Optional[Callable[..., Any]] = None dependency: Optional[Callable[..., Any]] = None
use_cache: bool = True 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 @dataclass
class Security(Depends): 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