Skip to content

Commit e92b43b

Browse files
authored
✨ Add parameter dependencies to path operation decorators and include_router (fastapi#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
1 parent 7c50025 commit e92b43b

File tree

16 files changed

+471
-66
lines changed

16 files changed

+471
-66
lines changed

docs/release-notes.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
## Next release
22

3+
* Add support for `dependencies` parameter:
4+
* 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*.
5+
* 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.
6+
* This is useful, for example, to require authentication or permissions in specific group of *path operations*.
7+
* Different `dependencies` can be applied to different routers.
8+
* These `dependencies` are run before the normal parameter dependencies. And normal dependencies are run too. They can be combined.
9+
* 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.
10+
* All this also supports using `Security` with `scopes` in those `dependencies` parameters, for more advanced OAuth 2.0 security scenarios with scopes.
11+
* New documentation about [dependencies in *path operation decorators*](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
12+
* 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).
13+
* PR [#235](https://github.com/tiangolo/fastapi/pull/235).
14+
315
* 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).
416

517
* 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).
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
from fastapi import FastAPI
1+
from fastapi import Depends, FastAPI, Header, HTTPException
22

33
from .routers import items, users
44

55
app = FastAPI()
66

7+
8+
async def get_token_header(x_token: str = Header(...)):
9+
if x_token != "fake-super-secret-token":
10+
raise HTTPException(status_code=400, detail="X-Token header invalid")
11+
12+
713
app.include_router(users.router)
814
app.include_router(
915
items.router,
1016
prefix="/items",
1117
tags=["items"],
18+
dependencies=[Depends(get_token_header)],
1219
responses={404: {"description": "Not found"}},
1320
)

docs/src/dependencies/tutorial006.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
from fastapi import Depends, FastAPI
1+
from fastapi import Depends, FastAPI, Header, HTTPException
22

33
app = FastAPI()
44

55

6-
class FixedContentQueryChecker:
7-
def __init__(self, fixed_content: str):
8-
self.fixed_content = fixed_content
6+
async def verify_token(x_token: str = Header(...)):
7+
if x_token != "fake-super-secret-token":
8+
raise HTTPException(status_code=400, detail="X-Token header invalid")
99

10-
def __call__(self, q: str = ""):
11-
if q:
12-
return self.fixed_content in q
13-
return False
1410

11+
async def verify_key(x_key: str = Header(...)):
12+
if x_key != "fake-super-secret-key":
13+
raise HTTPException(status_code=400, detail="X-Key header invalid")
14+
return x_key
1515

16-
checker = FixedContentQueryChecker("bar")
1716

18-
19-
@app.get("/query-checker/")
20-
async def read_query_check(fixed_content_included: bool = Depends(checker)):
21-
return {"fixed_content_in_query": fixed_content_included}
17+
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
18+
async def read_items():
19+
return [{"item": "Foo"}, {"item": "Bar"}]

docs/src/dependencies/tutorial007.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from fastapi import Depends, FastAPI
2+
3+
app = FastAPI()
4+
5+
6+
class FixedContentQueryChecker:
7+
def __init__(self, fixed_content: str):
8+
self.fixed_content = fixed_content
9+
10+
def __call__(self, q: str = ""):
11+
if q:
12+
return self.fixed_content in q
13+
return False
14+
15+
16+
checker = FixedContentQueryChecker("bar")
17+
18+
19+
@app.get("/query-checker/")
20+
async def read_query_check(fixed_content_included: bool = Depends(checker)):
21+
return {"fixed_content_in_query": fixed_content_included}

docs/tutorial/bigger-applications.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@ Let's say you have a file structure like this:
2222

2323
!!! tip
2424
There are two `__init__.py` files: one in each directory or subdirectory.
25-
25+
2626
This is what allows importing code from one file into another.
2727

2828
For example, in `app/main.py` you could have a line like:
29-
29+
3030
```
3131
from app.routers import items
3232
```
3333

34-
3534
* The `app` directory contains everything.
3635
* This `app` directory has an empty file `app/__init__.py`.
3736
* 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
107106
{!./src/bigger_applications/app/routers/items.py!}
108107
```
109108

110-
### Add some custom `tags` and `responses`
109+
### Add some custom `tags`, `responses`, and `dependencies`
111110

112111
We are not adding the prefix `/items/` nor the `tags=["items"]` to add them later.
113112

@@ -197,12 +196,11 @@ So, to be able to use both of them in the same file, we import the submodules di
197196
{!./src/bigger_applications/app/main.py!}
198197
```
199198

200-
201199
### Include an `APIRouter`
202200

203201
Now, let's include the `router` from the submodule `users`:
204202

205-
```Python hl_lines="7"
203+
```Python hl_lines="13"
206204
{!./src/bigger_applications/app/main.py!}
207205
```
208206

@@ -221,13 +219,12 @@ It will include all the routes from that router as part of it.
221219

222220
!!! check
223221
You don't have to worry about performance when including routers.
224-
222+
225223
This will take microseconds and will only happen at startup.
226-
227-
So it won't affect performance.
228224

225+
So it won't affect performance.
229226

230-
### Include an `APIRouter` with a `prefix`, `tags`, and `responses`
227+
### Include an `APIRouter` with a `prefix`, `tags`, `responses`, and `dependencies`
231228

232229
Now, let's include the router from the `items` submodule.
233230

@@ -251,7 +248,9 @@ We can also add a list of `tags` that will be applied to all the *path operation
251248

252249
And we can add predefined `responses` that will be included in all the *path operations* too.
253250

254-
```Python hl_lines="8 9 10 11 12 13"
251+
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.
252+
253+
```Python hl_lines="8 9 10 14 15 16 17 18 19 20"
255254
{!./src/bigger_applications/app/main.py!}
256255
```
257256

