Compare commits

..

34 Commits

Author SHA1 Message Date
Sebastián Ramírez
717a1ec409 🔖 Release version 0.68.0 2021-07-29 23:03:57 +02:00
Sebastián Ramírez
eba2c66532 🔖 Update release notes 2021-07-29 23:02:42 +02:00
github-actions
3788ca4640 📝 Update release notes 2021-07-29 21:00:53 +00:00
Sebastián Ramírez
97fa743ecb Update OpenAPI models, supporting recursive models and extensions (#3628) 2021-07-29 20:59:26 +00:00
github-actions
e726b7b771 📝 Update release notes 2021-07-29 20:28:25 +00:00
github-actions
6a22b75afd 📝 Update release notes 2021-07-29 20:25:17 +00:00
github-actions
4093d9b22a 📝 Update release notes 2021-07-29 20:23:19 +00:00
Urchin
18daf8f301 🌐 Add Russian translation for docs/python-types.md (#3039) 2021-07-29 22:22:43 +02:00
jaystone776
962578bec2 🌐 Add Chinese translation for docs/tutorial/dependencies/index.md (#3489)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-29 22:21:38 +02:00
Urchin
85cff59109 🌐 Add Russian translation for docs/external-links.md (#3036) 2021-07-29 20:21:06 +00:00
github-actions
b869c7c872 📝 Update release notes 2021-07-29 20:16:29 +00:00
jaystone776
016c3f0d5b 🌐 Add Chinese translation for docs/tutorial/dependencies/global-dependencies.md (#3493)
as title
2021-07-29 22:15:54 +02:00
github-actions
4eb9c60652 📝 Update release notes 2021-07-29 20:15:22 +00:00
Lucas
8879757a88 🌐 Add Portuguese translation for docs/deployment/versions.md (#3618)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
2021-07-29 22:14:40 +02:00
Sebastián Ramírez
fadfcfda4a 📝 Update release notes 2021-07-29 22:12:55 +02:00
github-actions
b8c4149e89 📝 Update release notes 2021-07-29 20:01:49 +00:00
Edouard Lavery-Plante
836bb97a2d Add support for extensions and updates to the OpenAPI schema in path operations (#1922)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-29 22:01:13 +02:00
github-actions
7db359182d 📝 Update release notes 2021-07-29 15:30:57 +00:00
James Curtin
4eada92883 Import and re-export data structures from Starlette, used by Request properties, on fastapi.datastructures (#1872)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-29 17:30:18 +02:00
github-actions
6a74f3a0c1 📝 Update release notes 2021-07-29 15:18:55 +00:00
Edward Knight
daa0765653 📝 Update docs about async and response-model with more gender neutral language (#1869) 2021-07-29 17:18:20 +02:00
github-actions
27e26b5939 📝 Update release notes 2021-07-29 15:10:59 +00:00
dkreeft
6f45f43709 Add additonal OpenAPI metadata parameters to FastAPI class, shown on the automatic API docs UI (#1812)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: dkreeft <dkreeft@xccelerated.io>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-29 17:10:22 +02:00
github-actions
6c80e9a8e0 📝 Update release notes 2021-07-29 10:31:22 +00:00
Hylke Postma
3b2e891917 Add description parameter to all the security scheme classes, e.g. APIKeyQuery(name="key", description="A very cool API key") (#1757)
Co-authored-by: Hylke Postma <h.postma@docuwork.nl>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-29 12:30:48 +02:00
github-actions
9121fccf55 📝 Update release notes 2021-07-29 09:26:40 +00:00
Marcelo Trylesinski
fa2c750443 Add the docs_src directory to test coverage and update tests (#1904)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-07-29 11:26:07 +02:00
github-actions
29e7b31ee6 📝 Update release notes 2021-07-28 15:36:03 +00:00
Yuya Sato
bb6c6ed5d1 🌐 Add Japanese translation for docs/tutorial/security/oauth2-jwt.md (#3526)
Co-authored-by: tokusumi <41147016+tokusumi@users.noreply.github.com>
Co-authored-by: Sho Nakamura <sh0nk.developer@gmail.com>
2021-07-28 17:35:25 +02:00
github-actions
1a5273773e 📝 Update release notes 2021-07-27 10:23:49 +00:00
Sebastián Ramírez
3f40c37905 🔧 Add new GitHub templates with forms for new issues (#3612) 2021-07-27 10:23:09 +00:00
Sebastián Ramírez
996dfd05bd 📝 Update release notes 2021-07-21 21:15:58 +02:00
github-actions
0a1dd7894c 📝 Update release notes 2021-07-21 19:14:32 +00:00
Sebastián Ramírez
dbfd3f7e18 📝 Add official FastAPI Twitter to docs (#3578) 2021-07-21 21:13:58 +02:00
78 changed files with 3168 additions and 317 deletions

View File

@@ -1 +1,4 @@
blank_issues_enabled: false
contact_links:
- name: Security Contact
about: Please report security vulnerabilities to security@tiangolo.com

View File

@@ -0,0 +1,181 @@
name: Feature Request
description: Suggest an idea or ask for a feature that you would like to have in FastAPI
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in FastAPI! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members, the [FastAPI Experts](https://fastapi.tiangolo.com/fastapi-people/#experts), that give a lot of their time to come here and help others.
That's a lot of work they are doing, but if more FastAPI users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: I added a very descriptive title to this issue.
required: true
- label: I used the GitHub search to find a similar issue and didn't find it.
required: true
- label: I searched the FastAPI documentation, with the integrated search.
required: true
- label: I already searched in Google "How to X in FastAPI" and didn't find any information.
required: true
- label: I already read and followed all the tutorial in the docs and didn't find an answer.
required: true
- label: I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
required: true
- label: I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
required: true
- label: I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
required: true
- type: checkboxes
id: help
attributes:
label: Commit to Help
description: |
After submitting this, I commit to one of:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
options:
- label: I commit to help with one of those options 👆
required: true
- type: textarea
id: example
attributes:
label: Example Code
description: |
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
placeholder: |
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
render: python
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
What is your feature request?
Write a short description telling me what you are trying to solve and what you are currently doing.
placeholder: |
* Open the browser and call the endpoint `/`.
* It returns a JSON with `{"Hello": "World"}`.
* I would like it to have an extra parameter to teleport me to the moon and back.
validations:
required: true
- type: textarea
id: wanted-solution
attributes:
label: Wanted Solution
description: |
Tell me what's the solution you would like.
placeholder: |
I would like it to have a `teleport_to_moon` parameter that defaults to `False`, and can be set to `True` to teleport me.
validations:
required: true
- type: textarea
id: wanted-code
attributes:
label: Wanted Code
description: Show me an example of how you would want the code to look like.
placeholder: |
from fastapi import FastAPI
app = FastAPI()
@app.get("/", teleport_to_moon=True)
def read_root():
return {"Hello": "World"}
render: python
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: |
Tell me about alternatives you've considered.
placeholder: |
To wait for Space X moon travel plans to drop down long after they release them. But I would rather teleport.
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you on?
multiple: true
options:
- Linux
- Windows
- macOS
- Other
validations:
required: true
- type: textarea
id: os-details
attributes:
label: Operating System Details
description: You can add more details about your operating system here, in particular if you chose "Other".
- type: input
id: fastapi-version
attributes:
label: FastAPI Version
description: |
What FastAPI version are you using?
You can find the FastAPI version with:
```bash
python -c "import fastapi; print(fastapi.__version__)"
```
validations:
required: true
- type: input
id: python-version
attributes:
label: Python Version
description: |
What Python version are you using?
You can find the Python version with:
```bash
python --version
```
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any additional context information or screenshots you think are useful.

View File

@@ -1,104 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: enhancement
assignees: ''
---
### First check
* [ ] I added a very descriptive title to this issue.
* [ ] I used the GitHub search to find a similar issue and didn't find it.
* [ ] I searched the FastAPI documentation, with the integrated search.
* [ ] I already searched in Google "How to X in FastAPI" and didn't find any information.
* [ ] I already read and followed all the tutorial in the docs and didn't find an answer.
* [ ] I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
* [ ] I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
* [ ] I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
* [ ] After submitting this, I commit to:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* Or, I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
<!--
I'm asking all this because answering questions and solving problems in GitHub issues consumes a lot of time. I end up not being able to add new features, fix bugs, review Pull Requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
That's a lot of work they are doing, but if more FastAPI users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
-->
### Example
Here's a self-contained [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with my use case:
<!-- Replace the code below with your own self-contained, minimal, reproducible, example -->
```Python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
```
### Description
<!-- Replace the content below with your own feature request -->
* Open the browser and call the endpoint `/`.
* It returns a JSON with `{"Hello": "World"}`.
* I would like it to have an extra parameter to teleport me to the moon and back.
### The solution you would like
<!-- Replace this with your own content -->
I would like it to have a `teleport_to_moon` parameter that defaults to `False`, and can be set to `True` to teleport me:
```Python
from fastapi import FastAPI
app = FastAPI()
@app.get("/", teleport_to_moon=True)
def read_root():
return {"Hello": "World"}
```
### Describe alternatives you've considered
<!-- Replace this with your own ideas -->
To wait for Space X moon travel plans to drop down long after they release them. But I would rather teleport.
### Environment
* OS: [e.g. Linux / Windows / macOS]:
* FastAPI Version [e.g. 0.3.0]:
To know the FastAPI version use:
```bash
python -c "import fastapi; print(fastapi.__version__)"
```
* Python version:
To know the Python version use:
```bash
python --version
```
### Additional context
<!-- Add any other context or screenshots about the question here. -->

View File

@@ -1,81 +0,0 @@
---
name: Question or Problem
about: Ask a question or ask about a problem
title: ""
labels: question
assignees: ""
---
### First check
* [ ] I added a very descriptive title to this issue.
* [ ] I used the GitHub search to find a similar issue and didn't find it.
* [ ] I searched the FastAPI documentation, with the integrated search.
* [ ] I already searched in Google "How to X in FastAPI" and didn't find any information.
* [ ] I already read and followed all the tutorial in the docs and didn't find an answer.
* [ ] I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
* [ ] I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
* [ ] I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
* [ ] After submitting this, I commit to one of:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
<!--
I'm asking all this because answering questions and solving problems in GitHub issues consumes a lot of time. I end up not being able to add new features, fix bugs, review Pull Requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
That's a lot of work they are doing, but if more FastAPI users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
-->
### Example
Here's a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with my use case:
<!-- Replace the code below with your own self-contained, minimal, reproducible, example, if I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you -->
```Python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
```
### Description
<!-- Replace the content below with your own problem, question, or error -->
* Open the browser and call the endpoint `/`.
* It returns a JSON with `{"Hello": "World"}`.
* But I expected it to return `{"Hello": "Sara"}`.
### Environment
* OS: [e.g. Linux / Windows / macOS]:
* FastAPI Version [e.g. 0.3.0]:
To know the FastAPI version use:
```bash
python -c "import fastapi; print(fastapi.__version__)"
```
* Python version:
To know the Python version use:
```bash
python --version
```
### Additional context
<!-- Add any other context or screenshots about the question here. -->

146
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@@ -0,0 +1,146 @@
name: Question or Problem
description: Ask a question or ask about a problem
labels: [question]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in FastAPI! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members, the [FastAPI Experts](https://fastapi.tiangolo.com/fastapi-people/#experts), that give a lot of their time to come here and help others.
That's a lot of work they are doing, but if more FastAPI users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: I added a very descriptive title to this issue.
required: true
- label: I used the GitHub search to find a similar issue and didn't find it.
required: true
- label: I searched the FastAPI documentation, with the integrated search.
required: true
- label: I already searched in Google "How to X in FastAPI" and didn't find any information.
required: true
- label: I already read and followed all the tutorial in the docs and didn't find an answer.
required: true
- label: I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
required: true
- label: I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
required: true
- label: I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
required: true
- type: checkboxes
id: help
attributes:
label: Commit to Help
description: |
After submitting this, I commit to one of:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
options:
- label: I commit to help with one of those options 👆
required: true
- type: textarea
id: example
attributes:
label: Example Code
description: |
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
placeholder: |
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
render: python
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
What is the problem, question, or error?
Write a short description telling me what you are doing, what you expect to happen, and what is currently happening.
placeholder: |
* Open the browser and call the endpoint `/`.
* It returns a JSON with `{"Hello": "World"}`.
* But I expected it to return `{"Hello": "Sara"}`.
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you on?
multiple: true
options:
- Linux
- Windows
- macOS
- Other
validations:
required: true
- type: textarea
id: os-details
attributes:
label: Operating System Details
description: You can add more details about your operating system here, in particular if you chose "Other".
- type: input
id: fastapi-version
attributes:
label: FastAPI Version
description: |
What FastAPI version are you using?
You can find the FastAPI version with:
```bash
python -c "import fastapi; print(fastapi.__version__)"
```
validations:
required: true
- type: input
id: python-version
attributes:
label: Python Version
description: |
What Python version are you using?
You can find the Python version with:
```bash
python --version
```
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any additional context information or screenshots you think are useful.

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -33,7 +33,7 @@ You should do it after adding all your *path operations*.
## Exclude from OpenAPI
To exclude a *path operation* from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`;
To exclude a *path operation* from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`:
```Python hl_lines="6"
{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!}
@@ -50,3 +50,121 @@ It won't show up in the documentation, but other tools (such as Sphinx) will be
```Python hl_lines="19-29"
{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!}
```
## Additional Responses
You probably have seen how to declare the `response_model` and `status_code` for a *path operation*.
That defines the metadata about the main response of a *path operation*.
You can also declare additional responses with their models, status codes, etc.
There's a whole chapter here in the documentation about it, you can read it at [Additional Responses in OpenAPI](./additional-responses.md){.internal-link target=_blank}.
## OpenAPI Extra
When you declare a *path operation* in your application, **FastAPI** automatically generates the relevant metadata about that *path operation* to be included in the OpenAPI schema.
!!! note "Technical details"
In the OpenAPI specification it is called the <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>.
It has all the information about the *path operation* and is used to generate the automatic documentation.
It includes the `tags`, `parameters`, `requestBody`, `responses`, etc.
This *path operation*-specific OpenAPI schema is normally generated automatically by **FastAPI**, but you can also extend it.
!!! tip
This is a low level extension point.
If you only need to declare additonal responses, a more convenient way to do it is with [Additional Responses in OpenAPI](./additional-responses.md){.internal-link target=_blank}.
You can extend the OpenAPI schema for a *path operation* using the parameter `openapi_extra`.
### OpenAPI Extensions
This `openapi_extra` can be helpful, for example, to declare [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
```Python hl_lines="6"
{!../../../docs_src/path_operation_advanced_configuration/tutorial005.py!}
```
If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*.
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
And if you see the resulting OpenAPI (at `/openapi.json` in your API), you will see your extension as part of the specific *path operation* too:
```JSON hl_lines="22"
{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"x-aperture-labs-portal": "blue"
}
}
}
}
```
### Custom OpenAPI *path operation* schema
The dictionary in `openapi_extra` will be deeply merged with the automatically generated OpenAPI schema for the *path operation*.
So, you could add additional data to the automatically generated schema.
For example, you could decide to read and validate the request with your own code, without using the automatic features of FastAPI with Pydantic, but you could still want to define the request in the OpenAPI schema.
You could do that with `openapi_extra`:
```Python hl_lines="20-37 39-40"
{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py!}
```
In this example, we didn't declare any Pydantic model. In fact, the request body is not even <abbr title="converted from some plain format, like bytes, into Python objects">parsed</abbr> as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way.
Nevertheless, we can declare the expected schema for the request body.
### Custom OpenAPI content type
Using this same trick, you could use a Pydantic model to define the JSON Schema that is then included in the custom OpenAPI schema section for the *path operation*.
And you could do this even if the data type in the request is not JSON.
For example, in this application we don't use FastAPI's integrated functionality to extract the JSON Schema from Pydantic models nor the automatic validation for JSON. In fact, we are declaring the request content type as YAML, not JSON:
```Python hl_lines="17-22 24"
{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!}
```
Nevertheless, although we are not using the default integrated functionality, we are still using a Pydantic model to manually generate the JSON Schema for the data that we want to receive in YAML.
Then we use the request directly, and extract the body as `bytes`. This means that FastAPI won't even try to parse the request payload as JSON.
And then in our code, we parse that YAML content directly, and then we are again using the same Pydantic model to validate the YAML content:
```Python hl_lines="26-33"
{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!}
```
!!! tip
Here we re-use the same Pydantic model.
But the same way, we could have validated it in some other way.

View File

@@ -235,7 +235,7 @@ And then we can require it from the *path operation function* as a dependency an
Then it would be very easy to provide a different settings object during testing by creating a dependency override for `get_settings`:
```Python hl_lines="8-9 12 21"
```Python hl_lines="9-10 13 21"
{!../../../docs_src/settings/app02/test_main.py!}
```
@@ -288,7 +288,7 @@ Reading a file from disk is normally a costly (slow) operation, so you probably
But every time we do:
```Python
config.Settings()
Settings()
```
a new `Settings` object would be created, and at creation it would read the `.env` file again.
@@ -297,7 +297,7 @@ If the dependency function was just like:
```Python
def get_settings():
return config.Settings()
return Settings()
```
we would create that object for each request, and we would be reading the `.env` file for each request. ⚠️

View File

@@ -102,13 +102,15 @@ To see the difference, imagine the following story about burgers:
### Concurrent Burgers
<!-- The gender neutral cook emoji "🧑‍🍳" does not render well in browsers. In the meantime, I'm using a mix of male "👨‍🍳" and female "👩‍🍳" cooks. -->
You go with your crush 😍 to get fast food 🍔, you stand in line while the cashier 💁 takes the orders from the people in front of you.
Then it's your turn, you place your order of 2 very fancy burgers 🍔 for your crush 😍 and you.
You pay 💸.
The cashier 💁 says something to the guy in the kitchen 👨‍🍳 so he knows he has to prepare your burgers 🍔 (even though he is currently preparing the ones for the previous clients).
The cashier 💁 says something to the cook in the kitchen 👨‍🍳 so they know they have to prepare your burgers 🍔 (even though they are currently preparing the ones for the previous clients).
The cashier 💁 gives you the number of your turn.
@@ -146,9 +148,9 @@ Now let's imagine these aren't "Concurrent Burgers", but "Parallel Burgers".
You go with your crush 😍 to get parallel fast food 🍔.
You stand in line while several (let's say 8) cashiers that at the same time are cooks 👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳 take the orders from the people in front of you.
You stand in line while several (let's say 8) cashiers that at the same time are cooks 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳 take the orders from the people in front of you.
Everyone before you is waiting 🕙 for their burgers 🍔 to be ready before leaving the counter because each of the 8 cashiers goes himself and prepares the burger right away before getting the next order.
Everyone before you is waiting 🕙 for their burgers 🍔 to be ready before leaving the counter because each of the 8 cashiers goes and prepares the burger right away before getting the next order.
Then it's finally your turn, you place your order of 2 very fancy burgers 🍔 for your crush 😍 and you.
@@ -174,7 +176,7 @@ There was not much talk or flirting as most of the time was spent waiting 🕙 i
In this scenario of the parallel burgers, you are a computer / program 🤖 with two processors (you and your crush 😍), both waiting 🕙 and dedicating their attention ⏯ to be "waiting on the counter" 🕙 for a long time.
The fast food store has 8 processors (cashiers/cooks) 👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳. While the concurrent burgers store might have had only 2 (one cashier and one cook) 💁 👨‍🍳.
The fast food store has 8 processors (cashiers/cooks) 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳. While the concurrent burgers store might have had only 2 (one cashier and one cook) 💁 👨‍🍳.
But still, the final experience is not the best 😞.
@@ -236,7 +238,7 @@ You could have turns as in the burgers example, first the living room, then the
It would take the same amount of time to finish with or without turns (concurrency) and you would have done the same amount of work.
But in this case, if you could bring the 8 ex-cashier/cooks/now-cleaners 👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳, and each one of them (plus you) could take a zone of the house to clean it, you could do all the work in **parallel**, with the extra help, and finish much sooner.
But in this case, if you could bring the 8 ex-cashier/cooks/now-cleaners 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳, and each one of them (plus you) could take a zone of the house to clean it, you could do all the work in **parallel**, with the extra help, and finish much sooner.
In this scenario, each one of the cleaners (including you) would be a processor, doing their part of the job.

View File

@@ -99,3 +99,7 @@ a.announce-link:hover {
display: flex;
align-items: center;
}
.twitter {
color: #00acee;
}

View File

@@ -20,6 +20,10 @@ You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/news
* Breaking changes 🚨
* Tips and tricks ✅
## Follow FastAPI on Twitter
<a href="https://twitter.com/fastapi" class="external-link" target="_blank">Follow @fastapi on **Twitter**</a> to get the latest news about **FastAPI**. 🐦
## Star **FastAPI** in GitHub
You can "star" FastAPI in GitHub (clicking the star button at the top right): <a href="https://github.com/tiangolo/fastapi" class="external-link" target="_blank">https://github.com/tiangolo/fastapi</a>. ⭐️
@@ -32,7 +36,7 @@ You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right)
There you can select "Releases only".
Doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **FastAPI** with bug fixes and new features.
By doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **FastAPI** with bug fixes and new features.
## Connect with the author
@@ -46,17 +50,18 @@ You can:
* <a href="https://twitter.com/tiangolo" class="external-link" target="_blank">Follow me on **Twitter**</a>.
* Tell me how you use FastAPI (I love to hear that).
* Hear when I make announcements or release new tools.
* You can also <a href="https://twitter.com/fastapi" class="external-link" target="_blank">follow @fastapi on Twitter</a> (a separate account).
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">Connect with me on **Linkedin**</a>.
* Hear when I make announcements or release new tools (although I use Twitter more often 🤷‍♂).
* Read what I write (or follow me) on <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> or <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>.
* Read other ideas, articles, and about tools I have created.
* Read other ideas, articles, and read about tools I have created.
* Follow me to read when I publish something new.
## Tweet about **FastAPI**
<a href="https://twitter.com/compose/tweet?text=I'm loving FastAPI because... https://github.com/tiangolo/fastapi cc @tiangolo" class="external-link" target="_blank">Tweet about **FastAPI**</a> and let me and others know why you like it. 🎉
<a href="https://twitter.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/tiangolo/fastapi" class="external-link" target="_blank">Tweet about **FastAPI**</a> and let me and others know why you like it. 🎉
I love to hear about how **FastAPI** is being used, what have you liked in it, in which project/company are you using it, etc.
I love to hear about how **FastAPI** is being used, what you have liked in it, in which project/company are you using it, etc.
## Vote for FastAPI
@@ -67,15 +72,15 @@ I love to hear about how **FastAPI** is being used, what have you liked in it, i
You can see <a href="https://github.com/tiangolo/fastapi/issues" class="external-link" target="_blank">existing issues</a> and try and help others, most of the times they are questions that you might already know the answer for. 🤓
If you are helping a lot of people on issues you might become an official [FastAPI Expert](fastapi-people.md#experts){.internal-link target=_blank}. 🎉
If you are helping a lot of people with issues, you might become an official [FastAPI Expert](fastapi-people.md#experts){.internal-link target=_blank}. 🎉
## Watch the GitHub repository
You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/fastapi" class="external-link" target="_blank">https://github.com/tiangolo/fastapi</a>. 👀
If you select "Watching" instead of "Releases only", you will receive notifications when someone creates a new issue.
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue.
Then you can try and help them solving those issues.
Then you can try and help them solve those issues.
## Create issues
@@ -84,7 +89,7 @@ You can <a href="https://github.com/tiangolo/fastapi/issues/new/choose" class="e
* Ask a **question** or ask about a **problem**.
* Suggest a new **feature**.
**Note**: if you create an issue then I'm going to ask you to also help others. 😉
**Note**: if you create an issue, then I'm going to ask you to also help others. 😉
## Create a Pull Request
@@ -94,7 +99,7 @@ You can [contribute](contributing.md){.internal-link target=_blank} to the sourc
* To share an article, video, or podcast you created or found about FastAPI by <a href="https://github.com/tiangolo/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">editing this file</a>.
* Make sure you add your link to the start of the corresponding section.
* To help [translate the documentation](contributing.md#translations){.internal-link target=_blank} to your language.
* You can also help reviewing the translations created by others.
* You can also help to review the translations created by others.
* To propose new documentation sections.
* To fix an existing issue/bug.
* To add a new feature.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -3,6 +3,35 @@
## Latest Changes
## 0.68.0
### Features
* ✨ Add support for extensions and updates to the OpenAPI schema in each *path operation*. New docs: [FastAPI Path Operation Advanced Configuration - OpenAPI Extra](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#openapi-extra). Initial PR [#1922](https://github.com/tiangolo/fastapi/pull/1922) by [@edouardlp](https://github.com/edouardlp).
* ✨ Add additonal OpenAPI metadata parameters to `FastAPI` class, shown on the automatic API docs UI. New docs: [Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/). Initial PR [#1812](https://github.com/tiangolo/fastapi/pull/1812) by [@dkreeft](https://github.com/dkreeft).
* ✨ Add `description` parameter to all the security scheme classes, e.g. `APIKeyQuery(name="key", description="A very cool API key")`. PR [#1757](https://github.com/tiangolo/fastapi/pull/1757) by [@hylkepostma](https://github.com/hylkepostma).
* ✨ Update OpenAPI models, supporting recursive models and extensions. PR [#3628](https://github.com/tiangolo/fastapi/pull/3628) by [@tiangolo](https://github.com/tiangolo).
* ✨ Import and re-export data structures from Starlette, used by Request properties, on `fastapi.datastructures`. Initial PR [#1872](https://github.com/tiangolo/fastapi/pull/1872) by [@jamescurtin](https://github.com/jamescurtin).
### Docs
* 📝 Update docs about async and response-model with more gender neutral language. PR [#1869](https://github.com/tiangolo/fastapi/pull/1869) by [@Edward-Knight](https://github.com/Edward-Knight).
### Translations
* 🌐 Add Russian translation for `docs/python-types.md`. PR [#3039](https://github.com/tiangolo/fastapi/pull/3039) by [@dukkee](https://github.com/dukkee).
* 🌐 Add Chinese translation for `docs/tutorial/dependencies/index.md`. PR [#3489](https://github.com/tiangolo/fastapi/pull/3489) by [@jaystone776](https://github.com/jaystone776).
* 🌐 Add Russian translation for `docs/external-links.md`. PR [#3036](https://github.com/tiangolo/fastapi/pull/3036) by [@dukkee](https://github.com/dukkee).
* 🌐 Add Chinese translation for `docs/tutorial/dependencies/global-dependencies.md`. PR [#3493](https://github.com/tiangolo/fastapi/pull/3493) by [@jaystone776](https://github.com/jaystone776).
* 🌐 Add Portuguese translation for `docs/deployment/versions.md`. PR [#3618](https://github.com/tiangolo/fastapi/pull/3618) by [@lsglucas](https://github.com/lsglucas).
* 🌐 Add Japanese translation for `docs/tutorial/security/oauth2-jwt.md`. PR [#3526](https://github.com/tiangolo/fastapi/pull/3526) by [@sattosan](https://github.com/sattosan).
### Internal
* ✅ Add the `docs_src` directory to test coverage and update tests. Initial PR [#1904](https://github.com/tiangolo/fastapi/pull/1904) by [@Kludex](https://github.com/Kludex).
* 🔧 Add new GitHub templates with forms for new issues. PR [#3612](https://github.com/tiangolo/fastapi/pull/3612) by [@tiangolo](https://github.com/tiangolo).
* 📝 Add official FastAPI Twitter to docs: [@fastapi](https://twitter.com/fastapi). PR [#3578](https://github.com/tiangolo/fastapi/pull/3578) by [@tiangolo](https://github.com/tiangolo).
## 0.67.0
### Features

View File

@@ -2,21 +2,28 @@
You can customize several metadata configurations in your **FastAPI** application.
## Title, description, and version
## Metadata for API
You can set the:
You can set the following fields that are used in the OpenAPI specification and the automatic API docs UIs:
* **Title**: used as your API's title/name, in OpenAPI and the automatic API docs UIs.
* **Description**: the description of your API, in OpenAPI and the automatic API docs UIs.
* **Version**: the version of your API, e.g. `v2` or `2.5.0`.
* Useful for example if you had a previous version of the application, also using OpenAPI.
| Parameter | Type | Description |
|------------|------|-------------|
| `title` | `str` | The title of the API. |
| `description` | `str` | A short description of the API. It can use Markdown. |
| `version` | `string` | The version of the API. This is the version of your own application, not of OpenAPI. For example `2.5.0`. |
| `terms_of_service` | `str` | A URL to the Terms of Service for the API. If provided, this has to be a URL. |
| `contact` | `dict` | The contact information for the exposed API. It can contain several fields. <details><summary><code>contact</code> fields</summary><table><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>The identifying name of the contact person/organization.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>The URL pointing to the contact information. MUST be in the format of a URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>The email address of the contact person/organization. MUST be in the format of an email address.</td></tr></tbody></table></details> |
| `license_info` | `dict` | The license information for the exposed API. It can contain several fields. <details><summary><code>license_info</code> fields</summary><table><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>REQUIRED</strong> (if a <code>license_info</code> is set). The license name used for the API.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>A URL to the license used for the API. MUST be in the format of a URL.</td></tr></tbody></table></details> |
To set them, use the parameters `title`, `description`, and `version`:
You can set them as follows:
```Python hl_lines="4-6"
```Python hl_lines="3-16 19-31"
{!../../../docs_src/metadata/tutorial001.py!}
```
!!! tip
You can write Markdown in the `description` field and it will be rendered in the output.
With this configuration, the automatic API docs would look like:
<img src="/img/tutorial/metadata/image01.png">

View File

@@ -47,7 +47,7 @@ And we are using this model to declare our input and the same model to declare o
Now, whenever a browser is creating a user with a password, the API will return the same password in the response.
In this case, it might not be a problem, because the user himself is sending the password.
In this case, it might not be a problem, because the user themself is sending the password.
But if we use the same model for another *path operation*, we could be sending our user's passwords to every client.

View File

@@ -180,7 +180,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -3,6 +3,13 @@
{% block announce %}
<div class="announce-wrapper">
<div id="announce-left">
<div class="item">
<a class="announce-link" href="https://twitter.com/fastapi" target="_blank">
<span class="twemoji twitter">
{% include ".icons/fontawesome/brands/twitter.svg" %}
</span> Follow <strong>@fastapi</strong> on <strong>Twitter</strong> to stay updated
</a>
</div>
<div class="item">
<a class="announce-link" href="https://fastapi.tiangolo.com/newsletter/">
<span class="twemoji">

View File

@@ -86,7 +86,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -84,7 +84,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

0
docs/id/overrides/.gitignore vendored Normal file
View File

View File

@@ -1,13 +0,0 @@
{% extends "base.html" %}
{% block announce %}
<a class="announce" href="https://fastapi.tiangolo.com/newsletter/">
<span class="twemoji">
{% include ".icons/material/email.svg" %}
</span> Subscribe to the <strong>FastAPI and friends</strong> newsletter 🎉
</a>
<!-- <a class="announce" href="https://tripetto.app/run/RXZ6OLDBXX?s=dc" target="_blank">
<span class="twemoji">
</span>Fill the first-ever <strong>FastAPI user survey</strong> for a chance to win official <strong>FastAPI and Typer stickers</strong> 🎁
</a> -->
{% endblock %}

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -0,0 +1,266 @@
# パスワードおよびハッシュ化によるOAuth2、JWTトークンによるBearer
これでセキュリティの流れが全てわかったので、<abbr title="JSON Web Tokens">JWT</abbr>トークンと安全なパスワードのハッシュ化を使用して、実際にアプリケーションを安全にしてみましょう。
このコードは、アプリケーションで実際に使用したり、パスワードハッシュをデータベースに保存するといった用途に利用できます。
本章では、前章の続きから始めて、コードをアップデートしていきます。
## JWT について
JWTとは「JSON Web Tokens」の略称です。
JSONオブジェクトをスペースのない長く密集した文字列で表現したトークンの仕様です。例えば次のようになります
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
これらは暗号化されていないので、誰でもコンテンツから情報を復元できてしまいます。
しかし、トークンは署名されているため、あなたが発行したトークンを受け取った人は、あなたが実際に発行したということを検証できます。
例えば、1週間の有効期限を持つトークンを作成したとします。ユーザーが翌日そのトークンを持って戻ってきたとき、そのユーザーはまだシステムにログインしていることがわかります。
1週間後、トークンが期限切れとなるとどうなるでしょうかユーザーは認可されず、新しいトークンを得るために再びサインインしなければなりません。また、ユーザーまたは第三者がトークンを修正して有効期限を変更しようとした場合、署名が一致しないため、トークンの修正を検知できます。
JWT トークンを使って遊んでみたいという方は、<a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a> をチェックしてください。
## `python-jose` のインストール
PythonでJWTトークンの生成と検証を行うために、`python-jose`をインストールする必要があります:
<div class="termy">
```console
$ pip install python-jose[cryptography]
---> 100%
```
</div>
また、<a href="https://github.com/mpdavis/python-jose" class="external-link" target="_blank">Python-jose</a>だけではなく、暗号を扱うためのパッケージを追加で必要とします。
ここでは、推奨されているものを使用します:<a href="https://cryptography.io/" class="external-link" target="_blank">pyca/cryptography</a>。
!!! tip "豆知識"
このチュートリアルでは以前、<a href="https://pyjwt.readthedocs.io/" class="external-link" target="_blank">PyJWT</a>を使用していました。
しかし、Python-joseは、PyJWTのすべての機能に加えて、後に他のツールと統合して構築する際におそらく必要となる可能性のあるいくつかの追加機能を提供しています。そのため、代わりにPython-joseを使用するように更新されました。
## パスワードのハッシュ化
「ハッシュ化」とは、あるコンテンツ(ここではパスワード)を、規則性のないバイト列(単なる文字列)に変換することです。
特徴として、全く同じ内容(全く同じパスワード)を渡すと、全く同じ規則性のないバイト列に変換されます。
しかし、規則性のないバイト列から元のパスワードに戻すことはできません。
### パスワードのハッシュ化を使う理由
データベースが盗まれても、ユーザーの平文のパスワードは盗まれず、ハッシュ値だけが盗まれます。
したがって、泥棒はそのパスワードを別のシステムで使えません(多くのユーザーはどこでも同じパスワードを使用しているため、危険性があります)。
## `passlib` のインストール
PassLib は、パスワードのハッシュを処理するための優れたPythonパッケージです。
このパッケージは、多くの安全なハッシュアルゴリズムとユーティリティをサポートします。
推奨されるアルゴリズムは「Bcrypt」です。
そのため、Bcryptを指定してPassLibをインストールします
<div class="termy">
```console
$ pip install passlib[bcrypt]
---> 100%
```
</div>
!!! tip "豆知識"
`passlib`を使用すると、**Django**や**Flask**のセキュリティプラグインなどで作成されたパスワードを読み取れるように設定できます。
例えば、Djangoアプリケーションからデータベース内の同じデータをFastAPIアプリケーションと共有できるだけではなく、同じデータベースを使用してDjangoアプリケーションを徐々に移行することもできます。
また、ユーザーはDjangoアプリまたは**FastAPI**アプリからも、同時にログインできるようになります。
## パスワードのハッシュ化と検証
必要なツールを `passlib`からインポートします。
PassLib の「context」を作成します。これは、パスワードのハッシュ化と検証に使用されるものです。
!!! tip "豆知識"
PassLibのcontextには、検証だけが許された非推奨の古いハッシュアルゴリズムを含む、様々なハッシュアルゴリズムを使用した検証機能もあります。
例えば、この機能を使用して、別のシステムDjangoなどによって生成されたパスワードを読み取って検証し、Bcryptなどの別のアルゴリズムを使用して新しいパスワードをハッシュするといったことができます。
そして、同時にそれらはすべてに互換性があります。
ユーザーから送られてきたパスワードをハッシュ化するユーティリティー関数を作成します。
また、受け取ったパスワードが保存されているハッシュと一致するかどうかを検証するユーティリティも作成します。
さらに、ユーザーを認証して返す関数も作成します。
```Python hl_lines="7 48 55-56 59-60 69-75"
{!../../../docs_src/security/tutorial004.py!}
```
!!! note "備考"
新しい(偽の)データベース`fake_users_db`を確認すると、ハッシュ化されたパスワードが次のようになっていることがわかります:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`
## JWTトークンの取り扱い
インストールした複数のモジュールをインポートします。
JWTトークンの署名に使用されるランダムな秘密鍵を生成します。
安全なランダム秘密鍵を生成するには、次のコマンドを使用します:
<div class="termy">
```console
$ openssl rand -hex 32
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
```
</div>
そして、出力された文字列を変数`SECRET_KEY`にコピーします。(例に記載している秘密鍵は実際に使用しないでください)
JWTトークンの署名に使用するアルゴリズム`"HS256"`を指定した変数`ALGORITHM`を作成します。
トークンの有効期限を指定した変数`ACCESS_TOKEN_EXPIRE_MINUTES`を作成します。
レスポンスのトークンエンドポイントで使用するPydanticモデルを定義します。
新しいアクセストークンを生成するユーティリティ関数を作成します。
```Python hl_lines="6 12-14 28-30 78-86"
{!../../../docs_src/security/tutorial004.py!}
```
## 依存関係の更新
`get_current_user`を更新して、先ほどと同じトークンを受け取るようにしますが、今回はJWTトークンを使用します。
受け取ったトークンを復号して検証し、現在のユーザーを返します。
トークンが無効な場合は、すぐにHTTPエラーを返します。
```Python hl_lines="89-106"
{!../../../docs_src/security/tutorial004.py!}
```
## `/token` パスオペレーションの更新
トークンの有効期限を表す`timedelta`を作成します。
JWTアクセストークンを作成し、それを返します。
```Python hl_lines="115-128"
{!../../../docs_src/security/tutorial004.py!}
```
### JWTの"subject" `sub` についての技術的な詳細
JWTの仕様では、トークンのsubjectを表すキー`sub`があるとされています。
使用するかどうかは任意ですが、`sub`はユーザーの識別情報を入れるように規定されているので、ここで使用します。
JWTは、ユーザーを識別して、そのユーザーがAPI上で直接操作を実行できるようにする以外にも、他の用途で使用されることがあります。
例えば、「車」や「ブログ記事」を識別することができます。
そして、「ドライブ」(車の場合)や「編集」(ブログの場合)など、そのエンティティに関する権限も追加できます。
また、JWTトークンをユーザーまたはボットに渡すことができます。ユーザーは、JWTトークンを使用するだけで、アカウントを持っていなくても、APIが生成したJWTトークンを使ってそれらの行動車の運転、ブログ投稿の編集を実行できるのです。
これらのアイデアを使用すると、JWTをより高度なシナリオに使用できます。
しかしながら、それらのエンティティのいくつかが同じIDを持つ可能性があります。例えば、`foo`(ユーザー`foo`、車 `foo`、ブログ投稿`foo`)などです。
IDの衝突を回避するために、ユーザーのJWTトークンを作成するとき、subキーの値にプレフィックスを付けることができます例えば、`username:`)。したがって、この例では、`sub`の値は次のようになっている可能性があります:`username:johndoe`
覚えておくべき重要なことは、`sub`キーはアプリケーション全体で一意の識別子を持ち、文字列である必要があるということです。
## 確認
サーバーを実行し、ドキュメントに移動します:<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>
次のようなユーザーインターフェイスが表示されます:
<img src="/img/tutorial/security/image07.png">
前回と同じ方法でアプリケーションの認可を行います。
次の認証情報を使用します:
Username: `johndoe`
Password: `secret`
!!! check "確認"
コードのどこにも平文のパスワード"`secret`"はなく、ハッシュ化されたものしかないことを確認してください。
<img src="/img/tutorial/security/image08.png">
エンドポイント`/users/me/`を呼び出すと、次のようなレスポンスが得られます:
```JSON
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false
}
```
<img src="/img/tutorial/security/image09.png">
開発者ツールを開くと、送信されるデータにはトークンだけが含まれており、パスワードはユーザーを認証してアクセストークンを取得する最初のリクエストでのみ送信され、その後は送信されないことがわかります。
<img src="/img/tutorial/security/image10.png">
!!! note "備考"
ヘッダーの`Authorization`には、`Bearer`で始まる値があります。
## `scopes` を使った高度なユースケース
OAuth2には、「スコープ」という概念があります。
これらを利用して、JWTトークンに特定の権限セットを追加することができます。
そして、このトークンをユーザーに直接、または第三者に与えて、制限付きでAPIを操作できます。
これらの使用方法や**FastAPI**への統合方法については、**高度なユーザーガイド**で後ほど説明します。
## まとめ
ここまでの説明で、OAuth2やJWTなどの規格を使った安全な**FastAPI**アプリケーションを設定することができます。
ほとんどのフレームワークにおいて、セキュリティを扱うことは非常に複雑な課題となります。
簡略化しすぎたパッケージの多くは、データモデルやデータベース、利用可能な機能について多くの妥協をしなければなりません。そして、あまりにも単純化されたパッケージの中には、実はセキュリティ上の欠陥があるものもあります。
---
**FastAPI**は、どのようなデータベース、データモデル、ツールに対しても妥協することはありません。
そのため、プロジェクトに合わせて自由に選択することができます。
また、**FastAPI**は外部パッケージを統合するために複雑な仕組みを必要としないため、`passlib`や`python-jose`のようなよく整備され広く使われている多くのパッケージを直接使用することができます。
しかし、柔軟性、堅牢性、セキュリティを損なうことなく、可能な限りプロセスを簡素化するためのツールを提供します。
また、OAuth2のような安全で標準的なプロトコルを比較的簡単な方法で使用できるだけではなく、実装することもできます。
OAuth2の「スコープ」を使って、同じ基準でより細かい権限システムを実現する方法については、**高度なユーザーガイド**で詳しく説明しています。スコープ付きのOAuth2は、Facebook、Google、GitHub、Microsoft、Twitterなど、多くの大手認証プロバイダが、サードパーティのアプリケーションと自社のAPIとのやり取りをユーザーに代わって認可するために使用している仕組みです。

View File

@@ -68,6 +68,7 @@ nav:
- tutorial/body-updates.md
- セキュリティ:
- tutorial/security/first-steps.md
- tutorial/security/oauth2-jwt.md
- tutorial/middleware.md
- tutorial/cors.md
- tutorial/static-files.md
@@ -115,7 +116,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -82,7 +82,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -0,0 +1,87 @@
# Sobre as versões do FastAPI
**FastAPI** já está sendo usado em produção em diversas aplicações e sistemas, a cobertura de testes é mantida em 100%, mas seu desenvolvimento está avançando rapidamente.
Novos recursos são adicionados com frequência, bugs são corrigidos regularmente e o código está sempre melhorando.
Esse é o motivo das versões atuais estarem em `0.x.x`, significando que em cada versão pode haver mudanças significativas, tudo isso seguindo as <a href="https://semver.org/lang/pt-BR/" class="external-link" target="_blank">convenções de controle de versão semântica.</a>
Já é possível criar aplicativos de produção com **FastAPI** (e provavelmente você já faz isso há algum tempo), apenas precisando ter certeza de usar uma versão que funcione corretamente com o resto do seu código.
## Fixe a sua versão de `fastapi`
A primeira coisa que você deve fazer é "fixar" a versão do **FastAPI** que você está utilizando na mais atual, na qual você sabe que funciona corretamente para o seu aplicativo.
Por exemplo, supondo que você está usando a versão `0.45.0` em sua aplicação.
Caso você utilize o arquivo `requirements.txt`, você poderia especificar a versão com:
```txt
fastapi==0.45.0
```
Isso significa que você conseguiria utilizar a versão exata `0.45.0`.
Ou, você poderia fixá-la com:
```txt
fastapi>=0.45.0,<0.46.0
```
isso significa que você iria usar as versões `0.45.0` ou acima, mas inferiores à `0.46.0`, por exemplo, a versão `0.45.2` ainda seria aceita.
Se você usar qualquer outra ferramenta para gerenciar suas instalações, como Poetry, Pipenv ou outras, todas elas têm uma maneira que você pode usar para definir as versões específicas dos seus pacotes.
## Versões disponíveis
Você pode ver as versões disponíveis (por exemplo, para verificar qual é a versão atual) em [Release Notes](../release-notes.md){.internal-link target=\_blank}.
## Sobre versões
Seguindo as convenções de controle de versão semântica, qualquer versão abaixo de `1.0.0` pode adicionar mudanças significativas.
FastAPI também segue a convenção de que qualquer alteração de versão "PATCH" é para correção de bugs e alterações não significativas.
!!! tip "Dica"
O "PATCH" é o último número, por exemplo, em `0.2.3`, a versão PATCH é `3`.
Logo, você deveria conseguir fixar a versão, como:
```txt
fastapi>=0.45.0,<0.46.0
```
Mudanças significativas e novos recursos são adicionados em versões "MINOR".
!!! tip "Dica"
O "MINOR" é o número que está no meio, por exemplo, em `0.2.3`, a versão MINOR é `2`.
## Atualizando as versões do FastAPI
Você deve adicionar testes para a sua aplicação.
Com **FastAPI** isso é muito fácil (graças a Starlette), verifique a documentação: [Testing](../tutorial/testing.md){.internal-link target=\_blank}
Após a criação dos testes, você pode atualizar a sua versão do **FastAPI** para uma mais recente, execute os testes para se certificar de que todo o seu código está funcionando corretamente.
Se tudo estiver funcionando, ou após você realizar as alterações necessárias e todos os testes estiverem passando, então você pode fixar sua versão de `FastAPI` para essa mais nova.
## Sobre Starlette
Não é recomendado fixar a versão de `starlette`.
Versões diferentes de **FastAPI** utilizarão uma versão específica e mais recente de Starlette.
Então, você pode deixar **FastAPI** escolher a versão compatível e correta de Starlette.
## Sobre Pydantic
Pydantic incluí os testes para **FastAPI** em seus próprios testes, então as novas versões de Pydantic (acima da `1.0.0`) sempre serão compatíveis com FastAPI.
Você pode fixar qualquer versão de Pydantic que desejar, desde que seja acima da `1.0.0` e abaixo da `2.0.0`.
Por exemplo:
```txt
pydantic>=1.2.0,<2.0.0
```

View File

@@ -62,6 +62,7 @@ nav:
- tutorial/security/index.md
- Implantação:
- deployment/index.md
- deployment/versions.md
- alternatives.md
- history-design-future.md
- external-links.md
@@ -89,7 +90,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -0,0 +1,82 @@
# Внешние ссылки и статьи
**FastAPI** имеет отличное и постоянно растущее сообщество.
Существует множество сообщений, статей, инструментов и проектов, связанных с **FastAPI**.
Вот неполный список некоторых из них.
!!! tip
Если у вас есть статья, проект, инструмент или что-либо, связанное с **FastAPI**, что еще не перечислено здесь, создайте <a href="https://github.com/tiangolo/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">Pull Request</a>.
## Статьи
### На английском
{% if external_links %}
{% for article in external_links.articles.english %}
* <a href="{{ article.link }}" class="external-link" target="_blank">{{ article.title }}</a> by <a href="{{ article.author_link }}" class="external-link" target="_blank">{{ article.author }}</a>.
{% endfor %}
{% endif %}
### На японском
{% if external_links %}
{% for article in external_links.articles.japanese %}
* <a href="{{ article.link }}" class="external-link" target="_blank">{{ article.title }}</a> by <a href="{{ article.author_link }}" class="external-link" target="_blank">{{ article.author }}</a>.
{% endfor %}
{% endif %}
### На вьетнамском
{% if external_links %}
{% for article in external_links.articles.vietnamese %}
* <a href="{{ article.link }}" class="external-link" target="_blank">{{ article.title }}</a> by <a href="{{ article.author_link }}" class="external-link" target="_blank">{{ article.author }}</a>.
{% endfor %}
{% endif %}
### На русском
{% if external_links %}
{% for article in external_links.articles.russian %}
* <a href="{{ article.link }}" class="external-link" target="_blank">{{ article.title }}</a> by <a href="{{ article.author_link }}" class="external-link" target="_blank">{{ article.author }}</a>.
{% endfor %}
{% endif %}
### На немецком
{% if external_links %}
{% for article in external_links.articles.german %}
* <a href="{{ article.link }}" class="external-link" target="_blank">{{ article.title }}</a> by <a href="{{ article.author_link }}" class="external-link" target="_blank">{{ article.author }}</a>.
{% endfor %}
{% endif %}
## Подкасты
{% if external_links %}
{% for article in external_links.podcasts.english %}
* <a href="{{ article.link }}" class="external-link" target="_blank">{{ article.title }}</a> by <a href="{{ article.author_link }}" class="external-link" target="_blank">{{ article.author }}</a>.
{% endfor %}
{% endif %}
## Talks
{% if external_links %}
{% for article in external_links.talks.english %}
* <a href="{{ article.link }}" class="external-link" target="_blank">{{ article.title }}</a> by <a href="{{ article.author_link }}" class="external-link" target="_blank">{{ article.author }}</a>.
{% endfor %}
{% endif %}
## Проекты
Последние GitHub-проекты с пометкой `fastapi`:
<div class="github-topic-projects">
</div>

View File

@@ -0,0 +1,314 @@
# Введение в аннотации типов Python
Python имеет поддержку необязательных аннотаций типов.
**Аннотации типов** являются специальным синтаксисом, который позволяет определять <abbr title="например: str, int, float, bool">тип</abbr> переменной.
Объявление типов для переменных позволяет улучшить поддержку вашего кода редакторами и различными инструментами.
Это просто **краткое руководство / напоминание** об аннотациях типов в Python. Оно охватывает только минимум, необходимый для их использования с **FastAPI**... что на самом деле очень мало.
**FastAPI** целиком основан на аннотациях типов, у них много выгод и преимуществ.
Но даже если вы никогда не используете **FastAPI**, вам будет полезно немного узнать о них.
!!! note
Если вы являетесь экспертом в Python и уже знаете всё об аннотациях типов, переходите к следующему разделу.
## Мотивация
Давайте начнем с простого примера:
```Python
{!../../../docs_src/python_types/tutorial001.py!}
```
Вызов этой программы выводит:
```
John Doe
```
Функция делает следующее:
* Принимает `first_name` и `last_name`.
* Преобразует первую букву содержимого каждой переменной в верхний регистр с `title()`.
* <abbr title="Объединяет в одно целое, последовательно, друг за другом.">Соединяет</abbr> их через пробел.
```Python hl_lines="2"
{!../../../docs_src/python_types/tutorial001.py!}
```
### Отредактируем пример
Это очень простая программа.
А теперь представьте, что вы пишете её с нуля.
В какой-то момент вы бы начали определение функции, у вас были бы готовы параметры...
Но затем вы должны вызвать «тот метод, который преобразует первую букву в верхний регистр».
Было это `upper`? Или `uppercase`? `first_uppercase`? `capitalize`?
Тогда вы попробуете с давним другом программиста: автодополнением редактора.
Вы вводите первый параметр функции, `first_name`, затем точку (`.`), а затем нажимаете `Ctrl+Space`, чтобы запустить дополнение.
Но, к сожалению, ничего полезного не выходит:
<img src="/img/python-types/image01.png">
### Добавим типы
Давайте изменим одну строчку в предыдущей версии.
Мы изменим именно этот фрагмент, параметры функции, с:
```Python
first_name, last_name
```
на:
```Python
first_name: str, last_name: str
```
Вот и все.
Это аннотации типов:
```Python hl_lines="1"
{!../../../docs_src/python_types/tutorial002.py!}
```
Это не то же самое, что объявление значений по умолчанию, например:
```Python
first_name="john", last_name="doe"
```
Это другая вещь.
Мы используем двоеточия (`:`), а не равно (`=`).
И добавление аннотаций типов обычно не меняет происходящего по сравнению с тем, что произошло бы без неё.
Но теперь представьте, что вы снова находитесь в процессе создания этой функции, но уже с аннотациями типов.
В тот же момент вы пытаетесь запустить автодополнение с помощью `Ctrl+Space` и вы видите:
<img src="/img/python-types/image02.png">
При этом вы можете просматривать варианты, пока не найдёте подходящий:
<img src="/img/python-types/image03.png">
## Больше мотивации
Проверьте эту функцию, она уже имеет аннотации типов:
```Python hl_lines="1"
{!../../../docs_src/python_types/tutorial003.py!}
```
Поскольку редактор знает типы переменных, вы получаете не только дополнение, но и проверки ошибок:
<img src="/img/python-types/image04.png">
Теперь вы знаете, что вам нужно исправить, преобразовав `age` в строку с `str(age)`:
```Python hl_lines="2"
{!../../../docs_src/python_types/tutorial004.py!}
```
## Объявление типов
Вы только что видели основное место для объявления подсказок типов. В качестве параметров функции.
Это также основное место, где вы можете использовать их с **FastAPI**.
### Простые типы
Вы можете объявить все стандартные типы Python, а не только `str`.
Вы можете использовать, к примеру:
* `int`
* `float`
* `bool`
* `bytes`
```Python hl_lines="1"
{!../../../docs_src/python_types/tutorial005.py!}
```
### Generic-типы с параметрами типов
Существуют некоторые структуры данных, которые могут содержать другие значения, например, `dict`, `list`, `set` и `tuple`. И внутренние значения тоже могут иметь свой тип.
Чтобы объявить эти типы и внутренние типы, вы можете использовать стандартный Python-модуль `typing`.
Он существует специально для поддержки подсказок этих типов.
#### `List`
Например, давайте определим переменную как `list`, состоящий из `str`.
Импортируйте `List` из `typing` (с заглавной `L`):
```Python hl_lines="1"
{!../../../docs_src/python_types/tutorial006.py!}
```
Объявите переменную с тем же синтаксисом двоеточия (`:`).
В качестве типа укажите `List`.
Поскольку список является типом, содержащим некоторые внутренние типы, вы помещаете их в квадратные скобки:
```Python hl_lines="4"
{!../../../docs_src/python_types/tutorial006.py!}
```
!!! tip
Эти внутренние типы в квадратных скобках называются «параметрами типов».
В этом случае `str` является параметром типа, передаваемым в `List`.
Это означает: "переменная `items` является `list`, и каждый из элементов этого списка является `str`".
Если вы будете так поступать, редактор может оказывать поддержку даже при обработке элементов списка:
<img src="/img/python-types/image05.png">
Без типов добиться этого практически невозможно.
Обратите внимание, что переменная `item` является одним из элементов списка `items`.
И все же редактор знает, что это `str`, и поддерживает это.
#### `Tuple` и `Set`
Вы бы сделали то же самое, чтобы объявить `tuple` и `set`:
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial007.py!}
```
Это означает:
* Переменная `items_t` является `tuple` с 3 элементами: `int`, другим `int` и `str`.
* Переменная `items_s` является `set` и каждый элемент имеет тип `bytes`.
#### `Dict`
Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённых запятыми.
Первый параметр типа предназначен для ключей `dict`.
Второй параметр типа предназначен для значений `dict`:
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial008.py!}
```
Это означает:
* Переменная `prices` является `dict`:
* Ключи этого `dict` имеют тип `str` (скажем, название каждого элемента).
* Значения этого `dict` имеют тип `float` (скажем, цена каждой позиции).
#### `Optional`
Вы также можете использовать `Optional`, чтобы объявить, что переменная имеет тип, например, `str`, но это является «необязательным», что означает, что она также может быть `None`:
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009.py!}
```
Использование `Optional[str]` вместо просто `str` позволит редактору помочь вам в обнаружении ошибок, в которых вы могли бы предположить, что значение всегда является `str`, хотя на самом деле это может быть и `None`.
#### Generic-типы
Эти типы принимают параметры в квадратных скобках:
* `List`
* `Tuple`
* `Set`
* `Dict`
* `Optional`
* ...и др.
называются **Generic-типами** или **Generics**.
### Классы как типы
Вы также можете объявить класс как тип переменной.
Допустим, у вас есть класс `Person` с полем `name`:
```Python hl_lines="1-3"
{!../../../docs_src/python_types/tutorial010.py!}
```
Тогда вы можете объявить переменную типа `Person`:
```Python hl_lines="6"
{!../../../docs_src/python_types/tutorial010.py!}
```
И снова вы получаете полную поддержку редактора:
<img src="/img/python-types/image06.png">
## Pydantic-модели
<a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> является Python-библиотекой для выполнения валидации данных.
Вы объявляете «форму» данных как классы с атрибутами.
И каждый атрибут имеет тип.
Затем вы создаете экземпляр этого класса с некоторыми значениями, и он проверяет значения, преобразует их в соответствующий тип (если все верно) и предоставляет вам объект со всеми данными.
И вы получаете полную поддержку редактора для этого итогового объекта.
Взято из официальной документации Pydantic:
```Python
{!../../../docs_src/python_types/tutorial011.py!}
```
!!! info
Чтобы узнать больше о <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic, читайте его документацию</a>.
**FastAPI** целиком основан на Pydantic.
Вы увидите намного больше всего этого на практике в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
## Аннотации типов в **FastAPI**
**FastAPI** получает преимущества аннотаций типов для выполнения определённых задач.
С **FastAPI** вы объявляете параметры с аннотациями типов и получаете:
* **Поддержку редактора**.
* **Проверки типов**.
...и **FastAPI** использует тот же механизм для:
* **Определения требований**: из параметров пути запроса, параметров запроса, заголовков, зависимостей и т.д.
* **Преобразования данных**: от запроса к нужному типу.
* **Валидации данных**: исходя из каждого запроса:
* Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные не являются корректными.
* **Документирования** API с использованием OpenAPI:
* который затем используется пользовательскими интерфейсами автоматической интерактивной документации.
Всё это может показаться абстрактным. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
Важно то, что при использовании стандартных типов Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.) **FastAPI** сделает за вас большую часть работы.
!!! info
Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом является <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">«шпаргалка» от `mypy`</a>.

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -76,7 +76,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -0,0 +1,18 @@
# 全局依赖项
有时,我们要为整个应用添加依赖项。
通过与定义[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。
这样一来,就可以为所有*路径操作*应用该依赖项:
```Python hl_lines="15"
{!../../../docs_src/dependencies/tutorial012.py!}
```
[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 一章的思路均适用于全局依赖项, 在本例中,这些依赖项可以用于应用中的所有*路径操作*。
## 为一组路径操作定义依赖项
稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。

View File

@@ -0,0 +1,212 @@
# 依赖项 - 第一步
FastAPI 提供了简单易用,但功能强大的**<abbr title="也称为组件、资源、提供者、服务、可注入项">依赖注入</abbr>**系统。
这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 **FastAPI**
## 什么是「依赖注入」
编程中的**「依赖注入」**是声明代码(本文中为*路径操作函数* )运行所需的,或要使用的「依赖」的一种方式。
然后,由系统(本文中为 **FastAPI**)负责执行任意需要的逻辑,为代码提供这些依赖(「注入」依赖项)。
依赖注入常用于以下场景:
* 共享业务逻辑(复用相同的代码逻辑)
* 共享数据库连接
* 实现安全、验证、角色权限
* 等……
上述场景均可以使用**依赖注入**,将代码重复最小化。
## 第一步
接下来,我们学习一个非常简单的例子,尽管它过于简单,不是很实用。
但通过这个例子,您可以初步了解「依赖注入」的工作机制。
### 创建依赖项
首先,要关注的是依赖项。
依赖项就是一个函数,且可以使用与*路径操作函数*相同的参数:
```Python hl_lines="8-9"
{!../../../docs_src/dependencies/tutorial001.py!}
```
大功告成。
只用了**2 行**代码。
依赖项函数的形式和结构与*路径操作函数*一样。
因此,可以把依赖项当作没有「装饰器」(即,没有 `@app.get("/some-path")` )的路径操作函数。
依赖项可以返回各种内容。
本例中的依赖项预期接收如下参数:
* 类型为 `str` 的可选查询参数 `q`
* 类型为 `int` 的可选查询参数 `skip`,默认值是 `0`
* 类型为 `int` 的可选查询参数 `limit`,默认值是 `100`
然后,依赖项函数返回包含这些值的 `dict`。
### 导入 `Depends`
```Python hl_lines="3"
{!../../../docs_src/dependencies/tutorial001.py!}
```
### 声明依赖项
与在*路径操作函数*参数中使用 `Body`、`Query` 的方式相同,声明依赖项需要使用 `Depends` 和一个新的参数:
```Python hl_lines="13 18"
{!../../../docs_src/dependencies/tutorial001.py!}
```
虽然,在路径操作函数的参数中使用 `Depends` 的方式与 `Body`、`Query` 相同,但 `Depends` 的工作方式略有不同。
这里只能传给 Depends 一个参数。
且该参数必须是可调用对象,比如函数。
该函数接收的参数和*路径操作函数*的参数一样。
!!! tip "提示"
下一章介绍,除了函数还有哪些「对象」可以用作依赖项。
接收到新的请求时,**FastAPI** 执行如下操作:
* 用正确的参数调用依赖项函数(「可依赖项」)
* 获取函数返回的结果
* 把函数返回的结果赋值给*路径操作函数*的参数
```mermaid
graph TB
common_parameters(["common_parameters"])
read_items["/items/"]
read_users["/users/"]
common_parameters --> read_items
common_parameters --> read_users
```
这样,只编写一次代码,**FastAPI** 就可以为多个*路径操作*共享这段代码 。
!!! check "检查"
注意,无需创建专门的类,并将之传递给 **FastAPI** 以进行「注册」或执行类似的操作。
只要把它传递给 `Depends`**FastAPI** 就知道该如何执行后续操作。
## 要不要使用 `async`
**FastAPI** 调用依赖项的方式与*路径操作函数*一样,因此,定义依赖项函数,也要应用与路径操作函数相同的规则。
即,既可以使用异步的 `async def`,也可以使用普通的 `def` 定义依赖项。
在普通的 `def` *路径操作函数*中,可以声明异步的 `async def` 依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项。
上述这些操作都是可行的,**FastAPI** 知道该怎么处理。
!!! note "笔记"
如里不了解异步,请参阅[异步:*“着急了?”*](../../async.md){.internal-link target=_blank} 一章中 `async` 和 `await` 的内容。
## 与 OpenAPI 集成
依赖项及子依赖项的所有请求声明、验证和需求都可以集成至同一个 OpenAPI 概图。
所以,交互文档里也会显示依赖项的所有信息:
<img src="/img/tutorial/dependencies/image01.png">
## 简单用法
观察一下就会发现,只要*路径* 和*操作*匹配,就可以使用声明的路径操作函数。然后,**FastAPI** 会用正确的参数调用函数,并提取请求中的数据。
实际上,所有(或大多数)网络框架的工作方式都是这样的。
开发人员永远都不需要直接调用这些函数,这些函数是由框架(在此为 **FastAPI** )调用的。
通过依赖注入系统,只要告诉 **FastAPI** *路径操作函数* 还要「依赖」其他在*路径操作函数*之前执行的内容,**FastAPI** 就会执行函数代码,并「注入」函数返回的结果。
其他与「依赖注入」概念相同的术语为:
* 资源Resource
* 提供方Provider
* 服务Service
* 可注入Injectable
* 组件Component
## **FastAPI** 插件
**依赖注入**系统支持构建集成和「插件」。但实际上FastAPI 根本**不需要创建「插件」**,因为使用依赖项可以声明不限数量的、可用于*路径操作函数*的集成与交互。
创建依赖项非常简单、直观,并且还支持导入 Python 包。毫不夸张地说,只要几行代码就可以把需要的 Python 包与 API 函数集成在一起。
下一章将详细介绍在关系型数据库、NoSQL 数据库、安全等方面使用依赖项的例子。
## **FastAPI** 兼容性
依赖注入系统如此简洁的特性,让 **FastAPI** 可以与下列系统兼容:
* 关系型数据库
* NoSQL 数据库
* 外部支持库
* 外部 API
* 认证和鉴权系统
* API 使用监控系统
* 响应数据注入系统
* 等等……
## 简单而强大
虽然,**层级式依赖注入系统**的定义与使用十分简单,但它却非常强大。
比如,可以定义依赖其他依赖项的依赖项。
最后,依赖项层级树构建后,**依赖注入系统**会处理所有依赖项及其子依赖项,并为每一步操作提供(注入)结果。
比如,下面有 4 个 API 路径操作(*端点*
* `/items/public/`
* `/items/private/`
* `/users/{user_id}/activate`
* `/items/pro/`
开发人员可以使用依赖项及其子依赖项为这些路径操作添加不同的权限:
```mermaid
graph TB
current_user(["current_user"])
active_user(["active_user"])
admin_user(["admin_user"])
paying_user(["paying_user"])
public["/items/public/"]
private["/items/private/"]
activate_user["/users/{user_id}/activate"]
pro_items["/items/pro/"]
current_user --> active_user
active_user --> admin_user
active_user --> paying_user
current_user --> public
active_user --> private
admin_user --> activate_user
paying_user --> pro_items
```
## 与 **OpenAPI** 集成
在声明需求时,所有这些依赖项还会把参数、验证等功能添加至路径操作。
**FastAPI** 负责把上述内容全部添加到 OpenAPI 概图,并显示在交互文档中。

View File

@@ -80,6 +80,9 @@ nav:
- tutorial/request-forms-and-files.md
- tutorial/handling-errors.md
- tutorial/body-updates.md
- 依赖项:
- tutorial/dependencies/index.md
- tutorial/dependencies/global-dependencies.md
- 安全性:
- tutorial/security/index.md
- tutorial/security/get-current-user.md
@@ -122,7 +125,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev

View File

@@ -1,12 +1,37 @@
from fastapi import FastAPI
description = """
ChimichangApp API helps you do awesome stuff. 🚀
## Items
You can **read items**.
## Users
You will be able to:
* **Create users** (_not implemented_).
* **Read users** (_not implemented_).
"""
app = FastAPI(
title="My Super Project",
description="This is a very fancy project, with auto docs for the API and everything",
version="2.5.0",
title="ChimichangApp",
description=description,
version="0.0.1",
terms_of_service="http://example.com/terms/",
contact={
"name": "Deadpoolio the Amazing",
"url": "http://x-force.example.com/contact/",
"email": "dp@x-force.example.com",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
return [{"name": "Katana"}]

View File

@@ -0,0 +1,8 @@
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"})
async def read_items():
return [{"item_id": "portal-gun"}]

View File

@@ -0,0 +1,41 @@
from fastapi import FastAPI, Request
app = FastAPI()
def magic_data_reader(raw_body: bytes):
return {
"size": len(raw_body),
"content": {
"name": "Maaaagic",
"price": 42,
"description": "Just kiddin', no magic here. ✨",
},
}
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {
"application/json": {
"schema": {
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"description": {"type": "string"},
},
}
}
},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
data = magic_data_reader(raw_body)
return data

View File

@@ -0,0 +1,34 @@
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.parse_obj(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())
return item

View File

@@ -1,6 +1,6 @@
from fastapi import FastAPI
from . import config
from .config import settings
app = FastAPI()
@@ -8,7 +8,7 @@ app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": config.settings.app_name,
"admin_email": config.settings.admin_email,
"items_per_user": config.settings.items_per_user,
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}

View File

@@ -2,18 +2,18 @@ from functools import lru_cache
from fastapi import Depends, FastAPI
from . import config
from .config import Settings
app = FastAPI()
@lru_cache()
def get_settings():
return config.Settings()
return Settings()
@app.get("/info")
async def info(settings: config.Settings = Depends(get_settings)):
async def info(settings: Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,

View File

@@ -1,19 +1,19 @@
from fastapi.testclient import TestClient
from . import config, main
from .config import Settings
from .main import app, get_settings
client = TestClient(main.app)
client = TestClient(app)
def get_settings_override():
return config.Settings(admin_email="testing_admin@example.com")
return Settings(admin_email="testing_admin@example.com")
main.app.dependency_overrides[main.get_settings] = get_settings_override
app.dependency_overrides[get_settings] = get_settings_override
def test_app():
response = client.get("/info")
data = response.json()
assert data == {

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.67.0"
__version__ = "0.68.0"
from starlette import status as status

View File

@@ -55,6 +55,9 @@ class FastAPI(Starlette):
] = None,
on_startup: Optional[Sequence[Callable[[], Any]]] = None,
on_shutdown: Optional[Sequence[Callable[[], Any]]] = None,
terms_of_service: Optional[str] = None,
contact: Optional[Dict[str, Union[str, Any]]] = None,
license_info: Optional[Dict[str, Union[str, Any]]] = None,
openapi_prefix: str = "",
root_path: str = "",
root_path_in_servers: bool = True,
@@ -97,6 +100,9 @@ class FastAPI(Starlette):
self.title = title
self.description = description
self.version = version
self.terms_of_service = terms_of_service
self.contact = contact
self.license_info = license_info
self.servers = servers or []
self.openapi_url = openapi_url
self.openapi_tags = openapi_tags
@@ -132,6 +138,9 @@ class FastAPI(Starlette):
version=self.version,
openapi_version=self.openapi_version,
description=self.description,
terms_of_service=self.terms_of_service,
contact=self.contact,
license_info=self.license_info,
routes=self.routes,
tags=self.openapi_tags,
servers=self.servers,
@@ -227,6 +236,7 @@ class FastAPI(Starlette):
JSONResponse
),
name: Optional[str] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> None:
self.router.add_api_route(
path,
@@ -251,6 +261,7 @@ class FastAPI(Starlette):
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
openapi_extra=openapi_extra,
)
def api_route(
@@ -277,6 +288,7 @@ class FastAPI(Starlette):
include_in_schema: bool = True,
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.router.add_api_route(
@@ -302,6 +314,7 @@ class FastAPI(Starlette):
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
openapi_extra=openapi_extra,
)
return func
@@ -370,6 +383,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.get(
path,
@@ -393,6 +407,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def put(
@@ -419,6 +434,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.put(
path,
@@ -442,6 +458,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def post(
@@ -468,6 +485,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.post(
path,
@@ -491,6 +509,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def delete(
@@ -517,6 +536,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.delete(
path,
@@ -540,6 +560,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def options(
@@ -566,6 +587,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.options(
path,
@@ -589,6 +611,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def head(
@@ -615,6 +638,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.head(
path,
@@ -638,6 +662,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def patch(
@@ -664,6 +689,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.patch(
path,
@@ -687,6 +713,7 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def trace(
@@ -713,6 +740,7 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.trace(
path,
@@ -736,4 +764,5 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)

View File

@@ -1,5 +1,10 @@
from typing import Any, Callable, Iterable, Type, TypeVar
from starlette.datastructures import URL as URL # noqa: F401
from starlette.datastructures import Address as Address # noqa: F401
from starlette.datastructures import FormData as FormData # noqa: F401
from starlette.datastructures import Headers as Headers # noqa: F401
from starlette.datastructures import QueryParams as QueryParams # noqa: F401
from starlette.datastructures import State as State # noqa: F401
from starlette.datastructures import UploadFile as StarletteUploadFile

View File

@@ -30,11 +30,17 @@ class Contact(BaseModel):
url: Optional[AnyUrl] = None
email: Optional[EmailStr] = None
class Config:
extra = "allow"
class License(BaseModel):
name: str
url: Optional[AnyUrl] = None
class Config:
extra = "allow"
class Info(BaseModel):
title: str
@@ -44,18 +50,27 @@ class Info(BaseModel):
license: Optional[License] = None
version: str
class Config:
extra = "allow"
class ServerVariable(BaseModel):
enum: Optional[List[str]] = None
default: str
description: Optional[str] = None
class Config:
extra = "allow"
class Server(BaseModel):
url: Union[AnyUrl, str]
description: Optional[str] = None
variables: Optional[Dict[str, ServerVariable]] = None
class Config:
extra = "allow"
class Reference(BaseModel):
ref: str = Field(..., alias="$ref")
@@ -73,13 +88,19 @@ class XML(BaseModel):
attribute: Optional[bool] = None
wrapped: Optional[bool] = None
class Config:
extra = "allow"
class ExternalDocumentation(BaseModel):
description: Optional[str] = None
url: AnyUrl
class Config:
extra = "allow"
class SchemaBase(BaseModel):
class Schema(BaseModel):
ref: Optional[str] = Field(None, alias="$ref")
title: Optional[str] = None
multipleOf: Optional[float] = None
@@ -98,13 +119,13 @@ class SchemaBase(BaseModel):
required: Optional[List[str]] = None
enum: Optional[List[Any]] = None
type: Optional[str] = None
allOf: Optional[List[Any]] = None
oneOf: Optional[List[Any]] = None
anyOf: Optional[List[Any]] = None
not_: Optional[Any] = Field(None, alias="not")
items: Optional[Any] = None
properties: Optional[Dict[str, Any]] = None
additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
allOf: Optional[List["Schema"]] = None
oneOf: Optional[List["Schema"]] = None
anyOf: Optional[List["Schema"]] = None
not_: Optional["Schema"] = Field(None, alias="not")
items: Optional["Schema"] = None
properties: Optional[Dict[str, "Schema"]] = None
additionalProperties: Optional[Union["Schema", Reference, bool]] = None
description: Optional[str] = None
format: Optional[str] = None
default: Optional[Any] = None
@@ -121,22 +142,15 @@ class SchemaBase(BaseModel):
extra: str = "allow"
class Schema(SchemaBase):
allOf: Optional[List[SchemaBase]] = None
oneOf: Optional[List[SchemaBase]] = None
anyOf: Optional[List[SchemaBase]] = None
not_: Optional[SchemaBase] = Field(None, alias="not")
items: Optional[SchemaBase] = None
properties: Optional[Dict[str, SchemaBase]] = None
additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
class Example(BaseModel):
summary: Optional[str] = None
description: Optional[str] = None
value: Optional[Any] = None
externalValue: Optional[AnyUrl] = None
class Config:
extra = "allow"
class ParameterInType(Enum):
query = "query"
@@ -147,12 +161,14 @@ class ParameterInType(Enum):
class Encoding(BaseModel):
contentType: Optional[str] = None
# Workaround OpenAPI recursive reference, using Any
headers: Optional[Dict[str, Union[Any, Reference]]] = None
headers: Optional[Dict[str, Union["Header", Reference]]] = None
style: Optional[str] = None
explode: Optional[bool] = None
allowReserved: Optional[bool] = None
class Config:
extra = "allow"
class MediaType(BaseModel):
schema_: Optional[Union[Schema, Reference]] = Field(None, alias="schema")
@@ -160,6 +176,9 @@ class MediaType(BaseModel):
examples: Optional[Dict[str, Union[Example, Reference]]] = None
encoding: Optional[Dict[str, Encoding]] = None
class Config:
extra = "allow"
class ParameterBase(BaseModel):
description: Optional[str] = None
@@ -175,6 +194,9 @@ class ParameterBase(BaseModel):
# Serialization rules for more complex scenarios
content: Optional[Dict[str, MediaType]] = None
class Config:
extra = "allow"
class Parameter(ParameterBase):
name: str
@@ -185,16 +207,14 @@ class Header(ParameterBase):
pass
# Workaround OpenAPI recursive reference
class EncodingWithHeaders(Encoding):
headers: Optional[Dict[str, Union[Header, Reference]]] = None
class RequestBody(BaseModel):
description: Optional[str] = None
content: Dict[str, MediaType]
required: Optional[bool] = None
class Config:
extra = "allow"
class Link(BaseModel):
operationRef: Optional[str] = None
@@ -204,6 +224,9 @@ class Link(BaseModel):
description: Optional[str] = None
server: Optional[Server] = None
class Config:
extra = "allow"
class Response(BaseModel):
description: str
@@ -211,6 +234,9 @@ class Response(BaseModel):
content: Optional[Dict[str, MediaType]] = None
links: Optional[Dict[str, Union[Link, Reference]]] = None
class Config:
extra = "allow"
class Operation(BaseModel):
tags: Optional[List[str]] = None
@@ -220,13 +246,16 @@ class Operation(BaseModel):
operationId: Optional[str] = None
parameters: Optional[List[Union[Parameter, Reference]]] = None
requestBody: Optional[Union[RequestBody, Reference]] = None
responses: Dict[str, Response]
# Workaround OpenAPI recursive reference
callbacks: Optional[Dict[str, Union[Dict[str, Any], Reference]]] = None
# Using Any for Specification Extensions
responses: Dict[str, Union[Response, Any]]
callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None
deprecated: Optional[bool] = None
security: Optional[List[Dict[str, List[str]]]] = None
servers: Optional[List[Server]] = None
class Config:
extra = "allow"
class PathItem(BaseModel):
ref: Optional[str] = Field(None, alias="$ref")
@@ -243,10 +272,8 @@ class PathItem(BaseModel):
servers: Optional[List[Server]] = None
parameters: Optional[List[Union[Parameter, Reference]]] = None
# Workaround OpenAPI recursive reference
class OperationWithCallbacks(BaseModel):
callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference]]] = None
class Config:
extra = "allow"
class SecuritySchemeType(Enum):
@@ -260,6 +287,9 @@ class SecurityBase(BaseModel):
type_: SecuritySchemeType = Field(..., alias="type")
description: Optional[str] = None
class Config:
extra = "allow"
class APIKeyIn(Enum):
query = "query"
@@ -287,6 +317,9 @@ class OAuthFlow(BaseModel):
refreshUrl: Optional[str] = None
scopes: Dict[str, str] = {}
class Config:
extra = "allow"
class OAuthFlowImplicit(OAuthFlow):
authorizationUrl: str
@@ -311,6 +344,9 @@ class OAuthFlows(BaseModel):
clientCredentials: Optional[OAuthFlowClientCredentials] = None
authorizationCode: Optional[OAuthFlowAuthorizationCode] = None
class Config:
extra = "allow"
class OAuth2(SecurityBase):
type_ = Field(SecuritySchemeType.oauth2, alias="type")
@@ -334,7 +370,11 @@ class Components(BaseModel):
headers: Optional[Dict[str, Union[Header, Reference]]] = None
securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None
links: Optional[Dict[str, Union[Link, Reference]]] = None
callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference]]] = None
# Using Any for Specification Extensions
callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None
class Config:
extra = "allow"
class Tag(BaseModel):
@@ -342,13 +382,25 @@ class Tag(BaseModel):
description: Optional[str] = None
externalDocs: Optional[ExternalDocumentation] = None
class Config:
extra = "allow"
class OpenAPI(BaseModel):
openapi: str
info: Info
servers: Optional[List[Server]] = None
paths: Dict[str, PathItem]
# Using Any for Specification Extensions
paths: Dict[str, Union[PathItem, Any]]
components: Optional[Components] = None
security: Optional[List[Dict[str, List[str]]]] = None
tags: Optional[List[Tag]] = None
externalDocs: Optional[ExternalDocumentation] = None
class Config:
extra = "allow"
Schema.update_forward_refs()
Operation.update_forward_refs()
Encoding.update_forward_refs()

View File

@@ -317,6 +317,8 @@ def get_openapi_path(
"HTTPValidationError": validation_error_response_definition,
}
)
if route.openapi_extra:
deep_dict_update(operation, route.openapi_extra)
path[method.lower()] = operation
return path, security_schemes, definitions
@@ -362,10 +364,19 @@ def get_openapi(
routes: Sequence[BaseRoute],
tags: Optional[List[Dict[str, Any]]] = None,
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
terms_of_service: Optional[str] = None,
contact: Optional[Dict[str, Union[str, Any]]] = None,
license_info: Optional[Dict[str, Union[str, Any]]] = None,
) -> Dict[str, Any]:
info = {"title": title, "version": version}
info: Dict[str, Any] = {"title": title, "version": version}
if description:
info["description"] = description
if terms_of_service:
info["termsOfService"] = terms_of_service
if contact:
info["contact"] = contact
if license_info:
info["license"] = license_info
output: Dict[str, Any] = {"openapi": openapi_version, "info": info}
if servers:
output["servers"] = servers

View File

@@ -320,6 +320,7 @@ class APIRoute(routing.Route):
),
dependency_overrides_provider: Optional[Any] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> None:
# normalise enums e.g. http.HTTPStatus
if isinstance(status_code, enum.IntEnum):
@@ -406,6 +407,7 @@ class APIRoute(routing.Route):
self.dependency_overrides_provider = dependency_overrides_provider
self.callbacks = callbacks
self.app = request_response(self.get_route_handler())
self.openapi_extra = openapi_extra
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
return get_request_handler(
@@ -496,6 +498,7 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
route_class_override: Optional[Type[APIRoute]] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> None:
route_class = route_class_override or self.route_class
responses = responses or {}
@@ -537,6 +540,7 @@ class APIRouter(routing.Router):
name=name,
dependency_overrides_provider=self.dependency_overrides_provider,
callbacks=current_callbacks,
openapi_extra=openapi_extra,
)
self.routes.append(route)
@@ -565,6 +569,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.add_api_route(
@@ -591,6 +596,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
return func
@@ -695,6 +701,7 @@ class APIRouter(routing.Router):
name=route.name,
route_class_override=type(route),
callbacks=current_callbacks,
openapi_extra=route.openapi_extra,
)
elif isinstance(route, routing.Route):
methods = list(route.methods or []) # type: ignore # in Starlette
@@ -742,6 +749,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -766,6 +774,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def put(
@@ -792,6 +801,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -816,6 +826,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def post(
@@ -842,6 +853,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -866,6 +878,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def delete(
@@ -892,6 +905,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -916,6 +930,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def options(
@@ -942,6 +957,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -966,6 +982,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def head(
@@ -992,6 +1009,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -1016,6 +1034,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def patch(
@@ -1042,6 +1061,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -1066,6 +1086,7 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)
def trace(
@@ -1092,6 +1113,7 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
@@ -1117,4 +1139,5 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
)

View File

@@ -13,9 +13,16 @@ class APIKeyBase(SecurityBase):
class APIKeyQuery(APIKeyBase):
def __init__(
self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
self,
*,
name: str,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True
):
self.model: APIKey = APIKey(**{"in": APIKeyIn.query}, name=name)
self.model: APIKey = APIKey(
**{"in": APIKeyIn.query}, name=name, description=description
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -33,9 +40,16 @@ class APIKeyQuery(APIKeyBase):
class APIKeyHeader(APIKeyBase):
def __init__(
self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
self,
*,
name: str,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True
):
self.model: APIKey = APIKey(**{"in": APIKeyIn.header}, name=name)
self.model: APIKey = APIKey(
**{"in": APIKeyIn.header}, name=name, description=description
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -53,9 +67,16 @@ class APIKeyHeader(APIKeyBase):
class APIKeyCookie(APIKeyBase):
def __init__(
self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
self,
*,
name: str,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True
):
self.model: APIKey = APIKey(**{"in": APIKeyIn.cookie}, name=name)
self.model: APIKey = APIKey(
**{"in": APIKeyIn.cookie}, name=name, description=description
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error

View File

@@ -24,9 +24,14 @@ class HTTPAuthorizationCredentials(BaseModel):
class HTTPBase(SecurityBase):
def __init__(
self, *, scheme: str, scheme_name: Optional[str] = None, auto_error: bool = True
self,
*,
scheme: str,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True,
):
self.model = HTTPBaseModel(scheme=scheme)
self.model = HTTPBaseModel(scheme=scheme, description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -51,9 +56,10 @@ class HTTPBasic(HTTPBase):
*,
scheme_name: Optional[str] = None,
realm: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True,
):
self.model = HTTPBaseModel(scheme="basic")
self.model = HTTPBaseModel(scheme="basic", description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.realm = realm
self.auto_error = auto_error
@@ -97,9 +103,10 @@ class HTTPBearer(HTTPBase):
*,
bearerFormat: Optional[str] = None,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True,
):
self.model = HTTPBearerModel(bearerFormat=bearerFormat)
self.model = HTTPBearerModel(bearerFormat=bearerFormat, description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -127,8 +134,14 @@ class HTTPBearer(HTTPBase):
class HTTPDigest(HTTPBase):
def __init__(self, *, scheme_name: Optional[str] = None, auto_error: bool = True):
self.model = HTTPBaseModel(scheme="digest")
def __init__(
self,
*,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True,
):
self.model = HTTPBaseModel(scheme="digest", description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error

View File

@@ -118,9 +118,10 @@ class OAuth2(SecurityBase):
*,
flows: Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]] = OAuthFlowsModel(),
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: Optional[bool] = True
):
self.model = OAuth2Model(flows=flows)
self.model = OAuth2Model(flows=flows, description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -142,12 +143,18 @@ class OAuth2PasswordBearer(OAuth2):
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
description: Optional[str] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
super().__init__(
flows=flows,
scheme_name=scheme_name,
description=description,
auto_error=auto_error,
)
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
@@ -172,6 +179,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
refreshUrl: Optional[str] = None,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
description: Optional[str] = None,
auto_error: bool = True,
):
if not scopes:
@@ -184,7 +192,12 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
"scopes": scopes,
}
)
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
super().__init__(
flows=flows,
scheme_name=scheme_name,
description=description,
auto_error=auto_error,
)
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")

View File

@@ -13,9 +13,12 @@ class OpenIdConnect(SecurityBase):
*,
openIdConnectUrl: str,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
auto_error: bool = True
):
self.model = OpenIdConnectModel(openIdConnectUrl=openIdConnectUrl)
self.model = OpenIdConnectModel(
openIdConnectUrl=openIdConnectUrl, description=description
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error

View File

@@ -7,4 +7,4 @@ bash ./scripts/lint.sh
# Check README.md is up to date
python ./scripts/docs.py verify-readme
export PYTHONPATH=./docs_src
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing --cov-report=xml tests ${@}
pytest --cov=fastapi --cov=tests --cov=docs_src --cov-report=term-missing:skip-covered --cov-report=xml tests ${@}

View File

@@ -0,0 +1,45 @@
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/", openapi_extra={"x-custom-extension": "value"})
def route_with_extras():
return {}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
},
"summary": "Route With Extras",
"operationId": "route_with_extras__get",
"x-custom-extension": "value",
}
},
},
}
def test_openapi():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_get_route():
response = client.get("/")
assert response.status_code == 200, response.text
assert response.json() == {}

View File

@@ -0,0 +1,73 @@
from fastapi import Depends, FastAPI, Security
from fastapi.security import APIKeyCookie
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
api_key = APIKeyCookie(name="key", description="An API Cookie Key")
class User(BaseModel):
username: str
def get_current_user(oauth_header: str = Security(api_key)):
user = User(username=oauth_header)
return user
@app.get("/users/me")
def read_current_user(current_user: User = Depends(get_current_user)):
return current_user
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"APIKeyCookie": []}],
}
}
},
"components": {
"securitySchemes": {
"APIKeyCookie": {
"type": "apiKey",
"name": "key",
"in": "cookie",
"description": "An API Cookie Key",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_api_key():
response = client.get("/users/me", cookies={"key": "secret"})
assert response.status_code == 200, response.text
assert response.json() == {"username": "secret"}
def test_security_api_key_no_key():
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}

View File

@@ -0,0 +1,73 @@
from fastapi import Depends, FastAPI, Security
from fastapi.security import APIKeyHeader
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
api_key = APIKeyHeader(name="key", description="An API Key Header")
class User(BaseModel):
username: str
def get_current_user(oauth_header: str = Security(api_key)):
user = User(username=oauth_header)
return user
@app.get("/users/me")
def read_current_user(current_user: User = Depends(get_current_user)):
return current_user
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"APIKeyHeader": []}],
}
}
},
"components": {
"securitySchemes": {
"APIKeyHeader": {
"type": "apiKey",
"name": "key",
"in": "header",
"description": "An API Key Header",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_api_key():
response = client.get("/users/me", headers={"key": "secret"})
assert response.status_code == 200, response.text
assert response.json() == {"username": "secret"}
def test_security_api_key_no_key():
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}

View File

@@ -0,0 +1,73 @@
from fastapi import Depends, FastAPI, Security
from fastapi.security import APIKeyQuery
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
api_key = APIKeyQuery(name="key", description="API Key Query")
class User(BaseModel):
username: str
def get_current_user(oauth_header: str = Security(api_key)):
user = User(username=oauth_header)
return user
@app.get("/users/me")
def read_current_user(current_user: User = Depends(get_current_user)):
return current_user
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"APIKeyQuery": []}],
}
}
},
"components": {
"securitySchemes": {
"APIKeyQuery": {
"type": "apiKey",
"name": "key",
"in": "query",
"description": "API Key Query",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_api_key():
response = client.get("/users/me?key=secret")
assert response.status_code == 200, response.text
assert response.json() == {"username": "secret"}
def test_security_api_key_no_key():
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}

View File

@@ -0,0 +1,62 @@
from fastapi import FastAPI, Security
from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBase
from fastapi.testclient import TestClient
app = FastAPI()
security = HTTPBase(scheme="Other", description="Other Security Scheme")
@app.get("/users/me")
def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
return {"scheme": credentials.scheme, "credentials": credentials.credentials}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"HTTPBase": []}],
}
}
},
"components": {
"securitySchemes": {
"HTTPBase": {
"type": "http",
"scheme": "Other",
"description": "Other Security Scheme",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_http_base():
response = client.get("/users/me", headers={"Authorization": "Other foobar"})
assert response.status_code == 200, response.text
assert response.json() == {"scheme": "Other", "credentials": "foobar"}
def test_security_http_base_no_credentials():
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}

View File

@@ -0,0 +1,85 @@
from base64 import b64encode
from fastapi import FastAPI, Security
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.testclient import TestClient
from requests.auth import HTTPBasicAuth
app = FastAPI()
security = HTTPBasic(realm="simple", description="HTTPBasic scheme")
@app.get("/users/me")
def read_current_user(credentials: HTTPBasicCredentials = Security(security)):
return {"username": credentials.username, "password": credentials.password}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"HTTPBasic": []}],
}
}
},
"components": {
"securitySchemes": {
"HTTPBasic": {
"type": "http",
"scheme": "basic",
"description": "HTTPBasic scheme",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_http_basic():
auth = HTTPBasicAuth(username="john", password="secret")
response = client.get("/users/me", auth=auth)
assert response.status_code == 200, response.text
assert response.json() == {"username": "john", "password": "secret"}
def test_security_http_basic_no_credentials():
response = client.get("/users/me")
assert response.json() == {"detail": "Not authenticated"}
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
def test_security_http_basic_invalid_credentials():
response = client.get(
"/users/me", headers={"Authorization": "Basic notabase64token"}
)
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
assert response.json() == {"detail": "Invalid authentication credentials"}
def test_security_http_basic_non_basic_credentials():
payload = b64encode(b"johnsecret").decode("ascii")
auth_header = f"Basic {payload}"
response = client.get("/users/me", headers={"Authorization": auth_header})
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
assert response.json() == {"detail": "Invalid authentication credentials"}

View File

@@ -0,0 +1,68 @@
from fastapi import FastAPI, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi.testclient import TestClient
app = FastAPI()
security = HTTPBearer(description="HTTP Bearer token scheme")
@app.get("/users/me")
def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
return {"scheme": credentials.scheme, "credentials": credentials.credentials}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"HTTPBearer": []}],
}
}
},
"components": {
"securitySchemes": {
"HTTPBearer": {
"type": "http",
"scheme": "bearer",
"description": "HTTP Bearer token scheme",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_http_bearer():
response = client.get("/users/me", headers={"Authorization": "Bearer foobar"})
assert response.status_code == 200, response.text
assert response.json() == {"scheme": "Bearer", "credentials": "foobar"}
def test_security_http_bearer_no_credentials():
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}
def test_security_http_bearer_incorrect_scheme_credentials():
response = client.get("/users/me", headers={"Authorization": "Basic notreally"})
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Invalid authentication credentials"}

View File

@@ -0,0 +1,70 @@
from fastapi import FastAPI, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest
from fastapi.testclient import TestClient
app = FastAPI()
security = HTTPDigest(description="HTTPDigest scheme")
@app.get("/users/me")
def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
return {"scheme": credentials.scheme, "credentials": credentials.credentials}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"HTTPDigest": []}],
}
}
},
"components": {
"securitySchemes": {
"HTTPDigest": {
"type": "http",
"scheme": "digest",
"description": "HTTPDigest scheme",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_http_digest():
response = client.get("/users/me", headers={"Authorization": "Digest foobar"})
assert response.status_code == 200, response.text
assert response.json() == {"scheme": "Digest", "credentials": "foobar"}
def test_security_http_digest_no_credentials():
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}
def test_security_http_digest_incorrect_scheme_credentials():
response = client.get(
"/users/me", headers={"Authorization": "Other invalidauthorization"}
)
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Invalid authentication credentials"}

View File

@@ -0,0 +1,81 @@
from typing import Optional
from fastapi import FastAPI, Security
from fastapi.security import OAuth2AuthorizationCodeBearer
from fastapi.testclient import TestClient
app = FastAPI()
oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl="authorize",
tokenUrl="token",
description="OAuth2 Code Bearer",
auto_error=True,
)
@app.get("/items/")
async def read_items(token: Optional[str] = Security(oauth2_scheme)):
return {"token": token}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"security": [{"OAuth2AuthorizationCodeBearer": []}],
}
}
},
"components": {
"securitySchemes": {
"OAuth2AuthorizationCodeBearer": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "authorize",
"tokenUrl": "token",
"scopes": {},
}
},
"description": "OAuth2 Code Bearer",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_no_token():
response = client.get("/items")
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
def test_incorrect_token():
response = client.get("/items", headers={"Authorization": "Non-existent testtoken"})
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
def test_token():
response = client.get("/items", headers={"Authorization": "Bearer testtoken"})
assert response.status_code == 200, response.text
assert response.json() == {"token": "testtoken"}

View File

@@ -0,0 +1,255 @@
from typing import Optional
import pytest
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
reusable_oauth2 = OAuth2(
flows={
"password": {
"tokenUrl": "token",
"scopes": {"read:users": "Read the users", "write:users": "Create users"},
}
},
description="OAuth2 security scheme",
auto_error=False,
)
class User(BaseModel):
username: str
def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)):
if oauth_header is None:
return None
user = User(username=oauth_header)
return user
@app.post("/login")
def login(form_data: OAuth2PasswordRequestFormStrict = Depends()):
return form_data
@app.get("/users/me")
def read_users_me(current_user: Optional[User] = Depends(get_current_user)):
if current_user is None:
return {"msg": "Create an account first"}
return current_user
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/login": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Login",
"operationId": "login_login_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_login_login_post"
}
}
},
"required": True,
},
}
},
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Users Me",
"operationId": "read_users_me_users_me_get",
"security": [{"OAuth2": []}],
}
},
},
"components": {
"schemas": {
"Body_login_login_post": {
"title": "Body_login_login_post",
"required": ["grant_type", "username", "password"],
"type": "object",
"properties": {
"grant_type": {
"title": "Grant Type",
"pattern": "password",
"type": "string",
},
"username": {"title": "Username", "type": "string"},
"password": {"title": "Password", "type": "string"},
"scope": {"title": "Scope", "type": "string", "default": ""},
"client_id": {"title": "Client Id", "type": "string"},
"client_secret": {"title": "Client Secret", "type": "string"},
},
},
"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"},
}
},
},
},
"securitySchemes": {
"OAuth2": {
"type": "oauth2",
"flows": {
"password": {
"scopes": {
"read:users": "Read the users",
"write:users": "Create users",
},
"tokenUrl": "token",
}
},
"description": "OAuth2 security scheme",
}
},
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_oauth2():
response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"})
assert response.status_code == 200, response.text
assert response.json() == {"username": "Bearer footokenbar"}
def test_security_oauth2_password_other_header():
response = client.get("/users/me", headers={"Authorization": "Other footokenbar"})
assert response.status_code == 200, response.text
assert response.json() == {"username": "Other footokenbar"}
def test_security_oauth2_password_bearer_no_header():
response = client.get("/users/me")
assert response.status_code == 200, response.text
assert response.json() == {"msg": "Create an account first"}
required_params = {
"detail": [
{
"loc": ["body", "grant_type"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "username"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "password"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
grant_type_required = {
"detail": [
{
"loc": ["body", "grant_type"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
grant_type_incorrect = {
"detail": [
{
"loc": ["body", "grant_type"],
"msg": 'string does not match regex "password"',
"type": "value_error.str.regex",
"ctx": {"pattern": "password"},
}
]
}
@pytest.mark.parametrize(
"data,expected_status,expected_response",
[
(None, 422, required_params),
({"username": "johndoe", "password": "secret"}, 422, grant_type_required),
(
{"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
422,
grant_type_incorrect,
),
(
{"username": "johndoe", "password": "secret", "grant_type": "password"},
200,
{
"grant_type": "password",
"username": "johndoe",
"password": "secret",
"scopes": [],
"client_id": None,
"client_secret": None,
},
),
],
)
def test_strict_login(data, expected_status, expected_response):
response = client.post("/login", data=data)
assert response.status_code == expected_status
assert response.json() == expected_response

View File

@@ -0,0 +1,76 @@
from typing import Optional
from fastapi import FastAPI, Security
from fastapi.security import OAuth2PasswordBearer
from fastapi.testclient import TestClient
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="/token",
description="OAuth2PasswordBearer security scheme",
auto_error=False,
)
@app.get("/items/")
async def read_items(token: Optional[str] = Security(oauth2_scheme)):
if token is None:
return {"msg": "Create an account first"}
return {"token": token}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"security": [{"OAuth2PasswordBearer": []}],
}
}
},
"components": {
"securitySchemes": {
"OAuth2PasswordBearer": {
"type": "oauth2",
"flows": {"password": {"scopes": {}, "tokenUrl": "/token"}},
"description": "OAuth2PasswordBearer security scheme",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_no_token():
response = client.get("/items")
assert response.status_code == 200, response.text
assert response.json() == {"msg": "Create an account first"}
def test_token():
response = client.get("/items", headers={"Authorization": "Bearer testtoken"})
assert response.status_code == 200, response.text
assert response.json() == {"token": "testtoken"}
def test_incorrect_token():
response = client.get("/items", headers={"Authorization": "Notexistent testtoken"})
assert response.status_code == 200, response.text
assert response.json() == {"msg": "Create an account first"}

View File

@@ -0,0 +1,80 @@
from fastapi import Depends, FastAPI, Security
from fastapi.security.open_id_connect_url import OpenIdConnect
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
oid = OpenIdConnect(
openIdConnectUrl="/openid", description="OpenIdConnect security scheme"
)
class User(BaseModel):
username: str
def get_current_user(oauth_header: str = Security(oid)):
user = User(username=oauth_header)
return user
@app.get("/users/me")
def read_current_user(current_user: User = Depends(get_current_user)):
return current_user
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Current User",
"operationId": "read_current_user_users_me_get",
"security": [{"OpenIdConnect": []}],
}
}
},
"components": {
"securitySchemes": {
"OpenIdConnect": {
"type": "openIdConnect",
"openIdConnectUrl": "/openid",
"description": "OpenIdConnect security scheme",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_oauth2():
response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"})
assert response.status_code == 200, response.text
assert response.json() == {"username": "Bearer footokenbar"}
def test_security_oauth2_password_other_header():
response = client.get("/users/me", headers={"Authorization": "Other footokenbar"})
assert response.status_code == 200, response.text
assert response.json() == {"username": "Other footokenbar"}
def test_security_oauth2_password_bearer_no_header():
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}

View File

@@ -359,6 +359,12 @@ no_jessica = {
("/users/foo", 422, no_jessica, {}),
("/users/me?token=jessica", 200, {"username": "fakecurrentuser"}, {}),
("/users/me", 422, no_jessica, {}),
(
"/users?token=monica",
400,
{"detail": "No Jessica token provided"},
{},
),
(
"/items?token=jessica",
200,
@@ -372,6 +378,12 @@ no_jessica = {
{"name": "Plumbus", "item_id": "plumbus"},
{"X-Token": "fake-super-secret-token"},
),
(
"/items/bar?token=jessica",
404,
{"detail": "Item not found"},
{"X-Token": "fake-super-secret-token"},
),
("/items/plumbus", 422, no_jessica, {"X-Token": "fake-super-secret-token"}),
(
"/items?token=jessica",

View File

@@ -44,3 +44,10 @@ def test_disable_openapi(monkeypatch):
assert response.status_code == 404, response.text
response = client.get("/redoc")
assert response.status_code == 404, response.text
def test_root():
client = TestClient(tutorial001.app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}

View File

@@ -7,21 +7,31 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {
"title": "My Super Project",
"version": "2.5.0",
"description": "This is a very fancy project, with auto docs for the API and everything",
"title": "ChimichangApp",
"description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n",
"termsOfService": "http://example.com/terms/",
"contact": {
"name": "Deadpoolio the Amazing",
"url": "http://x-force.example.com/contact/",
"email": "dp@x-force.example.com",
},
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
"version": "0.0.1",
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}
},
@@ -37,4 +47,4 @@ def test_openapi_schema():
def test_items():
response = client.get("/items/")
assert response.status_code == 200, response.text
assert response.json() == [{"name": "Foo"}]
assert response.json() == [{"name": "Katana"}]

View File

@@ -0,0 +1,36 @@
from fastapi.testclient import TestClient
from docs_src.path_operation_advanced_configuration.tutorial005 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"x-aperture-labs-portal": "blue",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_get():
response = client.get("/items/")
assert response.status_code == 200, response.text

View File

@@ -0,0 +1,59 @@
from fastapi.testclient import TestClient
from docs_src.path_operation_advanced_configuration.tutorial006 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"summary": "Create Item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"description": {"type": "string"},
},
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_post():
response = client.post("/items/", data=b"this is actually not validated")
assert response.status_code == 200, response.text
assert response.json() == {
"size": 30,
"content": {
"name": "Maaaagic",
"price": 42,
"description": "Just kiddin', no magic here. ✨",
},
}

View File

@@ -0,0 +1,97 @@
from fastapi.testclient import TestClient
from docs_src.path_operation_advanced_configuration.tutorial007 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"summary": "Create Item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/x-yaml": {
"schema": {
"title": "Item",
"required": ["name", "tags"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
},
},
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_post():
yaml_data = """
name: Deadpoolio
tags:
- x-force
- x-men
- x-avengers
"""
response = client.post("/items/", data=yaml_data)
assert response.status_code == 200, response.text
assert response.json() == {
"name": "Deadpoolio",
"tags": ["x-force", "x-men", "x-avengers"],
}
def test_post_broken_yaml():
yaml_data = """
name: Deadpoolio
tags:
x - x-force
x - x-men
x - x-avengers
"""
response = client.post("/items/", data=yaml_data)
assert response.status_code == 422, response.text
assert response.json() == {"detail": "Invalid YAML"}
def test_post_invalid():
yaml_data = """
name: Deadpoolio
tags:
- x-force
- x-men
- x-avengers
- sneaky: object
"""
response = client.post("/items/", data=yaml_data)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{"loc": ["tags", 3], "msg": "str type expected", "type": "type_error.str"}
]
}

View File

@@ -1,9 +1,17 @@
from fastapi.testclient import TestClient
from pytest import MonkeyPatch
from docs_src.settings.app02 import main, test_main
client = TestClient(main.app)
def test_setting_override():
def test_settings(monkeypatch: MonkeyPatch):
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
settings = main.get_settings()
assert settings.app_name == "Awesome API"
assert settings.items_per_user == 50
def test_override_settings():
test_main.test_app()

View File

@@ -0,0 +1,10 @@
from docs_src.app_testing import test_main_b
def test_app():
test_main_b.test_create_existing_item()
test_main_b.test_create_item()
test_main_b.test_create_item_bad_token()
test_main_b.test_read_inexistent_item()
test_main_b.test_read_item()
test_main_b.test_read_item_bad_token()

View File

@@ -1,10 +1,15 @@
from fastapi.testclient import TestClient
from docs_src.websockets.tutorial003 import app
from docs_src.websockets.tutorial003 import app, html
client = TestClient(app)
def test_get():
response = client.get("/")
assert response.text == html
def test_websocket_handle_disconnection():
with client.websocket_connect("/ws/1234") as connection, client.websocket_connect(
"/ws/5678"