committed by
GitHub
146 changed files with 5256 additions and 850 deletions
@ -1,5 +0,0 @@ |
|||
[flake8] |
|||
max-line-length = 88 |
|||
select = C,E,F,W,B,B9 |
|||
ignore = E203, E501, W503 |
|||
exclude = __init__.py |
@ -23,17 +23,14 @@ jobs: |
|||
with: |
|||
path: ${{ env.pythonLocation }} |
|||
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v03 |
|||
- name: Install Flit |
|||
if: steps.cache.outputs.cache-hit != 'true' |
|||
run: python3.7 -m pip install flit |
|||
- name: Install docs extras |
|||
if: steps.cache.outputs.cache-hit != 'true' |
|||
run: python3.7 -m flit install --deps production --extras doc |
|||
run: pip install .[doc] |
|||
- name: Install Material for MkDocs Insiders |
|||
if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true' |
|||
run: pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git |
|||
- name: Build Docs |
|||
run: python3.7 ./scripts/docs.py build-all |
|||
run: python ./scripts/docs.py build-all |
|||
- name: Zip docs |
|||
run: bash ./scripts/zip-docs.sh |
|||
- uses: actions/upload-artifact@v3 |
|||
@ -41,7 +38,7 @@ jobs: |
|||
name: docs-zip |
|||
path: ./docs.zip |
|||
- name: Deploy to Netlify |
|||
uses: nwtgck/[email protected].3 |
|||
uses: nwtgck/[email protected].4 |
|||
with: |
|||
publish-dir: './site' |
|||
production-branch: master |
|||
|
@ -12,7 +12,7 @@ jobs: |
|||
steps: |
|||
- uses: actions/checkout@v3 |
|||
- name: Download Artifact Docs |
|||
uses: dawidd6/[email protected]3.0 |
|||
uses: dawidd6/[email protected]4.2 |
|||
with: |
|||
github_token: ${{ secrets.GITHUB_TOKEN }} |
|||
workflow: build-docs.yml |
|||
@ -25,7 +25,7 @@ jobs: |
|||
rm -f docs.zip |
|||
- name: Deploy to Netlify |
|||
id: netlify |
|||
uses: nwtgck/[email protected].3 |
|||
uses: nwtgck/[email protected].4 |
|||
with: |
|||
publish-dir: './site' |
|||
production-deploy: false |
|||
|
@ -17,23 +17,23 @@ jobs: |
|||
- name: Set up Python |
|||
uses: actions/setup-python@v4 |
|||
with: |
|||
python-version: "3.6" |
|||
python-version: "3.7" |
|||
cache: "pip" |
|||
cache-dependency-path: pyproject.toml |
|||
- uses: actions/cache@v3 |
|||
id: cache |
|||
with: |
|||
path: ${{ env.pythonLocation }} |
|||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-publish |
|||
- name: Install Flit |
|||
- name: Install build dependencies |
|||
if: steps.cache.outputs.cache-hit != 'true' |
|||
run: pip install flit |
|||
- name: Install Dependencies |
|||
if: steps.cache.outputs.cache-hit != 'true' |
|||
run: flit install --symlink |
|||
run: pip install build |
|||
- name: Build distribution |
|||
run: python -m build |
|||
- name: Publish |
|||
env: |
|||
FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }} |
|||
FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }} |
|||
run: bash scripts/publish.sh |
|||
uses: pypa/[email protected] |
|||
with: |
|||
password: ${{ secrets.PYPI_API_TOKEN }} |
|||
- name: Dump GitHub context |
|||
env: |
|||
GITHUB_CONTEXT: ${{ toJson(github) }} |
|||
|
@ -0,0 +1,35 @@ |
|||
name: Smokeshow |
|||
|
|||
on: |
|||
workflow_run: |
|||
workflows: [Test] |
|||
types: [completed] |
|||
|
|||
permissions: |
|||
statuses: write |
|||
|
|||
jobs: |
|||
smokeshow: |
|||
if: ${{ github.event.workflow_run.conclusion == 'success' }} |
|||
runs-on: ubuntu-latest |
|||
|
|||
steps: |
|||
- uses: actions/setup-python@v4 |
|||
with: |
|||
python-version: '3.9' |
|||
|
|||
- run: pip install smokeshow |
|||
|
|||
- uses: dawidd6/[email protected] |
|||
with: |
|||
workflow: test.yml |
|||
commit: ${{ github.event.workflow_run.head_sha }} |
|||
|
|||
- run: smokeshow upload coverage-html |
|||
env: |
|||
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} |
|||
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100 |
|||
SMOKESHOW_GITHUB_CONTEXT: coverage |
|||
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
|||
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} |
|||
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} |
@ -0,0 +1,466 @@ |
|||
|
|||
{!../../../docs/missing-translation.md!} |
|||
|
|||
|
|||
<p align="center"> |
|||
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a> |
|||
</p> |
|||
<p align="center"> |
|||
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em> |
|||
</p> |
|||
<p align="center"> |
|||
<a href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest" target="_blank"> |
|||
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg" alt="Test"> |
|||
</a> |
|||
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank"> |
|||
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage"> |
|||
</a> |
|||
<a href="https://pypi.org/project/fastapi" target="_blank"> |
|||
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version"> |
|||
</a> |
|||
</p> |
|||
|
|||
--- |
|||
|
|||
**Documentation**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a> |
|||
|
|||
**Source Code**: <a href="https://github.com/tiangolo/fastapi" target="_blank">https://github.com/tiangolo/fastapi</a> |
|||
|
|||
--- |
|||
|
|||
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. |
|||
|
|||
The key features are: |
|||
|
|||
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). |
|||
|
|||
* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * |
|||
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * |
|||
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging. |
|||
* **Easy**: Designed to be easy to use and learn. Less time reading docs. |
|||
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. |
|||
* **Robust**: Get production-ready code. With automatic interactive documentation. |
|||
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (previously known as Swagger) and <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>. |
|||
|
|||
<small>* estimation based on tests on an internal development team, building production applications.</small> |
|||
|
|||
## Sponsors |
|||
|
|||
<!-- sponsors --> |
|||
|
|||
{% if sponsors %} |
|||
{% for sponsor in sponsors.gold -%} |
|||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> |
|||
{% endfor -%} |
|||
{%- for sponsor in sponsors.silver -%} |
|||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> |
|||
{% endfor %} |
|||
{% endif %} |
|||
|
|||
<!-- /sponsors --> |
|||
|
|||
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Other sponsors</a> |
|||
|
|||
## Opinions |
|||
|
|||
"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" |
|||
|
|||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div> |
|||
|
|||
--- |
|||
|
|||
"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" |
|||
|
|||
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div> |
|||
|
|||
--- |
|||
|
|||
"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" |
|||
|
|||
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div> |
|||
|
|||
--- |
|||
|
|||
"_I’m over the moon excited about **FastAPI**. It’s so fun!_" |
|||
|
|||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://twitter.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div> |
|||
|
|||
--- |
|||
|
|||
"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" |
|||
|
|||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://www.hug.rest/" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div> |
|||
|
|||
--- |
|||
|
|||
"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" |
|||
|
|||
"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" |
|||
|
|||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://twitter.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://twitter.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div> |
|||
|
|||
--- |
|||
|
|||
## **Typer**, the FastAPI of CLIs |
|||
|
|||
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a> |
|||
|
|||
If you are building a <abbr title="Command Line Interface">CLI</abbr> app to be used in the terminal instead of a web API, check out <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>. |
|||
|
|||
**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 |
|||
|
|||
## Requirements |
|||
|
|||
Python 3.7+ |
|||
|
|||
FastAPI stands on the shoulders of giants: |
|||
|
|||
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> for the web parts. |
|||
* <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> for the data parts. |
|||
|
|||
## Installation |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install fastapi |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
You will also need an ASGI server, for production such as <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "uvicorn[standard]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## Example |
|||
|
|||
### Create it |
|||
|
|||
* Create a file `main.py` with: |
|||
|
|||
```Python |
|||
from typing import Optional |
|||
|
|||
from fastapi import FastAPI |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
@app.get("/") |
|||
def read_root(): |
|||
return {"Hello": "World"} |
|||
|
|||
|
|||
@app.get("/items/{item_id}") |
|||
def read_item(item_id: int, q: Optional[str] = None): |
|||
return {"item_id": item_id, "q": q} |
|||
``` |
|||
|
|||
<details markdown="1"> |
|||
<summary>Or use <code>async def</code>...</summary> |
|||
|
|||
If your code uses `async` / `await`, use `async def`: |
|||
|
|||
```Python hl_lines="9 14" |
|||
from typing import Optional |
|||
|
|||
from fastapi import FastAPI |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
@app.get("/") |
|||
async def read_root(): |
|||
return {"Hello": "World"} |
|||
|
|||
|
|||
@app.get("/items/{item_id}") |
|||
async def read_item(item_id: int, q: Optional[str] = None): |
|||
return {"item_id": item_id, "q": q} |
|||
``` |
|||
|
|||
**Note**: |
|||
|
|||
If you don't know, check the _"In a hurry?"_ section about <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` and `await` in the docs</a>. |
|||
|
|||
</details> |
|||
|
|||
### Run it |
|||
|
|||
Run the server with: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --reload |
|||
|
|||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
INFO: Started reloader process [28720] |
|||
INFO: Started server process [28722] |
|||
INFO: Waiting for application startup. |
|||
INFO: Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
<details markdown="1"> |
|||
<summary>About the command <code>uvicorn main:app --reload</code>...</summary> |
|||
|
|||
The command `uvicorn main:app` refers to: |
|||
|
|||
* `main`: the file `main.py` (the Python "module"). |
|||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. |
|||
* `--reload`: make the server restart after code changes. Only do this for development. |
|||
|
|||
</details> |
|||
|
|||
### Check it |
|||
|
|||
Open your browser at <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>. |
|||
|
|||
You will see the JSON response as: |
|||
|
|||
```JSON |
|||
{"item_id": 5, "q": "somequery"} |
|||
``` |
|||
|
|||
You already created an API that: |
|||
|
|||
* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. |
|||
* Both _paths_ take `GET` <em>operations</em> (also known as HTTP _methods_). |
|||
* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. |
|||
* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. |
|||
|
|||
### Interactive API docs |
|||
|
|||
Now go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
You will see the automatic interactive API documentation (provided by <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): |
|||
|
|||
 |
|||
|
|||
### Alternative API docs |
|||
|
|||
And now, go to <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. |
|||
|
|||
You will see the alternative automatic documentation (provided by <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): |
|||
|
|||
 |
|||
|
|||
## Example upgrade |
|||
|
|||
Now modify the file `main.py` to receive a body from a `PUT` request. |
|||
|
|||
Declare the body using standard Python types, thanks to Pydantic. |
|||
|
|||
```Python hl_lines="4 9-12 25-27" |
|||
from typing import Optional |
|||
|
|||
from fastapi import FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
name: str |
|||
price: float |
|||
is_offer: Optional[bool] = None |
|||
|
|||
|
|||
@app.get("/") |
|||
def read_root(): |
|||
return {"Hello": "World"} |
|||
|
|||
|
|||
@app.get("/items/{item_id}") |
|||
def read_item(item_id: int, q: Optional[str] = None): |
|||
return {"item_id": item_id, "q": q} |
|||
|
|||
|
|||
@app.put("/items/{item_id}") |
|||
def update_item(item_id: int, item: Item): |
|||
return {"item_name": item.name, "item_id": item_id} |
|||
``` |
|||
|
|||
The server should reload automatically (because you added `--reload` to the `uvicorn` command above). |
|||
|
|||
### Interactive API docs upgrade |
|||
|
|||
Now go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
* The interactive API documentation will be automatically updated, including the new body: |
|||
|
|||
 |
|||
|
|||
* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: |
|||
|
|||
 |
|||
|
|||
* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: |
|||
|
|||
 |
|||
|
|||
### Alternative API docs upgrade |
|||
|
|||
And now, go to <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. |
|||
|
|||
* The alternative documentation will also reflect the new query parameter and body: |
|||
|
|||
 |
|||
|
|||
### Recap |
|||
|
|||
In summary, you declare **once** the types of parameters, body, etc. as function parameters. |
|||
|
|||
You do that with standard modern Python types. |
|||
|
|||
You don't have to learn a new syntax, the methods or classes of a specific library, etc. |
|||
|
|||
Just standard **Python 3.6+**. |
|||
|
|||
For example, for an `int`: |
|||
|
|||
```Python |
|||
item_id: int |
|||
``` |
|||
|
|||
or for a more complex `Item` model: |
|||
|
|||
```Python |
|||
item: Item |
|||
``` |
|||
|
|||
...and with that single declaration you get: |
|||
|
|||
* Editor support, including: |
|||
* Completion. |
|||
* Type checks. |
|||
* Validation of data: |
|||
* Automatic and clear errors when the data is invalid. |
|||
* Validation even for deeply nested JSON objects. |
|||
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network to Python data and types. Reading from: |
|||
* JSON. |
|||
* Path parameters. |
|||
* Query parameters. |
|||
* Cookies. |
|||
* Headers. |
|||
* Forms. |
|||
* Files. |
|||
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of output data: converting from Python data and types to network data (as JSON): |
|||
* Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). |
|||
* `datetime` objects. |
|||
* `UUID` objects. |
|||
* Database models. |
|||
* ...and many more. |
|||
* Automatic interactive API documentation, including 2 alternative user interfaces: |
|||
* Swagger UI. |
|||
* ReDoc. |
|||
|
|||
--- |
|||
|
|||
Coming back to the previous code example, **FastAPI** will: |
|||
|
|||
* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. |
|||
* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. |
|||
* If it is not, the client will see a useful, clear error. |
|||
* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. |
|||
* As the `q` parameter is declared with `= None`, it is optional. |
|||
* Without the `None` it would be required (as is the body in the case with `PUT`). |
|||
* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: |
|||
* Check that it has a required attribute `name` that should be a `str`. |
|||
* Check that it has a required attribute `price` that has to be a `float`. |
|||
* Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. |
|||
* All this would also work for deeply nested JSON objects. |
|||
* Convert from and to JSON automatically. |
|||
* Document everything with OpenAPI, that can be used by: |
|||
* Interactive documentation systems. |
|||
* Automatic client code generation systems, for many languages. |
|||
* Provide 2 interactive documentation web interfaces directly. |
|||
|
|||
--- |
|||
|
|||
We just scratched the surface, but you already get the idea of how it all works. |
|||
|
|||
Try changing the line with: |
|||
|
|||
```Python |
|||
return {"item_name": item.name, "item_id": item_id} |
|||
``` |
|||
|
|||
...from: |
|||
|
|||
```Python |
|||
... "item_name": item.name ... |
|||
``` |
|||
|
|||
...to: |
|||
|
|||
```Python |
|||
... "item_price": item.price ... |
|||
``` |
|||
|
|||
...and see how your editor will auto-complete the attributes and know their types: |
|||
|
|||
 |