@@ -262,27 +261,28 @@ The end result is that the item paths are now:
262261

263262
...as we intended.
264263

265-
They will be marked with a list of tags that contain a single string `"items"`.
264+
* They will be marked with a list of tags that contain a single string `"items"`.
265+
* The *path operation* that declared a `"custom"` tag will have both tags, `items` and `custom`.
266+
* These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
267+
* All of them will include the predefined `responses`.
268+
* The *path operation* that declared a custom `403` response will have both the predefined responses (`404`) and the `403` declared in it directly.
269+
* All these *path operations* will have the list of `dependencies` evaluated/executed before them.
270+
* If you also declare dependencies in a specific *path operation*, **they will be executed too**.
271+
* The router dependencies are executed first, then the <a href="https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-decorator/" target="_blank">`dependencies` in the decorator</a>, and then the normal parameter dependencies.
272+
* You can also add <a href="https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/" target="_blank">`Security` dependencies with `scopes`</a>.
266273

267-
The *path operation* that declared a `"custom"` tag will have both tags, `items` and `custom`.
268-
269-
These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
270-
271-
And all of them will include the the predefined `responses`.
272-
273-
The *path operation* that declared a custom `403` response will have both the predefined responses (`404`) and the `403` declared in it directly.
274+
!!! tip
275+
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.
274276

275277
!!! check
276-
The `prefix`, `tags`, and `responses` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
277-
278+
The `prefix`, `tags`, `responses` and `dependencies` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
278279

279280
!!! tip
280281
You could also add path operations directly, for example with: `@app.get(...)`.
281-
282+
282283
Apart from `app.include_router()`, in the same **FastAPI** app.
283-
284-
It would still work the same.
285284

285+
It would still work the same.
286286

287287
!!! info "Very Technical Details"
288288
**Note**: this is a very technical detail that you probably can **just skip**.

docs/tutorial/dependencies/advanced-dependencies.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Not the class itself (which is already a callable), but an instance of that clas
2222
To do that, we declare a method `__call__`:
2323

2424
```Python hl_lines="10"
25-
{!./src/dependencies/tutorial006.py!}
25+
{!./src/dependencies/tutorial007.py!}
2626
```
2727

2828
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
3232
And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency:
3333

3434
```Python hl_lines="7"
35-
{!./src/dependencies/tutorial006.py!}
35+
{!./src/dependencies/tutorial007.py!}
3636
```
3737

3838
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
4242
We could create an instance of this class with:
4343

4444
```Python hl_lines="16"
45-
{!./src/dependencies/tutorial006.py!}
45+
{!./src/dependencies/tutorial007.py!}
4646
```
4747

4848
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")
6060
...and pass whatever that returns as the value of the dependency in our path operation function as the parameter `fixed_content_included`:
6161

6262
```Python hl_lines="20"
63-
{!./src/dependencies/tutorial006.py!}
63+
{!./src/dependencies/tutorial007.py!}
6464
```
6565

6666
!!! tip
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
In some cases you don't really need the return value of a dependency inside your *path operation function*.
2+
3+
Or the dependency doesn't return a value.
4+
5+
But you still need it to be executed/solved.
6+
7+
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*.
8+
9+
## Add `dependencies` to the *path operation decorator*
10+
11+
The *path operation decorator* receives an optional argument `dependencies`.
12+
13+
It should be a `list` of `Depends()`:
14+
15+
```Python hl_lines="17"
16+
{!./src/dependencies/tutorial006.py!}
17+
```
18+
19+
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*.
20+
21+
!!! tip
22+
Some editors check for unused function parameters, and show them as errors.
23+
24+
Using these `dependencies` in the *path operation decorator* you can make sure they are executed while avoiding editor/tooling errors.
25+
26+
It might also help avoiding confusion for new developers that see an un-used parameter in your code and could think it's unnecessary.
27+
28+
## Dependencies errors and return values
29+
30+
You can use the same dependency *functions* you use normally.
31+
32+
### Dependency requirements
33+
34+
They can declare request requirements (like headers) or other sub-dependencies:
35+
36+
```Python hl_lines="6 11"
37+
{!./src/dependencies/tutorial006.py!}
38+
```
39+
40+
### Raise exceptions
41+
42+
These dependencies can `raise` exceptions, the same as normal dependencies:
43+
44+
```Python hl_lines="8 13"
45+
{!./src/dependencies/tutorial006.py!}
46+
```
47+
48+
### Return values
49+
50+
And they can return values or not, the values won't be used.
51+
52+
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:
53+
54+
```Python hl_lines="9 14"
55+
{!./src/dependencies/tutorial006.py!}
56+
```
57+
58+
## Dependencies for a group of *path operations*
59+
60+
Later, when reading about how to <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/" target="_blank">structure bigger applications</a>, possibly with multiple files, you will learn how to declare a single `dependencies` parameter for a group of *path operations*.

docs/tutorial/security/oauth2-scopes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,7 @@ The most secure is the code flow, but is more complex to implement as it require
244244
But in the end, they are implementing the same OAuth2 standard.
245245

246246
**FastAPI** includes utilities for all these OAuth2 authentication flows in `fastapi.security.oauth2`.
247+
248+
## `Security` in decorator `dependencies`
249+
250+
The same way you can define a `list` of <a href="https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-decorator/" target="_blank">`Depends` in the decorator's `dependencies` parameter</a>, you could also use `Security` with `scopes` there.

0 commit comments

Comments
 (0)