diff --git a/docs/release-notes.md b/docs/release-notes.md
index 180f8c6d5..eb8506069 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,5 +1,17 @@
## Next release
+* Add support for `dependencies` parameter:
+ * A parameter in *path operation decorators*, for dependencies that should be executed but the return value is not important or not used in the *path operation function*.
+ * A parameter in the `.include_router()` method of FastAPI applications and routers, to include dependencies that should be executed in each *path operation* in a router.
+ * This is useful, for example, to require authentication or permissions in specific group of *path operations*.
+ * Different `dependencies` can be applied to different routers.
+ * These `dependencies` are run before the normal parameter dependencies. And normal dependencies are run too. They can be combined.
+ * Dependencies declared in a router are executed first, then the ones defined in *path operation decorators*, and then the ones declared in normal parameters. They are all combined and executed.
+ * All this also supports using `Security` with `scopes` in those `dependencies` parameters, for more advanced OAuth 2.0 security scenarios with scopes.
+ * New documentation about [dependencies in *path operation decorators*](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
+ * New documentation about [dependencies in the `include_router()` method](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-prefix-tags-responses-and-dependencies).
+ * PR [#235](https://github.com/tiangolo/fastapi/pull/235).
+
* Fix OpenAPI documentation of Starlette URL convertors. Specially useful when using `path` convertors, to take a whole path as a parameter, like `/some/url/{p:path}`. PR [#234](https://github.com/tiangolo/fastapi/pull/234) by [@euri10](https://github.com/euri10).
* Make default parameter utilities exported from `fastapi` be functions instead of classes (the new functions return instances of those classes). To be able to override the return types and fix `mypy` errors in FastAPI's users' code. Applies to `Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, `File`, `Depends`, and `Security`. PR [#226](https://github.com/tiangolo/fastapi/pull/226) and PR [#231](https://github.com/tiangolo/fastapi/pull/231).
diff --git a/docs/src/bigger_applications/app/main.py b/docs/src/bigger_applications/app/main.py
index 2cebd4244..fdff13947 100644
--- a/docs/src/bigger_applications/app/main.py
+++ b/docs/src/bigger_applications/app/main.py
@@ -1,13 +1,20 @@
-from fastapi import FastAPI
+from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
+
+async def get_token_header(x_token: str = Header(...)):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
+ dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
diff --git a/docs/src/dependencies/tutorial006.py b/docs/src/dependencies/tutorial006.py
index 5d22f6823..a71d7cce6 100644
--- a/docs/src/dependencies/tutorial006.py
+++ b/docs/src/dependencies/tutorial006.py
@@ -1,21 +1,19 @@
-from fastapi import Depends, FastAPI
+from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
-class FixedContentQueryChecker:
- def __init__(self, fixed_content: str):
- self.fixed_content = fixed_content
+async def verify_token(x_token: str = Header(...)):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
- def __call__(self, q: str = ""):
- if q:
- return self.fixed_content in q
- return False
+async def verify_key(x_key: str = Header(...)):
+ if x_key != "fake-super-secret-key":
+ raise HTTPException(status_code=400, detail="X-Key header invalid")
+ return x_key
-checker = FixedContentQueryChecker("bar")
-
-@app.get("/query-checker/")
-async def read_query_check(fixed_content_included: bool = Depends(checker)):
- return {"fixed_content_in_query": fixed_content_included}
+@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
+async def read_items():
+ return [{"item": "Foo"}, {"item": "Bar"}]
diff --git a/docs/src/dependencies/tutorial007.py b/docs/src/dependencies/tutorial007.py
new file mode 100644
index 000000000..5d22f6823
--- /dev/null
+++ b/docs/src/dependencies/tutorial007.py
@@ -0,0 +1,21 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+class FixedContentQueryChecker:
+ def __init__(self, fixed_content: str):
+ self.fixed_content = fixed_content
+
+ def __call__(self, q: str = ""):
+ if q:
+ return self.fixed_content in q
+ return False
+
+
+checker = FixedContentQueryChecker("bar")
+
+
+@app.get("/query-checker/")
+async def read_query_check(fixed_content_included: bool = Depends(checker)):
+ return {"fixed_content_in_query": fixed_content_included}
diff --git a/docs/tutorial/bigger-applications.md b/docs/tutorial/bigger-applications.md
index 7adbab77c..f9e2f6aa7 100644
--- a/docs/tutorial/bigger-applications.md
+++ b/docs/tutorial/bigger-applications.md
@@ -22,16 +22,15 @@ Let's say you have a file structure like this:
!!! tip
There are two `__init__.py` files: one in each directory or subdirectory.
-
+
This is what allows importing code from one file into another.
For example, in `app/main.py` you could have a line like:
-
+
```
from app.routers import items
```
-
* The `app` directory contains everything.
* This `app` directory has an empty file `app/__init__.py`.
* So, the `app` directory is a "Python package" (a collection of "Python modules").
@@ -107,7 +106,7 @@ And we don't want to have to explicitly type `/items/` and `tags=["items"]` in e
{!./src/bigger_applications/app/routers/items.py!}
```
-### Add some custom `tags` and `responses`
+### Add some custom `tags`, `responses`, and `dependencies`
We are not adding the prefix `/items/` nor the `tags=["items"]` to add them later.
@@ -197,12 +196,11 @@ So, to be able to use both of them in the same file, we import the submodules di
{!./src/bigger_applications/app/main.py!}
```
-
### Include an `APIRouter`
Now, let's include the `router` from the submodule `users`:
-```Python hl_lines="7"
+```Python hl_lines="13"
{!./src/bigger_applications/app/main.py!}
```
@@ -221,13 +219,12 @@ It will include all the routes from that router as part of it.
!!! check
You don't have to worry about performance when including routers.
-
+
This will take microseconds and will only happen at startup.
-
- So it won't affect performance.
+ So it won't affect performance.
-### Include an `APIRouter` with a `prefix`, `tags`, and `responses`
+### Include an `APIRouter` with a `prefix`, `tags`, `responses`, and `dependencies`
Now, let's include the router from the `items` submodule.
@@ -251,7 +248,9 @@ We can also add a list of `tags` that will be applied to all the *path operation
And we can add predefined `responses` that will be included in all the *path operations* too.
-```Python hl_lines="8 9 10 11 12 13"
+And we can add a list of `dependencies` that will be added to all the *path operations* in the router and will be executed/solved for each request made to them.
+
+```Python hl_lines="8 9 10 14 15 16 17 18 19 20"
{!./src/bigger_applications/app/main.py!}
```
@@ -262,27 +261,28 @@ The end result is that the item paths are now:
...as we intended.
-They will be marked with a list of tags that contain a single string `"items"`.
+* They will be marked with a list of tags that contain a single string `"items"`.
+* The *path operation* that declared a `"custom"` tag will have both tags, `items` and `custom`.
+ * These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
+* All of them will include the predefined `responses`.
+* The *path operation* that declared a custom `403` response will have both the predefined responses (`404`) and the `403` declared in it directly.
+* All these *path operations* will have the list of `dependencies` evaluated/executed before them.
+ * If you also declare dependencies in a specific *path operation*, **they will be executed too**.
+ * The router dependencies are executed first, then the `dependencies` in the decorator, and then the normal parameter dependencies.
+ * You can also add `Security` dependencies with `scopes`.
-The *path operation* that declared a `"custom"` tag will have both tags, `items` and `custom`.
-
-These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
-
-And all of them will include the the predefined `responses`.
-
-The *path operation* that declared a custom `403` response will have both the predefined responses (`404`) and the `403` declared in it directly.
+!!! tip
+ Having `dependencies` in a decorator can be used, for example, to require authentication for a whole group of *path operations*. Even if the dependencies are not added individually to each one of them.
!!! check
- The `prefix`, `tags`, and `responses` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
-
+ The `prefix`, `tags`, `responses` and `dependencies` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
!!! tip
You could also add path operations directly, for example with: `@app.get(...)`.
-
+
Apart from `app.include_router()`, in the same **FastAPI** app.
-
- It would still work the same.
+ It would still work the same.
!!! info "Very Technical Details"
**Note**: this is a very technical detail that you probably can **just skip**.
diff --git a/docs/tutorial/dependencies/advanced-dependencies.md b/docs/tutorial/dependencies/advanced-dependencies.md
index 824006441..903090f23 100644
--- a/docs/tutorial/dependencies/advanced-dependencies.md
+++ b/docs/tutorial/dependencies/advanced-dependencies.md
@@ -22,7 +22,7 @@ Not the class itself (which is already a callable), but an instance of that clas
To do that, we declare a method `__call__`:
```Python hl_lines="10"
-{!./src/dependencies/tutorial006.py!}
+{!./src/dependencies/tutorial007.py!}
```
In this case, this `__call__` is what **FastAPI** will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your *path operation function* later.
@@ -32,7 +32,7 @@ In this case, this `__call__` is what **FastAPI** will use to check for addition
And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency:
```Python hl_lines="7"
-{!./src/dependencies/tutorial006.py!}
+{!./src/dependencies/tutorial007.py!}
```
In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code.
@@ -42,7 +42,7 @@ In this case, **FastAPI** won't ever touch or care about `__init__`, we will use
We could create an instance of this class with:
```Python hl_lines="16"
-{!./src/dependencies/tutorial006.py!}
+{!./src/dependencies/tutorial007.py!}
```
And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`.
@@ -60,7 +60,7 @@ checker(q="somequery")
...and pass whatever that returns as the value of the dependency in our path operation function as the parameter `fixed_content_included`:
```Python hl_lines="20"
-{!./src/dependencies/tutorial006.py!}
+{!./src/dependencies/tutorial007.py!}
```
!!! tip
diff --git a/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
new file mode 100644
index 000000000..477bb3d1e
--- /dev/null
+++ b/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -0,0 +1,60 @@
+In some cases you don't really need the return value of a dependency inside your *path operation function*.
+
+Or the dependency doesn't return a value.
+
+But you still need it to be executed/solved.
+
+For those cases, instead of declaring a *path operation function* parameter with `Depends`, you can add a `list` of `dependencies` to the *path operation decorator*.
+
+## Add `dependencies` to the *path operation decorator*
+
+The *path operation decorator* receives an optional argument `dependencies`.
+
+It should be a `list` of `Depends()`:
+
+```Python hl_lines="17"
+{!./src/dependencies/tutorial006.py!}
+```
+
+These dependencies will be executed/solved the same way normal dependencies. But their value (if they return any) won't be passed to your *path operation function*.
+
+!!! tip
+ Some editors check for unused function parameters, and show them as errors.
+
+ Using these `dependencies` in the *path operation decorator* you can make sure they are executed while avoiding editor/tooling errors.
+
+ It might also help avoiding confusion for new developers that see an un-used parameter in your code and could think it's unnecessary.
+
+## Dependencies errors and return values
+
+You can use the same dependency *functions* you use normally.
+
+### Dependency requirements
+
+They can declare request requirements (like headers) or other sub-dependencies:
+
+```Python hl_lines="6 11"
+{!./src/dependencies/tutorial006.py!}
+```
+
+### Raise exceptions
+
+These dependencies can `raise` exceptions, the same as normal dependencies:
+
+```Python hl_lines="8 13"
+{!./src/dependencies/tutorial006.py!}
+```
+
+### Return values
+
+And they can return values or not, the values won't be used.
+
+So, you can re-use a normal dependency (that returns a value) you already use somewhere else, and even though the value won't be used, the dependency will be executed:
+
+```Python hl_lines="9 14"
+{!./src/dependencies/tutorial006.py!}
+```
+
+## Dependencies for a group of *path operations*
+
+Later, when reading about how to structure bigger applications, possibly with multiple files, you will learn how to declare a single `dependencies` parameter for a group of *path operations*.
diff --git a/docs/tutorial/security/oauth2-scopes.md b/docs/tutorial/security/oauth2-scopes.md
index ef4f6798e..89c973e8f 100644
--- a/docs/tutorial/security/oauth2-scopes.md
+++ b/docs/tutorial/security/oauth2-scopes.md
@@ -244,3 +244,7 @@ The most secure is the code flow, but is more complex to implement as it require
But in the end, they are implementing the same OAuth2 standard.
**FastAPI** includes utilities for all these OAuth2 authentication flows in `fastapi.security.oauth2`.
+
+## `Security` in decorator `dependencies`
+
+The same way you can define a `list` of `Depends` in the decorator's `dependencies` parameter, you could also use `Security` with `scopes` there.
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 076ebfcc6..e4b9ab967 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -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,
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index a87f23c69..0530fd209 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -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
)
diff --git a/fastapi/routing.py b/fastapi/routing.py
index ac8192baf..ef8d9bed2 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -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,
diff --git a/mkdocs.yml b/mkdocs.yml
index d5b81d543..8fa41d012 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -54,6 +54,7 @@ nav:
- First Steps: 'tutorial/dependencies/first-steps.md'
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
- Sub-dependencies: 'tutorial/dependencies/sub-dependencies.md'
+ - Dependencies in path operation decorators: 'tutorial/dependencies/dependencies-in-path-operation-decorators.md'
- Advanced Dependencies: 'tutorial/dependencies/advanced-dependencies.md'
- Security:
- Security Intro: 'tutorial/security/intro.md'
diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py
index db094df7d..d0eff5b65 100644
--- a/tests/test_tutorial/test_bigger_applications/test_main.py
+++ b/tests/test_tutorial/test_bigger_applications/test_main.py
@@ -74,10 +74,28 @@ openapi_schema = {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
},
"tags": ["items"],
"summary": "Read Items",
"operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "X-Token", "type": "string"},
+ "name": "x-token",
+ "in": "header",
+ }
+ ],
}
},
"/items/{item_id}": {
@@ -108,7 +126,13 @@ openapi_schema = {
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
- }
+ },
+ {
+ "required": True,
+ "schema": {"title": "X-Token", "type": "string"},
+ "name": "x-token",
+ "in": "header",
+ },
],
},
"put": {
@@ -139,7 +163,13 @@ openapi_schema = {
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
- }
+ },
+ {
+ "required": True,
+ "schema": {"title": "X-Token", "type": "string"},
+ "name": "x-token",
+ "in": "header",
+ },
],
},
},
@@ -177,29 +207,94 @@ openapi_schema = {
@pytest.mark.parametrize(
- "path,expected_status,expected_response",
+ "path,expected_status,expected_response,headers",
[
- ("/users", 200, [{"username": "Foo"}, {"username": "Bar"}]),
- ("/users/foo", 200, {"username": "foo"}),
- ("/users/me", 200, {"username": "fakecurrentuser"}),
- ("/items", 200, [{"name": "Item Foo"}, {"name": "item Bar"}]),
- ("/items/bar", 200, {"name": "Fake Specific Item", "item_id": "bar"}),
- ("/openapi.json", 200, openapi_schema),
+ ("/users", 200, [{"username": "Foo"}, {"username": "Bar"}], {}),
+ ("/users/foo", 200, {"username": "foo"}, {}),
+ ("/users/me", 200, {"username": "fakecurrentuser"}, {}),
+ (
+ "/items",
+ 200,
+ [{"name": "Item Foo"}, {"name": "item Bar"}],
+ {"X-Token": "fake-super-secret-token"},
+ ),
+ (
+ "/items/bar",
+ 200,
+ {"name": "Fake Specific Item", "item_id": "bar"},
+ {"X-Token": "fake-super-secret-token"},
+ ),
+ ("/items", 400, {"detail": "X-Token header invalid"}, {"X-Token": "invalid"}),
+ (
+ "/items/bar",
+ 400,
+ {"detail": "X-Token header invalid"},
+ {"X-Token": "invalid"},
+ ),
+ (
+ "/items",
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["header", "x-token"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ },
+ {},
+ ),
+ (
+ "/items/bar",
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["header", "x-token"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ },
+ {},
+ ),
+ ("/openapi.json", 200, openapi_schema, {}),
],
)
-def test_get_path(path, expected_status, expected_response):
- response = client.get(path)
+def test_get_path(path, expected_status, expected_response, headers):
+ response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
-def test_put():
+def test_put_no_header():
response = client.put("/items/foo")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["header", "x-token"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ }
+
+
+def test_put_invalid_header():
+ response = client.put("/items/foo", headers={"X-Token": "invalid"})
+ assert response.status_code == 400
+ assert response.json() == {"detail": "X-Token header invalid"}
+
+
+def test_put():
+ response = client.put("/items/foo", headers={"X-Token": "fake-super-secret-token"})
assert response.status_code == 200
assert response.json() == {"item_id": "foo", "name": "The Fighters"}
def test_put_forbidden():
- response = client.put("/items/bar")
+ response = client.put("/items/bar", headers={"X-Token": "fake-super-secret-token"})
assert response.status_code == 403
assert response.json() == {"detail": "You can only update the item: foo"}
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py
new file mode 100644
index 000000000..fdc878977
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py
@@ -0,0 +1,128 @@
+from starlette.testclient import TestClient
+
+from dependencies.tutorial006 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "X-Token", "type": "string"},
+ "name": "x-token",
+ "in": "header",
+ },
+ {
+ "required": True,
+ "schema": {"title": "X-Key", "type": "string"},
+ "name": "x-key",
+ "in": "header",
+ },
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
+
+
+def test_get_no_headers():
+ response = client.get("/items/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["header", "x-token"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["header", "x-key"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+
+
+def test_get_invalid_one_header():
+ response = client.get("/items/", headers={"X-Token": "invalid"})
+ assert response.status_code == 400
+ assert response.json() == {"detail": "X-Token header invalid"}
+
+
+def test_get_invalid_second_header():
+ response = client.get(
+ "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "X-Key header invalid"}
+
+
+def test_get_valid_headers():
+ response = client.get(
+ "/items/",
+ headers={
+ "X-Token": "fake-super-secret-token",
+ "X-Key": "fake-super-secret-key",
+ },
+ )
+ assert response.status_code == 200
+ assert response.json() == [{"item": "Foo"}, {"item": "Bar"}]
diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py
index e6b7ba479..15ea952ba 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial002.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial002.py
@@ -166,8 +166,6 @@ def test_post_form_no_body():
def test_post_body_json():
response = client.post("/files/", json={"file": "Foo"})
- print(response)
- print(response.content)
assert response.status_code == 422
assert response.json() == file_required
diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py
index 96f9fa9c1..403130e49 100644
--- a/tests/test_tutorial/test_security/test_tutorial005.py
+++ b/tests/test_tutorial/test_security/test_tutorial005.py
@@ -227,7 +227,6 @@ def test_token():
response = client.get(
"/users/me", headers={"Authorization": f"Bearer {access_token}"}
)
- print(response.json())
assert response.status_code == 200
assert response.json() == {
"username": "johndoe",
@@ -319,7 +318,6 @@ def test_token_inactive_user():
response = client.get(
"/users/me", headers={"Authorization": f"Bearer {access_token}"}
)
- print(response.json())
assert response.status_code == 400
assert response.json() == {"detail": "Inactive user"}