Browse Source

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
pull/241/head
Sebastián Ramírez 6 years ago
committed by GitHub
parent
commit
e92b43b5c8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      docs/release-notes.md
  2. 9
      docs/src/bigger_applications/app/main.py
  3. 24
      docs/src/dependencies/tutorial006.py
  4. 21
      docs/src/dependencies/tutorial007.py
  5. 48
      docs/tutorial/bigger-applications.md
  6. 8
      docs/tutorial/dependencies/advanced-dependencies.md
  7. 60
      docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
  8. 4
      docs/tutorial/security/oauth2-scopes.md
  9. 28
      fastapi/applications.py
  10. 34
      fastapi/dependencies/utils.py
  11. 35
      fastapi/routing.py
  12. 1
      mkdocs.yml
  13. 121
      tests/test_tutorial/test_bigger_applications/test_main.py
  14. 128
      tests/test_tutorial/test_dependencies/test_tutorial006.py
  15. 2
      tests/test_tutorial/test_request_files/test_tutorial002.py
  16. 2
      tests/test_tutorial/test_security/test_tutorial005.py

12
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).

9
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"}},
)

24
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"}]

21
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}

48
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 <a href="https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-decorator/" target="_blank">`dependencies` in the decorator</a>, and then the normal parameter dependencies.
* You can also add <a href="https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/" target="_blank">`Security` dependencies with `scopes`</a>.
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**.

8
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

60
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 <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*.

4
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 <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.

28
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,

34
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
)

35
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,

1
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'

121
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"}

128
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"}]

2
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

2
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"}

Loading…
Cancel
Save