✨ Add parameter dependencies to path operation decorators and include_router (#235)

* ✨ Implement dependencies in decorator and .include_router

* 📝 Add docs for parameter dependencies

* ✅ Add tests for dependencies parameter

* 🔥 Remove debugging prints in tests

* 📝 Update release notes
This commit is contained in:
Sebastián Ramírez
2019-05-16 18:07:00 +04:00
committed by GitHub
parent 7c50025c47
commit e92b43b5c8
16 changed files with 472 additions and 67 deletions
+27 -1
View File
@@ -3,6 +3,7 @@ from typing import Any, Callable, Dict, List, Optional, Type, Union
from fastapi import routing
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from fastapi.params import Depends
from pydantic import BaseModel
from starlette.applications import Starlette
from starlette.exceptions import ExceptionMiddleware, HTTPException
@@ -111,6 +112,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -128,6 +130,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -147,6 +150,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -165,6 +169,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -186,10 +191,15 @@ class FastAPI(Starlette):
*,
prefix: str = "",
tags: List[str] = None,
dependencies: List[Depends] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
) -> None:
self.router.include_router(
router, prefix=prefix, tags=tags, responses=responses or {}
router,
prefix=prefix,
tags=tags,
dependencies=dependencies,
responses=responses or {},
)
def get(
@@ -199,6 +209,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -214,6 +225,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -232,6 +244,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -247,6 +260,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -265,6 +279,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -280,6 +295,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -298,6 +314,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -313,6 +330,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -331,6 +349,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -346,6 +365,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -364,6 +384,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -379,6 +400,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -397,6 +419,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -412,6 +435,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -430,6 +454,7 @@ class FastAPI(Starlette):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -445,6 +470,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
+29 -5
View File
@@ -52,7 +52,7 @@ sequence_types = (list, set, tuple)
sequence_shape_to_type = {Shape.LIST: list, Shape.SET: set, Shape.TUPLE: tuple}
def get_sub_dependant(
def get_param_sub_dependant(
*, param: inspect.Parameter, path: str, security_scopes: List[str] = None
) -> Dependant:
depends: params.Depends = param.default
@@ -60,6 +60,30 @@ def get_sub_dependant(
dependency = depends.dependency
else:
dependency = param.annotation
return get_sub_dependant(
depends=depends,
dependency=dependency,
path=path,
name=param.name,
security_scopes=security_scopes,
)
def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
assert callable(
depends.dependency
), "A parameter-less dependency must have a callable dependency"
return get_sub_dependant(depends=depends, dependency=depends.dependency, path=path)
def get_sub_dependant(
*,
depends: params.Depends,
dependency: Callable,
path: str,
name: str = None,
security_scopes: List[str] = None,
) -> Dependant:
security_requirement = None
security_scopes = security_scopes or []
if isinstance(depends, params.Security):
@@ -73,7 +97,7 @@ def get_sub_dependant(
security_scheme=dependency, scopes=use_scopes
)
sub_dependant = get_dependant(
path=path, call=dependency, name=param.name, security_scopes=security_scopes
path=path, call=dependency, name=name, security_scopes=security_scopes
)
if security_requirement:
sub_dependant.security_requirements.append(security_requirement)
@@ -111,7 +135,7 @@ def get_dependant(
for param_name in signature_params:
param = signature_params[param_name]
if isinstance(param.default, params.Depends):
sub_dependant = get_sub_dependant(
sub_dependant = get_param_sub_dependant(
param=param, path=path, security_scopes=security_scopes
)
dependant.dependencies.append(sub_dependant)
@@ -277,8 +301,8 @@ async def solve_dependencies(
solved = await sub_dependant.call(**sub_values)
else:
solved = await run_in_threadpool(sub_dependant.call, **sub_values)
assert sub_dependant.name is not None, "Subdependants always have a name"
values[sub_dependant.name] = solved
if sub_dependant.name is not None:
values[sub_dependant.name] = solved
path_values, path_errors = request_params_to_args(
dependant.path_params, request.path_params
)
+34 -1
View File
@@ -5,7 +5,12 @@ from typing import Any, Callable, Dict, List, Optional, Type, Union
from fastapi import params
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.utils import get_body_field, get_dependant, solve_dependencies
from fastapi.dependencies.utils import (
get_body_field,
get_dependant,
get_parameterless_sub_dependant,
solve_dependencies,
)
from fastapi.encoders import jsonable_encoder
from pydantic import BaseConfig, BaseModel, Schema
from pydantic.error_wrappers import ErrorWrapper, ValidationError
@@ -101,6 +106,7 @@ class APIRoute(routing.Route):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -135,6 +141,7 @@ class APIRoute(routing.Route):
self.response_field = None
self.status_code = status_code
self.tags = tags or []
self.dependencies = dependencies or []
self.summary = summary
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
self.response_description = response_description
@@ -175,6 +182,10 @@ class APIRoute(routing.Route):
endpoint
), f"An endpoint must be a function or method"
self.dependant = get_dependant(path=path, call=self.endpoint)
for depends in self.dependencies[::-1]:
self.dependant.dependencies.insert(
0, get_parameterless_sub_dependant(depends=depends, path=path)
)
self.body_field = get_body_field(dependant=self.dependant, name=self.name)
self.app = request_response(
get_app(
@@ -196,6 +207,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -213,6 +225,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -233,6 +246,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -251,6 +265,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -272,6 +287,7 @@ class APIRouter(routing.Router):
*,
prefix: str = "",
tags: List[str] = None,
dependencies: List[params.Depends] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
) -> None:
if prefix:
@@ -290,6 +306,7 @@ class APIRouter(routing.Router):
response_model=route.response_model,
status_code=route.status_code,
tags=(route.tags or []) + (tags or []),
dependencies=(dependencies or []) + (route.dependencies or []),
summary=route.summary,
description=route.description,
response_description=route.response_description,
@@ -321,6 +338,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -336,6 +354,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -355,6 +374,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -370,6 +390,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -389,6 +410,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -404,6 +426,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -423,6 +446,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -438,6 +462,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -457,6 +482,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -472,6 +498,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -491,6 +518,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -506,6 +534,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -525,6 +554,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -540,6 +570,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,
@@ -559,6 +590,7 @@ class APIRouter(routing.Router):
response_model: Type[BaseModel] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -574,6 +606,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
summary=summary,
description=description,
response_description=response_description,