mirror of https://github.com/tiangolo/fastapi.git
Improve error message for missing dependency type annotation
This commit is contained in:
parent
53d2453d1a
commit
fad0041185
|
|
@ -46,7 +46,7 @@ from fastapi.concurrency import (
|
|||
contextmanager_in_threadpool,
|
||||
)
|
||||
from fastapi.dependencies.models import Dependant
|
||||
from fastapi.exceptions import DependencyScopeError
|
||||
from fastapi.exceptions import DependencyScopeError, FastAPIError
|
||||
from fastapi.logger import logger
|
||||
from fastapi.security.oauth2 import SecurityScopes
|
||||
from fastapi.types import DependencyCacheKey
|
||||
|
|
@ -109,7 +109,9 @@ def ensure_multipart_is_installed() -> 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), (
|
||||
"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,
|
||||
scope=depends.scope,
|
||||
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)
|
||||
|
||||
|
||||
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(
|
||||
*,
|
||||
path: str,
|
||||
|
|
@ -257,6 +267,7 @@ def get_dependant(
|
|||
parent_oauth_scopes: Optional[list[str]] = None,
|
||||
use_cache: bool = True,
|
||||
scope: Union[Literal["function", "request"], None] = None,
|
||||
enforce_annotation: bool = True,
|
||||
) -> Dependant:
|
||||
dependant = Dependant(
|
||||
call=call,
|
||||
|
|
@ -281,6 +292,15 @@ def get_dependant(
|
|||
)
|
||||
if param_details.depends is not None:
|
||||
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 (
|
||||
(dependant.is_gen_callable or dependant.is_async_gen_callable)
|
||||
and dependant.computed_scope == "request"
|
||||
|
|
@ -303,6 +323,7 @@ def get_dependant(
|
|||
parent_oauth_scopes=current_scopes,
|
||||
use_cache=param_details.depends.use_cache,
|
||||
scope=param_details.depends.scope,
|
||||
enforce_annotation=False,
|
||||
)
|
||||
dependant.dependencies.append(sub_dependant)
|
||||
continue
|
||||
|
|
@ -315,7 +336,11 @@ def get_dependant(
|
|||
f"Cannot specify multiple FastAPI annotations for {param_name!r}"
|
||||
)
|
||||
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):
|
||||
dependant.body_params.append(param_details.field)
|
||||
else:
|
||||
|
|
@ -609,6 +634,7 @@ async def solve_dependencies(
|
|||
name=sub_dependant.name,
|
||||
parent_oauth_scopes=sub_dependant.oauth_scopes,
|
||||
scope=sub_dependant.scope,
|
||||
enforce_annotation=False,
|
||||
)
|
||||
|
||||
solved_result = await solve_dependencies(
|
||||
|
|
|
|||
|
|
@ -456,7 +456,10 @@ class APIWebSocketRoute(routing.WebSocketRoute):
|
|||
self.dependencies = list(dependencies or [])
|
||||
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
|
||||
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]:
|
||||
self.dependant.dependencies.insert(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from typing import Optional
|
|||
|
||||
import pytest
|
||||
from fastapi import APIRouter, Depends, FastAPI
|
||||
from fastapi.exceptions import FastAPIError
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
|
@ -389,3 +390,21 @@ def test_override_with_sub_router_decorator_depends_k_bar():
|
|||
assert response.status_code == 200
|
||||
assert response.json() == {"in": "router-decorator-depends"}
|
||||
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…
Reference in New Issue