Browse Source

Improve error message for missing dependency type annotation

pull/14621/head
“Uner” 7 months ago
parent
commit
fad0041185
  1. 32
      fastapi/dependencies/utils.py
  2. 5
      fastapi/routing.py
  3. 19
      tests/test_dependency_overrides.py

32
fastapi/dependencies/utils.py

@ -46,7 +46,7 @@ from fastapi.concurrency import (
contextmanager_in_threadpool, contextmanager_in_threadpool,
) )
from fastapi.dependencies.models import Dependant from fastapi.dependencies.models import Dependant
from fastapi.exceptions import DependencyScopeError from fastapi.exceptions import DependencyScopeError, FastAPIError
from fastapi.logger import logger from fastapi.logger import logger
from fastapi.security.oauth2 import SecurityScopes from fastapi.security.oauth2 import SecurityScopes
from fastapi.types import DependencyCacheKey from fastapi.types import DependencyCacheKey
@ -109,7 +109,9 @@ def ensure_multipart_is_installed() -> None:
raise RuntimeError(multipart_not_installed_error) from None raise RuntimeError(multipart_not_installed_error) from None
def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant: def get_parameterless_sub_dependant(
*, depends: params.Depends, path: str
) -> Dependant:
assert callable(depends.dependency), ( assert callable(depends.dependency), (
"A parameter-less dependency must have a callable dependency" "A parameter-less dependency must have a callable dependency"
) )
@ -121,6 +123,7 @@ def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> De
call=depends.dependency, call=depends.dependency,
scope=depends.scope, scope=depends.scope,
own_oauth_scopes=own_oauth_scopes, own_oauth_scopes=own_oauth_scopes,
enforce_annotation=False,
) )
@ -248,6 +251,13 @@ def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
return get_typed_annotation(annotation, globalns) return get_typed_annotation(annotation, globalns)
def _dependency_defines_type(depends: params.Depends) -> bool:
dependency = depends.dependency
if inspect.isclass(dependency):
return True
return get_typed_return_annotation(dependency) is not None
def get_dependant( def get_dependant(
*, *,
path: str, path: str,
@ -257,6 +267,7 @@ def get_dependant(
parent_oauth_scopes: Optional[list[str]] = None, parent_oauth_scopes: Optional[list[str]] = None,
use_cache: bool = True, use_cache: bool = True,
scope: Union[Literal["function", "request"], None] = None, scope: Union[Literal["function", "request"], None] = None,
enforce_annotation: bool = True,
) -> Dependant: ) -> Dependant:
dependant = Dependant( dependant = Dependant(
call=call, call=call,
@ -281,6 +292,15 @@ def get_dependant(
) )
if param_details.depends is not None: if param_details.depends is not None:
assert param_details.depends.dependency assert param_details.depends.dependency
if (
enforce_annotation
and param.annotation is inspect.Signature.empty
and not _dependency_defines_type(param_details.depends)
):
raise FastAPIError(
f'Dependency parameter "{param_name}" must have a type annotation. '
f'For example: `{param_name}: SomeType = Depends(...)`'
)
if ( if (
(dependant.is_gen_callable or dependant.is_async_gen_callable) (dependant.is_gen_callable or dependant.is_async_gen_callable)
and dependant.computed_scope == "request" and dependant.computed_scope == "request"
@ -303,6 +323,7 @@ def get_dependant(
parent_oauth_scopes=current_scopes, parent_oauth_scopes=current_scopes,
use_cache=param_details.depends.use_cache, use_cache=param_details.depends.use_cache,
scope=param_details.depends.scope, scope=param_details.depends.scope,
enforce_annotation=False,
) )
dependant.dependencies.append(sub_dependant) dependant.dependencies.append(sub_dependant)
continue continue
@ -315,7 +336,11 @@ def get_dependant(
f"Cannot specify multiple FastAPI annotations for {param_name!r}" f"Cannot specify multiple FastAPI annotations for {param_name!r}"
) )
continue continue
assert param_details.field is not None if enforce_annotation and param_details.field is None:
raise FastAPIError(
f'Dependency parameter "{param_name}" must have a type annotation. '
f'For example: `{param_name}: SomeType = Depends(...)`'
)
if isinstance(param_details.field.field_info, params.Body): if isinstance(param_details.field.field_info, params.Body):
dependant.body_params.append(param_details.field) dependant.body_params.append(param_details.field)
else: else:
@ -609,6 +634,7 @@ async def solve_dependencies(
name=sub_dependant.name, name=sub_dependant.name,
parent_oauth_scopes=sub_dependant.oauth_scopes, parent_oauth_scopes=sub_dependant.oauth_scopes,
scope=sub_dependant.scope, scope=sub_dependant.scope,
enforce_annotation=False,
) )
solved_result = await solve_dependencies( solved_result = await solve_dependencies(

5
fastapi/routing.py

@ -456,7 +456,10 @@ class APIWebSocketRoute(routing.WebSocketRoute):
self.dependencies = list(dependencies or []) self.dependencies = list(dependencies or [])
self.path_regex, self.path_format, self.param_convertors = compile_path(path) self.path_regex, self.path_format, self.param_convertors = compile_path(path)
self.dependant = get_dependant( self.dependant = get_dependant(
path=self.path_format, call=self.endpoint, scope="function" path=self.path_format,
call=self.endpoint,
scope="function",
enforce_annotation=False,
) )
for depends in self.dependencies[::-1]: for depends in self.dependencies[::-1]:
self.dependant.dependencies.insert( self.dependant.dependencies.insert(

19
tests/test_dependency_overrides.py

@ -2,6 +2,7 @@ from typing import Optional
import pytest import pytest
from fastapi import APIRouter, Depends, FastAPI from fastapi import APIRouter, Depends, FastAPI
from fastapi.exceptions import FastAPIError
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
app = FastAPI() app = FastAPI()
@ -389,3 +390,21 @@ def test_override_with_sub_router_decorator_depends_k_bar():
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"in": "router-decorator-depends"} assert response.json() == {"in": "router-decorator-depends"}
app.dependency_overrides = {} app.dependency_overrides = {}
def test_missing_type_annotation_dependency():
local_app = FastAPI()
def get_db():
return "db"
with pytest.raises(FastAPIError) as exc:
@local_app.get("/bad")
def bad(dep=Depends(get_db)):
return dep
TestClient(local_app)
msg = str(exc.value)
assert "Dependency parameter" in msg
assert "type annotation" in msg
assert "Depends" in msg

Loading…
Cancel
Save