|||
|
|||
For a more complete example including more features, see the <a href="https://fastapi.tiangolo.com/tutorial/">Tutorial - User Guide</a>. |
|||
|
|||
**Spoiler alert**: the tutorial - user guide includes: |
|||
|
|||
* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. |
|||
* How to set **validation constraints** as `maximum_length` or `regex`. |
|||
* A very powerful and easy to use **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system. |
|||
* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. |
|||
* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). |
|||
* Many extra features (thanks to Starlette) as: |
|||
* **WebSockets** |
|||
* **GraphQL** |
|||
* extremely easy tests based on `requests` and `pytest` |
|||
* **CORS** |
|||
* **Cookie Sessions** |
|||
* ...and more. |
|||
|
|||
## Performance |
|||
|
|||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) |
|||
|
|||
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>. |
|||
|
|||
## Optional Dependencies |
|||
|
|||
Used by Pydantic: |
|||
|
|||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - for faster JSON <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>. |
|||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - for email validation. |
|||
|
|||
Used by Starlette: |
|||
|
|||
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Required if you want to use the `TestClient`. |
|||
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Required if you want to use the default template configuration. |
|||
* <a href="https://andrew-d.github.io/python-multipart/" target="_blank"><code>python-multipart</code></a> - Required if you want to support form <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>, with `request.form()`. |
|||
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - Required for `SessionMiddleware` support. |
|||
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). |
|||
* <a href="https://graphene-python.org/" target="_blank"><code>graphene</code></a> - Required for `GraphQLApp` support. |
|||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Required if you want to use `UJSONResponse`. |
|||
|
|||
Used by FastAPI / Starlette: |
|||
|
|||
* <a href="https://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> - for the server that loads and serves your application. |
|||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Required if you want to use `ORJSONResponse`. |
|||
|
|||
You can install all of these with `pip install fastapi[all]`. |
|||
|
|||
## License |
|||
|
|||
This project is licensed under the terms of the MIT license. |
@ -0,0 +1,240 @@ |
|||
# Réponses supplémentaires dans OpenAPI |
|||
|
|||
!!! Attention |
|||
Ceci concerne un sujet plutôt avancé. |
|||
|
|||
Si vous débutez avec **FastAPI**, vous n'en aurez peut-être pas besoin. |
|||
|
|||
Vous pouvez déclarer des réponses supplémentaires, avec des codes HTTP, des types de médias, des descriptions, etc. |
|||
|
|||
Ces réponses supplémentaires seront incluses dans le schéma OpenAPI, elles apparaîtront donc également dans la documentation de l'API. |
|||
|
|||
Mais pour ces réponses supplémentaires, vous devez vous assurer de renvoyer directement une `Response` comme `JSONResponse`, avec votre code HTTP et votre contenu. |
|||
|
|||
## Réponse supplémentaire avec `model` |
|||
|
|||
Vous pouvez ajouter à votre décorateur de *paramètre de chemin* un paramètre `responses`. |
|||
|
|||
Il prend comme valeur un `dict` dont les clés sont des codes HTTP pour chaque réponse, comme `200`, et la valeur de ces clés sont d'autres `dict` avec des informations pour chacun d'eux. |
|||
|
|||
Chacun de ces `dict` de réponse peut avoir une clé `model`, contenant un modèle Pydantic, tout comme `response_model`. |
|||
|
|||
**FastAPI** prendra ce modèle, générera son schéma JSON et l'inclura au bon endroit dans OpenAPI. |
|||
|
|||
Par exemple, pour déclarer une autre réponse avec un code HTTP `404` et un modèle Pydantic `Message`, vous pouvez écrire : |
|||
|
|||
```Python hl_lines="18 22" |
|||
{!../../../docs_src/additional_responses/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! Remarque |
|||
Gardez à l'esprit que vous devez renvoyer directement `JSONResponse`. |
|||
|
|||
!!! Info |
|||
La clé `model` ne fait pas partie d'OpenAPI. |
|||
|
|||
**FastAPI** prendra le modèle Pydantic à partir de là, générera le `JSON Schema` et le placera au bon endroit. |
|||
|
|||
Le bon endroit est : |
|||
|
|||
* Dans la clé `content`, qui a pour valeur un autre objet JSON (`dict`) qui contient : |
|||
* Une clé avec le type de support, par ex. `application/json`, qui contient comme valeur un autre objet JSON, qui contient : |
|||
* Une clé `schema`, qui a pour valeur le schéma JSON du modèle, voici le bon endroit. |
|||
* **FastAPI** ajoute ici une référence aux schémas JSON globaux à un autre endroit de votre OpenAPI au lieu de l'inclure directement. De cette façon, d'autres applications et clients peuvent utiliser ces schémas JSON directement, fournir de meilleurs outils de génération de code, etc. |
|||
|
|||
Les réponses générées au format OpenAPI pour cette *opération de chemin* seront : |
|||
|
|||
```JSON hl_lines="3-12" |
|||
{ |
|||
"responses": { |
|||
"404": { |
|||
"description": "Additional Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/Message" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/Item" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Les schémas sont référencés à un autre endroit du modèle OpenAPI : |
|||
|
|||
```JSON hl_lines="4-16" |
|||
{ |
|||
"components": { |
|||
"schemas": { |
|||
"Message": { |
|||
"title": "Message", |
|||
"required": [ |
|||
"message" |
|||
], |
|||
"type": "object", |
|||
"properties": { |
|||
"message": { |
|||
"title": "Message", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"Item": { |
|||
"title": "Item", |
|||
"required": [ |
|||
"id", |
|||
"value" |
|||
], |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"title": "Id", |
|||
"type": "string" |
|||
}, |
|||
"value": { |
|||
"title": "Value", |
|||
"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" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Types de médias supplémentaires pour la réponse principale |
|||
|
|||
Vous pouvez utiliser ce même paramètre `responses` pour ajouter différents types de médias pour la même réponse principale. |
|||
|
|||
Par exemple, vous pouvez ajouter un type de média supplémentaire `image/png`, en déclarant que votre *opération de chemin* peut renvoyer un objet JSON (avec le type de média `application/json`) ou une image PNG : |
|||
|
|||
```Python hl_lines="19-24 28" |
|||
{!../../../docs_src/additional_responses/tutorial002.py!} |
|||
``` |
|||
|
|||
!!! Remarque |
|||
Notez que vous devez retourner l'image en utilisant directement un `FileResponse`. |
|||
|
|||
!!! Info |
|||
À moins que vous ne spécifiiez explicitement un type de média différent dans votre paramètre `responses`, FastAPI supposera que la réponse a le même type de média que la classe de réponse principale (par défaut `application/json`). |
|||
|
|||
Mais si vous avez spécifié une classe de réponse personnalisée avec `None` comme type de média, FastAPI utilisera `application/json` pour toute réponse supplémentaire associée à un modèle. |
|||
|
|||
## Combinaison d'informations |
|||
|
|||
Vous pouvez également combiner des informations de réponse provenant de plusieurs endroits, y compris les paramètres `response_model`, `status_code` et `responses`. |
|||
|
|||
Vous pouvez déclarer un `response_model`, en utilisant le code HTTP par défaut `200` (ou un code personnalisé si vous en avez besoin), puis déclarer des informations supplémentaires pour cette même réponse dans `responses`, directement dans le schéma OpenAPI. |
|||
|
|||
**FastAPI** conservera les informations supplémentaires des `responses` et les combinera avec le schéma JSON de votre modèle. |
|||
|
|||
Par exemple, vous pouvez déclarer une réponse avec un code HTTP `404` qui utilise un modèle Pydantic et a une `description` personnalisée. |
|||
|
|||
Et une réponse avec un code HTTP `200` qui utilise votre `response_model`, mais inclut un `example` personnalisé : |
|||
|
|||
```Python hl_lines="20-31" |
|||
{!../../../docs_src/additional_responses/tutorial003.py!} |
|||
``` |
|||
|
|||
Tout sera combiné et inclus dans votre OpenAPI, et affiché dans la documentation de l'API : |
|||
|
|||
<img src="/img/tutorial/additional-responses/image01.png"> |
|||
|
|||
## Combinez les réponses prédéfinies et les réponses personnalisées |
|||
|
|||
Vous voulez peut-être avoir des réponses prédéfinies qui s'appliquent à de nombreux *paramètre de chemin*, mais vous souhaitez les combiner avec des réponses personnalisées nécessaires à chaque *opération de chemin*. |
|||
|
|||
Dans ces cas, vous pouvez utiliser la technique Python "d'affection par décomposition" (appelé _unpacking_ en anglais) d'un `dict` avec `**dict_to_unpack` : |
|||
|
|||
``` Python |
|||
old_dict = { |
|||
"old key": "old value", |
|||
"second old key": "second old value", |
|||
} |
|||
new_dict = {**old_dict, "new key": "new value"} |
|||
``` |
|||
|
|||
Ici, `new_dict` contiendra toutes les paires clé-valeur de `old_dict` plus la nouvelle paire clé-valeur : |
|||
|
|||
``` Python |
|||
{ |
|||
"old key": "old value", |
|||
"second old key": "second old value", |
|||
"new key": "new value", |
|||
} |
|||
``` |
|||
|
|||
Vous pouvez utiliser cette technique pour réutiliser certaines réponses prédéfinies dans vos *paramètres de chemin* et les combiner avec des réponses personnalisées supplémentaires. |
|||
|
|||
Par exemple: |
|||
|
|||
```Python hl_lines="13-17 26" |
|||
{!../../../docs_src/additional_responses/tutorial004.py!} |
|||
``` |
|||
|
|||
## Plus d'informations sur les réponses OpenAPI |
|||
|
|||
Pour voir exactement ce que vous pouvez inclure dans les réponses, vous pouvez consulter ces sections dans la spécification OpenAPI : |
|||
|
|||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject" class="external-link" target="_blank">Objet Responses de OpenAPI </a>, il inclut le `Response Object`. |
|||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject" class="external-link" target="_blank">Objet Response de OpenAPI </a>, vous pouvez inclure n'importe quoi directement dans chaque réponse à l'intérieur de votre paramètre `responses`. Y compris `description`, `headers`, `content` (à l'intérieur de cela, vous déclarez différents types de médias et schémas JSON) et `links`. |
@ -0,0 +1,37 @@ |
|||
# Codes HTTP supplémentaires |
|||
|
|||
Par défaut, **FastAPI** renverra les réponses à l'aide d'une structure de données `JSONResponse`, en plaçant la réponse de votre *chemin d'accès* à l'intérieur de cette `JSONResponse`. |
|||
|
|||
Il utilisera le code HTTP par défaut ou celui que vous avez défini dans votre *chemin d'accès*. |
|||
|
|||
## Codes HTTP supplémentaires |
|||
|
|||
Si vous souhaitez renvoyer des codes HTTP supplémentaires en plus du code principal, vous pouvez le faire en renvoyant directement une `Response`, comme une `JSONResponse`, et en définissant directement le code HTTP supplémentaire. |
|||
|
|||
Par exemple, disons que vous voulez avoir un *chemin d'accès* qui permet de mettre à jour les éléments et renvoie les codes HTTP 200 "OK" en cas de succès. |
|||
|
|||
Mais vous voulez aussi qu'il accepte de nouveaux éléments. Et lorsque les éléments n'existaient pas auparavant, il les crée et renvoie un code HTTP de 201 "Créé". |
|||
|
|||
Pour y parvenir, importez `JSONResponse` et renvoyez-y directement votre contenu, en définissant le `status_code` que vous souhaitez : |
|||
|
|||
```Python hl_lines="4 25" |
|||
{!../../../docs_src/additional_status_codes/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! Attention |
|||
Lorsque vous renvoyez une `Response` directement, comme dans l'exemple ci-dessus, elle sera renvoyée directement. |
|||
|
|||
Elle ne sera pas sérialisée avec un modèle. |
|||
|
|||
Assurez-vous qu'il contient les données souhaitées et que les valeurs soient dans un format JSON valides (si vous utilisez une `JSONResponse`). |
|||
|
|||
!!! note "Détails techniques" |
|||
Vous pouvez également utiliser `from starlette.responses import JSONResponse`. |
|||
|
|||
Pour plus de commodités, **FastAPI** fournit les objets `starlette.responses` sous forme d'un alias accessible par `fastapi.responses`. Mais la plupart des réponses disponibles proviennent directement de Starlette. Il en est de même avec l'objet `statut`. |
|||
|
|||
## Documents OpenAPI et API |
|||
|
|||
Si vous renvoyez directement des codes HTTP et des réponses supplémentaires, ils ne seront pas inclus dans le schéma OpenAPI (la documentation de l'API), car FastAPI n'a aucun moyen de savoir à l'avance ce que vous allez renvoyer. |
|||
|
|||
Mais vous pouvez documenter cela dans votre code, en utilisant : [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. |
@ -0,0 +1,245 @@ |
|||
# Déployer FastAPI sur Deta |
|||
|
|||
Dans cette section, vous apprendrez à déployer facilement une application **FastAPI** sur <a href="https://www.deta. |
|||
sh/?ref=fastapi" class="external-link" target="_blank">Deta</a> en utilisant le plan tarifaire gratuit. 🎁 |
|||
|
|||
Cela vous prendra environ **10 minutes**. |
|||
|
|||
!!! info |
|||
<a href="https://www.deta.sh/?ref=fastapi" class="external-link" target="_blank">Deta</a> sponsorise **FastAPI**. 🎉 |
|||
|
|||
## Une application **FastAPI** de base |
|||
|
|||
* Créez un répertoire pour votre application, par exemple `./fastapideta/` et déplacez-vous dedans. |
|||
|
|||
### Le code FastAPI |
|||
|
|||
* Créer un fichier `main.py` avec : |
|||
|
|||
```Python |
|||
from fastapi import FastAPI |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
@app.get("/") |
|||
def read_root(): |
|||
return {"Hello": "World"} |
|||
|
|||
|
|||
@app.get("/items/{item_id}") |
|||
def read_item(item_id: int): |
|||
return {"item_id": item_id} |
|||
``` |
|||
|
|||
### Dépendances |
|||
|
|||
Maintenant, dans le même répertoire, créez un fichier `requirements.txt` avec : |
|||
|
|||
```text |
|||
fastapi |
|||
``` |
|||
|
|||
!!! tip "Astuce" |
|||
Il n'est pas nécessaire d'installer Uvicorn pour déployer sur Deta, bien qu'il soit probablement souhaitable de l'installer localement pour tester votre application. |
|||
|
|||
### Structure du répertoire |
|||
|
|||
Vous aurez maintenant un répertoire `./fastapideta/` avec deux fichiers : |
|||
|
|||
``` |
|||
. |
|||
└── main.py |
|||
└── requirements.txt |
|||
``` |
|||
|
|||
## Créer un compte gratuit sur Deta |
|||
|
|||
Créez maintenant un <a href="https://www.deta.sh/?ref=fastapi" class="external-link" target="_blank">compte gratuit |
|||
sur Deta</a>, vous avez juste besoin d'une adresse email et d'un mot de passe. |
|||
|
|||
Vous n'avez même pas besoin d'une carte de crédit. |
|||
|
|||
## Installer le CLI (Interface en Ligne de Commande) |
|||
|
|||
Une fois que vous avez votre compte, installez le <abbr title="Command Line Interface application">CLI</abbr> de Deta : |
|||
|
|||
=== "Linux, macOS" |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ curl -fsSL https://get.deta.dev/cli.sh | sh |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
=== "Windows PowerShell" |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ iwr https://get.deta.dev/cli.ps1 -useb | iex |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Après l'avoir installé, ouvrez un nouveau terminal afin que la nouvelle installation soit détectée. |
|||
|
|||
Dans un nouveau terminal, confirmez qu'il a été correctement installé avec : |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ deta --help |
|||
|
|||
Deta command line interface for managing deta micros. |
|||
Complete documentation available at https://docs.deta.sh |
|||
|
|||
Usage: |
|||
deta [flags] |
|||
deta [command] |
|||
|
|||
Available Commands: |
|||
auth Change auth settings for a deta micro |
|||
|
|||
... |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! tip "Astuce" |
|||
Si vous rencontrez des problèmes pour installer le CLI, consultez la <a href="https://docs.deta. sh/docs/micros/getting_started?ref=fastapi" class="external-link" target="_blank">documentation officielle de Deta (en anglais)</a>. |
|||
|
|||
## Connexion avec le CLI |
|||
|
|||
Maintenant, connectez-vous à Deta depuis le CLI avec : |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ deta login |
|||
|
|||
Please, log in from the web page. Waiting.. |
|||
Logged in successfully. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Cela ouvrira un navigateur web et permettra une authentification automatique. |
|||
|
|||
## Déployer avec Deta |
|||
|
|||
Ensuite, déployez votre application avec le CLI de Deta : |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ deta new |
|||
|
|||
Successfully created a new micro |
|||
|
|||
// Notice the "endpoint" 🔍 |
|||
|
|||
{ |
|||
"name": "fastapideta", |
|||
"runtime": "python3.7", |
|||
"endpoint": "https://qltnci.deta.dev", |
|||
"visor": "enabled", |
|||
"http_auth": "enabled" |
|||
} |
|||
|
|||
Adding dependencies... |
|||
|
|||
|
|||
---> 100% |
|||
|
|||
|
|||
Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Vous verrez un message JSON similaire à : |
|||
|
|||
```JSON hl_lines="4" |
|||
{ |
|||
"name": "fastapideta", |
|||
"runtime": "python3.7", |
|||
"endpoint": "https://qltnci.deta.dev", |
|||
"visor": "enabled", |
|||
"http_auth": "enabled" |
|||
} |
|||
``` |
|||
|
|||
!!! tip "Astuce" |
|||
Votre déploiement aura une URL `"endpoint"` différente. |
|||
|
|||
## Vérifiez |
|||
|
|||
Maintenant, dans votre navigateur ouvrez votre URL `endpoint`. Dans l'exemple ci-dessus, c'était |
|||
`https://qltnci.deta.dev`, mais la vôtre sera différente. |
|||
|
|||
Vous verrez la réponse JSON de votre application FastAPI : |
|||
|
|||
```JSON |
|||
{ |
|||
"Hello": "World" |
|||
} |
|||
``` |
|||
|
|||
Et maintenant naviguez vers `/docs` dans votre API, dans l'exemple ci-dessus ce serait `https://qltnci.deta.dev/docs`. |
|||
|
|||
Vous verrez votre documentation comme suit : |
|||
|
|||
<img src="/img/deployment/deta/image01.png"> |
|||
|
|||
## Activer l'accès public |
|||
|
|||
Par défaut, Deta va gérer l'authentification en utilisant des cookies pour votre compte. |
|||
|
|||
Mais une fois que vous êtes prêt, vous pouvez le rendre public avec : |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ deta auth disable |
|||
|
|||
Successfully disabled http auth |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Maintenant, vous pouvez partager cette URL avec n'importe qui et ils seront en mesure d'accéder à votre API. 🚀 |
|||
|
|||
## HTTPS |
|||
|
|||
Félicitations ! Vous avez déployé votre application FastAPI sur Deta ! 🎉 🍰 |
|||
|
|||
Remarquez également que Deta gère correctement HTTPS pour vous, vous n'avez donc pas à vous en occuper et pouvez être sûr que vos clients auront une connexion cryptée sécurisée. ✅ 🔒 |
|||
|
|||
## Vérifiez le Visor |
|||
|
|||
À partir de l'interface graphique de votre documentation (dans une URL telle que `https://qltnci.deta.dev/docs`) |
|||
envoyez une requête à votre *opération de chemin* `/items/{item_id}`. |
|||
|
|||
Par exemple avec l'ID `5`. |
|||
|
|||
Allez maintenant sur <a href="https://web.deta.sh/" class="external-link" target="_blank">https://web.deta.sh</a>. |
|||
|
|||
Vous verrez qu'il y a une section à gauche appelée <abbr title="ça vient de Micro(server)">"Micros"</abbr> avec chacune de vos applications. |
|||
|
|||
Vous verrez un onglet avec "Details", et aussi un onglet "Visor", allez à l'onglet "Visor". |
|||
|
|||
Vous pouvez y consulter les requêtes récentes envoyées à votre application. |
|||
|
|||
Vous pouvez également les modifier et les relancer. |
|||
|
|||
<img src="/img/deployment/deta/image02.png"> |
|||
|
|||
## En savoir plus |
|||
|
|||
À un moment donné, vous voudrez probablement stocker certaines données pour votre application d'une manière qui |
|||
persiste dans le temps. Pour cela, vous pouvez utiliser <a href="https://docs.deta.sh/docs/base/py_tutorial?ref=fastapi" class="external-link" target="_blank">Deta Base</a>, il dispose également d'un généreux **plan gratuit**. |
|||
|
|||
Vous pouvez également en lire plus dans la <a href="https://docs.deta.sh?ref=fastapi" class="external-link" target="_blank">documentation Deta</a>. |
@ -0,0 +1,53 @@ |
|||
# À propos de HTTPS |
|||
|
|||
Il est facile de penser que HTTPS peut simplement être "activé" ou non. |
|||
|
|||
Mais c'est beaucoup plus complexe que cela. |
|||
|
|||
!!! tip |
|||
Si vous êtes pressé ou si cela ne vous intéresse pas, passez aux sections suivantes pour obtenir des instructions étape par étape afin de tout configurer avec différentes techniques. |
|||
|
|||
Pour apprendre les bases du HTTPS, du point de vue d'un utilisateur, consultez <a href="https://howhttps.works/" |
|||
class="external-link" target="_blank">https://howhttps.works/</a>. |
|||
|
|||
Maintenant, du point de vue d'un développeur, voici plusieurs choses à avoir en tête en pensant au HTTPS : |
|||
|
|||
* Pour le HTTPS, le serveur a besoin de "certificats" générés par une tierce partie. |
|||
* Ces certificats sont en fait acquis auprès de la tierce partie, et non "générés". |
|||
* Les certificats ont une durée de vie. |
|||
* Ils expirent. |
|||
* Puis ils doivent être renouvelés et acquis à nouveau auprès de la tierce partie. |
|||
* Le cryptage de la connexion se fait au niveau du protocole TCP. |
|||
* C'est une couche en dessous de HTTP. |
|||
* Donc, le certificat et le traitement du cryptage sont faits avant HTTP. |
|||
* TCP ne connaît pas les "domaines", seulement les adresses IP. |
|||
* L'information sur le domaine spécifique demandé se trouve dans les données HTTP. |
|||
* Les certificats HTTPS "certifient" un certain domaine, mais le protocole et le cryptage se font au niveau TCP, avant de savoir quel domaine est traité. |
|||
* Par défaut, cela signifie que vous ne pouvez avoir qu'un seul certificat HTTPS par adresse IP. |
|||
* Quelle que soit la taille de votre serveur ou la taille de chacune des applications qu'il contient. |
|||
* Il existe cependant une solution à ce problème. |
|||
* Il existe une extension du protocole TLS (celui qui gère le cryptage au niveau TCP, avant HTTP) appelée <a |
|||
href="https://fr.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr |
|||
title="Server Name Indication (indication du nom du serveur)">SNI (indication du nom du serveur)</abbr></a>. |
|||
* Cette extension SNI permet à un seul serveur (avec une seule adresse IP) d'avoir plusieurs certificats HTTPS et de servir plusieurs domaines/applications HTTPS. |
|||
* Pour que cela fonctionne, un seul composant (programme) fonctionnant sur le serveur, écoutant sur l'adresse IP publique, doit avoir tous les certificats HTTPS du serveur. |
|||
* Après avoir obtenu une connexion sécurisée, le protocole de communication est toujours HTTP. |
|||
* Le contenu est crypté, même s'il est envoyé avec le protocole HTTP. |
|||
|
|||
Il est courant d'avoir un seul programme/serveur HTTP fonctionnant sur le serveur (la machine, l'hôte, etc.) et |
|||
gérant toutes les parties HTTPS : envoyer les requêtes HTTP décryptées à l'application HTTP réelle fonctionnant sur |
|||
le même serveur (dans ce cas, l'application **FastAPI**), prendre la réponse HTTP de l'application, la crypter en utilisant le certificat approprié et la renvoyer au client en utilisant HTTPS. Ce serveur est souvent appelé un <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">Proxy de terminaison TLS</a>. |
|||
|
|||
## Let's Encrypt |
|||
|
|||
Avant Let's Encrypt, ces certificats HTTPS étaient vendus par des tiers de confiance. |
|||
|
|||
Le processus d'acquisition d'un de ces certificats était auparavant lourd, nécessitait pas mal de paperasses et les certificats étaient assez chers. |
|||
|
|||
Mais ensuite, <a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a> a été créé. |
|||
|
|||
Il s'agit d'un projet de la Fondation Linux. Il fournit des certificats HTTPS gratuitement. De manière automatisée. Ces certificats utilisent toutes les sécurités cryptographiques standard et ont une durée de vie courte (environ 3 mois), de sorte que la sécurité est en fait meilleure en raison de leur durée de vie réduite. |
|||
|
|||
Les domaines sont vérifiés de manière sécurisée et les certificats sont générés automatiquement. Cela permet également d'automatiser le renouvellement de ces certificats. |
|||
|
|||
L'idée est d'automatiser l'acquisition et le renouvellement de ces certificats, afin que vous puissiez disposer d'un HTTPS sécurisé, gratuitement et pour toujours. |
@ -0,0 +1,95 @@ |
|||
# À propos des versions de FastAPI |
|||
|
|||
**FastAPI** est déjà utilisé en production dans de nombreuses applications et systèmes. Et la couverture de test est maintenue à 100 %. Mais son développement est toujours aussi rapide. |
|||
|
|||
De nouvelles fonctionnalités sont ajoutées fréquemment, des bogues sont corrigés régulièrement et le code est |
|||
amélioré continuellement. |
|||
|
|||
C'est pourquoi les versions actuelles sont toujours `0.x.x`, cela reflète que chaque version peut potentiellement |
|||
recevoir des changements non rétrocompatibles. Cela suit les conventions de <a href="https://semver.org/" class="external-link" |
|||
target="_blank">versionnage sémantique</a>. |
|||
|
|||
Vous pouvez créer des applications de production avec **FastAPI** dès maintenant (et vous le faites probablement depuis un certain temps), vous devez juste vous assurer que vous utilisez une version qui fonctionne correctement avec le reste de votre code. |
|||
|
|||
## Épinglez votre version de `fastapi` |
|||
|
|||
Tout d'abord il faut "épingler" la version de **FastAPI** que vous utilisez à la dernière version dont vous savez |
|||
qu'elle fonctionne correctement pour votre application. |
|||
|
|||
Par exemple, disons que vous utilisez la version `0.45.0` dans votre application. |
|||
|
|||
Si vous utilisez un fichier `requirements.txt`, vous pouvez spécifier la version avec : |
|||
|
|||
```txt |
|||
fastapi==0.45.0 |
|||
``` |
|||
|
|||
ce qui signifierait que vous utiliseriez exactement la version `0.45.0`. |
|||
|
|||
Ou vous pourriez aussi l'épingler avec : |
|||
|
|||
```txt |
|||
fastapi>=0.45.0,<0.46.0 |
|||
``` |
|||
|
|||
cela signifierait que vous utiliseriez les versions `0.45.0` ou supérieures, mais inférieures à `0.46.0`, par exemple, une version `0.45.2` serait toujours acceptée. |
|||
|
|||
Si vous utilisez un autre outil pour gérer vos installations, comme Poetry, Pipenv, ou autres, ils ont tous un moyen que vous pouvez utiliser pour définir des versions spécifiques pour vos paquets. |
|||
|
|||
## Versions disponibles |
|||
|
|||
Vous pouvez consulter les versions disponibles (par exemple, pour vérifier quelle est la dernière version en date) dans les [Notes de version](../release-notes.md){.internal-link target=_blank}. |
|||
|
|||
## À propos des versions |
|||
|
|||
Suivant les conventions de versionnage sémantique, toute version inférieure à `1.0.0` peut potentiellement ajouter |
|||
des changements non rétrocompatibles. |
|||
|
|||
FastAPI suit également la convention que tout changement de version "PATCH" est pour des corrections de bogues et |
|||
des changements rétrocompatibles. |
|||
|
|||
!!! tip "Astuce" |
|||
Le "PATCH" est le dernier chiffre, par exemple, dans `0.2.3`, la version PATCH est `3`. |
|||
|
|||
Donc, vous devriez être capable d'épingler une version comme suit : |
|||
|
|||
```txt |
|||
fastapi>=0.45.0,<0.46.0 |
|||
``` |
|||
|
|||
Les changements non rétrocompatibles et les nouvelles fonctionnalités sont ajoutés dans les versions "MINOR". |
|||
|
|||
!!! tip "Astuce" |
|||
Le "MINOR" est le numéro au milieu, par exemple, dans `0.2.3`, la version MINOR est `2`. |
|||
|
|||
## Mise à jour des versions FastAPI |
|||
|
|||
Vous devriez tester votre application. |
|||
|
|||
Avec **FastAPI** c'est très facile (merci à Starlette), consultez la documentation : [Testing](../tutorial/testing.md){.internal-link target=_blank} |
|||
|
|||
Après avoir effectué des tests, vous pouvez mettre à jour la version **FastAPI** vers une version plus récente, et vous assurer que tout votre code fonctionne correctement en exécutant vos tests. |
|||
|
|||
Si tout fonctionne, ou après avoir fait les changements nécessaires, et que tous vos tests passent, vous pouvez |
|||
épingler votre version de `fastapi` à cette nouvelle version récente. |
|||
|
|||
## À propos de Starlette |
|||
|
|||
Vous ne devriez pas épingler la version de `starlette`. |
|||
|
|||
Différentes versions de **FastAPI** utiliseront une version spécifique plus récente de Starlette. |
|||
|
|||
Ainsi, vous pouvez simplement laisser **FastAPI** utiliser la bonne version de Starlette. |
|||
|
|||
## À propos de Pydantic |
|||
|
|||
Pydantic inclut des tests pour **FastAPI** avec ses propres tests, ainsi les nouvelles versions de Pydantic (au-dessus |
|||
de `1.0.0`) sont toujours compatibles avec **FastAPI**. |
|||
|
|||
Vous pouvez épingler Pydantic à toute version supérieure à `1.0.0` qui fonctionne pour vous et inférieure à `2.0.0`. |
|||
|
|||
Par exemple : |
|||
|
|||
```txt |
|||
pydantic>=1.2.0,<2.0.0 |
|||
``` |
@ -0,0 +1,122 @@ |
|||
# Help FastAPI - Obtenir de l'aide |
|||
|
|||
Aimez-vous **FastAPI** ? |
|||
|
|||
Vous souhaitez aider FastAPI, les autres utilisateurs et l'auteur ? |
|||
|
|||
Ou souhaitez-vous obtenir de l'aide avec le **FastAPI** ? |
|||
|
|||
Il existe des moyens très simples d'aider (plusieurs ne nécessitent qu'un ou deux clics). |
|||
|
|||
Il existe également plusieurs façons d'obtenir de l'aide. |
|||
|
|||
## Star **FastAPI** sur GitHub |
|||
|
|||
Vous pouvez "star" FastAPI dans GitHub (en cliquant sur le bouton étoile en haut à droite) : <a href="https://github.com/tiangolo/fastapi" class="external-link" target="_blank">https://github.com/tiangolo/fastapi</a>. ⭐️ |
|||
|
|||
En ajoutant une étoile, les autres utilisateurs pourront la trouver plus facilement et constater qu'elle a déjà été utile à d'autres. |
|||
|
|||
## Watch le dépôt GitHub pour les releases |
|||
|
|||
Vous pouvez "watch" FastAPI dans GitHub (en cliquant sur le bouton "watch" en haut à droite) : <a href="https://github.com/tiangolo/fastapi" class="external-link" target="_blank">https://github.com/tiangolo/fastapi</a>. 👀 |
|||
|
|||
Vous pouvez y sélectionner "Releases only". |
|||
|
|||
Ainsi, vous recevrez des notifications (dans votre courrier électronique) chaque fois qu'il y aura une nouvelle version de **FastAPI** avec des corrections de bugs et de nouvelles fonctionnalités. |
|||
|
|||
## Se rapprocher de l'auteur |
|||
|
|||
Vous pouvez vous rapprocher de <a href="https://tiangolo.com" class="external-link" target="_blank">moi (Sebastián Ramírez / `tiangolo`)</a>, l'auteur. |
|||
|
|||
Vous pouvez : |
|||
|
|||
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">Me suivre sur **GitHub**</a>. |
|||
* Voir d'autres projets Open Source que j'ai créés et qui pourraient vous aider. |
|||
* Suivez-moi pour voir quand je crée un nouveau projet Open Source. |
|||
* <a href="https://twitter.com/tiangolo" class="external-link" target="_blank">Me suivre sur **Twitter**</a>. |
|||
* Dites-moi comment vous utilisez FastAPI (j'adore entendre ça). |
|||
* Entendre quand je fais des annonces ou que je lance de nouveaux outils. |
|||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">Vous connectez à moi sur **Linkedin**</a>. |
|||
* Etre notifié quand je fais des annonces ou que je lance de nouveaux outils (bien que j'utilise plus souvent Twitte 🤷♂). |
|||
* Lire ce que j’écris (ou me suivre) sur <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> ou <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>. |
|||
* Lire d'autres idées, articles, et sur les outils que j'ai créés. |
|||
* Suivez-moi pour lire quand je publie quelque chose de nouveau. |
|||
|
|||
## Tweeter sur **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">Tweetez à propos de **FastAPI**</a> et faites-moi savoir, ainsi qu'aux autres, pourquoi vous aimez ça. 🎉 |
|||
|
|||
J'aime entendre parler de l'utilisation du **FastAPI**, de ce que vous avez aimé dedans, dans quel projet/entreprise l'utilisez-vous, etc. |
|||
|
|||
## Voter pour FastAPI |
|||
|
|||
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">Votez pour **FastAPI** sur Slant</a>. |
|||
* <a href="https://alternativeto.net/software/fastapi/" class="external-link" target="_blank">Votez pour **FastAPI** sur AlternativeTo</a>. |
|||
* <a href="https://github.com/marmelab/awesome-rest/pull/93" class="external-link" target="_blank">Votez pour **FastAPI** sur awesome-rest</a>. |
|||
|
|||
## Aider les autres à résoudre les problèmes dans GitHub |
|||
|
|||
Vous pouvez voir <a href="https://github.com/tiangolo/fastapi/issues" class="external-link" target="_blank">les problèmes existants</a> et essayer d'aider les autres, la plupart du temps il s'agit de questions dont vous connaissez peut-être déjà la réponse. 🤓 |
|||
|
|||
## Watch le dépôt GitHub |
|||
|
|||
Vous pouvez "watch" FastAPI dans GitHub (en cliquant sur le bouton "watch" en haut à droite) : <a href="https://github.com/tiangolo/fastapi" class="external-link" target="_blank">https://github.com/tiangolo/fastapi</a>. 👀 |
|||
|
|||
Si vous sélectionnez "Watching" au lieu de "Releases only", vous recevrez des notifications lorsque quelqu'un crée une nouvelle Issue. |
|||
|
|||
Vous pouvez alors essayer de les aider à résoudre ces problèmes. |
|||
|
|||
## Créer une Issue |
|||
|
|||
Vous pouvez <a href="https://github.com/tiangolo/fastapi/issues/new/choose" class="external-link" target="_blank">créer une Issue</a> dans le dépôt GitHub, par exemple pour : |
|||
|
|||
* Poser une question ou s'informer sur un problème. |
|||
* Suggérer une nouvelle fonctionnalité. |
|||
|
|||
**Note** : si vous créez un problème, alors je vais vous demander d'aider aussi les autres. 😉 |
|||
|
|||
## Créer une Pull Request |
|||
|
|||
Vous pouvez <a href="https://github.com/tiangolo/fastapi" class="external-link" target="_blank">créer une Pull Request</a>, par exemple : |
|||
|
|||
* Pour corriger une faute de frappe que vous avez trouvée sur la documentation. |
|||
* Proposer de nouvelles sections de documentation. |
|||
* Pour corriger une Issue/Bug existant. |
|||
* Pour ajouter une nouvelle fonctionnalité. |
|||
|
|||
## Rejoindre le chat |
|||
|
|||
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank"> |
|||
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Rejoindre le chat à https://gitter.im/tiangolo/fastapi"> |
|||
</a> |
|||
|
|||
Rejoignez le chat sur Gitter: <a href="https://gitter.im/tiangolo/fastapi" class="external-link" target="_blank">https://gitter.im/tiangolo/fastapi</a>. |
|||
|
|||
Vous pouvez y avoir des conversations rapides avec d'autres personnes, aider les autres, partager des idées, etc. |
|||
|
|||
Mais gardez à l'esprit que, comme il permet une "conversation plus libre", il est facile de poser des questions trop générales et plus difficiles à répondre, de sorte que vous risquez de ne pas recevoir de réponses. |
|||
|
|||
Dans les Issues de GitHub, le modèle vous guidera pour écrire la bonne question afin que vous puissiez plus facilement obtenir une bonne réponse, ou même résoudre le problème vous-même avant même de le poser. Et dans GitHub, je peux m'assurer que je réponds toujours à tout, même si cela prend du temps. Je ne peux pas faire cela personnellement avec le chat Gitter. 😅 |
|||
|
|||
Les conversations dans Gitter ne sont pas non plus aussi facilement consultables que dans GitHub, de sorte que les questions et les réponses peuvent se perdre dans la conversation. |
|||
|
|||
De l'autre côté, il y a plus de 1000 personnes dans le chat, il y a donc de fortes chances que vous y trouviez quelqu'un à qui parler, presque tout le temps. 😄 |
|||
|
|||
## Parrainer l'auteur |
|||
|
|||
Vous pouvez également soutenir financièrement l'auteur (moi) via <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub sponsors</a>. |
|||
|
|||
Là, vous pourriez m'offrir un café ☕️ pour me remercier 😄. |
|||
|
|||
## Sponsoriser les outils qui font fonctionner FastAPI |
|||
|
|||
Comme vous l'avez vu dans la documentation, FastAPI se tient sur les épaules des géants, Starlette et Pydantic. |
|||
|
|||
Vous pouvez également parrainer : |
|||
|
|||
* <a href="https://github.com/sponsors/samuelcolvin" class="external-link" target="_blank">Samuel Colvin (Pydantic)</a> |
|||
* <a href="https://github.com/sponsors/encode" class="external-link" target="_blank">Encode (Starlette, Uvicorn)</a> |
|||
|
|||
--- |
|||
|
|||
Merci ! 🚀 |
@ -0,0 +1,186 @@ |
|||
# WebSocket |
|||
|
|||
**FastAPI**で<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSocket</a>が使用できます。 |
|||
|
|||
## `WebSockets`のインストール |
|||
|
|||
まず `WebSockets`のインストールが必要です。 |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install websockets |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## WebSocket クライアント |
|||
|
|||
### 本番環境 |
|||
|
|||
本番環境では、React、Vue.js、Angularなどの最新のフレームワークで作成されたフロントエンドを使用しているでしょう。 |
|||
|
|||
そして、バックエンドとWebSocketを使用して通信するために、おそらくフロントエンドのユーティリティを使用することになるでしょう。 |
|||
|
|||
または、ネイティブコードでWebSocketバックエンドと直接通信するネイティブモバイルアプリケーションがあるかもしれません。 |
|||
|
|||
他にも、WebSocketのエンドポイントと通信する方法があるかもしれません。 |
|||
|
|||
--- |
|||
|
|||
ただし、この例では非常にシンプルなHTML文書といくつかのJavaScriptを、すべてソースコードの中に入れて使用することにします。 |
|||
|
|||
もちろん、これは最適な方法ではありませんし、本番環境で使うことはないでしょう。 |
|||
|
|||
本番環境では、上記の方法のいずれかの選択肢を採用することになるでしょう。 |
|||
|
|||
しかし、これはWebSocketのサーバーサイドに焦点を当て、実用的な例を示す最も簡単な方法です。 |
|||
|
|||
```Python hl_lines="2 6-38 41-43" |
|||
{!../../../docs_src/websockets/tutorial001.py!} |
|||
``` |
|||
|
|||
## `websocket` を作成する |
|||
|
|||
**FastAPI** アプリケーションで、`websocket` を作成します。 |
|||
|
|||
```Python hl_lines="1 46-47" |
|||
{!../../../docs_src/websockets/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! note "技術詳細" |
|||
`from starlette.websockets import WebSocket` を使用しても構いません. |
|||
|
|||
**FastAPI** は開発者の利便性のために、同じ `WebSocket` を提供します。しかし、こちらはStarletteから直接提供されるものです。 |
|||
|
|||
## メッセージの送受信 |
|||
|
|||
WebSocketルートでは、 `await` を使ってメッセージの送受信ができます。 |
|||
|
|||
```Python hl_lines="48-52" |
|||
{!../../../docs_src/websockets/tutorial001.py!} |
|||
``` |
|||
|
|||
バイナリやテキストデータ、JSONデータを送受信できます。 |
|||
|
|||
## 試してみる |
|||
|
|||
ファイル名が `main.py` である場合、以下の方法でアプリケーションを実行します。 |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
ブラウザで <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a> を開きます。 |
|||
|
|||
次のようなシンプルなページが表示されます。 |
|||
|
|||
<img src="/img/tutorial/websockets/image01.png"> |
|||
|
|||
入力ボックスにメッセージを入力して送信できます。 |
|||
|
|||
<img src="/img/tutorial/websockets/image02.png"> |
|||
|
|||
そして、 WebSocketを使用した**FastAPI**アプリケーションが応答します。 |
|||
|
|||
<img src="/img/tutorial/websockets/image03.png"> |
|||
|
|||
複数のメッセージを送信(および受信)できます。 |
|||
|
|||
<img src="/img/tutorial/websockets/image04.png"> |
|||
|
|||
そして、これらの通信はすべて同じWebSocket接続を使用します。 |
|||
|
|||
## 依存関係 |
|||
|
|||
WebSocketエンドポイントでは、`fastapi` から以下をインポートして使用できます。 |
|||
|
|||
* `Depends` |
|||
* `Security` |
|||
* `Cookie` |
|||
* `Header` |
|||
* `Path` |
|||
* `Query` |
|||
|
|||
これらは、他のFastAPI エンドポイント/*path operation* の場合と同じように機能します。 |
|||
|
|||
```Python hl_lines="58-65 68-83" |
|||
{!../../../docs_src/websockets/tutorial002.py!} |
|||
``` |
|||
|
|||
!!! info "情報" |
|||
WebSocket で `HTTPException` を発生させることはあまり意味がありません。したがって、WebSocketの接続を直接閉じる方がよいでしょう。 |
|||
|
|||
クロージングコードは、<a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">仕様で定義された有効なコード</a>の中から使用することができます。 |
|||
|
|||
将来的には、どこからでも `raise` できる `WebSocketException` が用意され、専用の例外ハンドラを追加できるようになる予定です。これは、Starlette の <a href="https://github.com/encode/starlette/pull/527" class="external-link" target="_blank">PR #527</a> に依存するものです。 |
|||
|
|||
### 依存関係を用いてWebSocketsを試してみる |
|||
|
|||
ファイル名が `main.py` である場合、以下の方法でアプリケーションを実行します。 |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
ブラウザで <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a> を開きます。 |
|||
|
|||
クライアントが設定できる項目は以下の通りです。 |
|||
|
|||
* パスで使用される「Item ID」 |
|||
* クエリパラメータとして使用される「Token」 |
|||
|
|||
!!! tip "豆知識" |
|||
クエリ `token` は依存パッケージによって処理されることに注意してください。 |
|||
|
|||
これにより、WebSocketに接続してメッセージを送受信できます。 |
|||
|
|||
<img src="/img/tutorial/websockets/image05.png"> |
|||
|
|||
## 切断や複数クライアントへの対応 |
|||
|
|||
WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。 |
|||
|
|||
```Python hl_lines="81-83" |
|||
{!../../../docs_src/websockets/tutorial003.py!} |
|||
``` |
|||
|
|||
試してみるには、 |
|||
|
|||
* いくつかのブラウザタブでアプリを開きます。 |
|||
* それらのタブでメッセージを記入してください。 |
|||
* そして、タブのうち1つを閉じてください。 |
|||
|
|||
これにより例外 `WebSocketDisconnect` が発生し、他のすべてのクライアントは次のようなメッセージを受信します。 |
|||
|
|||
``` |
|||
Client #1596980209979 left the chat |
|||
``` |
|||
|
|||
!!! tip "豆知識" |
|||
上記のアプリは、複数の WebSocket 接続に対してメッセージを処理し、ブロードキャストする方法を示すための最小限のシンプルな例です。 |
|||
|
|||
しかし、すべての接続がメモリ内の単一のリストで処理されるため、プロセスの実行中にのみ機能し、単一のプロセスでのみ機能することに注意してください。 |
|||
|
|||
もしFastAPIと簡単に統合できて、RedisやPostgreSQLなどでサポートされている、より堅牢なものが必要なら、<a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a> を確認してください。 |
|||
|
|||
## その他のドキュメント |
|||
|
|||
オプションの詳細については、Starletteのドキュメントを確認してください。 |
|||
|
|||
* <a href="https://www.starlette.io/websockets/" class="external-link" target="_blank"> `WebSocket` クラス</a> |
|||
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" class="external-link" target="_blank">クラスベースのWebSocket処理</a> |
@ -0,0 +1,701 @@ |
|||
# FastAPI em contêineres - Docker |
|||
|
|||
Ao fazer o deploy de aplicações FastAPI uma abordagem comum é construir uma **imagem de contêiner Linux**. Isso normalmente é feito usando o <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Você pode a partir disso fazer o deploy dessa imagem de algumas maneiras. |
|||
|
|||
Usando contêineres Linux você tem diversas vantagens incluindo **segurança**, **replicabilidade**, **simplicidade**, entre outras. |
|||
|
|||
!!! Dica |
|||
Está com pressa e já sabe dessas coisas? Pode ir direto para [`Dockerfile` abaixo 👇](#build-a-docker-image-for-fastapi). |
|||
|
|||
|
|||
<details> |
|||
<summary>Visualização do Dockerfile 👀</summary> |
|||
|
|||
```Dockerfile |
|||
FROM python:3.9 |
|||
|
|||
WORKDIR /code |
|||
|
|||
COPY ./requirements.txt /code/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
COPY ./app /code/app |
|||
|
|||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
|
|||
# If running behind a proxy like Nginx or Traefik add --proxy-headers |
|||
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] |
|||
``` |
|||
|
|||
</details> |
|||
|
|||
## O que é um Contêiner |
|||
|
|||
Contêineres (especificamente contêineres Linux) são um jeito muito **leve** de empacotar aplicações contendo todas as dependências e arquivos necessários enquanto os mantém isolados de outros contêineres (outras aplicações ou componentes) no mesmo sistema. |
|||
|
|||
Contêineres Linux rodam usando o mesmo kernel Linux do hospedeiro (máquina, máquina virtual, servidor na nuvem, etc). Isso simplesmente significa que eles são muito leves (comparados com máquinas virtuais emulando um sistema operacional completo). |
|||
|
|||
Dessa forma, contêineres consomem **poucos recursos**, uma quantidade comparável com rodar os processos diretamente (uma máquina virtual consumiria muito mais). |
|||
|
|||
Contêineres também possuem seus próprios processos (comumente um único processo), sistema de arquivos e rede **isolados** simplificando deploy, segurança, desenvolvimento, etc. |
|||
|
|||
## O que é uma Imagem de Contêiner |
|||
|
|||
Um **contêiner** roda a partir de uma **imagem de contêiner**. |
|||
|
|||
Uma imagem de contêiner é uma versão **estática** de todos os arquivos, variáveis de ambiente e do comando/programa padrão que deve estar presente num contêiner. **Estática** aqui significa que a **imagem** de contêiner não está rodando, não está sendo executada, somente contém os arquivos e metadados empacotados. |
|||
|
|||
Em contraste com a "**imagem de contêiner**" que contém os conteúdos estáticos armazenados, um "**contêiner**" normalmente se refere à instância rodando, a coisa que está sendo **executada**. |
|||
|
|||
Quando o **contêiner** é iniciado e está rodando (iniciado a partir de uma **imagem de contêiner**), ele pode criar ou modificar arquivos, variáveis de ambiente, etc. Essas mudanças vão existir somente nesse contêiner, mas não persistirão na imagem subjacente do container (não serão salvas no disco). |
|||
|
|||
Uma imagem de contêiner é comparável ao arquivo de **programa** e seus conteúdos, ex.: `python` e algum arquivo `main.py`. |
|||
|
|||
E o **contêiner** em si (em contraste à **imagem de contêiner**) é a própria instância da imagem rodando, comparável a um **processo**. Na verdade, um contêiner está rodando somente quando há um **processo rodando** (e normalmente é somente um processo). O contêiner finaliza quando não há um processo rodando nele. |
|||
|
|||
## Imagens de contêiner |
|||
|
|||
Docker tem sido uma das principais ferramentas para criar e gerenciar **imagens de contêiner** e **contêineres**. |
|||
|
|||
E existe um <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> público com **imagens de contêiner oficiais** pré-prontas para diversas ferramentas, ambientes, bancos de dados e aplicações. |
|||
|
|||
Por exemplo, há uma <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">Imagem Python</a> oficial. |
|||
|
|||
E existe muitas outras imagens para diferentes coisas, como bancos de dados, por exemplo: |
|||
|
|||
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a> |
|||
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a> |
|||
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a> |
|||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>, etc. |
|||
|
|||
Usando imagens de contêiner pré-prontas é muito fácil **combinar** e usar diferentes ferramentas. Por exemplo, para testar um novo banco de dados. Em muitos casos, você pode usar as **imagens oficiais** precisando somente de variáveis de ambiente para configurá-las. |
|||
|
|||
Dessa forma, em muitos casos você pode aprender sobre contêineres e Docker e re-usar essa experiência com diversos componentes e ferramentas. |
|||
|
|||
Então, você rodaria **vários contêineres** com coisas diferentes, como um banco de dados, uma aplicação Python, um servidor web com uma aplicação frontend React, e conectá-los juntos via sua rede interna. |
|||
|
|||
Todos os sistemas de gerenciamento de contêineres (como Docker ou Kubernetes) possuem essas funcionalidades de rede integradas a eles. |
|||
|
|||
## Contêineres e Processos |
|||
|
|||
Uma **imagem de contêiner** normalmente inclui em seus metadados o programa padrão ou comando que deve ser executado quando o **contêiner** é iniciado e os parâmetros a serem passados para esse programa. Muito similar ao que seria se estivesse na linha de comando. |
|||
|
|||
Quando um **contêiner** é iniciado, ele irá rodar esse comando/programa (embora você possa sobrescrevê-lo e fazer com que ele rode um comando/programa diferente). |
|||
|
|||
Um contêiner está rodando enquanto o **processo principal** (comando ou programa) estiver rodando. |
|||
|
|||
Um contêiner normalmente tem um **único processo**, mas também é possível iniciar sub-processos a partir do processo principal, e dessa forma você terá **vários processos** no mesmo contêiner. |
|||
|
|||
Mas não é possível ter um contêiner rodando sem **pelo menos um processo rodando**. Se o processo principal parar, o contêiner também para. |
|||
|
|||
## Construindo uma Imagem Docker para FastAPI |
|||
|
|||
Okay, vamos construir algo agora! 🚀 |
|||
|
|||
Eu vou mostrar como construir uma **imagem Docker** para FastAPI **do zero**, baseado na **imagem oficial do Python**. |
|||
|
|||
Isso é o que você quer fazer na **maioria dos casos**, por exemplo: |
|||
|
|||
* Usando **Kubernetes** ou ferramentas similares |
|||
* Quando rodando em uma **Raspberry Pi** |
|||
* Usando um serviço em nuvem que irá rodar uma imagem de contêiner para você, etc. |
|||
|
|||
### O Pacote Requirements |
|||
|
|||
Você normalmente teria os **requisitos do pacote** para sua aplicação em algum arquivo. |
|||
|
|||
Isso pode depender principalmente da ferramenta que você usa para **instalar** esses requisitos. |
|||
|
|||
O caminho mais comum de fazer isso é ter um arquivo `requirements.txt` com os nomes dos pacotes e suas versões, um por linha. |
|||
|
|||
Você, naturalmente, usaria as mesmas ideias que você leu em [Sobre Versões do FastAPI](./versions.md){.internal-link target=_blank} para definir os intervalos de versões. |
|||
|
|||
Por exemplo, seu `requirements.txt` poderia parecer com: |
|||
|
|||
``` |
|||
fastapi>=0.68.0,<0.69.0 |
|||
pydantic>=1.8.0,<2.0.0 |
|||
uvicorn>=0.15.0,<0.16.0 |
|||
``` |
|||
|
|||
E você normalmente instalaria essas dependências de pacote com `pip`, por exemplo: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install -r requirements.txt |
|||
---> 100% |
|||
Successfully installed fastapi pydantic uvicorn |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! info |
|||
Há outros formatos e ferramentas para definir e instalar dependências de pacote. |
|||
|
|||
Eu vou mostrar um exemplo depois usando Poetry em uma seção abaixo. 👇 |
|||
|
|||
### Criando o Código do **FastAPI** |
|||
|
|||
* Crie um diretório `app` e entre nele. |
|||
* Crie um arquivo vazio `__init__.py`. |
|||
* Crie um arquivo `main.py` com: |
|||
|
|||
```Python |
|||
from typing import Optional |
|||
|
|||
from fastapi import FastAPI |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
@app.get("/") |
|||
def read_root(): |
|||
return {"Hello": "World"} |
|||
|
|||
|
|||
@app.get("/items/{item_id}") |
|||
def read_item(item_id: int, q: Union[str, None] = None): |
|||
return {"item_id": item_id, "q": q} |
|||
``` |
|||
|
|||
### Dockerfile |
|||
|
|||
Agora, no mesmo diretório do projeto, crie um arquivo `Dockerfile` com: |
|||
|
|||
```{ .dockerfile .annotate } |
|||
# (1) |
|||
FROM python:3.9 |
|||
|
|||
# (2) |
|||
WORKDIR /code |
|||
|
|||
# (3) |
|||
COPY ./requirements.txt /code/requirements.txt |
|||
|
|||
# (4) |
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
# (5) |
|||
COPY ./app /code/app |
|||
|
|||
# (6) |
|||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
1. Inicie a partir da imagem base oficial do Python. |
|||
|
|||
2. Defina o diretório de trabalho atual para `/code`. |
|||
|
|||
Esse é o diretório onde colocaremos o arquivo `requirements.txt` e o diretório `app`. |
|||
|
|||
3. Copie o arquivo com os requisitos para o diretório `/code`. |
|||
|
|||
Copie **somente** o arquivo com os requisitos primeiro, não o resto do código. |
|||
|
|||
Como esse arquivo **não muda com frequência**, o Docker irá detectá-lo e usar o **cache** para esse passo, habilitando o cache para o próximo passo também. |
|||
|
|||
4. Instale as dependências de pacote vindas do arquivo de requisitos. |
|||
|
|||
A opção `--no-cache-dir` diz ao `pip` para não salvar os pacotes baixados localmente, pois isso só aconteceria se `pip` fosse executado novamente para instalar os mesmos pacotes, mas esse não é o caso quando trabalhamos com contêineres. |
|||
|
|||
!!! note |
|||
`--no-cache-dir` é apenas relacionado ao `pip`, não tem nada a ver com Docker ou contêineres. |
|||
|
|||
A opção `--upgrade` diz ao `pip` para atualizar os pacotes se eles já estiverem instalados. |
|||
|
|||
Por causa do passo anterior de copiar o arquivo, ele pode ser detectado pelo **cache do Docker**, esse passo também **usará o cache do Docker** quando disponível. |
|||
|
|||
Usando o cache nesse passo irá **salvar** muito **tempo** quando você for construir a imagem repetidas vezes durante o desenvolvimento, ao invés de **baixar e instalar** todas as dependências **toda vez**. |
|||
|
|||
5. Copie o diretório `./app` dentro do diretório `/code`. |
|||
|
|||
Como isso tem todo o código contendo o que **muda com mais frequência**, o **cache do Docker** não será usado para esse passo ou para **qualquer passo seguinte** facilmente. |
|||
|
|||
Então, é importante colocar isso **perto do final** do `Dockerfile`, para otimizar o tempo de construção da imagem do contêiner. |
|||
|
|||
6. Defina o **comando** para rodar o servidor `uvicorn`. |
|||
|
|||
`CMD` recebe uma lista de strings, cada uma dessas strings é o que você digitaria na linha de comando separado por espaços. |
|||
|
|||
Esse comando será executado a partir do **diretório de trabalho atual**, o mesmo diretório `/code` que você definiu acima com `WORKDIR /code`. |
|||
|
|||
Porque o programa será iniciado em `/code` e dentro dele está o diretório `./app` com seu código, o **Uvicorn** será capaz de ver e **importar** `app` de `app.main`. |
|||
|
|||
!!! tip |
|||
Revise o que cada linha faz clicando em cada bolha com o número no código. 👆 |
|||
|
|||
Agora você deve ter uma estrutura de diretório como: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ └── main.py |
|||
├── Dockerfile |
|||
└── requirements.txt |
|||
``` |
|||
|
|||
#### Por Trás de um Proxy de Terminação TLS |
|||
|
|||
Se você está executando seu contêiner atrás de um Proxy de Terminação TLS (load balancer) como Nginx ou Traefik, adicione a opção `--proxy-headers`, isso fará com que o Uvicorn confie nos cabeçalhos enviados por esse proxy, informando que o aplicativo está sendo executado atrás do HTTPS, etc. |
|||
|
|||
```Dockerfile |
|||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
#### Cache Docker |
|||
|
|||
Existe um truque importante nesse `Dockerfile`, primeiro copiamos o **arquivo com as dependências sozinho**, não o resto do código. Deixe-me te contar o porquê disso. |
|||
|
|||
```Dockerfile |
|||
COPY ./requirements.txt /code/requirements.txt |
|||
``` |
|||
|
|||
Docker e outras ferramentas **constróem** essas imagens de contêiner **incrementalmente**, adicionando **uma camada em cima da outra**, começando do topo do `Dockerfile` e adicionando qualquer arquivo criado por cada uma das instruções do `Dockerfile`. |
|||
|
|||
Docker e ferramentas similares também usam um **cache interno** ao construir a imagem, se um arquivo não mudou desde a última vez que a imagem do contêiner foi construída, então ele irá **reutilizar a mesma camada** criada na última vez, ao invés de copiar o arquivo novamente e criar uma nova camada do zero. |
|||
|
|||
Somente evitar a cópia de arquivos não melhora muito as coisas, mas porque ele usou o cache para esse passo, ele pode **usar o cache para o próximo passo**. Por exemplo, ele pode usar o cache para a instrução que instala as dependências com: |
|||
|
|||
```Dockerfile |
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
``` |
|||
|
|||
O arquivo com os requisitos de pacote **não muda com frequência**. Então, ao copiar apenas esse arquivo, o Docker será capaz de **usar o cache** para esse passo. |
|||
|
|||
E então, o Docker será capaz de **usar o cache para o próximo passo** que baixa e instala essas dependências. E é aqui que **salvamos muito tempo**. ✨ ...e evitamos tédio esperando. 😪😆 |
|||
|
|||
Baixar e instalar as dependências do pacote **pode levar minutos**, mas usando o **cache** leva **segundos** no máximo. |
|||
|
|||
E como você estaria construindo a imagem do contêiner novamente e novamente durante o desenvolvimento para verificar se suas alterações de código estão funcionando, há muito tempo acumulado que isso economizaria. |
|||
|
|||
A partir daí, perto do final do `Dockerfile`, copiamos todo o código. Como isso é o que **muda com mais frequência**, colocamos perto do final, porque quase sempre, qualquer coisa depois desse passo não será capaz de usar o cache. |
|||
|
|||
```Dockerfile |
|||
COPY ./app /code/app |
|||
``` |
|||
|
|||
### Construindo a Imagem Docker |
|||
|
|||
Agora que todos os arquivos estão no lugar, vamos construir a imagem do contêiner. |
|||
|
|||
* Vá para o diretório do projeto (onde está o seu `Dockerfile`, contendo o diretório `app`). |
|||
* Construa sua imagem FastAPI: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ docker build -t myimage . |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! tip |
|||
Note o `.` no final, é equivalente a `./`, ele diz ao Docker o diretório a ser usado para construir a imagem do contêiner. |
|||
|
|||
Nesse caso, é o mesmo diretório atual (`.`). |
|||
|
|||
### Inicie o contêiner Docker |
|||
|
|||
* Execute um contêiner baseado na sua imagem: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ docker run -d --name mycontêiner -p 80:80 myimage |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## Verifique |
|||
|
|||
Você deve ser capaz de verificar isso no URL do seu contêiner Docker, por exemplo: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> ou <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (ou equivalente, usando seu host Docker). |
|||
|
|||
Você verá algo como: |
|||
|
|||
```JSON |
|||
{"item_id": 5, "q": "somequery"} |
|||
``` |
|||
|
|||
## Documentação interativa da API |
|||
|
|||
Agora você pode ir para <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> ou <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (ou equivalente, usando seu host Docker). |
|||
|
|||
Você verá a documentação interativa automática da API (fornecida pelo <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): |
|||
|
|||
 |
|||
|
|||
## Documentação alternativa da API |
|||
|
|||
E você também pode ir para <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> ou <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (ou equivalente, usando seu host Docker). |
|||
|
|||
Você verá a documentação alternativa automática (fornecida pela <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): |
|||
|
|||
 |
|||
|
|||
## Construindo uma Imagem Docker com um Arquivo Único FastAPI |
|||
|
|||
Se seu FastAPI for um único arquivo, por exemplo, `main.py` sem um diretório `./app`, sua estrutura de arquivos poderia ser assim: |
|||
|
|||
``` |
|||
. |
|||
├── Dockerfile |
|||
├── main.py |
|||
└── requirements.txt |
|||
``` |
|||
|
|||
Então você só teria que alterar os caminhos correspondentes para copiar o arquivo dentro do `Dockerfile`: |
|||
|
|||
```{ .dockerfile .annotate hl_lines="10 13" } |
|||
FROM python:3.9 |
|||
|
|||
WORKDIR /code |
|||
|
|||
COPY ./requirements.txt /code/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
# (1) |
|||
COPY ./main.py /code/ |
|||
|
|||
# (2) |
|||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
1. Copie o arquivo `main.py` para o diretório `/code` diretamente (sem nenhum diretório `./app`). |
|||
|
|||
2. Execute o Uvicorn e diga a ele para importar o objeto `app` de `main` (em vez de importar de `app.main`). |
|||
|
|||
Então ajuste o comando Uvicorn para usar o novo módulo `main` em vez de `app.main` para importar o objeto FastAPI `app`. |
|||
|
|||
## Conceitos de Implantação |
|||
|
|||
Vamos falar novamente sobre alguns dos mesmos [Conceitos de Implantação](./concepts.md){.internal-link target=_blank} em termos de contêineres. |
|||
|
|||
Contêineres são principalmente uma ferramenta para simplificar o processo de **construção e implantação** de um aplicativo, mas eles não impõem uma abordagem particular para lidar com esses **conceitos de implantação** e existem várias estratégias possíveis. |
|||
|
|||
A **boa notícia** é que com cada estratégia diferente há uma maneira de cobrir todos os conceitos de implantação. 🎉 |
|||
|
|||
Vamos revisar esses **conceitos de implantação** em termos de contêineres: |
|||
|
|||
* HTTPS |
|||
* Executando na inicialização |
|||
* Reinicializações |
|||
* Replicação (número de processos rodando) |
|||
* Memória |
|||
* Passos anteriores antes de começar |
|||
|
|||
## HTTPS |
|||
|
|||
Se nos concentrarmos apenas na **imagem do contêiner** para um aplicativo FastAPI (e posteriormente no **contêiner** em execução), o HTTPS normalmente seria tratado **externamente** por outra ferramenta. |
|||
|
|||
Isso poderia ser outro contêiner, por exemplo, com <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, lidando com **HTTPS** e aquisição **automática** de **certificados**. |
|||
|
|||
!!! tip |
|||
Traefik tem integrações com Docker, Kubernetes e outros, portanto, é muito fácil configurar e configurar o HTTPS para seus contêineres com ele. |
|||
|
|||
Alternativamente, o HTTPS poderia ser tratado por um provedor de nuvem como um de seus serviços (enquanto ainda executasse o aplicativo em um contêiner). |
|||
|
|||
## Executando na inicialização e reinicializações |
|||
|
|||
Normalmente, outra ferramenta é responsável por **iniciar e executar** seu contêiner. |
|||
|
|||
Ela poderia ser o **Docker** diretamente, **Docker Compose**, **Kubernetes**, um **serviço de nuvem**, etc. |
|||
|
|||
Na maioria (ou em todos) os casos, há uma opção simples para habilitar a execução do contêiner na inicialização e habilitar reinicializações em falhas. Por exemplo, no Docker, é a opção de linha de comando `--restart`. |
|||
|
|||
Sem usar contêineres, fazer aplicativos executarem na inicialização e com reinicializações pode ser trabalhoso e difícil. Mas quando **trabalhando com contêineres** em muitos casos essa funcionalidade é incluída por padrão. ✨ |
|||
|
|||
## Replicação - Número de Processos |
|||
|
|||
Se você tiver um <abbr title="Um grupo de máquinas que são configuradas para estarem conectadas e trabalharem juntas de alguma forma">cluster</abbr> de máquinas com **Kubernetes**, Docker Swarm Mode, Nomad ou outro sistema complexo semelhante para gerenciar contêineres distribuídos em várias máquinas, então provavelmente desejará **lidar com a replicação** no **nível do cluster** em vez de usar um **gerenciador de processos** (como o Gunicorn com workers) em cada contêiner. |
|||
|
|||
Um desses sistemas de gerenciamento de contêineres distribuídos como o Kubernetes normalmente tem alguma maneira integrada de lidar com a **replicação de contêineres** enquanto ainda oferece **balanceamento de carga** para as solicitações recebidas. Tudo no **nível do cluster**. |
|||
|
|||
Nesses casos, você provavelmente desejará criar uma **imagem do contêiner do zero** como [explicado acima](#dockerfile), instalando suas dependências e executando **um único processo Uvicorn** em vez de executar algo como Gunicorn com trabalhadores Uvicorn. |
|||
|
|||
### Balanceamento de Carga |
|||
|
|||
Quando usando contêineres, normalmente você terá algum componente **escutando na porta principal**. Poderia ser outro contêiner que também é um **Proxy de Terminação TLS** para lidar com **HTTPS** ou alguma ferramenta semelhante. |
|||
|
|||
Como esse componente assumiria a **carga** de solicitações e distribuiria isso entre os trabalhadores de uma maneira (esperançosamente) **balanceada**, ele também é comumente chamado de **Balanceador de Carga**. |
|||
|
|||
!!! tip |
|||
O mesmo componente **Proxy de Terminação TLS** usado para HTTPS provavelmente também seria um **Balanceador de Carga**. |
|||
|
|||
E quando trabalhar com contêineres, o mesmo sistema que você usa para iniciar e gerenciá-los já terá ferramentas internas para transmitir a **comunicação de rede** (por exemplo, solicitações HTTP) do **balanceador de carga** (que também pode ser um **Proxy de Terminação TLS**) para o(s) contêiner(es) com seu aplicativo. |
|||
|
|||
### Um Balanceador de Carga - Múltiplos Contêineres de Workers |
|||
|
|||
Quando trabalhando com **Kubernetes** ou sistemas similares de gerenciamento de contêiner distribuído, usando seus mecanismos de rede internos permitiria que o único **balanceador de carga** que estivesse escutando na **porta principal** transmitisse comunicação (solicitações) para possivelmente **múltiplos contêineres** executando seu aplicativo. |
|||
|
|||
Cada um desses contêineres executando seu aplicativo normalmente teria **apenas um processo** (ex.: um processo Uvicorn executando seu aplicativo FastAPI). Todos seriam **contêineres idênticos**, executando a mesma coisa, mas cada um com seu próprio processo, memória, etc. Dessa forma, você aproveitaria a **paralelização** em **núcleos diferentes** da CPU, ou até mesmo em **máquinas diferentes**. |
|||
|
|||
E o sistema de contêiner com o **balanceador de carga** iria **distribuir as solicitações** para cada um dos contêineres com seu aplicativo **em turnos**. Portanto, cada solicitação poderia ser tratada por um dos múltiplos **contêineres replicados** executando seu aplicativo. |
|||
|
|||
E normalmente esse **balanceador de carga** seria capaz de lidar com solicitações que vão para *outros* aplicativos em seu cluster (por exemplo, para um domínio diferente, ou sob um prefixo de URL diferente), e transmitiria essa comunicação para os contêineres certos para *esse outro* aplicativo em execução em seu cluster. |
|||
|
|||
### Um Processo por Contêiner |
|||
|
|||
Nesse tipo de cenário, provavelmente você desejará ter **um único processo (Uvicorn) por contêiner**, pois já estaria lidando com a replicação no nível do cluster. |
|||
|
|||
Então, nesse caso, você **não** desejará ter um gerenciador de processos como o Gunicorn com trabalhadores Uvicorn, ou o Uvicorn usando seus próprios trabalhadores Uvicorn. Você desejará ter apenas um **único processo Uvicorn** por contêiner (mas provavelmente vários contêineres). |
|||
|
|||
Tendo outro gerenciador de processos dentro do contêiner (como seria com o Gunicorn ou o Uvicorn gerenciando trabalhadores Uvicorn) só adicionaria **complexidade desnecessária** que você provavelmente já está cuidando com seu sistema de cluster. |
|||
|
|||
### Contêineres com Múltiplos Processos e Casos Especiais |
|||
|
|||
Claro, existem **casos especiais** em que você pode querer ter um **contêiner** com um **gerenciador de processos Gunicorn** iniciando vários **processos trabalhadores Uvicorn** dentro. |
|||
|
|||
Nesses casos, você pode usar a **imagem oficial do Docker** que inclui o **Gunicorn** como um gerenciador de processos executando vários **processos trabalhadores Uvicorn**, e algumas configurações padrão para ajustar o número de trabalhadores com base nos atuais núcleos da CPU automaticamente. Eu vou te contar mais sobre isso abaixo em [Imagem Oficial do Docker com Gunicorn - Uvicorn](#imagem-oficial-do-docker-com-gunicorn-uvicorn). |
|||
|
|||
Aqui estão alguns exemplos de quando isso pode fazer sentido: |
|||
|
|||
#### Um Aplicativo Simples |
|||
|
|||
Você pode querer um gerenciador de processos no contêiner se seu aplicativo for **simples o suficiente** para que você não precise (pelo menos não agora) ajustar muito o número de processos, e você pode simplesmente usar um padrão automatizado (com a imagem oficial do Docker), e você está executando em um **único servidor**, não em um cluster. |
|||
|
|||
#### Docker Compose |
|||
|
|||
Você pode estar implantando em um **único servidor** (não em um cluster) com o **Docker Compose**, então você não teria uma maneira fácil de gerenciar a replicação de contêineres (com o Docker Compose) enquanto preserva a rede compartilhada e o **balanceamento de carga**. |
|||
|
|||
Então você pode querer ter **um único contêiner** com um **gerenciador de processos** iniciando **vários processos trabalhadores** dentro. |
|||
|
|||
#### Prometheus and Outros Motivos |
|||
|
|||
Você também pode ter **outros motivos** que tornariam mais fácil ter um **único contêiner** com **múltiplos processos** em vez de ter **múltiplos contêineres** com **um único processo** em cada um deles. |
|||
|
|||
Por exemplo (dependendo de sua configuração), você poderia ter alguma ferramenta como um exportador do Prometheus no mesmo contêiner que deve ter acesso a **cada uma das solicitações** que chegam. |
|||
|
|||
Nesse caso, se você tivesse **múltiplos contêineres**, por padrão, quando o Prometheus fosse **ler as métricas**, ele receberia as métricas de **um único contêiner cada vez** (para o contêiner que tratou essa solicitação específica), em vez de receber as **métricas acumuladas** de todos os contêineres replicados. |
|||
|
|||
Então, nesse caso, poderia ser mais simples ter **um único contêiner** com **múltiplos processos**, e uma ferramenta local (por exemplo, um exportador do Prometheus) no mesmo contêiner coletando métricas do Prometheus para todos os processos internos e expor essas métricas no único contêiner. |
|||
|
|||
--- |
|||
|
|||
O ponto principal é que **nenhum** desses são **regras escritas em pedra** que você deve seguir cegamente. Você pode usar essas idéias para **avaliar seu próprio caso de uso** e decidir qual é a melhor abordagem para seu sistema, verificando como gerenciar os conceitos de: |
|||
|
|||
* Segurança - HTTPS |
|||
* Executando na inicialização |
|||
* Reinicializações |
|||
* Replicação (o número de processos em execução) |
|||
* Memória |
|||
* Passos anteriores antes de inicializar |
|||
|
|||
## Memória |
|||
|
|||
Se você executar **um único processo por contêiner**, terá uma quantidade mais ou menos bem definida, estável e limitada de memória consumida por cada um desses contêineres (mais de um se eles forem replicados). |
|||
|
|||
E então você pode definir esses mesmos limites e requisitos de memória em suas configurações para seu sistema de gerenciamento de contêineres (por exemplo, no **Kubernetes**). Dessa forma, ele poderá **replicar os contêineres** nas **máquinas disponíveis** levando em consideração a quantidade de memória necessária por eles e a quantidade disponível nas máquinas no cluster. |
|||
|
|||
Se sua aplicação for **simples**, isso provavelmente **não será um problema**, e você pode não precisar especificar limites de memória rígidos. Mas se você estiver **usando muita memória** (por exemplo, com **modelos de aprendizado de máquina**), deve verificar quanta memória está consumindo e ajustar o **número de contêineres** que executa em **cada máquina** (e talvez adicionar mais máquinas ao seu cluster). |
|||
|
|||
Se você executar **múltiplos processos por contêiner** (por exemplo, com a imagem oficial do Docker), deve garantir que o número de processos iniciados não **consuma mais memória** do que o disponível. |
|||
|
|||
## Passos anteriores antes de inicializar e contêineres |
|||
|
|||
Se você estiver usando contêineres (por exemplo, Docker, Kubernetes), existem duas abordagens principais que você pode usar. |
|||
|
|||
### Contêineres Múltiplos |
|||
|
|||
Se você tiver **múltiplos contêineres**, provavelmente cada um executando um **único processo** (por exemplo, em um cluster do **Kubernetes**), então provavelmente você gostaria de ter um **contêiner separado** fazendo o trabalho dos **passos anteriores** em um único contêiner, executando um único processo, **antes** de executar os contêineres trabalhadores replicados. |
|||
|
|||
!!! info |
|||
Se você estiver usando o Kubernetes, provavelmente será um <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>. |
|||
|
|||
Se no seu caso de uso não houver problema em executar esses passos anteriores **em paralelo várias vezes** (por exemplo, se você não estiver executando migrações de banco de dados, mas apenas verificando se o banco de dados está pronto), então você também pode colocá-los em cada contêiner logo antes de iniciar o processo principal. |
|||
|
|||
### Contêiner Único |
|||
|
|||
Se você tiver uma configuração simples, com um **único contêiner** que então inicia vários **processos trabalhadores** (ou também apenas um processo), então poderia executar esses passos anteriores no mesmo contêiner, logo antes de iniciar o processo com o aplicativo. A imagem oficial do Docker suporta isso internamente. |
|||
|
|||
## Imagem Oficial do Docker com Gunicorn - Uvicorn |
|||
|
|||
Há uma imagem oficial do Docker que inclui o Gunicorn executando com trabalhadores Uvicorn, conforme detalhado em um capítulo anterior: [Server Workers - Gunicorn com Uvicorn](./server-workers.md){.internal-link target=_blank}. |
|||
|
|||
Essa imagem seria útil principalmente nas situações descritas acima em: [Contêineres com Múltiplos Processos e Casos Especiais](#contêineres-com-múltiplos-processos-e-casos-Especiais). |
|||
|
|||
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. |
|||
|
|||
!!! warning |
|||
Existe uma grande chance de que você **não** precise dessa imagem base ou de qualquer outra semelhante, e seria melhor construir a imagem do zero, como [descrito acima em: Construa uma Imagem Docker para o FastAPI](#construa-uma-imagem-docker-para-o-fastapi). |
|||
|
|||
Essa imagem tem um mecanismo de **auto-ajuste** incluído para definir o **número de processos trabalhadores** com base nos núcleos de CPU disponíveis. |
|||
|
|||
Isso tem **padrões sensíveis**, mas você ainda pode alterar e atualizar todas as configurações com **variáveis de ambiente** ou arquivos de configuração. |
|||
|
|||
Há também suporte para executar <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**passos anteriores antes de iniciar**</a> com um script. |
|||
|
|||
!!! tip |
|||
Para ver todas as configurações e opções, vá para a página da imagem Docker: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. |
|||
|
|||
### Número de Processos na Imagem Oficial do Docker |
|||
|
|||
O **número de processos** nesta imagem é **calculado automaticamente** a partir dos **núcleos de CPU** disponíveis. |
|||
|
|||
Isso significa que ele tentará **aproveitar** o máximo de **desempenho** da CPU possível. |
|||
|
|||
Você também pode ajustá-lo com as configurações usando **variáveis de ambiente**, etc. |
|||
|
|||
Mas isso também significa que, como o número de processos depende da CPU do contêiner em execução, a **quantidade de memória consumida** também dependerá disso. |
|||
|
|||
Então, se seu aplicativo consumir muito memória (por exemplo, com modelos de aprendizado de máquina), e seu servidor tiver muitos núcleos de CPU **mas pouca memória**, então seu contêiner pode acabar tentando usar mais memória do que está disponível e degradar o desempenho muito (ou até mesmo travar). 🚨 |
|||
|
|||
### Criando um `Dockerfile` |
|||
|
|||
Aqui está como você criaria um `Dockerfile` baseado nessa imagem: |
|||
|
|||
```Dockerfile |
|||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 |
|||
|
|||
COPY ./requirements.txt /app/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt |
|||
|
|||
COPY ./app /app |
|||
``` |
|||
|
|||
### Aplicações Maiores |
|||
|
|||
Se você seguiu a seção sobre a criação de [Aplicações Maiores com Múltiplos Arquivos](../tutorial/bigger-applications.md){.internal-link target=_blank}, seu `Dockerfile` pode parecer com isso: |
|||
|
|||
```Dockerfile |
|||
|
|||
```Dockerfile hl_lines="7" |
|||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 |
|||
|
|||
COPY ./requirements.txt /app/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt |
|||
|
|||
COPY ./app /app/app |
|||
``` |
|||
|
|||
### Quando Usar |
|||
|
|||
Você provavelmente **não** deve usar essa imagem base oficial (ou qualquer outra semelhante) se estiver usando **Kubernetes** (ou outros) e já estiver definindo **replicação** no nível do cluster, com vários **contêineres**. Nesses casos, é melhor **construir uma imagem do zero** conforme descrito acima: [Construindo uma Imagem Docker para FastAPI](#construindo-uma-imagem-docker-para-fastapi). |
|||
|
|||
Essa imagem seria útil principalmente nos casos especiais descritos acima em [Contêineres com Múltiplos Processos e Casos Especiais](#contêineres-com-múltiplos-processos-e-casos-Especiais). Por exemplo, se sua aplicação for **simples o suficiente** para que a configuração padrão de número de processos com base na CPU funcione bem, você não quer se preocupar com a configuração manual da replicação no nível do cluster e não está executando mais de um contêiner com seu aplicativo. Ou se você estiver implantando com **Docker Compose**, executando em um único servidor, etc. |
|||
|
|||
## Deploy da Imagem do Contêiner |
|||
|
|||
Depois de ter uma imagem de contêiner (Docker), existem várias maneiras de implantá-la. |
|||
|
|||
Por exemplo: |
|||
|
|||
* Com **Docker Compose** em um único servidor |
|||
* Com um cluster **Kubernetes** |
|||
* Com um cluster Docker Swarm Mode |
|||
* Com outra ferramenta como o Nomad |
|||
* Com um serviço de nuvem que pega sua imagem de contêiner e a implanta |
|||
|
|||
## Imagem Docker com Poetry |
|||
|
|||
Se você usa <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> para gerenciar as dependências do seu projeto, pode usar a construção multi-estágio do Docker: |
|||
|
|||
```{ .dockerfile .annotate } |
|||
# (1) |
|||
FROM python:3.9 as requirements-stage |
|||
|
|||
# (2) |
|||
WORKDIR /tmp |
|||
|
|||
# (3) |
|||
RUN pip install poetry |
|||
|
|||
# (4) |
|||
COPY ./pyproject.toml ./poetry.lock* /tmp/ |
|||
|
|||
# (5) |
|||
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes |
|||
|
|||
# (6) |
|||
FROM python:3.9 |
|||
|
|||
# (7) |
|||
WORKDIR /code |
|||
|
|||
# (8) |
|||
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt |
|||
|
|||
# (9) |
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
# (10) |
|||
COPY ./app /code/app |
|||
|
|||
# (11) |
|||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
1. Esse é o primeiro estágio, ele é chamado `requirements-stage`. |
|||
|
|||
2. Defina `/tmp` como o diretório de trabalho atual. |
|||
|
|||
Aqui é onde geraremos o arquivo `requirements.txt` |
|||
|
|||
3. Instale o Poetry nesse estágio do Docker. |
|||
|
|||
4. Copie os arquivos `pyproject.toml` e `poetry.lock` para o diretório `/tmp`. |
|||
|
|||
Porque está usando `./poetry.lock*` (terminando com um `*`), não irá falhar se esse arquivo ainda não estiver disponível. |
|||
|
|||
5. Gere o arquivo `requirements.txt`. |
|||
|
|||
6. Este é o estágio final, tudo aqui será preservado na imagem final do contêiner. |
|||
|
|||
7. Defina o diretório de trabalho atual como `/code`. |
|||
|
|||
8. Copie o arquivo `requirements.txt` para o diretório `/code`. |
|||
|
|||
Essse arquivo só existe no estágio anterior do Docker, é por isso que usamos `--from-requirements-stage` para copiá-lo. |
|||
|
|||
9. Instale as dependências de pacote do arquivo `requirements.txt` gerado. |
|||
|
|||
10. Copie o diretório `app` para o diretório `/code`. |
|||
|
|||
11. Execute o comando `uvicorn`, informando-o para usar o objeto `app` importado de `app.main`. |
|||
|
|||
!!! tip |
|||
Clique nos números das bolhas para ver o que cada linha faz. |
|||
|
|||
Um **estágio do Docker** é uma parte de um `Dockerfile` que funciona como uma **imagem temporária do contêiner** que só é usada para gerar alguns arquivos para serem usados posteriormente. |
|||
|
|||
O primeiro estágio será usado apenas para **instalar Poetry** e para **gerar o `requirements.txt`** com as dependências do seu projeto a partir do arquivo `pyproject.toml` do Poetry. |
|||
|
|||
Esse arquivo `requirements.txt` será usado com `pip` mais tarde no **próximo estágio**. |
|||
|
|||
Na imagem final do contêiner, **somente o estágio final** é preservado. Os estágios anteriores serão descartados. |
|||
|
|||
Quando usar Poetry, faz sentido usar **construções multi-estágio do Docker** porque você realmente não precisa ter o Poetry e suas dependências instaladas na imagem final do contêiner, você **apenas precisa** ter o arquivo `requirements.txt` gerado para instalar as dependências do seu projeto. |
|||
|
|||
Então, no próximo (e último) estágio, você construiria a imagem mais ou menos da mesma maneira descrita anteriormente. |
|||
|
|||
### Por trás de um proxy de terminação TLS - Poetry |
|||
|
|||
Novamente, se você estiver executando seu contêiner atrás de um proxy de terminação TLS (balanceador de carga) como Nginx ou Traefik, adicione a opção `--proxy-headers` ao comando: |
|||
|
|||
```Dockerfile |
|||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
## Recapitulando |
|||
|
|||
Usando sistemas de contêiner (por exemplo, com **Docker** e **Kubernetes**), torna-se bastante simples lidar com todos os **conceitos de implantação**: |
|||
|
|||
* HTTPS |
|||
* Executando na inicialização |
|||
* Reinícios |
|||
* Replicação (o número de processos rodando) |
|||
* Memória |
|||
* Passos anteriores antes de inicializar |
|||
|
|||
Na maioria dos casos, você provavelmente não desejará usar nenhuma imagem base e, em vez disso, **construir uma imagem de contêiner do zero** baseada na imagem oficial do Docker Python. |
|||
|
|||
Tendo cuidado com a **ordem** das instruções no `Dockerfile` e o **cache do Docker**, você pode **minimizar os tempos de construção**, para maximizar sua produtividade (e evitar a tédio). 😎 |
|||
|
|||
Em alguns casos especiais, você pode querer usar a imagem oficial do Docker para o FastAPI. 🤓 |
@ -0,0 +1,213 @@ |
|||
# Corpo - Múltiplos parâmetros |
|||
|
|||
Agora que nós vimos como usar `Path` e `Query`, veremos usos mais avançados de declarações no corpo da requisição. |
|||
|
|||
## Misture `Path`, `Query` e parâmetros de corpo |
|||
|
|||
Primeiro, é claro, você pode misturar `Path`, `Query` e declarações de parâmetro no corpo da requisição livremente e o **FastAPI** saberá o que fazer. |
|||
|
|||
E você também pode declarar parâmetros de corpo como opcionais, definindo o valor padrão com `None`: |
|||
|
|||
=== "Python 3.6 e superiores" |
|||
|
|||
```Python hl_lines="19-21" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 e superiores" |
|||
|
|||
```Python hl_lines="17-19" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
!!! nota |
|||
Repare que, neste caso, o `item` que seria capturado a partir do corpo é opcional. Visto que ele possui `None` como valor padrão. |
|||
|
|||
## Múltiplos parâmetros de corpo |
|||
|
|||
No exemplo anterior, as *operações de rota* esperariam um JSON no corpo contendo os atributos de um `Item`, exemplo: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
|
|||
Mas você pode também declarar múltiplos parâmetros de corpo, por exemplo, `item` e `user`: |
|||
|
|||
=== "Python 3.6 e superiores" |
|||
|
|||
```Python hl_lines="22" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial002.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 e superiores" |
|||
|
|||
```Python hl_lines="20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
Neste caso, o **FastAPI** perceberá que existe mais de um parâmetro de corpo na função (dois parâmetros que são modelos Pydantic). |
|||
|
|||
Então, ele usará o nome dos parâmetros como chaves (nome dos campos) no corpo, e espera um corpo como: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
!!! nota |
|||
Repare que mesmo que o `item` esteja declarado da mesma maneira que antes, agora é esperado que ele esteja dentro do corpo com uma chave `item`. |
|||
|
|||
|
|||
O **FastAPI** fará a conversão automática a partir da requisição, assim esse parâmetro `item` receberá seu respectivo conteúdo e o mesmo ocorrerá com `user`. |
|||
|
|||
Ele executará a validação dos dados compostos e irá documentá-los de maneira compatível com o esquema OpenAPI e documentação automática. |
|||
|
|||
## Valores singulares no corpo |
|||
|
|||
Assim como existem uma `Query` e uma `Path` para definir dados adicionais para parâmetros de consulta e de rota, o **FastAPI** provê o equivalente para `Body`. |
|||
|
|||
Por exemplo, extendendo o modelo anterior, você poder decidir por ter uma outra chave `importance` no mesmo corpo, além de `item` e `user`. |
|||
|
|||
Se você declará-lo como é, porque é um valor singular, o **FastAPI** assumirá que se trata de um parâmetro de consulta. |
|||
|
|||
Mas você pode instruir o **FastAPI** para tratá-lo como outra chave do corpo usando `Body`: |
|||
|
|||
=== "Python 3.6 e superiores" |
|||
|
|||
```Python hl_lines="22" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 e superiores" |
|||
|
|||
```Python hl_lines="20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
Neste caso, o **FastAPI** esperará um corpo como: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
}, |
|||
"importance": 5 |
|||
} |
|||
``` |
|||
|
|||
Mais uma vez, ele converterá os tipos de dados, validar, documentar, etc. |
|||
|
|||
## Múltiplos parâmetros de corpo e consulta |
|||
|
|||
Obviamente, você também pode declarar parâmetros de consulta assim que você precisar, de modo adicional a quaisquer parâmetros de corpo. |
|||
|
|||
Dado que, por padrão, valores singulares são interpretados como parâmetros de consulta, você não precisa explicitamente adicionar uma `Query`, você pode somente: |
|||
|
|||
```Python |
|||
q: Union[str, None] = None |
|||
``` |
|||
|
|||
Ou como em Python 3.10 e versões superiores: |
|||
|
|||
```Python |
|||
q: str | None = None |
|||
``` |
|||
|
|||
Por exemplo: |
|||
|
|||
=== "Python 3.6 e superiores" |
|||
|
|||
```Python hl_lines="27" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 e superiores" |
|||
|
|||
```Python hl_lines="26" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
!!! info "Informação" |
|||
`Body` também possui todas as validações adicionais e metadados de parâmetros como em `Query`,`Path` e outras que você verá depois. |
|||
|
|||
## Declare um único parâmetro de corpo indicando sua chave |
|||
|
|||
Suponha que você tem um único parâmetro de corpo `item`, a partir de um modelo Pydantic `Item`. |
|||
|
|||
Por padrão, o **FastAPI** esperará que seu conteúdo venha no corpo diretamente. |
|||
|
|||
Mas se você quiser que ele espere por um JSON com uma chave `item` e dentro dele os conteúdos do modelo, como ocorre ao declarar vários parâmetros de corpo, você pode usar o parâmetro especial de `Body` chamado `embed`: |
|||
|
|||
```Python |
|||
item: Item = Body(embed=True) |
|||
``` |
|||
|
|||
como em: |
|||
|
|||
=== "Python 3.6 e superiores" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 e superiores" |
|||
|
|||
```Python hl_lines="15" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} |
|||
``` |
|||
|
|||
Neste caso o **FastAPI** esperará um corpo como: |
|||
|
|||
```JSON hl_lines="2" |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ao invés de: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
|
|||
## Recapitulando |
|||
|
|||
Você pode adicionar múltiplos parâmetros de corpo para sua *função de operação de rota*, mesmo que a requisição possa ter somente um único corpo. |
|||
|
|||
E o **FastAPI** vai manipulá-los, mandar para você os dados corretos na sua função, e validar e documentar o schema correto na *operação de rota*. |
|||
|
|||
Você também pode declarar valores singulares para serem recebidos como parte do corpo. |
|||
|
|||
E você pode instruir o **FastAPI** para requisitar no corpo a indicação de chave mesmo quando existe somente um único parâmetro declarado. |
@ -0,0 +1,138 @@ |
|||
# Parâmetros da Rota e Validações Numéricas |
|||
|
|||
Do mesmo modo que você pode declarar mais validações e metadados para parâmetros de consulta com `Query`, você pode declarar os mesmos tipos de validações e metadados para parâmetros de rota com `Path`. |
|||
|
|||
## Importe `Path` |
|||
|
|||
Primeiro, importe `Path` de `fastapi`: |
|||
|
|||
=== "Python 3.6 e superiores" |
|||
|
|||
```Python hl_lines="3" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 e superiores" |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
## Declare metadados |
|||
|
|||
Você pode declarar todos os parâmetros da mesma maneira que na `Query`. |
|||
|
|||
Por exemplo para declarar um valor de metadado `title` para o parâmetro de rota `item_id` você pode digitar: |
|||
|
|||
=== "Python 3.6 e superiores" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 e superiores" |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
!!! note "Nota" |
|||
Um parâmetro de rota é sempre obrigatório, como se fizesse parte da rota. |
|||
|
|||
Então, você deve declará-lo com `...` para marcá-lo como obrigatório. |
|||
|
|||
Mesmo que você declare-o como `None` ou defina um valor padrão, isso não teria efeito algum, o parâmetro ainda seria obrigatório. |
|||
|
|||
## Ordene os parâmetros de acordo com sua necessidade |
|||
|
|||
Suponha que você queira declarar o parâmetro de consulta `q` como uma `str` obrigatória. |
|||
|
|||
E você não precisa declarar mais nada em relação a este parâmetro, então você não precisa necessariamente usar `Query`. |
|||
|
|||
Mas você ainda precisa usar `Path` para o parâmetro de rota `item_id`. |
|||
|
|||
O Python irá acusar se você colocar um elemento com um valor padrão definido antes de outro que não tenha um valor padrão. |
|||
|
|||
Mas você pode reordená-los, colocando primeiro o elemento sem o valor padrão (o parâmetro de consulta `q`). |
|||
|
|||
Isso não faz diferença para o **FastAPI**. Ele vai detectar os parâmetros pelos seus nomes, tipos e definições padrão (`Query`, `Path`, etc), sem se importar com a ordem. |
|||
|
|||
Então, você pode declarar sua função assim: |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} |
|||
``` |
|||
|
|||
## Ordene os parâmetros de a acordo com sua necessidade, truques |
|||
|
|||
Se você quiser declarar o parâmetro de consulta `q` sem um `Query` nem um valor padrão, e o parâmetro de rota `item_id` usando `Path`, e definí-los em uma ordem diferente, Python tem um pequeno truque na sintaxe para isso. |
|||
|
|||
Passe `*`, como o primeiro parâmetro da função. |
|||
|
|||
O Python não vai fazer nada com esse `*`, mas ele vai saber que a partir dali os parâmetros seguintes deverão ser chamados argumentos nomeados (pares chave-valor), também conhecidos como <abbr title="Do inglês: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Mesmo que eles não possuam um valor padrão. |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} |
|||
``` |
|||
|
|||
## Validações numéricas: maior que ou igual |
|||
|
|||
Com `Query` e `Path` (e outras que você verá mais tarde) você pode declarar restrições numéricas. |
|||
|
|||
Aqui, com `ge=1`, `item_id` precisará ser um número inteiro maior que ("`g`reater than") ou igual ("`e`qual") a 1. |
|||
|
|||
```Python hl_lines="8" |
|||
{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} |
|||
``` |
|||
|
|||
## Validações numéricas: maior que e menor que ou igual |
|||
|
|||
O mesmo se aplica para: |
|||
|
|||
* `gt`: maior que (`g`reater `t`han) |
|||
* `le`: menor que ou igual (`l`ess than or `e`qual) |
|||
|
|||
```Python hl_lines="9" |
|||
{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} |
|||
``` |
|||
|
|||
## Validações numéricas: valores do tipo float, maior que e menor que |
|||
|
|||
Validações numéricas também funcionam para valores do tipo `float`. |
|||
|
|||
Aqui é onde se torna importante a possibilidade de declarar <abbr title="greater than"><code>gt</code></abbr> e não apenas <abbr title="greater than or equal"><code>ge</code></abbr>. Com isso você pode especificar, por exemplo, que um valor deve ser maior que `0`, ainda que seja menor que `1`. |
|||
|
|||
Assim, `0.5` seria um valor válido. Mas `0.0` ou `0` não seria. |
|||
|
|||
E o mesmo para <abbr title="less than"><code>lt</code></abbr>. |
|||
|
|||
```Python hl_lines="11" |
|||
{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} |
|||
``` |
|||
|
|||
## Recapitulando |
|||
|
|||
Com `Query`, `Path` (e outras que você ainda não viu) você pode declarar metadados e validações de texto do mesmo modo que com [Parâmetros de consulta e validações de texto](query-params-str-validations.md){.internal-link target=_blank}. |
|||
|
|||
E você também pode declarar validações numéricas: |
|||
|
|||
* `gt`: maior que (`g`reater `t`han) |
|||
* `ge`: maior que ou igual (`g`reater than or `e`qual) |
|||
* `lt`: menor que (`l`ess `t`han) |
|||
* `le`: menor que ou igual (`l`ess than or `e`qual) |
|||
|
|||
!!! info "Informação" |
|||
`Query`, `Path` e outras classes que você verá a frente são subclasses de uma classe comum `Param`. |
|||
|
|||
Todas elas compartilham os mesmos parâmetros para validação adicional e metadados que você viu. |
|||
|
|||
!!! note "Detalhes Técnicos" |
|||
Quando você importa `Query`, `Path` e outras de `fastapi`, elas são na verdade funções. |
|||
|
|||
Que quando chamadas, retornam instâncias de classes de mesmo nome. |
|||
|
|||
Então, você importa `Query`, que é uma função. E quando você a chama, ela retorna uma instância de uma classe também chamada `Query`. |
|||
|
|||
Estas funções são assim (ao invés de apenas usar as classes diretamente) para que seu editor não acuse erros sobre seus tipos. |
|||
|
|||
Dessa maneira você pode user seu editor e ferramentas de desenvolvimento sem precisar adicionar configurações customizadas para ignorar estes erros. |
@ -0,0 +1,36 @@ |
|||
# Formulários e Arquivos da Requisição |
|||
|
|||
Você pode definir arquivos e campos de formulário ao mesmo tempo usando `File` e `Form`. |
|||
|
|||
!!! info "Informação" |
|||
Para receber arquivos carregados e/ou dados de formulário, primeiro instale <a href="https://andrew-d.github.io/python-multipart/" class="external-link" target="_blank">`python-multipart`</a>. |
|||
|
|||
Por exemplo: `pip install python-multipart`. |
|||
|
|||
|
|||
## Importe `File` e `Form` |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/request_forms_and_files/tutorial001.py!} |
|||
``` |
|||
|
|||
## Defina parâmetros de `File` e `Form` |
|||
|
|||
Crie parâmetros de arquivo e formulário da mesma forma que você faria para `Body` ou `Query`: |
|||
|
|||
```Python hl_lines="8" |
|||
{!../../../docs_src/request_forms_and_files/tutorial001.py!} |
|||
``` |
|||
|
|||
Os arquivos e campos de formulário serão carregados como dados de formulário e você receberá os arquivos e campos de formulário. |
|||
|
|||
E você pode declarar alguns dos arquivos como `bytes` e alguns como `UploadFile`. |
|||
|
|||
!!! warning "Aviso" |
|||
Você pode declarar vários parâmetros `File` e `Form` em uma *operação de caminho*, mas não é possível declarar campos `Body` para receber como JSON, pois a requisição terá o corpo codificado usando `multipart/form-data` ao invés de `application/json`. |
|||
|
|||
Isso não é uma limitação do **FastAPI** , é parte do protocolo HTTP. |
|||
|
|||
## Recapitulando |
|||
|
|||
Usar `File` e `Form` juntos quando precisar receber dados e arquivos na mesma requisição. |
@ -0,0 +1,58 @@ |
|||
# Dados do formulário |
|||
|
|||
Quando você precisar receber campos de formulário ao invés de JSON, você pode usar `Form`. |
|||
|
|||
!!! info "Informação" |
|||
Para usar formulários, primeiro instale <a href="https://andrew-d.github.io/python-multipart/" class="external-link" target="_blank">`python-multipart`</a>. |
|||
|
|||
Ex: `pip install python-multipart`. |
|||
|
|||
## Importe `Form` |
|||
|
|||
Importe `Form` de `fastapi`: |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/request_forms/tutorial001.py!} |
|||
``` |
|||
|
|||
## Declare parâmetros de `Form` |
|||
|
|||
Crie parâmetros de formulário da mesma forma que você faria para `Body` ou `Query`: |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/request_forms/tutorial001.py!} |
|||
``` |
|||
|
|||
Por exemplo, em uma das maneiras que a especificação OAuth2 pode ser usada (chamada "fluxo de senha"), é necessário enviar um `username` e uma `password` como campos do formulário. |
|||
|
|||
A <abbr title="especificação">spec</abbr> exige que os campos sejam exatamente nomeados como `username` e `password` e sejam enviados como campos de formulário, não JSON. |
|||
|
|||
Com `Form` você pode declarar os mesmos metadados e validação que com `Body` (e `Query`, `Path`, `Cookie`). |
|||
|
|||
!!! info "Informação" |
|||
`Form` é uma classe que herda diretamente de `Body`. |
|||
|
|||
!!! tip "Dica" |
|||
Para declarar corpos de formulário, você precisa usar `Form` explicitamente, porque sem ele os parâmetros seriam interpretados como parâmetros de consulta ou parâmetros de corpo (JSON). |
|||
|
|||
## Sobre "Campos de formulário" |
|||
|
|||
A forma como os formulários HTML (`<form></form>`) enviam os dados para o servidor normalmente usa uma codificação "especial" para esses dados, é diferente do JSON. |
|||
|
|||
O **FastAPI** fará a leitura desses dados no lugar certo em vez de JSON. |
|||
|
|||
!!! note "Detalhes técnicos" |
|||
Os dados dos formulários são normalmente codificados usando o "tipo de mídia" `application/x-www-form-urlencoded`. |
|||
|
|||
Mas quando o formulário inclui arquivos, ele é codificado como `multipart/form-data`. Você lerá sobre como lidar com arquivos no próximo capítulo. |
|||
|
|||
Se você quiser ler mais sobre essas codificações e campos de formulário, vá para o <a href="https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs para <code>POST</code></a>. |
|||
|
|||
!!! warning "Aviso" |
|||
Você pode declarar vários parâmetros `Form` em uma *operação de caminho*, mas não pode declarar campos `Body` que espera receber como JSON, pois a solicitação terá o corpo codificado usando `application/x-www- form-urlencoded` em vez de `application/json`. |
|||
|
|||
Esta não é uma limitação do **FastAPI**, é parte do protocolo HTTP. |
|||
|
|||
## Recapitulando |
|||
|
|||
Use `Form` para declarar os parâmetros de entrada de dados de formulário. |
@ -0,0 +1,91 @@ |
|||
# Código de status de resposta |
|||
|
|||
Da mesma forma que você pode especificar um modelo de resposta, você também pode declarar o código de status HTTP usado para a resposta com o parâmetro `status_code` em qualquer uma das *operações de caminho*: |
|||
|
|||
* `@app.get()` |
|||
* `@app.post()` |
|||
* `@app.put()` |
|||
* `@app.delete()` |
|||
* etc. |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/response_status_code/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! note "Nota" |
|||
Observe que `status_code` é um parâmetro do método "decorador" (get, post, etc). Não da sua função de *operação de caminho*, como todos os parâmetros e corpo. |
|||
|
|||
O parâmetro `status_code` recebe um número com o código de status HTTP. |
|||
|
|||
!!! info "Informação" |
|||
`status_code` também pode receber um `IntEnum`, como o do Python <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>. |
|||
|
|||
Dessa forma: |
|||
|
|||
* Este código de status será retornado na resposta. |
|||
* Será documentado como tal no esquema OpenAPI (e, portanto, nas interfaces do usuário): |
|||
|
|||
<img src="/img/tutorial/response-status-code/image01.png"> |
|||
|
|||
!!! note "Nota" |
|||
Alguns códigos de resposta (consulte a próxima seção) indicam que a resposta não possui um corpo. |
|||
|
|||
O FastAPI sabe disso e produzirá documentos OpenAPI informando que não há corpo de resposta. |
|||
|
|||
## Sobre os códigos de status HTTP |
|||
|
|||
!!! note "Nota" |
|||
Se você já sabe o que são códigos de status HTTP, pule para a próxima seção. |
|||
|
|||
Em HTTP, você envia um código de status numérico de 3 dígitos como parte da resposta. |
|||
|
|||
Esses códigos de status têm um nome associado para reconhecê-los, mas o importante é o número. |
|||
|
|||
Resumidamente: |
|||
|
|||
|
|||
* `100` e acima são para "Informações". Você raramente os usa diretamente. As respostas com esses códigos de status não podem ter um corpo. |
|||
* **`200`** e acima são para respostas "Bem-sucedidas". Estes são os que você mais usaria. |
|||
* `200` é o código de status padrão, o que significa que tudo estava "OK". |
|||
* Outro exemplo seria `201`, "Criado". É comumente usado após a criação de um novo registro no banco de dados. |
|||
* Um caso especial é `204`, "Sem Conteúdo". Essa resposta é usada quando não há conteúdo para retornar ao cliente e, portanto, a resposta não deve ter um corpo. |
|||
* **`300`** e acima são para "Redirecionamento". As respostas com esses códigos de status podem ou não ter um corpo, exceto `304`, "Não modificado", que não deve ter um. |
|||
* **`400`** e acima são para respostas de "Erro do cliente". Este é o segundo tipo que você provavelmente mais usaria. |
|||
* Um exemplo é `404`, para uma resposta "Não encontrado". |
|||
* Para erros genéricos do cliente, você pode usar apenas `400`. |
|||
* `500` e acima são para erros do servidor. Você quase nunca os usa diretamente. Quando algo der errado em alguma parte do código do seu aplicativo ou servidor, ele retornará automaticamente um desses códigos de status. |
|||
|
|||
!!! tip "Dica" |
|||
Para saber mais sobre cada código de status e qual código serve para quê, verifique o <a href="https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> documentação sobre códigos de status HTTP</a>. |
|||
|
|||
## Atalho para lembrar os nomes |
|||
|
|||
Vamos ver o exemplo anterior novamente: |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/response_status_code/tutorial001.py!} |
|||
``` |
|||
|
|||
`201` é o código de status para "Criado". |
|||
|
|||
Mas você não precisa memorizar o que cada um desses códigos significa. |
|||
|
|||
Você pode usar as variáveis de conveniência de `fastapi.status`. |
|||
|
|||
```Python hl_lines="1 6" |
|||
{!../../../docs_src/response_status_code/tutorial002.py!} |
|||
``` |
|||
|
|||
Eles são apenas uma conveniência, eles possuem o mesmo número, mas dessa forma você pode usar o autocomplete do editor para encontrá-los: |
|||
|
|||
<img src="/img/tutorial/response-status-code/image02.png"> |
|||
|
|||
!!! note "Detalhes técnicos" |
|||
Você também pode usar `from starlette import status`. |
|||
|
|||
**FastAPI** fornece o mesmo `starlette.status` como `fastapi.status` apenas como uma conveniência para você, o desenvolvedor. Mas vem diretamente da Starlette. |
|||
|
|||
|
|||
## Alterando o padrão |
|||
|
|||
Mais tarde, no [Guia do usuário avançado](../advanced/response-change-status-code.md){.internal-link target=_blank}, você verá como retornar um código de status diferente do padrão que você está declarando aqui. |
@ -0,0 +1,21 @@ |
|||
# Развёртывание - Введение |
|||
|
|||
Развернуть приложение **FastAPI** довольно просто. |
|||
|
|||
## Да что такое это ваше - "развёртывание"?! |
|||
|
|||
Термин **развёртывание** (приложения) означает выполнение необходимых шагов, чтобы сделать приложение **доступным для пользователей**. |
|||
|
|||
Обычно **веб-приложения** размещают на удалённом компьютере с серверной программой, которая обеспечивает хорошую производительность, стабильность и т. д., Чтобы ваши пользователи могли эффективно, беспрерывно и беспроблемно обращаться к приложению. |
|||
|
|||
Это отличется от **разработки**, когда вы постоянно меняете код, делаете в нём намеренные ошибки и исправляете их, останавливаете и перезапускаете сервер разработки и т. д. |
|||
|
|||
## Стратегии развёртывания |
|||
|
|||
В зависимости от вашего конкретного случая, есть несколько способов сделать это. |
|||
|
|||
Вы можете **развернуть сервер** самостоятельно, используя различные инструменты. Например, можно использовать **облачный сервис**, который выполнит часть работы за вас. Также возможны и другие варианты. |
|||
|
|||
В этом блоке я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений). |
|||
|
|||
В последующих разделах вы узнаете больше деталей и методов, необходимых для этого. ✨ |
@ -0,0 +1,37 @@ |
|||
# 包含 WSGI - Flask,Django,其它 |
|||
|
|||
您可以挂载多个 WSGI 应用,正如您在 [Sub Applications - Mounts](./sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](./behind-a-proxy.md){.internal-link target=_blank} 中所看到的那样。 |
|||
|
|||
为此, 您可以使用 `WSGIMiddleware` 来包装你的 WSGI 应用,如:Flask,Django,等等。 |
|||
|
|||
## 使用 `WSGIMiddleware` |
|||
|
|||
您需要导入 `WSGIMiddleware`。 |
|||
|
|||
然后使用该中间件包装 WSGI 应用(例如 Flask)。 |
|||
|
|||
之后将其挂载到某一个路径下。 |
|||
|
|||
```Python hl_lines="2-3 22" |
|||
{!../../../docs_src/wsgi/tutorial001.py!} |
|||
``` |
|||
|
|||
## 检查 |
|||
|
|||
现在,所有定义在 `/v1/` 路径下的请求将会被 Flask 应用处理。 |
|||
|
|||
其余的请求则会被 **FastAPI** 处理。 |
|||
|
|||
如果您使用 Uvicorn 运行应用实例并且访问 <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>,您将会看到由 Flask 返回的响应: |
|||
|
|||
```txt |
|||
Hello, World from Flask! |
|||
``` |
|||
|
|||
并且如果您访问 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>,您将会看到由 FastAPI 返回的响应: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World" |
|||
} |
|||
``` |
@ -0,0 +1,247 @@ |
|||
# 类作为依赖项 |
|||
|
|||
在深入探究 **依赖注入** 系统之前,让我们升级之前的例子。 |
|||
|
|||
## 来自前一个例子的`dict` |
|||
|
|||
在前面的例子中, 我们从依赖项 ("可依赖对象") 中返回了一个 `dict`: |
|||
|
|||
=== "Python 3.6 以及 以上" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/dependencies/tutorial001.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 以及以上" |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/dependencies/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
但是后面我们在路径操作函数的参数 `commons` 中得到了一个 `dict`。 |
|||
|
|||
我们知道编辑器不能为 `dict` 提供很多支持(比如补全),因为编辑器不知道 `dict` 的键和值类型。 |
|||
|
|||
对此,我们可以做的更好... |
|||
|
|||
## 什么构成了依赖项? |
|||
|
|||
到目前为止,您看到的依赖项都被声明为函数。 |
|||
|
|||
但这并不是声明依赖项的唯一方法(尽管它可能是更常见的方法)。 |
|||
|
|||
关键因素是依赖项应该是 "可调用对象"。 |
|||
|
|||
Python 中的 "**可调用对象**" 是指任何 Python 可以像函数一样 "调用" 的对象。 |
|||
|
|||
所以,如果你有一个对象 `something` (可能*不是*一个函数),你可以 "调用" 它(执行它),就像: |
|||
|
|||
```Python |
|||
something() |
|||
``` |
|||
|
|||
或者 |
|||
|
|||
```Python |
|||
something(some_argument, some_keyword_argument="foo") |
|||
``` |
|||
|
|||
这就是 "可调用对象"。 |
|||
|
|||
## 类作为依赖项 |
|||
|
|||
您可能会注意到,要创建一个 Python 类的实例,您可以使用相同的语法。 |
|||
|
|||
举个例子: |
|||
|
|||
```Python |
|||
class Cat: |
|||
def __init__(self, name: str): |
|||
self.name = name |
|||
|
|||
|
|||
fluffy = Cat(name="Mr Fluffy") |
|||
``` |
|||
|
|||
在这个例子中, `fluffy` 是一个 `Cat` 类的实例。 |
|||
|
|||
为了创建 `fluffy`,你调用了 `Cat` 。 |
|||
|
|||
所以,Python 类也是 **可调用对象**。 |
|||
|
|||
因此,在 **FastAPI** 中,你可以使用一个 Python 类作为一个依赖项。 |
|||
|
|||
实际上 FastAPI 检查的是它是一个 "可调用对象"(函数,类或其他任何类型)以及定义的参数。 |
|||
|
|||
如果您在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。包括子依赖项。 |
|||
|
|||
这也适用于完全没有参数的可调用对象。这与不带参数的路径操作函数一样。 |
|||
|
|||
所以,我们可以将上面的依赖项 "可依赖对象" `common_parameters` 更改为类 `CommonQueryParams`: |
|||
|
|||
=== "Python 3.6 以及 以上" |
|||
|
|||
```Python hl_lines="11-15" |
|||
{!> ../../../docs_src/dependencies/tutorial002.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 以及 以上" |
|||
|
|||
```Python hl_lines="9-13" |
|||
{!> ../../../docs_src/dependencies/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
注意用于创建类实例的 `__init__` 方法: |
|||
|
|||
=== "Python 3.6 以及 以上" |
|||
|
|||
```Python hl_lines="12" |
|||
{!> ../../../docs_src/dependencies/tutorial002.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 以及 以上" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/dependencies/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
...它与我们以前的 `common_parameters` 具有相同的参数: |
|||
|
|||
=== "Python 3.6 以及 以上" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/dependencies/tutorial001.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 以及 以上" |
|||
|
|||
```Python hl_lines="6" |
|||
{!> ../../../docs_src/dependencies/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
这些参数就是 **FastAPI** 用来 "处理" 依赖项的。 |
|||
|
|||
在两个例子下,都有: |
|||
|
|||
* 一个可选的 `q` 查询参数,是 `str` 类型。 |
|||
* 一个 `skip` 查询参数,是 `int` 类型,默认值为 `0`。 |
|||
* 一个 `limit` 查询参数,是 `int` 类型,默认值为 `100`。 |
|||
|
|||
在两个例子下,数据都将被转换、验证、在 OpenAPI schema 上文档化,等等。 |
|||
|
|||
## 使用它 |
|||
|
|||
现在,您可以使用这个类来声明你的依赖项了。 |
|||
|
|||
=== "Python 3.6 以及 以上" |
|||
|
|||
```Python hl_lines="19" |
|||
{!> ../../../docs_src/dependencies/tutorial002.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 以及 以上" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/dependencies/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
**FastAPI** 调用 `CommonQueryParams` 类。这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。 |
|||
|
|||
## 类型注解 vs `Depends` |
|||
|
|||
注意,我们在上面的代码中编写了两次`CommonQueryParams`: |
|||
|
|||
```Python |
|||
commons: CommonQueryParams = Depends(CommonQueryParams) |
|||
``` |
|||
|
|||
最后的 `CommonQueryParams`: |
|||
|
|||
```Python |
|||
... = Depends(CommonQueryParams) |
|||
``` |
|||
|
|||
...实际上是 **Fastapi** 用来知道依赖项是什么的。 |
|||
|
|||
FastAPI 将从依赖项中提取声明的参数,这才是 FastAPI 实际调用的。 |
|||
|
|||
--- |
|||
|
|||
在本例中,第一个 `CommonQueryParams` : |
|||
|
|||
```Python |
|||
commons: CommonQueryParams ... |
|||
``` |
|||
|
|||
...对于 **FastAPI** 没有任何特殊的意义。FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `= Depends(CommonQueryParams)`)。 |
|||
|
|||
你实际上可以只这样编写: |
|||
|
|||
```Python |
|||
commons = Depends(CommonQueryParams) |
|||
``` |
|||
|
|||
..就像: |
|||
|
|||
=== "Python 3.6 以及 以上" |
|||
|
|||
```Python hl_lines="19" |
|||
{!> ../../../docs_src/dependencies/tutorial003.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 以及 以上" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/dependencies/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
但是声明类型是被鼓励的,因为那样你的编辑器就会知道将传递什么作为参数 `commons` ,然后它可以帮助你完成代码,类型检查,等等: |
|||
|
|||
<img src="/img/tutorial/dependencies/image02.png"> |
|||
|
|||
## 快捷方式 |
|||
|
|||
但是您可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次: |
|||
|
|||
```Python |
|||
commons: CommonQueryParams = Depends(CommonQueryParams) |
|||
``` |
|||
|
|||
**FastAPI** 为这些情况提供了一个快捷方式,在这些情况下,依赖项 *明确地* 是一个类,**FastAPI** 将 "调用" 它来创建类本身的一个实例。 |
|||
|
|||
对于这些特定的情况,您可以跟随以下操作: |
|||
|
|||
不是写成这样: |
|||
|
|||
```Python |
|||
commons: CommonQueryParams = Depends(CommonQueryParams) |
|||
``` |
|||
|
|||
...而是这样写: |
|||
|
|||
```Python |
|||
commons: CommonQueryParams = Depends() |
|||
``` |
|||
|
|||
您声明依赖项作为参数的类型,并使用 `Depends()` 作为该函数的参数的 "默认" 值(在 `=` 之后),而在 `Depends()` 中没有任何参数,而不是在 `Depends(CommonQueryParams)` 编写完整的类。 |
|||
|
|||
同样的例子看起来像这样: |
|||
|
|||
=== "Python 3.6 以及 以上" |
|||
|
|||
```Python hl_lines="19" |
|||
{!> ../../../docs_src/dependencies/tutorial004.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 以及 以上" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/dependencies/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
... **FastAPI** 会知道怎么处理。 |
|||
|
|||
!!! tip |
|||
如果这看起来更加混乱而不是更加有帮助,那么请忽略它,你不*需要*它。 |
|||
|
|||
这只是一个快捷方式。因为 **FastAPI** 关心的是帮助您减少代码重复。 |
@ -0,0 +1,770 @@ |
|||
# SQL (关系型) 数据库 |
|||
|
|||
**FastAPI**不需要你使用SQL(关系型)数据库。 |
|||
|
|||
但是您可以使用任何您想要的关系型数据库。 |
|||
|
|||
在这里,让我们看一个使用着[SQLAlchemy](https://www.sqlalchemy.org/)的示例。 |
|||
|
|||
您可以很容易地将SQLAlchemy支持任何数据库,像: |
|||
|
|||
* PostgreSQL |
|||
* MySQL |
|||
* SQLite |
|||
* Oracle |
|||
* Microsoft SQL Server,等等其它数据库 |
|||
|
|||
在此示例中,我们将使用**SQLite**,因为它使用单个文件并且 在Python中具有集成支持。因此,您可以复制此示例并按原样来运行它。 |
|||
|
|||
稍后,对于您的产品级别的应用程序,您可能会要使用像**PostgreSQL**这样的数据库服务器。 |
|||
|
|||
!!! tip |
|||
这儿有一个**FastAPI**和**PostgreSQL**的官方项目生成器,全部基于**Docker**,包括前端和更多工具:<a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a> |
|||
|
|||
!!! note |
|||
请注意,大部分代码是`SQLAlchemy`的标准代码,您可以用于任何框架。FastAPI特定的代码和往常一样少。 |
|||
|
|||
## ORMs(对象关系映射) |
|||
|
|||
**FastAPI**可与任何数据库在任何样式的库中一起与 数据库进行通信。 |
|||
|
|||
一种常见的模式是使用“ORM”:对象关系映射。 |
|||
|
|||
ORM 具有在代码和数据库表(“*关系型”)中的**对象**之间转换(“*映射*”)的工具。 |
|||
|
|||
使用 ORM,您通常会在 SQL 数据库中创建一个代表映射的类,该类的每个属性代表一个列,具有名称和类型。 |
|||
|
|||
例如,一个类`Pet`可以表示一个 SQL 表`pets`。 |
|||
|
|||
该类的每个*实例对象都代表数据库中的一行数据。* |
|||
|
|||
又例如,一个对象`orion_cat`(`Pet`的一个实例)可以有一个属性`orion_cat.type`, 对标数据库中的`type`列。并且该属性的值可以是其它,例如`"cat"`。 |
|||
|
|||
这些 ORM 还具有在表或实体之间建立关系的工具(比如创建多表关系)。 |
|||
|
|||
这样,您还可以拥有一个属性`orion_cat.owner`,它包含该宠物所有者的数据,这些数据取自另外一个表。 |
|||
|
|||
因此,`orion_cat.owner.name`可能是该宠物主人的姓名(来自表`owners`中的列`name`)。 |
|||
|
|||
它可能有一个像`"Arquilian"`(一种业务逻辑)。 |
|||
|
|||
当您尝试从您的宠物对象访问它时,ORM 将完成所有工作以从相应的表*所有者那里再获取信息。* |
|||
|
|||
常见的 ORM 例如:Django-ORM(Django 框架的一部分)、SQLAlchemy ORM(SQLAlchemy 的一部分,独立于框架)和 Peewee(独立于框架)等。 |
|||
|
|||
在这里,我们将看到如何使用**SQLAlchemy ORM**。 |
|||
|
|||
以类似的方式,您也可以使用任何其他 ORM。 |
|||
|
|||
!!! tip |
|||
在文档中也有一篇使用 Peewee 的等效的文章。 |
|||
|
|||
## 文件结构 |
|||
|
|||
对于这些示例,假设您有一个名为的目录`my_super_project`,其中包含一个名为的子目录`sql_app`,其结构如下: |
|||
|
|||
``` |
|||
. |
|||
└── sql_app |
|||
├── __init__.py |
|||
├── crud.py |
|||
├── database.py |
|||
├── main.py |
|||
├── models.py |
|||
└── schemas.py |
|||
``` |
|||
|
|||
该文件`__init__.py`只是一个空文件,但它告诉 Python 其中`sql_app`的所有模块(Python 文件)都是一个包。 |
|||
|
|||
现在让我们看看每个文件/模块的作用。 |
|||
|
|||
## 创建 SQLAlchemy 部件 |
|||
|
|||
让我们涉及到文件`sql_app/database.py`。 |
|||
|
|||
### 导入 SQLAlchemy 部件 |
|||
|
|||
```Python hl_lines="1-3" |
|||
{!../../../docs_src/sql_databases/sql_app/database.py!} |
|||
``` |
|||
|
|||
### 为 SQLAlchemy 定义数据库 URL地址 |
|||
|
|||
```Python hl_lines="5-6" |
|||
{!../../../docs_src/sql_databases/sql_app/database.py!} |
|||
``` |
|||
|
|||
在这个例子中,我们正在“连接”到一个 SQLite 数据库(用 SQLite 数据库打开一个文件)。 |
|||
|
|||
该文件将位于文件中的同一目录中`sql_app.db`。 |
|||
|
|||
这就是为什么最后一部分是`./sql_app.db`. |
|||
|
|||
如果您使用的是**PostgreSQL**数据库,则只需取消注释该行: |
|||
|
|||
```Python |
|||
SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" |
|||
``` |
|||
|
|||
...并根据您的数据库数据和相关凭据(也适用于 MySQL、MariaDB 或任何其他)对其进行调整。 |
|||
|
|||
!!! tip |
|||
|
|||
如果您想使用不同的数据库,这是就是您必须修改的地方。 |
|||
|
|||
### 创建 SQLAlchemy 引擎 |
|||
|
|||
第一步,创建一个 SQLAlchemy的“引擎”。 |
|||
|
|||
我们稍后会将这个`engine`在其他地方使用。 |
|||
|
|||
```Python hl_lines="8-10" |
|||
{!../../../docs_src/sql_databases/sql_app/database.py!} |
|||
``` |
|||
|
|||
#### 注意 |
|||
|
|||
参数: |
|||
|
|||
```Python |
|||
connect_args={"check_same_thread": False} |
|||
``` |
|||
|
|||
...仅用于`SQLite`,在其他数据库不需要它。 |
|||
|
|||
!!! info "技术细节" |
|||
|
|||
默认情况下,SQLite 只允许一个线程与其通信,假设有多个线程的话,也只将处理一个独立的请求。 |
|||
|
|||
这是为了防止意外地为不同的事物(不同的请求)共享相同的连接。 |
|||
|
|||
但是在 FastAPI 中,普遍使用def函数,多个线程可以为同一个请求与数据库交互,所以我们需要使用`connect_args={"check_same_thread": False}`来让SQLite允许这样。 |
|||
|
|||
此外,我们将确保每个请求都在依赖项中获得自己的数据库连接会话,因此不需要该默认机制。 |
|||
|
|||
### 创建一个`SessionLocal`类 |
|||
|
|||
每个实例`SessionLocal`都会是一个数据库会话。当然该类本身还不是数据库会话。 |
|||
|
|||
但是一旦我们创建了一个`SessionLocal`类的实例,这个实例将是实际的数据库会话。 |
|||
|
|||
我们命名它是`SessionLocal`为了将它与我们从 SQLAlchemy 导入的`Session`区别开来。 |
|||
|
|||
稍后我们将使用`Session`(从 SQLAlchemy 导入的那个)。 |
|||
|
|||
要创建`SessionLocal`类,请使用函数`sessionmaker`: |
|||
|
|||
```Python hl_lines="11" |
|||
{!../../../docs_src/sql_databases/sql_app/database.py!} |
|||
``` |
|||
|
|||
### 创建一个`Base`类 |
|||
|
|||
现在我们将使用`declarative_base()`返回一个类。 |
|||
|
|||
稍后我们将用这个类继承,来创建每个数据库模型或类(ORM 模型): |
|||
|
|||
```Python hl_lines="13" |
|||
{!../../../docs_src/sql_databases/sql_app/database.py!} |
|||
``` |
|||
|
|||
## 创建数据库模型 |
|||
|
|||
现在让我们看看文件`sql_app/models.py`。 |
|||
|
|||
### 用`Base`类来创建 SQLAlchemy 模型 |
|||
|
|||
我们将使用我们之前创建的`Base`类来创建 SQLAlchemy 模型。 |
|||
|
|||
!!! tip |
|||
SQLAlchemy 使用的“**模型**”这个术语 来指代与数据库交互的这些类和实例。 |
|||
|
|||
而 Pydantic 也使用“模型”这个术语 来指代不同的东西,即数据验证、转换以及文档类和实例。 |
|||
|
|||
从`database`(来自上面的`database.py`文件)导入`Base`。 |
|||
|
|||
创建从它继承的类。 |
|||
|
|||
这些类就是 SQLAlchemy 模型。 |
|||
|
|||
```Python hl_lines="4 7-8 18-19" |
|||
{!../../../docs_src/sql_databases/sql_app/models.py!} |
|||
``` |
|||
|
|||
这个`__tablename__`属性是用来告诉 SQLAlchemy 要在数据库中为每个模型使用的数据库表的名称。 |
|||
|
|||
### 创建模型属性/列 |
|||
|
|||
现在创建所有模型(类)属性。 |
|||
|
|||
这些属性中的每一个都代表其相应数据库表中的一列。 |
|||
|
|||
我们使用`Column`来表示 SQLAlchemy 中的默认值。 |
|||
|
|||
我们传递一个 SQLAlchemy “类型”,如`Integer`、`String`和`Boolean`,它定义了数据库中的类型,作为参数。 |
|||
|
|||
```Python hl_lines="1 10-13 21-24" |
|||
{!../../../docs_src/sql_databases/sql_app/models.py!} |
|||
``` |
|||
|
|||
### 创建关系 |
|||
|
|||
现在创建关系。 |
|||
|
|||
为此,我们使用SQLAlchemy ORM提供的`relationship`。 |
|||
|
|||
这将或多或少会成为一种“神奇”属性,其中表示该表与其他相关的表中的值。 |
|||
|
|||
```Python hl_lines="2 15 26" |
|||
{!../../../docs_src/sql_databases/sql_app/models.py!} |
|||
``` |
|||
|
|||
当访问 user 中的属性`items`时,如 中`my_user.items`,它将有一个`Item`SQLAlchemy 模型列表(来自`items`表),这些模型具有指向`users`表中此记录的外键。 |
|||
|
|||
当您访问`my_user.items`时,SQLAlchemy 实际上会从`items`表中的获取一批记录并在此处填充进去。 |
|||
|
|||
同样,当访问 Item中的属性`owner`时,它将包含表中的`User`SQLAlchemy 模型`users`。使用`owner_id`属性/列及其外键来了解要从`users`表中获取哪条记录。 |
|||
|
|||
## 创建 Pydantic 模型 |
|||
|
|||
现在让我们查看一下文件`sql_app/schemas.py`。 |
|||
|
|||
!!! tip |
|||
为了避免 SQLAlchemy*模型*和 Pydantic*模型*之间的混淆,我们将有`models.py`(SQLAlchemy 模型的文件)和`schemas.py`( Pydantic 模型的文件)。 |
|||
|
|||
这些 Pydantic 模型或多或少地定义了一个“schema”(一个有效的数据形状)。 |
|||
|
|||
因此,这将帮助我们在使用两者时避免混淆。 |
|||
|
|||
### 创建初始 Pydantic*模型*/模式 |
|||
|
|||
创建一个`ItemBase`和`UserBase`Pydantic*模型*(或者我们说“schema”)以及在创建或读取数据时具有共同的属性。 |
|||
|
|||
`ItemCreate`为 创建一个`UserCreate`继承自它们的所有属性(因此它们将具有相同的属性),以及创建所需的任何其他数据(属性)。 |
|||
|
|||
因此在创建时也应当有一个`password`属性。 |
|||
|
|||
但是为了安全起见,`password`不会出现在其他同类 Pydantic*模型*中,例如用户请求时不应该从 API 返回响应中包含它。 |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="3 6-8 11-12 23-24 27-28" |
|||
{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="3 6-8 11-12 23-24 27-28" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 及以上版本" |
|||
|
|||
```Python hl_lines="1 4-6 9-10 21-22 25-26" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} |
|||
``` |
|||
|
|||
#### SQLAlchemy 风格和 Pydantic 风格 |
|||
|
|||
请注意,SQLAlchemy*模型*使用 `=`来定义属性,并将类型作为参数传递给`Column`,例如: |
|||
|
|||
```Python |
|||
name = Column(String) |
|||
``` |
|||
|
|||
虽然 Pydantic*模型*使用`:` 声明类型,但新的类型注释语法/类型提示是: |
|||
|
|||
```Python |
|||
name: str |
|||
``` |
|||
|
|||
请牢记这一点,这样您在使用`:`还是`=`时就不会感到困惑。 |
|||
|
|||
### 创建用于读取/返回的Pydantic*模型/模式* |
|||
|
|||
现在创建当从 API 返回数据时、将在读取数据时使用的Pydantic*模型(schemas)。* |
|||
|
|||
例如,在创建一个项目之前,我们不知道分配给它的 ID 是什么,但是在读取它时(从 API 返回时)我们已经知道它的 ID。 |
|||
|
|||
同样,当读取用户时,我们现在可以声明`items`,将包含属于该用户的项目。 |
|||
|
|||
不仅是这些项目的 ID,还有我们在 Pydantic*模型*中定义的用于读取项目的所有数据:`Item`. |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="15-17 31-34" |
|||
{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="15-17 31-34" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 及以上版本" |
|||
|
|||
```Python hl_lines="13-15 29-32" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} |
|||
``` |
|||
|
|||
!!! tip |
|||
请注意,读取用户(从 API 返回)时将使用不包括`password`的`User` Pydantic*模型*。 |
|||
|
|||
### 使用 Pydantic 的`orm_mode` |
|||
|
|||
现在,在用于查询的 Pydantic*模型*`Item`中`User`,添加一个内部`Config`类。 |
|||
|
|||
此类[`Config`](https://pydantic-docs.helpmanual.io/usage/model_config/)用于为 Pydantic 提供配置。 |
|||
|
|||
在`Config`类中,设置属性`orm_mode = True`。 |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="15 19-20 31 36-37" |
|||
{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="15 19-20 31 36-37" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 及以上版本" |
|||
|
|||
```Python hl_lines="13 17-18 29 34-35" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} |
|||
``` |
|||
|
|||
!!! tip |
|||
请注意,它使用`=`分配一个值,例如: |
|||
|
|||
`orm_mode = True` |
|||
|
|||
它不使用之前的`:`来类型声明。 |
|||
|
|||
这是设置配置值,而不是声明类型。 |
|||
|
|||
Pydantic`orm_mode`将告诉 Pydantic*模型*读取数据,即它不是一个`dict`,而是一个 ORM 模型(或任何其他具有属性的任意对象)。 |
|||
|
|||
这样,而不是仅仅试图从`dict`上 `id` 中获取值,如下所示: |
|||
|
|||
```Python |
|||
id = data["id"] |
|||
``` |
|||
|
|||
尝试从属性中获取它,如: |
|||
|
|||
```Python |
|||
id = data.id |
|||
``` |
|||
|
|||
有了这个,Pydantic*模型*与 ORM 兼容,您只需在*路径操作*`response_model`的参数中声明它即可。 |
|||
|
|||
您将能够返回一个数据库模型,它将从中读取数据。 |
|||
|
|||
#### ORM 模式的技术细节 |
|||
|
|||
SQLAlchemy 和许多其他默认情况下是“延迟加载”。 |
|||
|
|||
这意味着,例如,除非您尝试访问包含该数据的属性,否则它们不会从数据库中获取关系数据。 |
|||
|
|||
例如,访问属性`items`: |
|||
|
|||
```Python |
|||
current_user.items |
|||
``` |
|||
|
|||
将使 SQLAlchemy 转到`items`表并获取该用户的项目,在调用`.items`之前不会去查询数据库。 |
|||
|
|||
没有`orm_mode`,如果您从*路径操作*返回一个 SQLAlchemy 模型,它不会包含关系数据。 |
|||
|
|||
即使您在 Pydantic 模型中声明了这些关系,也没有用处。 |
|||
|
|||
但是在 ORM 模式下,由于 Pydantic 本身会尝试从属性访问它需要的数据(而不是假设为 `dict`),你可以声明你想要返回的特定数据,它甚至可以从 ORM 中获取它。 |
|||
|
|||
## CRUD工具 |
|||
|
|||
现在让我们看看文件`sql_app/crud.py`。 |
|||
|
|||
在这个文件中,我们将编写可重用的函数用来与数据库中的数据进行交互。 |
|||
|
|||
**CRUD**分别为:**增加**、**查询**、**更改**和**删除**,即增删改查。 |
|||
|
|||
...虽然在这个例子中我们只是新增和查询。 |
|||
|
|||
### 读取数据 |
|||
|
|||
从 `sqlalchemy.orm`中导入`Session`,这将允许您声明`db`参数的类型,并在您的函数中进行更好的类型检查和完成。 |
|||
|
|||
导入之前的`models`(SQLAlchemy 模型)和`schemas`(Pydantic*模型*/模式)。 |
|||
|
|||
创建一些实用函数来完成: |
|||
|
|||
* 通过 ID 和电子邮件查询单个用户。 |
|||
* 查询多个用户。 |
|||
* 查询多个项目。 |
|||
|
|||
```Python hl_lines="1 3 6-7 10-11 14-15 27-28" |
|||
{!../../../docs_src/sql_databases/sql_app/crud.py!} |
|||
``` |
|||
|
|||
!!! tip |
|||
通过创建仅专用于与数据库交互(获取用户或项目)的函数,独立于*路径操作函数*,您可以更轻松地在多个部分中重用它们,并为它们添加单元测试。 |
|||
|
|||
### 创建数据 |
|||
|
|||
现在创建实用程序函数来创建数据。 |
|||
|
|||
它的步骤是: |
|||
|
|||
* 使用您的数据创建一个 SQLAlchemy 模型*实例。* |
|||
* 使用`add`来将该实例对象添加到您的数据库。 |
|||
* 使用`commit`来对数据库的事务提交(以便保存它们)。 |
|||
* 使用`refresh`来刷新您的数据库实例(以便它包含来自数据库的任何新数据,例如生成的 ID)。 |
|||
|
|||
```Python hl_lines="18-24 31-36" |
|||
{!../../../docs_src/sql_databases/sql_app/crud.py!} |
|||
``` |
|||
|
|||
!!! tip |
|||
SQLAlchemy 模型`User`包含一个`hashed_password`,它应该是一个包含散列的安全密码。 |
|||
|
|||
但由于 API 客户端提供的是原始密码,因此您需要将其提取并在应用程序中生成散列密码。 |
|||
|
|||
然后将hashed_password参数与要保存的值一起传递。 |
|||
|
|||
!!! warning |
|||
此示例不安全,密码未经过哈希处理。 |
|||
|
|||
在现实生活中的应用程序中,您需要对密码进行哈希处理,并且永远不要以明文形式保存它们。 |
|||
|
|||
有关更多详细信息,请返回教程中的安全部分。 |
|||
|
|||
在这里,我们只关注数据库的工具和机制。 |
|||
|
|||
!!! tip |
|||
这里不是将每个关键字参数传递给Item并从Pydantic模型中读取每个参数,而是先生成一个字典,其中包含Pydantic模型的数据: |
|||
|
|||
`item.dict()` |
|||
|
|||
然后我们将dict的键值对 作为关键字参数传递给 SQLAlchemy `Item`: |
|||
|
|||
`Item(**item.dict())` |
|||
|
|||
然后我们传递 Pydantic模型未提供的额外关键字参数`owner_id`: |
|||
|
|||
`Item(**item.dict(), owner_id=user_id)` |
|||
|
|||
## 主**FastAPI**应用程序 |
|||
|
|||
现在在`sql_app/main.py`文件中 让我们集成和使用我们之前创建的所有其他部分。 |
|||
|
|||
### 创建数据库表 |
|||
|
|||
以非常简单的方式创建数据库表: |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/sql_databases/sql_app/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} |
|||
``` |
|||
|
|||
#### Alembic 注意 |
|||
|
|||
通常你可能会使用 <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a>,来进行格式化数据库(创建表等)。 |
|||
|
|||
而且您还可以将 Alembic 用于“迁移”(这是它的主要工作)。 |
|||
|
|||
“迁移”是每当您更改 SQLAlchemy 模型的结构、添加新属性等以在数据库中复制这些更改、添加新列、新表等时所需的一组步骤。 |
|||
|
|||
您可以在[Project Generation - Template](https://fastapi.tiangolo.com/zh/project-generation/)的模板中找到一个 FastAPI 项目中的 Alembic 示例。具体在[`alembic`代码目录中](https://github.com/tiangolo/full-stack-fastapi-postgresql/tree/master/%7B%7Bcookiecutter.project_slug%7D%7D/backend/app/alembic/)。 |
|||
|
|||
### 创建依赖项 |
|||
|
|||
现在使用我们在`sql_app/database.py`文件中创建的`SessionLocal`来创建依赖项。 |
|||
|
|||
我们需要每个请求有一个独立的数据库会话/连接(`SessionLocal`),在所有请求中使用相同的会话,然后在请求完成后关闭它。 |
|||
|
|||
然后将为下一个请求创建一个新会话。 |
|||
|
|||
为此,我们将创建一个新的依赖项`yield`,正如前面关于[Dependencies with`yield`](https://fastapi.tiangolo.com/zh/tutorial/dependencies/dependencies-with-yield/)的部分中所解释的那样。 |
|||
|
|||
我们的依赖项将创建一个新的 SQLAlchemy `SessionLocal`,它将在单个请求中使用,然后在请求完成后关闭它。 |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="15-20" |
|||
{!> ../../../docs_src/sql_databases/sql_app/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="13-18" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} |
|||
``` |
|||
|
|||
!!! info |
|||
我们将`SessionLocal()`请求的创建和处理放在一个`try`块中。 |
|||
|
|||
然后我们在finally块中关闭它。 |
|||
|
|||
通过这种方式,我们确保数据库会话在请求后始终关闭。即使在处理请求时出现异常。 |
|||
|
|||
但是您不能从退出代码中引发另一个异常(在yield之后)。可以查阅 [Dependencies with yield and HTTPException](https://fastapi.tiangolo.com/zh/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-httpexception) |
|||
|
|||
*然后,当在路径操作函数*中使用依赖项时,我们使用`Session`,直接从 SQLAlchemy 导入的类型声明它。 |
|||
|
|||
*这将为我们在路径操作函数*中提供更好的编辑器支持,因为编辑器将知道`db`参数的类型`Session`: |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="24 32 38 47 53" |
|||
{!> ../../../docs_src/sql_databases/sql_app/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="22 30 36 45 51" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} |
|||
``` |
|||
|
|||
!!! info "技术细节" |
|||
参数`db`实际上是 type `SessionLocal`,但是这个类(用 创建`sessionmaker()`)是 SQLAlchemy 的“代理” `Session`,所以,编辑器并不真正知道提供了哪些方法。 |
|||
|
|||
但是通过将类型声明为Session,编辑器现在可以知道可用的方法(.add()、.query()、.commit()等)并且可以提供更好的支持(比如完成)。类型声明不影响实际对象。 |
|||
|
|||
### 创建您的**FastAPI** *路径操作* |
|||
|
|||
现在,到了最后,编写标准的**FastAPI** *路径操作*代码。 |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="23-28 31-34 37-42 45-49 52-55" |
|||
{!> ../../../docs_src/sql_databases/sql_app/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="21-26 29-32 35-40 43-47 50-53" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} |
|||
``` |
|||
|
|||
我们在依赖项中的每个请求之前利用`yield`创建数据库会话,然后关闭它。 |
|||
|
|||
所以我们就可以在*路径操作函数*中创建需要的依赖,就能直接获取会话。 |
|||
|
|||
这样,我们就可以直接从*路径操作函数*内部调用`crud.get_user`并使用该会话,来进行对数据库操作。 |
|||
|
|||
!!! tip |
|||
请注意,您返回的值是 SQLAlchemy 模型或 SQLAlchemy 模型列表。 |
|||
|
|||
但是由于所有路径操作的response_model都使用 Pydantic模型/使用orm_mode模式,因此您的 Pydantic 模型中声明的数据将从它们中提取并返回给客户端,并进行所有正常的过滤和验证。 |
|||
|
|||
!!! tip |
|||
另请注意,`response_models`应当是标准 Python 类型,例如`List[schemas.Item]`. |
|||
|
|||
但是由于它的内容/参数List是一个 使用orm_mode模式的Pydantic模型,所以数据将被正常检索并返回给客户端,所以没有问题。 |
|||
|
|||
### 关于 `def` 对比 `async def` |
|||
|
|||
*在这里,我们在路径操作函数*和依赖项中都使用着 SQLAlchemy 模型,它将与外部数据库进行通信。 |
|||
|
|||
这会需要一些“等待时间”。 |
|||
|
|||
但是由于 SQLAlchemy 不具有`await`直接使用的兼容性,因此类似于: |
|||
|
|||
```Python |
|||
user = await db.query(User).first() |
|||
``` |
|||
|
|||
...相反,我们可以使用: |
|||
|
|||
```Python |
|||
user = db.query(User).first() |
|||
``` |
|||
|
|||
然后我们应该声明*路径操作函数*和不带 的依赖关系`async def`,只需使用普通的`def`,如下: |
|||
|
|||
```Python hl_lines="2" |
|||
@app.get("/users/{user_id}", response_model=schemas.User) |
|||
def read_user(user_id: int, db: Session = Depends(get_db)): |
|||
db_user = crud.get_user(db, user_id=user_id) |
|||
... |
|||
``` |
|||
|
|||
!!! info |
|||
如果您需要异步连接到关系数据库,请参阅[Async SQL (Relational) Databases](https://fastapi.tiangolo.com/zh/advanced/async-sql-databases/) |
|||
|
|||
!!! note "Very Technical Details" |
|||
如果您很好奇并且拥有深厚的技术知识,您可以在[Async](https://fastapi.tiangolo.com/zh/async/#very-technical-details)文档中查看有关如何处理 `async def`于`def`差别的技术细节。 |
|||
|
|||
## 迁移 |
|||
|
|||
因为我们直接使用 SQLAlchemy,并且我们不需要任何类型的插件来使用**FastAPI**,所以我们可以直接将数据库迁移至[Alembic](https://alembic.sqlalchemy.org/)进行集成。 |
|||
|
|||
由于与 SQLAlchemy 和 SQLAlchemy 模型相关的代码位于单独的独立文件中,您甚至可以使用 Alembic 执行迁移,而无需安装 FastAPI、Pydantic 或其他任何东西。 |
|||
|
|||
同样,您将能够在与**FastAPI**无关的代码的其他部分中使用相同的 SQLAlchemy 模型和实用程序。 |
|||
|
|||
例如,在具有[Celery](https://docs.celeryq.dev/)、[RQ](https://python-rq.org/)或[ARQ](https://arq-docs.helpmanual.io/)的后台任务工作者中。 |
|||
|
|||
## 审查所有文件 |
|||
|
|||
最后回顾整个案例,您应该有一个名为的目录`my_super_project`,其中包含一个名为`sql_app`。 |
|||
|
|||
`sql_app`中应该有以下文件: |
|||
|
|||
* `sql_app/__init__.py`:这是一个空文件。 |
|||
|
|||
* `sql_app/database.py`: |
|||
|
|||
```Python |
|||
{!../../../docs_src/sql_databases/sql_app/database.py!} |
|||
``` |
|||
|
|||
* `sql_app/models.py`: |
|||
|
|||
```Python |
|||
{!../../../docs_src/sql_databases/sql_app/models.py!} |
|||
``` |
|||
|
|||
* `sql_app/schemas.py`: |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10 及以上版本" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} |
|||
``` |
|||
|
|||
* `sql_app/crud.py`: |
|||
|
|||
```Python |
|||
{!../../../docs_src/sql_databases/sql_app/crud.py!} |
|||
``` |
|||
|
|||
* `sql_app/main.py`: |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/sql_databases/sql_app/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} |
|||
``` |
|||
|
|||
## 执行项目 |
|||
|
|||
您可以复制这些代码并按原样使用它。 |
|||
|
|||
!!! info |
|||
|
|||
事实上,这里的代码只是大多数测试代码的一部分。 |
|||
|
|||
你可以用 Uvicorn 运行它: |
|||
|
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn sql_app.main:app --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
打开浏览器进入 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs。</a> |
|||
|
|||
您将能够与您的**FastAPI**应用程序交互,从真实数据库中读取数据: |
|||
|
|||
<img src="/img/tutorial/sql-databases/image01.png"> |
|||
|
|||
## 直接与数据库交互 |
|||
|
|||
如果您想独立于 FastAPI 直接浏览 SQLite 数据库(文件)以调试其内容、添加表、列、记录、修改数据等,您可以使用[SQLite 的 DB Browser](https://sqlitebrowser.org/) |
|||
|
|||
它看起来像这样: |
|||
|
|||
<img src="/img/tutorial/sql-databases/image02.png"> |
|||
|
|||
您还可以使用[SQLite Viewer](https://inloop.github.io/sqlite-viewer/)或[ExtendsClass](https://extendsclass.com/sqlite-browser.html)等在线 SQLite 浏览器。 |
|||
|
|||
## 中间件替代数据库会话 |
|||
|
|||
如果你不能使用依赖项`yield`——例如,如果你没有使用**Python 3.7**并且不能安装上面提到的**Python 3.6**的“backports” ——你可以在类似的“中间件”中设置会话方法。 |
|||
|
|||
“中间件”基本功能是一个为每个请求执行的函数在请求之前进行执行相应的代码,以及在请求执行之后执行相应的代码。 |
|||
|
|||
### 创建中间件 |
|||
|
|||
我们将添加中间件(只是一个函数)将为每个请求创建一个新的 SQLAlchemy`SessionLocal`,将其添加到请求中,然后在请求完成后关闭它。 |
|||
|
|||
=== "Python 3.6 及以上版本" |
|||
|
|||
```Python hl_lines="14-22" |
|||
{!> ../../../docs_src/sql_databases/sql_app/alt_main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9 及以上版本" |
|||
|
|||
```Python hl_lines="12-20" |
|||
{!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!} |
|||
``` |
|||
|
|||
!!! info |
|||
我们将`SessionLocal()`请求的创建和处理放在一个`try`块中。 |
|||
|
|||
然后我们在finally块中关闭它。 |
|||
|
|||
通过这种方式,我们确保数据库会话在请求后始终关闭,即使在处理请求时出现异常也会关闭。 |
|||
|
|||
### 关于`request.state` |
|||
|
|||
`request.state`是每个`Request`对象的属性。它用于存储附加到请求本身的任意对象,例如本例中的数据库会话。您可以在[Starlette 的关于`Request`state](https://www.starlette.io/requests/#other-state)的文档中了解更多信息。 |
|||
|
|||
对于这种情况下,它帮助我们确保在所有请求中使用单个数据库会话,然后关闭(在中间件中)。 |
|||
|
|||
### 使用`yield`依赖项与使用中间件的区别 |
|||
|
|||
在此处添加**中间件**与`yield`的依赖项的作用效果类似,但也有一些区别: |
|||
|
|||
* 中间件需要更多的代码并且更复杂一些。 |
|||
* 中间件必须是一个`async`函数。 |
|||
* 如果其中有代码必须“等待”网络,它可能会在那里“阻止”您的应用程序并稍微降低性能。 |
|||
* 尽管这里的`SQLAlchemy`工作方式可能不是很成问题。 |
|||
* 但是,如果您向等待大量I/O的中间件添加更多代码,则可能会出现问题。 |
|||
* *每个*请求都会运行一个中间件。 |
|||
* 将为每个请求创建一个连接。 |
|||
* 即使处理该请求的*路径操作*不需要数据库。 |
|||
|
|||
!!! tip |
|||
`tyield`当依赖项 足以满足用例时,使用`tyield`依赖项方法会更好。 |
|||
|
|||
!!! info |
|||
`yield`的依赖项是最近刚加入**FastAPI**中的。 |
|||
|
|||
所以本教程的先前版本只有带有中间件的示例,并且可能有多个应用程序使用中间件进行数据库会话管理。 |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue