mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-27 00:01:03 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc89eb8f81 | ||
|
|
6fca1041e9 | ||
|
|
9db1f5641b | ||
|
|
c3beb56e63 | ||
|
|
325edd5f00 | ||
|
|
08322ef359 | ||
|
|
01b43e6e25 | ||
|
|
3cf92a156c | ||
|
|
f54d8d57a4 | ||
|
|
56ab106bbb | ||
|
|
e92b43b5c8 | ||
|
|
7c50025c47 | ||
|
|
adfbd27100 | ||
|
|
eada8bf7db | ||
|
|
440b7a8efa | ||
|
|
fcaff64646 | ||
|
|
d240421378 | ||
|
|
ca27317b65 | ||
|
|
ce02d3cb83 | ||
|
|
95475aaa9c | ||
|
|
7a8b054a12 |
2
Pipfile
2
Pipfile
@@ -25,7 +25,7 @@ sqlalchemy = "*"
|
||||
uvicorn = "*"
|
||||
|
||||
[packages]
|
||||
starlette = "==0.11.1"
|
||||
starlette = "==0.12.0"
|
||||
pydantic = "==0.25.0"
|
||||
databases = {extras = ["sqlite"],version = "*"}
|
||||
hypercorn = "*"
|
||||
|
||||
43
Pipfile.lock
generated
43
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "3366422de5c4cdc49b82ebef5fe9268c48c8582a444a4fa1ae304dcb2654c469"
|
||||
"sha256": "8d23c38a8d3018315f49a2298e9098d8b5f248338dbba9e024246c9abb5949a2"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@@ -21,7 +21,6 @@
|
||||
"sha256:885daf8261818767d8f7cbd79f9d4482d118f024b6586ef6e67980236a27bfa3",
|
||||
"sha256:f027372dc48641f683c559f247bd84962becaacdc9ba711d583c3871fb5652aa"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"aiosqlite": {
|
||||
@@ -134,10 +133,10 @@
|
||||
},
|
||||
"starlette": {
|
||||
"hashes": [
|
||||
"sha256:9d48b35d1fc7521d59ae53c421297ab3878d3c7cd4b75266d77f6c73cccb78bb"
|
||||
"sha256:d313433ef5cc38e0a276b59688ca2b11b8f031c78808c1afdf9d55cb86f34590"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.11.1"
|
||||
"version": "==0.12.0"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
@@ -348,10 +347,10 @@
|
||||
},
|
||||
"ipykernel": {
|
||||
"hashes": [
|
||||
"sha256:0aeb7ec277ac42cc2b59ae3d08b10909b2ec161dc6908096210527162b53675d",
|
||||
"sha256:0fc0bf97920d454102168ec2008620066878848fcfca06c22b669696212e292f"
|
||||
"sha256:346189536b88859937b5f4848a6fd85d1ad0729f01724a411de5cae9b618819c",
|
||||
"sha256:f0e962052718068ad3b1d8bcc703794660858f58803c3798628817f492a8769c"
|
||||
],
|
||||
"version": "==5.1.0"
|
||||
"version": "==5.1.1"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
@@ -377,11 +376,11 @@
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:49293e2ff590cc8d48bc1f51970548b5b102bf038439ca1af77f352164725628",
|
||||
"sha256:ba69a4be8474be11720636bc2f0cf66f7054d417d4c1dbc1dfe504bb8e739541"
|
||||
"sha256:c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a",
|
||||
"sha256:f57abacd059dc3bd666258d1efb0377510a89777fda3e3274e3c01f7c03ae22d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.19"
|
||||
"version": "==4.3.20"
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
@@ -443,10 +442,10 @@
|
||||
},
|
||||
"markdown": {
|
||||
"hashes": [
|
||||
"sha256:fc4a6f69a656b8d858d7503bda633f4dd63c2d70cf80abdc6eafa64c4ae8c250",
|
||||
"sha256:fe463ff51e679377e3624984c829022e2cfb3be5518726b06f608a07a3aad680"
|
||||
"sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a",
|
||||
"sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"
|
||||
],
|
||||
"version": "==3.1"
|
||||
"version": "==3.1.1"
|
||||
},
|
||||
"markdown-include": {
|
||||
"hashes": [
|
||||
@@ -512,11 +511,11 @@
|
||||
},
|
||||
"mkdocs-material": {
|
||||
"hashes": [
|
||||
"sha256:4ffe7d8c0c3c53c5313a910c14a88820be74beebb53ed14c9056e521ea9793d5",
|
||||
"sha256:d64b9555ae4ee86fe07a18612c9bd488f3b74a0afd3d9ead7e29efc59d98ca80"
|
||||
"sha256:1c39b6af13a900d9f47ab2b8ac67b3258799f4570b552573e9d6868ad6a438e9",
|
||||
"sha256:22073941cff7176e810b719aced6a90381e64a96d346b8a6803a06b7192b7ad5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.0"
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
@@ -760,11 +759,11 @@
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
|
||||
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.21.0"
|
||||
"version": "==2.22.0"
|
||||
},
|
||||
"send2trash": {
|
||||
"hashes": [
|
||||
@@ -859,10 +858,10 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4",
|
||||
"sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"
|
||||
"sha256:a53063d8b9210a7bdec15e7b272776b9d42b2fd6816401a0d43006ad2f9902db",
|
||||
"sha256:d363e3607d8de0c220d31950a8f38b18d5ba7c0830facd71a1c6b1036b7ce06c"
|
||||
],
|
||||
"version": "==1.24.3"
|
||||
"version": "==1.25.2"
|
||||
},
|
||||
"uvicorn": {
|
||||
"hashes": [
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
## Next release
|
||||
|
||||
## 0.23.0
|
||||
|
||||
* Upgrade the compatible version of Starlette to `0.12.0`.
|
||||
* This includes support for ASGI 3 (the latest version of the standard).
|
||||
* It's now possible to use [Starlette's `StreamingResponse`](https://www.starlette.io/responses/#streamingresponse) with iterators, like [file-like](https://docs.python.org/3/glossary.html#term-file-like-object) objects (as those returned by `open()`).
|
||||
* It's now possible to use the low level utility `iterate_in_threadpool` from `starlette.concurrency` (for advanced scenarios).
|
||||
* PR [#243](https://github.com/tiangolo/fastapi/pull/243).
|
||||
|
||||
* Add OAuth2 redirect page for Swagger UI. This allows having delegated authentication in the Swagger UI docs. For this to work, you need to add `{your_origin}/docs/oauth2-redirect` to the allowed callbacks in your OAuth2 provider (in Auth0, Facebook, Google, etc).
|
||||
* For example, during development, it could be `http://localhost:8000/docs/oauth2-redirect`.
|
||||
* Have in mind that this callback URL is independent of whichever one is used by your frontend. You might also have another callback at `https://yourdomain.com/login/callback`.
|
||||
* This is only to allow delegated authentication in the API docs with Swagger UI.
|
||||
* PR [#198](https://github.com/tiangolo/fastapi/pull/198) by [@steinitzu](https://github.com/steinitzu).
|
||||
|
||||
* Make Swagger UI and ReDoc route handlers (*path operations*) be `async` functions instead of lambdas to improve performance. PR [#241](https://github.com/tiangolo/fastapi/pull/241) by [@Trim21](https://github.com/Trim21).
|
||||
|
||||
* Make Swagger UI and ReDoc URLs parameterizable, allowing to host and serve local versions of them and have offline docs. PR [#112](https://github.com/tiangolo/fastapi/pull/112) by [@euri10](https://github.com/euri10).
|
||||
|
||||
## 0.22.0
|
||||
|
||||
* 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).
|
||||
|
||||
* Separate development scripts `test.sh`, `lint.sh`, and `format.sh`. PR [#232](https://github.com/tiangolo/fastapi/pull/232).
|
||||
|
||||
* Re-enable `black` formatting checks for Python 3.7. PR [#229](https://github.com/tiangolo/fastapi/pull/229) by [@zamiramir](https://github.com/zamiramir).
|
||||
|
||||
## 0.21.0
|
||||
|
||||
* On body parsing errors, raise `from` previous exception, to allow better introspection in logging code. PR [#192](https://github.com/tiangolo/fastapi/pull/195) by [@ricardomomm](https://github.com/ricardomomm).
|
||||
|
||||
@@ -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"}},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
def __call__(self, q: str = ""):
|
||||
if q:
|
||||
return self.fixed_content in q
|
||||
return False
|
||||
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")
|
||||
|
||||
|
||||
checker = FixedContentQueryChecker("bar")
|
||||
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
|
||||
|
||||
|
||||
@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
Normal file
21
docs/src/dependencies/tutorial007.py
Normal file
@@ -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}
|
||||
@@ -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.
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
Apart from `app.include_router()`, in the same **FastAPI** app.
|
||||
|
||||
It would still work the same.
|
||||
|
||||
!!! info "Very Technical Details"
|
||||
**Note**: this is a very technical detail that you probably can **just skip**.
|
||||
|
||||
@@ -22,12 +22,13 @@ You can then use `Schema` with model attributes:
|
||||
|
||||
`Schema` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
|
||||
|
||||
|
||||
!!! info
|
||||
!!! note "Technical Details"
|
||||
Actually, `Query`, `Path` and others you'll see next are subclasses of a common `Param` which is itself a subclass of Pydantic's `Schema`.
|
||||
|
||||
`Body` is also a subclass of `Schema` directly. And there are others you will see later that are subclasses of `Body`.
|
||||
|
||||
But remember that when you import `Query`, `Path` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
!!! tip
|
||||
Notice how each model's attribute with a type, default value and `Schema` has the same structure as a path operation function's parameter, with `Schema` instead of `Path`, `Query` and `Body`.
|
||||
|
||||
|
||||
@@ -18,9 +18,11 @@ The first value is the default value, you can pass all the extra validation or a
|
||||
{!./src/cookie_params/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
!!! note "Technical Details"
|
||||
`Cookie` is a "sister" class of `Path` and `Query`. It also inherits from the same common `Param` class.
|
||||
|
||||
But remember that when you import `Query`, `Path`, `Cookie` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
!!! info
|
||||
To declare cookies, you need to use `Cookie`, because otherwise the parameters would be interpreted as query parameters.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*.
|
||||
@@ -18,9 +18,11 @@ The first value is the default value, you can pass all the extra validation or a
|
||||
{!./src/header_params/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
!!! note "Technical Details"
|
||||
`Header` is a "sister" class of `Path`, `Query` and `Cookie`. It also inherits from the same common `Param` class.
|
||||
|
||||
But remember that when you import `Query`, `Path`, `Header`, and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
!!! info
|
||||
To declare headers, you need to use `Header`, because otherwise the parameters would be interpreted as query parameters.
|
||||
|
||||
|
||||
@@ -103,6 +103,17 @@ And you can also declare numeric validations:
|
||||
* `le`: `l`ess than or `e`qual
|
||||
|
||||
!!! info
|
||||
`Query`, `Path` and others you will see later are subclasses of a common `Param` class (that you don't need to use).
|
||||
|
||||
And all of them share the same all these same parameters of additional validation and metadata you have seen.
|
||||
`Query`, `Path` and others you will see later subclasses of a common `Param` class (that you don't need to use).
|
||||
|
||||
And all of them share the same all these same parameters of additional validation and metadata you have seen.
|
||||
|
||||
!!! note "Technical Details"
|
||||
When you import `Query`, `Path` and others from `fastapi`, they are actually functions.
|
||||
|
||||
That when called, return instances of classes of the same name.
|
||||
|
||||
So, you import `Query`, which is a function. And when you call it, it returns an instance of a class also named `Query`.
|
||||
|
||||
These functions are there (instead of just using the classes directly) so that your editor doesn't mark errors about their types.
|
||||
|
||||
That way you can use your normal editor and coding tools without having to add custom configurations to disregard those errors.
|
||||
|
||||
@@ -19,6 +19,8 @@ Create file parameters the same way you would for `Body` or `Form`:
|
||||
!!! info
|
||||
`File` is a class that inherits directly from `Form`.
|
||||
|
||||
But remember that when you import `Query`, `Path`, `File` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
!!! info
|
||||
To declare File bodies, you need to use `File`, because otherwise the parameters would be interpreted as query parameters or body (JSON) parameters.
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ In this case, it requires the scope `me` (it could require more than one scope).
|
||||
|
||||
But by using `Security` instead of `Depends`, **FastAPI** will know that it can declare security scopes, use them internally, and document the API with OpenAPI.
|
||||
|
||||
But when you import `Query`, `Path`, `Depends`, `Security` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
## Use `SecurityScopes`
|
||||
|
||||
Now update the dependency `get_current_user`.
|
||||
@@ -242,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.
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.21.0"
|
||||
__version__ = "0.23.0"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
from .applications import FastAPI
|
||||
from .datastructures import UploadFile
|
||||
from .exceptions import HTTPException
|
||||
from .params import Body, Cookie, Depends, File, Form, Header, Path, Query, Security
|
||||
from .param_functions import (
|
||||
Body,
|
||||
Cookie,
|
||||
Depends,
|
||||
File,
|
||||
Form,
|
||||
Header,
|
||||
Path,
|
||||
Query,
|
||||
Security,
|
||||
)
|
||||
from .routing import APIRouter
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
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.docs import (
|
||||
get_redoc_html,
|
||||
get_swagger_ui_html,
|
||||
get_swagger_ui_oauth2_redirect_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
|
||||
from starlette.middleware.errors import ServerErrorMiddleware
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.responses import HTMLResponse, JSONResponse, Response
|
||||
from starlette.routing import BaseRoute
|
||||
|
||||
|
||||
@@ -35,6 +40,7 @@ class FastAPI(Starlette):
|
||||
openapi_prefix: str = "",
|
||||
docs_url: Optional[str] = "/docs",
|
||||
redoc_url: Optional[str] = "/redoc",
|
||||
swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
|
||||
**extra: Dict[str, Any],
|
||||
) -> None:
|
||||
self._debug = debug
|
||||
@@ -51,6 +57,7 @@ class FastAPI(Starlette):
|
||||
self.openapi_prefix = openapi_prefix.rstrip("/")
|
||||
self.docs_url = docs_url
|
||||
self.redoc_url = redoc_url
|
||||
self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
|
||||
self.extra = extra
|
||||
|
||||
self.openapi_version = "3.0.2"
|
||||
@@ -78,29 +85,41 @@ class FastAPI(Starlette):
|
||||
|
||||
def setup(self) -> None:
|
||||
if self.openapi_url:
|
||||
self.add_route(
|
||||
self.openapi_url,
|
||||
lambda req: JSONResponse(self.openapi()),
|
||||
include_in_schema=False,
|
||||
)
|
||||
|
||||
async def openapi(req: Request) -> JSONResponse:
|
||||
return JSONResponse(self.openapi())
|
||||
|
||||
self.add_route(self.openapi_url, openapi, include_in_schema=False)
|
||||
openapi_url = self.openapi_prefix + self.openapi_url
|
||||
if self.openapi_url and self.docs_url:
|
||||
self.add_route(
|
||||
self.docs_url,
|
||||
lambda r: get_swagger_ui_html(
|
||||
openapi_url=self.openapi_prefix + self.openapi_url,
|
||||
|
||||
async def swagger_ui_html(req: Request) -> HTMLResponse:
|
||||
return get_swagger_ui_html(
|
||||
openapi_url=openapi_url,
|
||||
title=self.title + " - Swagger UI",
|
||||
),
|
||||
include_in_schema=False,
|
||||
)
|
||||
oauth2_redirect_url=self.swagger_ui_oauth2_redirect_url,
|
||||
)
|
||||
|
||||
self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
|
||||
|
||||
if self.swagger_ui_oauth2_redirect_url:
|
||||
|
||||
async def swagger_ui_redirect(req: Request) -> HTMLResponse:
|
||||
return get_swagger_ui_oauth2_redirect_html()
|
||||
|
||||
self.add_route(
|
||||
self.swagger_ui_oauth2_redirect_url,
|
||||
swagger_ui_redirect,
|
||||
include_in_schema=False,
|
||||
)
|
||||
if self.openapi_url and self.redoc_url:
|
||||
self.add_route(
|
||||
self.redoc_url,
|
||||
lambda r: get_redoc_html(
|
||||
openapi_url=self.openapi_prefix + self.openapi_url,
|
||||
title=self.title + " - ReDoc",
|
||||
),
|
||||
include_in_schema=False,
|
||||
)
|
||||
|
||||
async def redoc_html(req: Request) -> HTMLResponse:
|
||||
return get_redoc_html(
|
||||
openapi_url=openapi_url, title=self.title + " - ReDoc"
|
||||
)
|
||||
|
||||
self.add_route(self.redoc_url, redoc_html, include_in_schema=False)
|
||||
self.add_exception_handler(HTTPException, http_exception)
|
||||
|
||||
def add_api_route(
|
||||
@@ -111,6 +130,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 +148,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 +168,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 +187,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 +209,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 +227,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 +243,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 +262,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 +278,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 +297,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 +313,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 +332,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 +348,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 +367,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 +383,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 +402,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 +418,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 +437,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 +453,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 +472,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 +488,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,
|
||||
|
||||
@@ -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,20 +60,44 @@ 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):
|
||||
dependency_scopes = depends.scopes
|
||||
security_scopes.extend(dependency_scopes)
|
||||
if isinstance(dependency, SecurityBase):
|
||||
use_scopes = []
|
||||
use_scopes: List[str] = []
|
||||
if isinstance(dependency, (OAuth2, OpenIdConnect)):
|
||||
use_scopes = security_scopes
|
||||
security_requirement = SecurityRequirement(
|
||||
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
|
||||
)
|
||||
|
||||
@@ -1,80 +1,158 @@
|
||||
from typing import Optional
|
||||
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
|
||||
def get_swagger_ui_html(*, openapi_url: str, title: str) -> HTMLResponse:
|
||||
return HTMLResponse(
|
||||
"""
|
||||
def get_swagger_ui_html(
|
||||
*,
|
||||
openapi_url: str,
|
||||
title: str,
|
||||
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js",
|
||||
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css",
|
||||
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
|
||||
oauth2_redirect_url: Optional[str] = None,
|
||||
) -> HTMLResponse:
|
||||
|
||||
html = f"""
|
||||
<! doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
|
||||
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
|
||||
<title>
|
||||
"""
|
||||
+ title
|
||||
+ """
|
||||
</title>
|
||||
<link type="text/css" rel="stylesheet" href="{swagger_css_url}">
|
||||
<link rel="shortcut icon" href="{swagger_favicon_url}">
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui">
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
|
||||
<script src="{swagger_js_url}"></script>
|
||||
<!-- `SwaggerUIBundle` is now available on the page -->
|
||||
<script>
|
||||
|
||||
const ui = SwaggerUIBundle({
|
||||
url: '"""
|
||||
+ openapi_url
|
||||
+ """',
|
||||
const ui = SwaggerUIBundle({{
|
||||
url: '{openapi_url}',
|
||||
"""
|
||||
|
||||
if oauth2_redirect_url:
|
||||
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
|
||||
|
||||
html += """
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "BaseLayout",
|
||||
deepLinking: true
|
||||
|
||||
layout: "BaseLayout"
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
)
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
def get_redoc_html(*, openapi_url: str, title: str) -> HTMLResponse:
|
||||
return HTMLResponse(
|
||||
"""
|
||||
def get_redoc_html(
|
||||
*,
|
||||
openapi_url: str,
|
||||
title: str,
|
||||
redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
|
||||
redoc_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
|
||||
) -> HTMLResponse:
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
"""
|
||||
+ title
|
||||
+ """
|
||||
</title>
|
||||
<html>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
|
||||
|
||||
<link rel="shortcut icon" href="{redoc_favicon_url}">
|
||||
<!--
|
||||
ReDoc doesn't change outer page styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
body {{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url='"""
|
||||
+ openapi_url
|
||||
+ """'></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url="{openapi_url}"></redoc>
|
||||
<script src="{redoc_js_url}"> </script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
)
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
||||
html = """
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
@@ -114,7 +114,7 @@ def get_openapi_operation_request_body(
|
||||
def generate_operation_id(*, route: routing.APIRoute, method: str) -> str:
|
||||
if route.operation_id:
|
||||
return route.operation_id
|
||||
path: str = route.path
|
||||
path: str = route.path_format
|
||||
operation_id = route.name + path
|
||||
operation_id = operation_id.replace("{", "_").replace("}", "_").replace("/", "_")
|
||||
operation_id = operation_id + "_" + method.lower()
|
||||
@@ -253,7 +253,9 @@ def get_openapi(
|
||||
if result:
|
||||
path, security_schemes, path_definitions = result
|
||||
if path:
|
||||
paths.setdefault(openapi_prefix + route.path, {}).update(path)
|
||||
paths.setdefault(openapi_prefix + route.path_format, {}).update(
|
||||
path
|
||||
)
|
||||
if security_schemes:
|
||||
components.setdefault("securitySchemes", {}).update(
|
||||
security_schemes
|
||||
|
||||
248
fastapi/param_functions.py
Normal file
248
fastapi/param_functions.py
Normal file
@@ -0,0 +1,248 @@
|
||||
from typing import Any, Callable, Sequence
|
||||
|
||||
from fastapi import params
|
||||
|
||||
|
||||
def Path( # noqa: N802
|
||||
default: Any,
|
||||
*,
|
||||
alias: str = None,
|
||||
title: str = None,
|
||||
description: str = None,
|
||||
gt: float = None,
|
||||
ge: float = None,
|
||||
lt: float = None,
|
||||
le: float = None,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
regex: str = None,
|
||||
deprecated: bool = None,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Path(
|
||||
default=default,
|
||||
alias=alias,
|
||||
title=title,
|
||||
description=description,
|
||||
gt=gt,
|
||||
ge=ge,
|
||||
lt=lt,
|
||||
le=le,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
deprecated=deprecated,
|
||||
**extra,
|
||||
)
|
||||
|
||||
|
||||
def Query( # noqa: N802
|
||||
default: Any,
|
||||
*,
|
||||
alias: str = None,
|
||||
title: str = None,
|
||||
description: str = None,
|
||||
gt: float = None,
|
||||
ge: float = None,
|
||||
lt: float = None,
|
||||
le: float = None,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
regex: str = None,
|
||||
deprecated: bool = None,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Query(
|
||||
default,
|
||||
alias=alias,
|
||||
title=title,
|
||||
description=description,
|
||||
gt=gt,
|
||||
ge=ge,
|
||||
lt=lt,
|
||||
le=le,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
deprecated=deprecated,
|
||||
**extra,
|
||||
)
|
||||
|
||||
|
||||
def Header( # noqa: N802
|
||||
default: Any,
|
||||
*,
|
||||
alias: str = None,
|
||||
convert_underscores: bool = True,
|
||||
title: str = None,
|
||||
description: str = None,
|
||||
gt: float = None,
|
||||
ge: float = None,
|
||||
lt: float = None,
|
||||
le: float = None,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
regex: str = None,
|
||||
deprecated: bool = None,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Header(
|
||||
default,
|
||||
alias=alias,
|
||||
convert_underscores=convert_underscores,
|
||||
title=title,
|
||||
description=description,
|
||||
gt=gt,
|
||||
ge=ge,
|
||||
lt=lt,
|
||||
le=le,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
deprecated=deprecated,
|
||||
**extra,
|
||||
)
|
||||
|
||||
|
||||
def Cookie( # noqa: N802
|
||||
default: Any,
|
||||
*,
|
||||
alias: str = None,
|
||||
title: str = None,
|
||||
description: str = None,
|
||||
gt: float = None,
|
||||
ge: float = None,
|
||||
lt: float = None,
|
||||
le: float = None,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
regex: str = None,
|
||||
deprecated: bool = None,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Cookie(
|
||||
default,
|
||||
alias=alias,
|
||||
title=title,
|
||||
description=description,
|
||||
gt=gt,
|
||||
ge=ge,
|
||||
lt=lt,
|
||||
le=le,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
deprecated=deprecated,
|
||||
**extra,
|
||||
)
|
||||
|
||||
|
||||
def Body( # noqa: N802
|
||||
default: Any,
|
||||
*,
|
||||
embed: bool = False,
|
||||
media_type: str = "application/json",
|
||||
alias: str = None,
|
||||
title: str = None,
|
||||
description: str = None,
|
||||
gt: float = None,
|
||||
ge: float = None,
|
||||
lt: float = None,
|
||||
le: float = None,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
regex: str = None,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Body(
|
||||
default,
|
||||
embed=embed,
|
||||
media_type=media_type,
|
||||
alias=alias,
|
||||
title=title,
|
||||
description=description,
|
||||
gt=gt,
|
||||
ge=ge,
|
||||
lt=lt,
|
||||
le=le,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
**extra,
|
||||
)
|
||||
|
||||
|
||||
def Form( # noqa: N802
|
||||
default: Any,
|
||||
*,
|
||||
media_type: str = "application/x-www-form-urlencoded",
|
||||
alias: str = None,
|
||||
title: str = None,
|
||||
description: str = None,
|
||||
gt: float = None,
|
||||
ge: float = None,
|
||||
lt: float = None,
|
||||
le: float = None,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
regex: str = None,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Form(
|
||||
default,
|
||||
media_type=media_type,
|
||||
alias=alias,
|
||||
title=title,
|
||||
description=description,
|
||||
gt=gt,
|
||||
ge=ge,
|
||||
lt=lt,
|
||||
le=le,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
**extra,
|
||||
)
|
||||
|
||||
|
||||
def File( # noqa: N802
|
||||
default: Any,
|
||||
*,
|
||||
media_type: str = "multipart/form-data",
|
||||
alias: str = None,
|
||||
title: str = None,
|
||||
description: str = None,
|
||||
gt: float = None,
|
||||
ge: float = None,
|
||||
lt: float = None,
|
||||
le: float = None,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
regex: str = None,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.File(
|
||||
default,
|
||||
media_type=media_type,
|
||||
alias=alias,
|
||||
title=title,
|
||||
description=description,
|
||||
gt=gt,
|
||||
ge=ge,
|
||||
lt=lt,
|
||||
le=le,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
**extra,
|
||||
)
|
||||
|
||||
|
||||
def Depends(dependency: Callable = None) -> Any: # noqa: N802
|
||||
return params.Depends(dependency=dependency)
|
||||
|
||||
|
||||
def Security( # noqa: N802
|
||||
dependency: Callable = None, scopes: Sequence[str] = None
|
||||
) -> Any:
|
||||
return params.Security(dependency=dependency, scopes=scopes)
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -19,7 +19,7 @@ classifiers = [
|
||||
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
|
||||
]
|
||||
requires = [
|
||||
"starlette ==0.11.1",
|
||||
"starlette >=0.11.1,<=0.12.0",
|
||||
"pydantic >=0.17,<=0.25.0"
|
||||
]
|
||||
description-file = "README.md"
|
||||
|
||||
6
scripts/format.sh
Executable file
6
scripts/format.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh -e
|
||||
set -x
|
||||
|
||||
autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place docs/src/ fastapi tests --exclude=__init__.py
|
||||
black fastapi tests docs/src
|
||||
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --thirdparty fastapi --apply fastapi tests docs/src
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/sh -e
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place docs/src/ fastapi tests --exclude=__init__.py
|
||||
black fastapi tests docs/src
|
||||
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --thirdparty fastapi --apply fastapi tests docs/src
|
||||
mypy fastapi --disallow-untyped-defs --follow-imports=skip
|
||||
black fastapi tests --check
|
||||
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --check-only --thirdparty fastapi fastapi tests
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
export VERSION_SCRIPT="import sys; print('%s.%s' % sys.version_info[0:2])"
|
||||
export PYTHON_VERSION=`python -c "$VERSION_SCRIPT"`
|
||||
|
||||
# Remove temporary DB
|
||||
if [ -f ./test.db ]; then
|
||||
rm ./test.db
|
||||
@@ -13,10 +10,4 @@ fi
|
||||
|
||||
export PYTHONPATH=./docs/src
|
||||
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing ${@}
|
||||
mypy fastapi --disallow-untyped-defs --follow-imports=skip
|
||||
if [ "${PYTHON_VERSION}" = '3.7' ]; then
|
||||
echo "Skipping 'black' on 3.7. See issue https://github.com/ambv/black/issues/494"
|
||||
else
|
||||
black fastapi tests --check
|
||||
fi
|
||||
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --check-only --thirdparty fastapi fastapi tests
|
||||
bash ./scripts/lint.sh
|
||||
|
||||
@@ -1131,6 +1131,17 @@ def test_swagger_ui():
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/html; charset=utf-8"
|
||||
assert "swagger-ui-dist" in response.text
|
||||
assert (
|
||||
f"oauth2RedirectUrl: window.location.origin + '/docs/oauth2-redirect'"
|
||||
in response.text
|
||||
)
|
||||
|
||||
|
||||
def test_swagger_ui_oauth2_redirect():
|
||||
response = client.get("/docs/oauth2-redirect")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/html; charset=utf-8"
|
||||
assert "window.opener.swaggerUIRedirectOauth2" in response.text
|
||||
|
||||
|
||||
def test_redoc():
|
||||
|
||||
38
tests/test_custom_swagger_ui_redirect.py
Normal file
38
tests/test_custom_swagger_ui_redirect.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
swagger_ui_oauth2_redirect_url = "/docs/redirect"
|
||||
|
||||
app = FastAPI(swagger_ui_oauth2_redirect_url=swagger_ui_oauth2_redirect_url)
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return {"id": "foo"}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_swagger_ui():
|
||||
response = client.get("/docs")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/html; charset=utf-8"
|
||||
assert "swagger-ui-dist" in response.text
|
||||
print(client.base_url)
|
||||
assert (
|
||||
f"oauth2RedirectUrl: window.location.origin + '{swagger_ui_oauth2_redirect_url}'"
|
||||
in response.text
|
||||
)
|
||||
|
||||
|
||||
def test_swagger_ui_oauth2_redirect():
|
||||
response = client.get(swagger_ui_oauth2_redirect_url)
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/html; charset=utf-8"
|
||||
assert "window.opener.swaggerUIRedirectOauth2" in response.text
|
||||
|
||||
|
||||
def test_response():
|
||||
response = client.get("/items/")
|
||||
assert response.json() == {"id": "foo"}
|
||||
56
tests/test_local_docs.py
Normal file
56
tests/test_local_docs.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import inspect
|
||||
|
||||
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
||||
|
||||
|
||||
def test_strings_in_generated_swagger():
|
||||
sig = inspect.signature(get_swagger_ui_html)
|
||||
swagger_js_url = sig.parameters.get("swagger_js_url").default
|
||||
swagger_css_url = sig.parameters.get("swagger_css_url").default
|
||||
swagger_favicon_url = sig.parameters.get("swagger_favicon_url").default
|
||||
html = get_swagger_ui_html(openapi_url="/docs", title="title")
|
||||
body_content = html.body.decode()
|
||||
assert swagger_js_url in body_content
|
||||
assert swagger_css_url in body_content
|
||||
assert swagger_favicon_url in body_content
|
||||
|
||||
|
||||
def test_strings_in_custom_swagger():
|
||||
swagger_js_url = "swagger_fake_file.js"
|
||||
swagger_css_url = "swagger_fake_file.css"
|
||||
swagger_favicon_url = "swagger_fake_file.png"
|
||||
html = get_swagger_ui_html(
|
||||
openapi_url="/docs",
|
||||
title="title",
|
||||
swagger_js_url=swagger_js_url,
|
||||
swagger_css_url=swagger_css_url,
|
||||
swagger_favicon_url=swagger_favicon_url,
|
||||
)
|
||||
body_content = html.body.decode()
|
||||
assert swagger_js_url in body_content
|
||||
assert swagger_css_url in body_content
|
||||
assert swagger_favicon_url in body_content
|
||||
|
||||
|
||||
def test_strings_in_generated_redoc():
|
||||
sig = inspect.signature(get_redoc_html)
|
||||
redoc_js_url = sig.parameters.get("redoc_js_url").default
|
||||
redoc_favicon_url = sig.parameters.get("redoc_favicon_url").default
|
||||
html = get_redoc_html(openapi_url="/docs", title="title")
|
||||
body_content = html.body.decode()
|
||||
assert redoc_js_url in body_content
|
||||
assert redoc_favicon_url in body_content
|
||||
|
||||
|
||||
def test_strings_in_custom_redoc():
|
||||
redoc_js_url = "fake_redoc_file.js"
|
||||
redoc_favicon_url = "fake_redoc_file.png"
|
||||
html = get_redoc_html(
|
||||
openapi_url="/docs",
|
||||
title="title",
|
||||
redoc_js_url=redoc_js_url,
|
||||
redoc_favicon_url=redoc_favicon_url,
|
||||
)
|
||||
body_content = html.body.decode()
|
||||
assert redoc_js_url in body_content
|
||||
assert redoc_favicon_url in body_content
|
||||
31
tests/test_no_swagger_ui_redirect.py
Normal file
31
tests/test_no_swagger_ui_redirect.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI(swagger_ui_oauth2_redirect_url=None)
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return {"id": "foo"}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_swagger_ui():
|
||||
response = client.get("/docs")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/html; charset=utf-8"
|
||||
assert "swagger-ui-dist" in response.text
|
||||
print(client.base_url)
|
||||
assert "oauth2RedirectUrl" not in response.text
|
||||
|
||||
|
||||
def test_swagger_ui_no_oauth2_redirect():
|
||||
response = client.get("/docs/oauth2-redirect")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_response():
|
||||
response = client.get("/items/")
|
||||
assert response.json() == {"id": "foo"}
|
||||
51
tests/test_starlette_urlconvertors.py
Normal file
51
tests/test_starlette_urlconvertors.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from fastapi import FastAPI, Path
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/int/{param:int}")
|
||||
def int_convertor(param: int = Path(...)):
|
||||
return {"int": param}
|
||||
|
||||
|
||||
@app.get("/float/{param:float}")
|
||||
def float_convertor(param: float = Path(...)):
|
||||
return {"float": param}
|
||||
|
||||
|
||||
@app.get("/path/{param:path}")
|
||||
def path_convertor(param: str = Path(...)):
|
||||
return {"path": param}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_route_converters_int():
|
||||
# Test integer conversion
|
||||
response = client.get("/int/5")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"int": 5}
|
||||
assert app.url_path_for("int_convertor", param=5) == "/int/5"
|
||||
|
||||
|
||||
def test_route_converters_float():
|
||||
# Test float conversion
|
||||
response = client.get("/float/25.5")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"float": 25.5}
|
||||
assert app.url_path_for("float_convertor", param=25.5) == "/float/25.5"
|
||||
|
||||
|
||||
def test_route_converters_path():
|
||||
# Test path conversion
|
||||
response = client.get("/path/some/example")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"path": "some/example"}
|
||||
|
||||
|
||||
def test_url_path_for_path_convertor():
|
||||
assert (
|
||||
app.url_path_for("path_convertor", param="some/example") == "/path/some/example"
|
||||
)
|
||||
@@ -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
Normal file
128
tests/test_tutorial/test_dependencies/test_tutorial006.py
Normal file
@@ -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"}]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user