committed by
GitHub
386 changed files with 39027 additions and 33638 deletions
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 5.6 KiB |
@ -0,0 +1,469 @@ |
|||
<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+event%3Apush+branch%3Amaster" target="_blank"> |
|||
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg?event=push&branch=master" alt="Test"> |
|||
</a> |
|||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/fastapi" target="_blank"> |
|||
<img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/fastapi.svg" 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> |
|||
<a href="https://pypi.org/project/fastapi" target="_blank"> |
|||
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions"> |
|||
</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.7+ 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> |
|||
|
|||
--- |
|||
|
|||
"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._" |
|||
|
|||
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" 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://github.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 Union |
|||
|
|||
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} |
|||
``` |
|||
|
|||
<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 Union |
|||
|
|||
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: Union[str, None] = 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 Union |
|||
|
|||
from fastapi import FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Item(BaseModel): |
|||
name: str |
|||
price: float |
|||
is_offer: Union[bool, None] = None |
|||
|
|||
|
|||
@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} |
|||
|
|||
|
|||
@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.7+**. |
|||
|
|||
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). |
|||
* **GraphQL** integration with <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> and other libraries. |
|||
* Many extra features (thanks to Starlette) as: |
|||
* **WebSockets** |
|||
* extremely easy tests based on HTTPX 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://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,157 @@ |
|||
site_name: FastAPI |
|||
site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production |
|||
site_url: https://fastapi.tiangolo.com/lo/ |
|||
theme: |
|||
name: material |
|||
custom_dir: overrides |
|||
palette: |
|||
- media: '(prefers-color-scheme: light)' |
|||
scheme: default |
|||
primary: teal |
|||
accent: amber |
|||
toggle: |
|||
icon: material/lightbulb |
|||
name: Switch to light mode |
|||
- media: '(prefers-color-scheme: dark)' |
|||
scheme: slate |
|||
primary: teal |
|||
accent: amber |
|||
toggle: |
|||
icon: material/lightbulb-outline |
|||
name: Switch to dark mode |
|||
features: |
|||
- search.suggest |
|||
- search.highlight |
|||
- content.tabs.link |
|||
icon: |
|||
repo: fontawesome/brands/github-alt |
|||
logo: https://fastapi.tiangolo.com/img/icon-white.svg |
|||
favicon: https://fastapi.tiangolo.com/img/favicon.png |
|||
language: en |
|||
repo_name: tiangolo/fastapi |
|||
repo_url: https://github.com/tiangolo/fastapi |
|||
edit_uri: '' |
|||
plugins: |
|||
- search |
|||
- markdownextradata: |
|||
data: data |
|||
nav: |
|||
- FastAPI: index.md |
|||
- Languages: |
|||
- en: / |
|||
- az: /az/ |
|||
- de: /de/ |
|||
- em: /em/ |
|||
- es: /es/ |
|||
- fa: /fa/ |
|||
- fr: /fr/ |
|||
- he: /he/ |
|||
- hy: /hy/ |
|||
- id: /id/ |
|||
- it: /it/ |
|||
- ja: /ja/ |
|||
- ko: /ko/ |
|||
- lo: /lo/ |
|||
- nl: /nl/ |
|||
- pl: /pl/ |
|||
- pt: /pt/ |
|||
- ru: /ru/ |
|||
- sq: /sq/ |
|||
- sv: /sv/ |
|||
- ta: /ta/ |
|||
- tr: /tr/ |
|||
- uk: /uk/ |
|||
- zh: /zh/ |
|||
markdown_extensions: |
|||
- toc: |
|||
permalink: true |
|||
- markdown.extensions.codehilite: |
|||
guess_lang: false |
|||
- mdx_include: |
|||
base_path: docs |
|||
- admonition |
|||
- codehilite |
|||
- extra |
|||
- pymdownx.superfences: |
|||
custom_fences: |
|||
- name: mermaid |
|||
class: mermaid |
|||
format: !!python/name:pymdownx.superfences.fence_code_format '' |
|||
- pymdownx.tabbed: |
|||
alternate_style: true |
|||
- attr_list |
|||
- md_in_html |
|||
extra: |
|||
analytics: |
|||
provider: google |
|||
property: G-YNEVN69SC3 |
|||
social: |
|||
- icon: fontawesome/brands/github-alt |
|||
link: https://github.com/tiangolo/fastapi |
|||
- icon: fontawesome/brands/discord |
|||
link: https://discord.gg/VQjSZaeJmf |
|||
- icon: fontawesome/brands/twitter |
|||
link: https://twitter.com/fastapi |
|||
- icon: fontawesome/brands/linkedin |
|||
link: https://www.linkedin.com/in/tiangolo |
|||
- icon: fontawesome/brands/dev |
|||
link: https://dev.to/tiangolo |
|||
- icon: fontawesome/brands/medium |
|||
link: https://medium.com/@tiangolo |
|||
- icon: fontawesome/solid/globe |
|||
link: https://tiangolo.com |
|||
alternate: |
|||
- link: / |
|||
name: en - English |
|||
- link: /az/ |
|||
name: az |
|||
- link: /de/ |
|||
name: de |
|||
- link: /em/ |
|||
name: 😉 |
|||
- link: /es/ |
|||
name: es - español |
|||
- link: /fa/ |
|||
name: fa |
|||
- link: /fr/ |
|||
name: fr - français |
|||
- link: /he/ |
|||
name: he |
|||
- link: /hy/ |
|||
name: hy |
|||
- link: /id/ |
|||
name: id |
|||
- link: /it/ |
|||
name: it - italiano |
|||
- link: /ja/ |
|||
name: ja - 日本語 |
|||
- link: /ko/ |
|||
name: ko - 한국어 |
|||
- link: /lo/ |
|||
name: lo - ພາສາລາວ |
|||
- link: /nl/ |
|||
name: nl |
|||
- link: /pl/ |
|||
name: pl |
|||
- link: /pt/ |
|||
name: pt - português |
|||
- link: /ru/ |
|||
name: ru - русский язык |
|||
- link: /sq/ |
|||
name: sq - shqip |
|||
- link: /sv/ |
|||
name: sv - svenska |
|||
- link: /ta/ |
|||
name: ta - தமிழ் |
|||
- link: /tr/ |
|||
name: tr - Türkçe |
|||
- link: /uk/ |
|||
name: uk - українська мова |
|||
- link: /zh/ |
|||
name: zh - 汉语 |
|||
extra_css: |
|||
- https://fastapi.tiangolo.com/css/termynal.css |
|||
- https://fastapi.tiangolo.com/css/custom.css |
|||
extra_javascript: |
|||
- https://fastapi.tiangolo.com/js/termynal.js |
|||
- https://fastapi.tiangolo.com/js/custom.js |
@ -0,0 +1,163 @@ |
|||
# Eventos de vida útil |
|||
|
|||
Você pode definir a lógica (código) que poderia ser executada antes da aplicação **inicializar**. Isso significa que esse código será executado **uma vez**, **antes** da aplicação **começar a receber requisições**. |
|||
|
|||
Do mesmo modo, você pode definir a lógica (código) que será executada quando a aplicação estiver sendo **encerrada**. Nesse caso, este código será executado **uma vez**, **depois** de ter possivelmente tratado **várias requisições**. |
|||
|
|||
Por conta desse código ser executado antes da aplicação **começar** a receber requisições, e logo após **terminar** de lidar com as requisições, ele cobre toda a **vida útil** (_lifespan_) da aplicação (o termo "vida útil" será importante em um segundo 😉). |
|||
|
|||
Pode ser muito útil para configurar **recursos** que você precisa usar por toda aplicação, e que são **compartilhados** entre as requisições, e/ou que você precisa **limpar** depois. Por exemplo, o pool de uma conexão com o banco de dados ou carregamento de um modelo compartilhado de aprendizado de máquina (_machine learning_). |
|||
|
|||
## Caso de uso |
|||
|
|||
Vamos iniciar com um exemplo de **caso de uso** e então ver como resolvê-lo com isso. |
|||
|
|||
Vamos imaginar que você tem alguns **modelos de _machine learning_** que deseja usar para lidar com as requisições. 🤖 |
|||
|
|||
Os mesmos modelos são compartilhados entre as requisições, então não é um modelo por requisição, ou um por usuário ou algo parecido. |
|||
|
|||
Vamos imaginar que o carregamento do modelo pode **demorar bastante tempo**, porque ele tem que ler muitos **dados do disco**. Então você não quer fazer isso a cada requisição. |
|||
|
|||
Você poderia carregá-lo no nível mais alto do módulo/arquivo, mas isso também poderia significaria **carregar o modelo** mesmo se você estiver executando um simples teste automatizado, então esse teste poderia ser **lento** porque teria que esperar o carregamento do modelo antes de ser capaz de executar uma parte independente do código. |
|||
|
|||
|
|||
Isso é que nós iremos resolver, vamos carregar o modelo antes das requisições serem manuseadas, mas apenas um pouco antes da aplicação começar a receber requisições, não enquanto o código estiver sendo carregado. |
|||
|
|||
## Vida útil (_Lifespan_) |
|||
|
|||
Você pode definir essa lógica de *inicialização* e *encerramento* usando os parâmetros de `lifespan` da aplicação `FastAPI`, e um "gerenciador de contexto" (te mostrarei o que é isso a seguir). |
|||
|
|||
Vamos iniciar com um exemplo e ver isso detalhadamente. |
|||
|
|||
Nós criamos uma função assíncrona chamada `lifespan()` com `yield` como este: |
|||
|
|||
```Python hl_lines="16 19" |
|||
{!../../../docs_src/events/tutorial003.py!} |
|||
``` |
|||
|
|||
Aqui nós estamos simulando a *inicialização* custosa do carregamento do modelo colocando a (falsa) função de modelo no dicionário com modelos de _machine learning_ antes do `yield`. Este código será executado **antes** da aplicação **começar a receber requisições**, durante a *inicialização*. |
|||
|
|||
E então, logo após o `yield`, descarregaremos o modelo. Esse código será executado **após** a aplicação **terminar de lidar com as requisições**, pouco antes do *encerramento*. Isso poderia, por exemplo, liberar recursos como memória ou GPU. |
|||
|
|||
!!! tip "Dica" |
|||
O `shutdown` aconteceria quando você estivesse **encerrando** a aplicação. |
|||
|
|||
Talvez você precise inicializar uma nova versão, ou apenas cansou de executá-la. 🤷 |
|||
|
|||
### Função _lifespan_ |
|||
|
|||
A primeira coisa a notar, é que estamos definindo uma função assíncrona com `yield`. Isso é muito semelhante à Dependências com `yield`. |
|||
|
|||
```Python hl_lines="14-19" |
|||
{!../../../docs_src/events/tutorial003.py!} |
|||
``` |
|||
|
|||
A primeira parte da função, antes do `yield`, será executada **antes** da aplicação inicializar. |
|||
|
|||
E a parte posterior do `yield` irá executar **após** a aplicação ser encerrada. |
|||
|
|||
### Gerenciador de Contexto Assíncrono |
|||
|
|||
Se você verificar, a função está decorada com um `@asynccontextmanager`. |
|||
|
|||
Que converte a função em algo chamado de "**Gerenciador de Contexto Assíncrono**". |
|||
|
|||
```Python hl_lines="1 13" |
|||
{!../../../docs_src/events/tutorial003.py!} |
|||
``` |
|||
|
|||
Um **gerenciador de contexto** em Python é algo que você pode usar em uma declaração `with`, por exemplo, `open()` pode ser usado como um gerenciador de contexto: |
|||
|
|||
```Python |
|||
with open("file.txt") as file: |
|||
file.read() |
|||
``` |
|||
|
|||
Nas versões mais recentes de Python, há também um **gerenciador de contexto assíncrono**. Você o usaria com `async with`: |
|||
|
|||
```Python |
|||
async with lifespan(app): |
|||
await do_stuff() |
|||
``` |
|||
|
|||
Quando você cria um gerenciador de contexto ou um gerenciador de contexto assíncrono como mencionado acima, o que ele faz é que, antes de entrar no bloco `with`, ele irá executar o código anterior ao `yield`, e depois de sair do bloco `with`, ele irá executar o código depois do `yield`. |
|||
|
|||
No nosso exemplo de código acima, nós não usamos ele diretamente, mas nós passamos para o FastAPI para ele usá-lo. |
|||
|
|||
O parâmetro `lifespan` da aplicação `FastAPI` usa um **Gerenciador de Contexto Assíncrono**, então nós podemos passar nosso novo gerenciador de contexto assíncrono do `lifespan` para ele. |
|||
|
|||
```Python hl_lines="22" |
|||
{!../../../docs_src/events/tutorial003.py!} |
|||
``` |
|||
|
|||
## Eventos alternativos (deprecados) |
|||
|
|||
!!! warning "Aviso" |
|||
A maneira recomendada para lidar com a *inicialização* e o *encerramento* é usando o parâmetro `lifespan` da aplicação `FastAPI` como descrito acima. |
|||
|
|||
Você provavelmente pode pular essa parte. |
|||
|
|||
Existe uma forma alternativa para definir a execução dessa lógica durante *inicialização* e durante *encerramento*. |
|||
|
|||
Você pode definir manipuladores de eventos (funções) que precisam ser executadas antes da aplicação inicializar, ou quando a aplicação estiver encerrando. |
|||
|
|||
Essas funções podem ser declaradas com `async def` ou `def` normal. |
|||
|
|||
### Evento `startup` |
|||
|
|||
Para adicionar uma função que deve rodar antes da aplicação iniciar, declare-a com o evento `"startup"`: |
|||
|
|||
```Python hl_lines="8" |
|||
{!../../../docs_src/events/tutorial001.py!} |
|||
``` |
|||
|
|||
Nesse caso, a função de manipulação de evento `startup` irá inicializar os itens do "banco de dados" (só um `dict`) com alguns valores. |
|||
|
|||
Você pode adicionar mais que uma função de manipulação de evento. |
|||
|
|||
E sua aplicação não irá começar a receber requisições até que todos os manipuladores de eventos de `startup` sejam concluídos. |
|||
|
|||
### Evento `shutdown` |
|||
|
|||
Para adicionar uma função que deve ser executada quando a aplicação estiver encerrando, declare ela com o evento `"shutdown"`: |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/events/tutorial002.py!} |
|||
``` |
|||
|
|||
Aqui, a função de manipulação de evento `shutdown` irá escrever uma linha de texto `"Application shutdown"` no arquivo `log.txt`. |
|||
|
|||
!!! info "Informação" |
|||
Na função `open()`, o `mode="a"` significa "acrescentar", então, a linha irá ser adicionada depois de qualquer coisa que esteja naquele arquivo, sem sobrescrever o conteúdo anterior. |
|||
|
|||
!!! tip "Dica" |
|||
Perceba que nesse caso nós estamos usando a função padrão do Python `open()` que interage com um arquivo. |
|||
|
|||
Então, isso envolve I/O (input/output), que exige "esperar" que coisas sejam escritas em disco. |
|||
|
|||
Mas `open()` não usa `async` e `await`. |
|||
|
|||
Então, nós declaramos uma função de manipulação de evento com o padrão `def` ao invés de `async def`. |
|||
|
|||
### `startup` e `shutdown` juntos |
|||
|
|||
Há uma grande chance que a lógica para sua *inicialização* e *encerramento* esteja conectada, você pode querer iniciar alguma coisa e então finalizá-la, adquirir um recurso e então liberá-lo, etc. |
|||
|
|||
Fazendo isso em funções separadas que não compartilham lógica ou variáveis entre elas é mais difícil já que você precisa armazenar os valores em variáveis globais ou truques parecidos. |
|||
|
|||
Por causa disso, agora é recomendado em vez disso usar o `lifespan` como explicado acima. |
|||
|
|||
## Detalhes técnicos |
|||
|
|||
Só um detalhe técnico para nerds curiosos. 🤓 |
|||
|
|||
Por baixo, na especificação técnica ASGI, essa é a parte do <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Protocolo Lifespan</a>, e define eventos chamados `startup` e `shutdown`. |
|||
|
|||
!!! info "Informação" |
|||
Você pode ler mais sobre o manipulador `lifespan` do Starlette na <a href="https://www.starlette.io/lifespan/" class="external-link" target="_blank">Documentação do Lifespan Starlette</a>. |
|||
|
|||
Incluindo como manipular estado do lifespan que pode ser usado em outras áreas do seu código. |
|||
|
|||
## Sub Aplicações |
|||
|
|||
🚨 Tenha em mente que esses eventos de lifespan (de inicialização e desligamento) irão somente ser executados para a aplicação principal, não para [Sub Aplicações - Montagem](./sub-applications.md){.internal-link target=_blank}. |
@ -0,0 +1,311 @@ |
|||
# Концепции развёртывания |
|||
|
|||
Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых Вы можете выбрать **наиболее подходящий** способ. |
|||
|
|||
Самые важные из них: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения. |
|||
|
|||
Рассмотрим ниже влияние каждого из них на процесс **развёртывания**. |
|||
|
|||
Наша конечная цель - **обслуживать клиентов Вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀 |
|||
|
|||
Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у Вас сложится **интуитивное понимание**, какой способ выбрать при развертывании Вашего API в различных окружениях, возможно, даже **ещё не существующих**. |
|||
|
|||
Ознакомившись с этими концепциями, Вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**. |
|||
|
|||
В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI. |
|||
|
|||
А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡 |
|||
|
|||
## Использование более безопасного протокола HTTPS |
|||
|
|||
В [предыдущей главе об HTTPS](./https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для Вашего API. |
|||
|
|||
Также мы заметили, что обычно для работы с HTTPS Вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**. |
|||
|
|||
И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия. |
|||
|
|||
### Примеры инструментов для работы с HTTPS |
|||
|
|||
Вот некоторые инструменты, которые Вы можете применять как прокси-серверы: |
|||
|
|||
* Traefik |
|||
* С автоматическим обновлением сертификатов ✨ |
|||
* Caddy |
|||
* С автоматическим обновлением сертификатов ✨ |
|||
* Nginx |
|||
* С дополнительным компонентом типа Certbot для обновления сертификатов |
|||
* HAProxy |
|||
* С дополнительным компонентом типа Certbot для обновления сертификатов |
|||
* Kubernetes с Ingress Controller похожим на Nginx |
|||
* С дополнительным компонентом типа cert-manager для обновления сертификатов |
|||
* Использование услуг облачного провайдера (читайте ниже 👇) |
|||
|
|||
В последнем варианте Вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера. |
|||
|
|||
В дальнейшем я покажу Вам некоторые конкретные примеры их применения. |
|||
|
|||
--- |
|||
|
|||
Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn). |
|||
|
|||
## Программа и процесс |
|||
|
|||
Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними. |
|||
|
|||
### Что такое программа |
|||
|
|||
Термином **программа** обычно описывают множество вещей: |
|||
|
|||
* **Код**, который Вы написали, в нашем случае **Python-файлы**. |
|||
* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`. |
|||
* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**. |
|||
|
|||
### Что такое процесс |
|||
|
|||
Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца): |
|||
|
|||
* Конкретная программа, **запущенная** операционной системой. |
|||
* Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой. |
|||
* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**. |
|||
* Процесс может быть **прерван** (или "убит") Вами или Вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**. |
|||
* Каждое приложение, которое Вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов. |
|||
* И **одна программа** может запустить **несколько параллельных процессов**. |
|||
|
|||
Если Вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) Вашей операционной системы, то увидите множество работающих процессов. |
|||
|
|||
Вполне вероятно, что Вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы. |
|||
|
|||
<img class="shadow" src="/img/deployment/concepts/image01.png"> |
|||
|
|||
--- |
|||
|
|||
Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания. |
|||
|
|||
## Настройки запуска приложения |
|||
|
|||
В большинстве случаев когда Вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у Вас могут быть причины, чтоб оно запускалось только при определённых условиях. |
|||
|
|||
### Удалённый сервер |
|||
|
|||
Когда Вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как Вы делаете при локальной разработке. |
|||
|
|||
Это рабочий способ и он полезен **во время разработки**. |
|||
|
|||
Но если Вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**. |
|||
|
|||
И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), Вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱 |
|||
|
|||
### Автоматический запуск программ |
|||
|
|||
Вероятно Вы пожелаете, чтоб Ваша серверная программа (такая как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI). |
|||
|
|||
### Отдельная программа |
|||
|
|||
Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных |
|||
|
|||
### Примеры инструментов, управляющих запуском программ |
|||
|
|||
Вот несколько примеров, которые могут справиться с такой задачей: |
|||
|
|||
* Docker |
|||
* Kubernetes |
|||
* Docker Compose |
|||
* Docker в режиме Swarm |
|||
* Systemd |
|||
* Supervisor |
|||
* Использование услуг облачного провайдера |
|||
* Прочие... |
|||
|
|||
Я покажу Вам некоторые примеры их использования в следующих главах. |
|||
|
|||
## Перезапуск |
|||
|
|||
Вы, вероятно, также пожелаете, чтоб Ваше приложение **перезапускалось**, если в нём произошёл сбой. |
|||
|
|||
### Мы ошибаемся |
|||
|
|||
Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛 |
|||
|
|||
И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅). |
|||
|
|||
### Небольшие ошибки обрабатываются автоматически |
|||
|
|||
Когда Вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡 |
|||
|
|||
Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами. |
|||
|
|||
### Большие ошибки - Падение приложений |
|||
|
|||
Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥 |
|||
|
|||
Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок. |
|||
|
|||
### Перезапуск после падения |
|||
|
|||
Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз... |
|||
|
|||
!!! tip "Заметка" |
|||
... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, Вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания. |
|||
|
|||
Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново. |
|||
|
|||
Возможно Вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск Вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в Вашем коде внутри приложения, не может быть выполнено в принципе. |
|||
|
|||
### Примеры инструментов для автоматического перезапуска |
|||
|
|||
В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы. |
|||
|
|||
В качестве примера можно взять те же: |
|||
|
|||
* Docker |
|||
* Kubernetes |
|||
* Docker Compose |
|||
* Docker в режиме Swarm |
|||
* Systemd |
|||
* Supervisor |
|||
* Использование услуг облачного провайдера |
|||
* Прочие... |
|||
|
|||
## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память |
|||
|
|||
Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно. |
|||
|
|||
Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов. |
|||
|
|||
### Множество процессов - Воркеры (Workers) |
|||
|
|||
Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то Вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами. |
|||
|
|||
**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**. |
|||
|
|||
### Процессы и порты́ |
|||
|
|||
Помните ли Вы, как на странице [Об HTTPS](./https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта? |
|||
|
|||
С тех пор ничего не изменилось. |
|||
|
|||
Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу. |
|||
|
|||
### У каждого процесса своя память |
|||
|
|||
Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера. |
|||
|
|||
Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения Вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти. |
|||
|
|||
### Память сервера |
|||
|
|||
Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда Вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если Вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате Вашему API потребуется **4 ГБ оперативной памяти (RAM)**. |
|||
|
|||
И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨 |
|||
|
|||
### Множество процессов - Пример |
|||
|
|||
В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**. |
|||
|
|||
Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам. |
|||
|
|||
Каждый из этих процессов будет запускать Ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память. |
|||
|
|||
<img src="/img/deployment/concepts/process-ram.svg"> |
|||
|
|||
Безусловно, на этом же сервере будут работать и **другие процессы**, которые не относятся к Вашему приложению. |
|||
|
|||
Интересная деталь - обычно в течение времени процент **использования центрального процессора (CPU)** каждым процессом может очень сильно **изменяться**, но объём занимаемой **оперативной памяти (RAM)** остаётся относительно **стабильным**. |
|||
|
|||
Если у Вас есть API, который каждый раз выполняет сопоставимый объем вычислений, и у Вас много клиентов, то **загрузка процессора**, вероятно, *также будет стабильной* (вместо того, чтобы постоянно быстро увеличиваться и уменьшаться). |
|||
|
|||
### Примеры стратегий и инструментов для запуска нескольких экземпляров приложения |
|||
|
|||
Существует несколько подходов для достижения целей репликации и я расскажу Вам больше о конкретных стратегиях в следующих главах, например, когда речь пойдет о Docker и контейнерах. |
|||
|
|||
Основное ограничение при этом - только **один** компонент может работать с определённым **портом публичного IP**. И должен быть способ **передачи** данных между этим компонентом и копиями **процессов/воркеров**. |
|||
|
|||
Вот некоторые возможные комбинации и стратегии: |
|||
|
|||
* **Gunicorn** управляющий **воркерами Uvicorn** |
|||
* Gunicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **множества работающих процессов Uvicorn**. |
|||
* **Uvicorn** управляющий **воркерами Uvicorn** |
|||
* Один процесс Uvicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Он будет запускать **множество работающих процессов Uvicorn**. |
|||
* **Kubernetes** и аналогичные **контейнерные системы** |
|||
* Какой-то компонент в **Kubernetes** будет слушать **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**. |
|||
* **Облачные сервисы**, которые позаботятся обо всём за Вас |
|||
* Возможно, что облачный сервис умеет **управлять запуском дополнительных экземпляров приложения**. Вероятно, он потребует, чтоб Вы указали - какой **процесс** или **образ** следует клонировать. Скорее всего, Вы укажете **один процесс Uvicorn** и облачный сервис будет запускать его копии при необходимости. |
|||
|
|||
!!! tip "Заметка" |
|||
Если Вы не знаете, что такое **контейнеры**, Docker или Kubernetes, не переживайте. |
|||
|
|||
Я поведаю Вам о контейнерах, образах, Docker, Kubernetes и т.п. в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}. |
|||
|
|||
## Шаги, предшествующие запуску |
|||
|
|||
Часто бывает, что Вам необходимо произвести какие-то подготовительные шаги **перед запуском** своего приложения. |
|||
|
|||
Например, запустить **миграции базы данных**. |
|||
|
|||
Но в большинстве случаев такие действия достаточно произвести **однократно**. |
|||
|
|||
Поэтому Вам нужен будет **один процесс**, выполняющий эти **подготовительные шаги** до запуска приложения. |
|||
|
|||
Также Вам нужно будет убедиться, что этот процесс выполнил подготовительные шаги *даже* если впоследствии Вы запустите **несколько процессов** (несколько воркеров) самого приложения. Если бы эти шаги выполнялись в каждом **клонированном процессе**, они бы **дублировали** работу, пытаясь выполнить её **параллельно**. И если бы эта работа была бы чем-то деликатным, вроде миграции базы данных, то это может вызвать конфликты между ними. |
|||
|
|||
Безусловно, возможны случаи, когда нет проблем при выполнении предварительной подготовки параллельно или несколько раз. Тогда Вам повезло, работать с ними намного проще. |
|||
|
|||
!!! tip "Заметка" |
|||
Имейте в виду, что в некоторых случаях запуск Вашего приложения **может не требовать каких-либо предварительных шагов вовсе**. |
|||
|
|||
Что ж, тогда Вам не нужно беспокоиться об этом. 🤷 |
|||
|
|||
### Примеры стратегий запуска предварительных шагов |
|||
|
|||
Существует **сильная зависимость** от того, как Вы **развёртываете свою систему**, запускаете программы, обрабатываете перезапуски и т.д. |
|||
|
|||
Вот некоторые возможные идеи: |
|||
|
|||
* При использовании Kubernetes нужно предусмотреть "инициализирующий контейнер", запускаемый до контейнера с приложением. |
|||
* Bash-скрипт, выполняющий предварительные шаги, а затем запускающий приложение. |
|||
* При этом Вам всё ещё нужно найти способ - как запускать/перезапускать *такой* bash-скрипт, обнаруживать ошибки и т.п. |
|||
|
|||
!!! tip "Заметка" |
|||
Я приведу Вам больше конкретных примеров работы с контейнерами в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}. |
|||
|
|||
## Утилизация ресурсов |
|||
|
|||
Ваш сервер располагает ресурсами, которые Ваши программы могут потреблять или **утилизировать**, а именно - время работы центрального процессора и объём оперативной памяти. |
|||
|
|||
Как много системных ресурсов Вы предполагаете потребить/утилизировать? Если не задумываться, то можно ответить - "немного", но на самом деле Вы, вероятно, пожелаете использовать **максимально возможное количество**. |
|||
|
|||
Если Вы платите за содержание трёх серверов, но используете лишь малую часть системных ресурсов каждого из них, то Вы **выбрасываете деньги на ветер**, а также **впустую тратите электроэнергию** и т.п. |
|||
|
|||
В таком случае было бы лучше обойтись двумя серверами, но более полно утилизировать их ресурсы (центральный процессор, оперативную память, жёсткий диск, сети передачи данных и т.д). |
|||
|
|||
С другой стороны, если Вы располагаете только двумя серверами и используете **на 100% их процессоры и память**, но какой-либо процесс запросит дополнительную память, то операционная система сервера будет использовать жёсткий диск для расширения оперативной памяти (а диск работает в тысячи раз медленнее), а то вовсе **упадёт**. Или если какому-то процессу понадобится произвести вычисления, то ему придётся подождать, пока процессор освободится. |
|||
|
|||
В такой ситуации лучше подключить **ещё один сервер** и перераспределить процессы между серверами, чтоб всем **хватало памяти и процессорного времени**. |
|||
|
|||
Также есть вероятность, что по какой-то причине возник **всплеск** запросов к Вашему API. Возможно, это был вирус, боты или другие сервисы начали пользоваться им. И для таких происшествий Вы можете захотеть иметь дополнительные ресурсы. |
|||
|
|||
При настройке логики развёртываний, Вы можете указать **целевое значение** утилизации ресурсов, допустим, **от 50% до 90%**. Обычно эти метрики и используют. |
|||
|
|||
Вы можете использовать простые инструменты, такие как `htop`, для отслеживания загрузки центрального процессора и оперативной памяти сервера, в том числе каждым процессом. Или более сложные системы мониторинга нескольких серверов. |
|||
|
|||
## Резюме |
|||
|
|||
Вы прочитали некоторые из основных концепций, которые необходимо иметь в виду при принятии решения о развертывании приложений: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения. |
|||
|
|||
Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓 |
|||
|
|||
В следующих разделах я приведу более конкретные примеры возможных стратегий, которым Вы можете следовать. 🚀 |
@ -0,0 +1,198 @@ |
|||
# Об HTTPS |
|||
|
|||
Обычно представляется, что HTTPS это некая опция, которая либо "включена", либо нет. |
|||
|
|||
Но всё несколько сложнее. |
|||
|
|||
!!! tip "Заметка" |
|||
Если Вы торопитесь или Вам не интересно, можете перейти на следующую страницу этого пошагового руководства по размещению приложений на серверах с использованием различных технологий. |
|||
|
|||
Чтобы **изучить основы HTTPS** для клиента, перейдите по ссылке <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>. |
|||
|
|||
Здесь же представлены некоторые концепции, которые **разработчик** должен иметь в виду при размышлениях об HTTPS: |
|||
|
|||
* Протокол HTTPS предполагает, что **серверу** нужно **располагать "сертификатами"** сгенерированными **третьей стороной**. |
|||
* На самом деле эти сертификаты **приобретены** у третьей стороны, а не "сгенерированы". |
|||
* У сертификатов есть **срок годности**. |
|||
* Срок годности **истекает**. |
|||
* По истечении срока годности их нужно **обновить**, то есть **снова получить** у третьей стороны. |
|||
* Шифрование соединения происходит **на уровне протокола TCP**. |
|||
* Протокол TCP находится на один уровень **ниже протокола HTTP**. |
|||
* Поэтому **проверка сертификатов и шифрование** происходит **до HTTP**. |
|||
* **TCP не знает о "доменах"**, но знает об IP-адресах. |
|||
* Информация о **запрашиваемом домене** извлекается из запроса **на уровне HTTP**. |
|||
* **Сертификаты HTTPS** "сертифицируют" **конкретный домен**, но проверка сертификатов и шифрование данных происходит на уровне протокола TCP, то есть **до того**, как станет известен домен-получатель данных. |
|||
* **По умолчанию** это означает, что у Вас может быть **только один сертификат HTTPS на один IP-адрес**. |
|||
* Не важно, насколько большой у Вас сервер и насколько маленькие приложения на нём могут быть. |
|||
* Однако, у этой проблемы есть **решение**. |
|||
* Существует **расширение** протокола **TLS** (который работает на уровне TCP, то есть до HTTP) называемое **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Указание имени сервера">SNI</abbr></a>**. |
|||
* Расширение SNI позволяет одному серверу (с **одним IP-адресом**) иметь **несколько сертификатов HTTPS** и обслуживать **множество HTTPS-доменов/приложений**. |
|||
* Чтобы эта конструкция работала, **один** её компонент (программа) запущенный на сервере и слушающий **публичный IP-адрес**, должен иметь **все сертификаты HTTPS** для этого сервера. |
|||
* **После** установления защищённого соединения, протоколом передачи данных **остаётся HTTP**. |
|||
* Но данные теперь **зашифрованы**, несмотря на то, что они передаются по **протоколу HTTP**. |
|||
|
|||
Обычной практикой является иметь **одну программу/HTTP-сервер** запущенную на сервере (машине, хосте и т.д.) и **ответственную за всю работу с HTTPS**: |
|||
|
|||
* получение **зашифрованных HTTPS-запросов** |
|||
* отправка **расшифрованных HTTP запросов** в соответствующее HTTP-приложение, работающее на том же сервере (в нашем случае, это приложение **FastAPI**) |
|||
* получние **HTTP-ответа** от приложения |
|||
* **шифрование ответа** используя подходящий **сертификат HTTPS** |
|||
* отправка зашифрованного **HTTPS-ответа клиенту**. |
|||
Такой сервер часто называют **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">Прокси-сервер завершения работы TLS</a>** или просто "прокси-сервер". |
|||
|
|||
Вот некоторые варианты, которые Вы можете использовать в качестве такого прокси-сервера: |
|||
|
|||
* Traefik (может обновлять сертификаты) |
|||
* Caddy (может обновлять сертификаты) |
|||
* Nginx |
|||
* HAProxy |
|||
|
|||
## Let's Encrypt (центр сертификации) |
|||
|
|||
До появления Let's Encrypt **сертификаты HTTPS** приходилось покупать у третьих сторон. |
|||
|
|||
Процесс получения такого сертификата был трудоёмким, требовал предоставления подтверждающих документов и сертификаты стоили дорого. |
|||
|
|||
Но затем консорциумом Linux Foundation был создан проект **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>**. |
|||
|
|||
Он автоматически предоставляет **бесплатные сертификаты HTTPS**. Эти сертификаты используют все стандартные криптографические способы шифрования. Они имеют небольшой срок годности (около 3 месяцев), благодаря чему они даже **более безопасны**. |
|||
|
|||
При запросе на получение сертификата, он автоматически генерируется и домен проверяется на безопасность. Это позволяет обновлять сертификаты автоматически. |
|||
|
|||
Суть идеи в автоматическом получении и обновлении этих сертификатов, чтобы все могли пользоваться **безопасным HTTPS. Бесплатно. В любое время.** |
|||
|
|||
## HTTPS для разработчиков |
|||
|
|||
Ниже, шаг за шагом, с заострением внимания на идеях, важных для разработчика, описано, как может выглядеть HTTPS API. |
|||
|
|||
### Имя домена |
|||
|
|||
Чаще всего, всё начинается с **приобретения имени домена**. Затем нужно настроить DNS-сервер (вероятно у того же провайдера, который выдал Вам домен). |
|||
|
|||
Далее, возможно, Вы получаете "облачный" сервер (виртуальную машину) или что-то типа этого, у которого есть <abbr title="Не изменяемый">постоянный</abbr> **публичный IP-адрес**. |
|||
|
|||
На DNS-сервере (серверах) Вам следует настроить соответствующую ресурсную запись ("`запись A`"), указав, что **Ваш домен** связан с публичным **IP-адресом Вашего сервера**. |
|||
|
|||
Обычно эту запись достаточно указать один раз, при первоначальной настройке всего сервера. |
|||
|
|||
!!! tip "Заметка" |
|||
Уровни протоколов, работающих с именами доменов, намного ниже HTTPS, но об этом следует упомянуть здесь, так как всё зависит от доменов и IP-адресов. |
|||
|
|||
### DNS |
|||
|
|||
Теперь давайте сфокусируемся на работе с HTTPS. |
|||
|
|||
Всё начинается с того, что браузер спрашивает у **DNS-серверов**, какой **IP-адрес связан с доменом**, для примера возьмём домен `someapp.example.com`. |
|||
|
|||
DNS-сервера присылают браузеру определённый **IP-адрес**, тот самый публичный IP-адрес Вашего сервера, который Вы указали в ресурсной "записи А" при настройке. |
|||
|
|||
<img src="/img/deployment/https/https01.svg"> |
|||
|
|||
### Рукопожатие TLS |
|||
|
|||
В дальнейшем браузер будет взаимодействовать с этим IP-адресом через **port 443** (общепринятый номер порта для HTTPS). |
|||
|
|||
Первым шагом будет установление соединения между клиентом (браузером) и сервером и выбор криптографического ключа (для шифрования). |
|||
|
|||
<img src="/img/deployment/https/https02.svg"> |
|||
|
|||
Эта часть клиент-серверного взаимодействия устанавливает TLS-соединение и называется **TLS-рукопожатием**. |
|||
|
|||
### TLS с расширением SNI |
|||
|
|||
На сервере **только один процесс** может прослушивать определённый **порт** определённого **IP-адреса**. На сервере могут быть и другие процессы, слушающие другие порты этого же IP-адреса, но никакой процесс не может быть привязан к уже занятой комбинации IP-адрес:порт. Эта комбинация называется "сокет". |
|||
|
|||
По умолчанию TLS (HTTPS) использует порт `443`. Потому этот же порт будем использовать и мы. |
|||
|
|||
И раз уж только один процесс может слушать этот порт, то это будет процесс **прокси-сервера завершения работы TLS**. |
|||
|
|||
Прокси-сервер завершения работы TLS будет иметь доступ к одному или нескольким **TLS-сертификатам** (сертификаты HTTPS). |
|||
|
|||
Используя **расширение SNI** упомянутое выше, прокси-сервер из имеющихся сертификатов TLS (HTTPS) выберет тот, который соответствует имени домена, указанному в запросе от клиента. |
|||
|
|||
То есть будет выбран сертификат для домена `someapp.example.com`. |
|||
|
|||
<img src="/img/deployment/https/https03.svg"> |
|||
|
|||
Клиент уже **доверяет** тому, кто выдал этот TLS-сертификат (в нашем случае - Let's Encrypt, но мы ещё обсудим это), потому может **проверить**, действителен ли полученный от сервера сертификат. |
|||
|
|||
Затем, используя этот сертификат, клиент и прокси-сервер **выбирают способ шифрования** данных для устанавливаемого **TCP-соединения**. На этом операция **TLS-рукопожатия** завершена. |
|||
|
|||
В дальнейшем клиент и сервер будут взаимодействовать по **зашифрованному TCP-соединению**, как предлагается в протоколе TLS. И на основе этого TCP-соедениния будет создано **HTTP-соединение**. |
|||
|
|||
Таким образом, **HTTPS** это тот же **HTTP**, но внутри **безопасного TLS-соединения** вместо чистого (незашифрованного) TCP-соединения. |
|||
|
|||
!!! tip "Заметка" |
|||
Обратите внимание, что шифрование происходит на **уровне TCP**, а не на более высоком уровне HTTP. |
|||
|
|||
### HTTPS-запрос |
|||
|
|||
Теперь, когда между клиентом и сервером (в нашем случае, браузером и прокси-сервером) создано **зашифрованное TCP-соединение**, они могут начать **обмен данными по протоколу HTTP**. |
|||
|
|||
Так клиент отправляет **HTTPS-запрос**. То есть обычный HTTP-запрос, но через зашифрованное TLS-содинение. |
|||
|
|||
<img src="/img/deployment/https/https04.svg"> |
|||
|
|||
### Расшифровка запроса |
|||
|
|||
Прокси-сервер, используя согласованный с клиентом ключ, расшифрует полученный **зашифрованный запрос** и передаст **обычный (незашифрованный) HTTP-запрос** процессу, запускающему приложение (например, процессу Uvicorn запускающему приложение FastAPI). |
|||
|
|||
<img src="/img/deployment/https/https05.svg"> |
|||
|
|||
### HTTP-ответ |
|||
|
|||
Приложение обработает запрос и вернёт **обычный (незашифрованный) HTTP-ответ** прокси-серверу. |
|||
|
|||
<img src="/img/deployment/https/https06.svg"> |
|||
|
|||
### HTTPS-ответ |
|||
|
|||
Пркоси-сервер **зашифрует ответ** используя ранее согласованный с клиентом способ шифрования (которые содержатся в сертификате для домена `someapp.example.com`) и отправит его браузеру. |
|||
|
|||
Наконец, браузер проверит ответ, в том числе, что тот зашифрован с нужным ключом, **расшифрует его** и обработает. |
|||
|
|||
<img src="/img/deployment/https/https07.svg"> |
|||
|
|||
Клиент (браузер) знает, что ответ пришёл от правильного сервера, так как использует методы шифрования, согласованные ими раннее через **HTTPS-сертификат**. |
|||
|
|||
### Множество приложений |
|||
|
|||
На одном и том же сервере (или серверах) можно разместить **множество приложений**, например, другие программы с API или базы данных. |
|||
|
|||
Напомню, что только один процесс (например, прокси-сервер) может прослушивать определённый порт определённого IP-адреса. |
|||
Но другие процессы и приложения тоже могут работать на этом же сервере (серверах), если они не пытаются использовать уже занятую **комбинацию IP-адреса и порта** (сокет). |
|||
|
|||
<img src="/img/deployment/https/https08.svg"> |
|||
|
|||
Таким образом, сервер завершения TLS может обрабатывать HTTPS-запросы и использовать сертификаты для **множества доменов** или приложений и передавать запросы правильным адресатам (другим приложениям). |
|||
|
|||
### Обновление сертификата |
|||
|
|||
В недалёком будущем любой сертификат станет **просроченным** (примерно через три месяца после получения). |
|||
|
|||
Когда это произойдёт, можно запустить другую программу, которая подключится к Let's Encrypt и обновит сертификат(ы). Существуют прокси-серверы, которые могут сделать это действие самостоятельно. |
|||
|
|||
<img src="/img/deployment/https/https.svg"> |
|||
|
|||
**TLS-сертификаты** не привязаны к IP-адресу, но **связаны с именем домена**. |
|||
|
|||
Так что при обновлении сертификатов программа должна **подтвердить** центру сертификации (Let's Encrypt), что обновление запросил **"владелец", который контролирует этот домен**. |
|||
|
|||
Есть несколько путей осуществления этого. Самые популярные из них: |
|||
|
|||
* **Изменение записей DNS**. |
|||
* Для такого варианта Ваша программа обновления должна уметь работать с API DNS-провайдера, обслуживающего Ваши DNS-записи. Не у всех провайдеров есть такой API, так что этот способ не всегда применим. |
|||
* **Запуск в качестве программы-сервера** (как минимум, на время обновления сертификатов) на публичном IP-адресе домена. |
|||
* Как уже не раз упоминалось, только один процесс может прослушивать определённый порт определённого IP-адреса. |
|||
* Это одна из причин использования прокси-сервера ещё и в качестве программы обновления сертификатов. |
|||
* В случае, если обновлением сертификатов занимается другая программа, Вам понадобится остановить прокси-сервер, запустить программу обновления сертификатов на сокете, предназначенном для прокси-сервера, настроить прокси-сервер на работу с новыми сертификатами и перезапустить его. Эта схема далека от идеальной, так как Ваши приложения будут недоступны на время отключения прокси-сервера. |
|||
|
|||
Весь этот процесс обновления, одновременный с обслуживанием запросов, является одной из основных причин, по которой желательно иметь **отдельную систему для работы с HTTPS** в виде прокси-сервера завершения TLS, а не просто использовать сертификаты TLS непосредственно с сервером приложений (например, Uvicorn). |
|||
|
|||
## Резюме |
|||
|
|||
Наличие **HTTPS** очень важно и довольно **критично** в большинстве случаев. Однако, Вам, как разработчику, не нужно тратить много сил на это, достаточно **понимать эти концепции** и принципы их работы. |
|||
|
|||
Но узнав базовые основы **HTTPS** Вы можете легко совмещать разные инструменты, которые помогут Вам в дальнейшей разработке. |
|||
|
|||
В следующих главах я покажу Вам несколько примеров, как настраивать **HTTPS** для приложений **FastAPI**. 🔒 |
@ -0,0 +1,150 @@ |
|||
# Запуск сервера вручную - Uvicorn |
|||
|
|||
Для запуска приложения **FastAPI** на удалённой серверной машине Вам необходим программный сервер, поддерживающий протокол ASGI, такой как **Uvicorn**. |
|||
|
|||
Существует три наиболее распространённые альтернативы: |
|||
|
|||
* <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>: высокопроизводительный ASGI сервер. |
|||
* <a href="https://pgjones.gitlab.io/hypercorn/" class="external-link" target="_blank">Hypercorn</a>: ASGI сервер, помимо прочего поддерживающий HTTP/2 и Trio. |
|||
* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>: ASGI сервер, созданный для Django Channels. |
|||
|
|||
## Сервер как машина и сервер как программа |
|||
|
|||
В этих терминах есть некоторые различия и Вам следует запомнить их. 💡 |
|||
|
|||
Слово "**сервер**" чаще всего используется в двух контекстах: |
|||
|
|||
- удалённый или расположенный в "облаке" компьютер (физическая или виртуальная машина). |
|||
- программа, запущенная на таком компьютере (например, Uvicorn). |
|||
|
|||
Просто запомните, если Вам встретился термин "сервер", то обычно он подразумевает что-то из этих двух смыслов. |
|||
|
|||
Когда имеют в виду именно удалённый компьютер, часто говорят просто **сервер**, но ещё его называют **машина**, **ВМ** (виртуальная машина), **нода**. Все эти термины обозначают одно и то же - удалённый компьютер, обычно под управлением Linux, на котором Вы запускаете программы. |
|||
|
|||
## Установка программного сервера |
|||
|
|||
Вы можете установить сервер, совместимый с протоколом ASGI, так: |
|||
|
|||
=== "Uvicorn" |
|||
|
|||
* <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>, молниесный ASGI сервер, основанный на библиотеках uvloop и httptools. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "uvicorn[standard]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! tip "Подсказка" |
|||
С опцией `standard`, Uvicorn будет установливаться и использоваться с некоторыми дополнительными рекомендованными зависимостями. |
|||
|
|||
В них входит `uvloop`, высокопроизводительная замена `asyncio`, которая значительно ускоряет работу асинхронных программ. |
|||
|
|||
=== "Hypercorn" |
|||
|
|||
* <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>, ASGI сервер, поддерживающий протокол HTTP/2. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install hypercorn |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
...или какой-либо другой ASGI сервер. |
|||
|
|||
## Запуск серверной программы |
|||
|
|||
Затем запустите Ваше приложение так же, как было указано в руководстве ранее, но без опции `--reload`: |
|||
|
|||
=== "Uvicorn" |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --host 0.0.0.0 --port 80 |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
=== "Hypercorn" |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ hypercorn main:app --bind 0.0.0.0:80 |
|||
|
|||
Running on 0.0.0.0:8080 over http (CTRL + C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! warning "Предупреждение" |
|||
|
|||
Не забудьте удалить опцию `--reload`, если ранее пользовались ею. |
|||
|
|||
Включение опции `--reload` требует дополнительных ресурсов, влияет на стабильность работы приложения и может повлечь прочие неприятности. |
|||
|
|||
Она сильно помогает во время **разработки**, но **не следует** использовать её при **реальной работе** приложения. |
|||
|
|||
## Hypercorn с Trio |
|||
|
|||
Starlette и **FastAPI** основаны на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, которая делает их совместимыми как с <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> - стандартной библиотекой Python, так и с <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>. |
|||
|
|||
|
|||
Тем не менее Uvicorn совместим только с asyncio и обычно используется совместно с <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a>, высокопроизводительной заменой `asyncio`. |
|||
|
|||
Но если Вы хотите использовать **Trio** напрямую, то можете воспользоваться **Hypercorn**, так как они совместимы. ✨ |
|||
|
|||
### Установка Hypercorn с Trio |
|||
|
|||
Для начала, Вам нужно установить Hypercorn с поддержкой Trio: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "hypercorn[trio]" |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
### Запуск с Trio |
|||
|
|||
Далее запустите Hypercorn с опцией `--worker-class` и аргументом `trio`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ hypercorn main:app --worker-class trio |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Hypercorn, в свою очередь, запустит Ваше приложение использующее Trio. |
|||
|
|||
Таким образом, Вы сможете использовать Trio в своём приложении. Но лучше использовать AnyIO, для сохранения совместимости и с Trio, и с asyncio. 🎉 |
|||
|
|||
## Концепции развёртывания |
|||
|
|||
В вышеприведённых примерах серверные программы (например Uvicorn) запускали только **один процесс**, принимающий входящие запросы с любого IP (на это указывал аргумент `0.0.0.0`) на определённый порт (в примерах мы указывали порт `80`). |
|||
|
|||
Это основная идея. Но возможно, Вы озаботитесь добавлением дополнительных возможностей, таких как: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения. |
|||
|
|||
Я поведаю Вам больше о каждой из этих концепций в следующих главах, с конкретными примерами стратегий работы с ними. 🚀 |
@ -0,0 +1,309 @@ |
|||
# Body - Множество параметров |
|||
|
|||
Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры обьявления тела запроса. |
|||
|
|||
## Обьединение `Path`, `Query` и параметров тела запроса |
|||
|
|||
Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать. |
|||
|
|||
Вы также можете объявить параметры тела запроса как необязательные, установив значение по умолчанию, равное `None`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="18-20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="18-20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="19-21" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="17-19" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="19-21" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! Заметка |
|||
Заметьте, что в данном случае параметр `item`, который будет взят из тела запроса, необязателен. Так как было установлено значение `None` по умолчанию. |
|||
|
|||
## Несколько параметров тела запроса |
|||
|
|||
В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON-тело с параметрами, соответствующими атрибутам `Item`, например: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
|
|||
Но вы также можете объявить множество параметров тела запроса, например `item` и `user`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="22" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial002.py!} |
|||
``` |
|||
|
|||
В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются моделями Pydantic). |
|||
|
|||
Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
!!! Внимание |
|||
Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предпологается, что он находится внутри тела с ключом `item`. |
|||
|
|||
|
|||
**FastAPI** сделает автоматические преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`. |
|||
|
|||
Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах. |
|||
|
|||
## Отдельные значения в теле запроса |
|||
|
|||
Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`. |
|||
|
|||
Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`. |
|||
|
|||
Если вы объявите его без указания, какой именно объект (Path, Query, Body и .т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр. |
|||
|
|||
Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="23" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="23" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="24" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="22" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003.py!} |
|||
``` |
|||
|
|||
В этом случае, **FastAPI** будет ожидать тело запроса в формате: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
}, |
|||
"importance": 5 |
|||
} |
|||
``` |
|||
|
|||
И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д. |
|||
|
|||
## Множество body и query параметров |
|||
|
|||
Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам. |
|||
|
|||
Поскольку по умолчанию, отдельные значения интерпретируются как query-параметры, вам не нужно явно добавлять `Query`, вы можете просто сделать так: |
|||
|
|||
```Python |
|||
q: Union[str, None] = None |
|||
``` |
|||
|
|||
Или в Python 3.10 и выше: |
|||
|
|||
```Python |
|||
q: str | None = None |
|||
``` |
|||
|
|||
Например: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="27" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="27" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="28" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="25" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="27" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004.py!} |
|||
``` |
|||
|
|||
!!! Информация |
|||
`Body` также имеет все те же дополнительные параметры валидации и метаданных, как у `Query`,`Path` и других, которые вы увидите позже. |
|||
|
|||
## Добавление одного body-параметра |
|||
|
|||
Предположим, у вас есть только один body-параметр `item` из Pydantic модели `Item`. |
|||
|
|||
По умолчанию, **FastAPI** ожидает получить тело запроса напрямую. |
|||
|
|||
Но если вы хотите чтобы он ожидал JSON с ключом `item` с содержимым модели внутри, также как это происходит при объявлении дополнительных body-параметров, вы можете использовать специальный параметр `embed` у типа `Body`: |
|||
|
|||
```Python |
|||
item: Item = Body(embed=True) |
|||
``` |
|||
|
|||
так же, как в этом примере: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="18" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="15" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005.py!} |
|||
``` |
|||
|
|||
В этом случае **FastAPI** будет ожидать тело запроса в формате: |
|||
|
|||
```JSON hl_lines="2" |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
вместо этого: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
|
|||
## Резюме |
|||
|
|||
Вы можете добавлять несколько body-параметров вашей *функции операции пути*, несмотря даже на то, что запрос может содержать только одно тело. |
|||
|
|||
Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*. |
|||
|
|||
Вы также можете объявить отдельные значения для получения в рамках тела запроса. |
|||
|
|||
И вы можете настроить **FastAPI** таким образом, чтобы включить тело запроса в ключ, даже если объявлен только один параметр. |
@ -0,0 +1,165 @@ |
|||
# Тело запроса |
|||
|
|||
Когда вам необходимо отправить данные из клиента (допустим, браузера) в ваш API, вы отправляете их как **тело запроса**. |
|||
|
|||
Тело **запроса** --- это данные, отправляемые клиентом в ваш API. Тело **ответа** --- это данные, которые ваш API отправляет клиенту. |
|||
|
|||
Ваш API почти всегда отправляет тело **ответа**. Но клиентам не обязательно всегда отправлять тело **запроса**. |
|||
|
|||
Чтобы объявить тело **запроса**, необходимо использовать модели <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>, со всей их мощью и преимуществами. |
|||
|
|||
!!! info "Информация" |
|||
Чтобы отправить данные, необходимо использовать один из методов: `POST` (обычно), `PUT`, `DELETE` или `PATCH`. |
|||
|
|||
Отправка тела с запросом `GET` имеет неопределенное поведение в спецификациях, тем не менее, оно поддерживается FastAPI только для очень сложных/экстремальных случаев использования. |
|||
|
|||
Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании метода GET, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса. |
|||
|
|||
## Импортирование `BaseModel` из Pydantic |
|||
|
|||
Первое, что вам необходимо сделать, это импортировать `BaseModel` из пакета `pydantic`: |
|||
|
|||
```Python hl_lines="4" |
|||
{!../../../docs_src/body/tutorial001.py!} |
|||
``` |
|||
|
|||
## Создание вашей собственной модели |
|||
|
|||
После этого вы описываете вашу модель данных как класс, наследующий от `BaseModel`. |
|||
|
|||
Используйте аннотации типов Python для всех атрибутов: |
|||
|
|||
```Python hl_lines="7-11" |
|||
{!../../../docs_src/body/tutorial001.py!} |
|||
``` |
|||
|
|||
Также как и при описании параметров запроса, когда атрибут модели имеет значение по умолчанию, он является необязательным. Иначе он обязателен. Используйте `None`, чтобы сделать его необязательным без использования конкретных значений по умолчанию. |
|||
|
|||
Например, модель выше описывает вот такой JSON "объект" (или словарь Python): |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "An optional description", |
|||
"price": 45.2, |
|||
"tax": 3.5 |
|||
} |
|||
``` |
|||
|
|||
...поскольку `description` и `tax` являются необязательными (с `None` в качестве значения по умолчанию), вот такой JSON "объект" также подходит: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"price": 45.2 |
|||
} |
|||
``` |
|||
|
|||
## Объявление как параметра функции |
|||
|
|||
Чтобы добавить параметр к вашему *обработчику*, объявите его также, как вы объявляли параметры пути или параметры запроса: |
|||
|
|||
```Python hl_lines="18" |
|||
{!../../../docs_src/body/tutorial001.py!} |
|||
``` |
|||
|
|||
...и укажите созданную модель в качестве типа параметра, `Item`. |
|||
|
|||
## Результаты |
|||
|
|||
Всего лишь с помощью аннотации типов Python, **FastAPI**: |
|||
|
|||
* Читает тело запроса как JSON. |
|||
* Приводит к соответствующим типам (если есть необходимость). |
|||
* Проверяет корректность данных. |
|||
* Если данные некорректны, будет возращена читаемая и понятная ошибка, показывающая что именно и в каком месте некорректно в данных. |
|||
* Складывает полученные данные в параметр `item`. |
|||
* Поскольку внутри функции вы объявили его с типом `Item`, то теперь у вас есть поддержка со стороны редактора (автодополнение и т.п.) для всех атрибутов и их типов. |
|||
* Генерирует декларативное описание модели в виде <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a>, так что вы можете его использовать где угодно, если это имеет значение для вашего проекта. |
|||
* Эти схемы являются частью сгенерированной схемы OpenAPI и используются для автоматического документирования <abbr title="Пользовательских интерфейсов (User Interfaces)">UI</abbr>. |
|||
|
|||
## Автоматическое документирование |
|||
|
|||
Схема JSON ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображена в интерактивной документации API: |
|||
|
|||
<img src="/img/tutorial/body/image01.png"> |
|||
|
|||
Также она будет указана в документации по API внутри каждой *операции пути*, в которой используются: |
|||
|
|||
<img src="/img/tutorial/body/image02.png"> |
|||
|
|||
## Поддержка редактора |
|||
|
|||
В вашем редакторе внутри вашей функции у вас будут подсказки по типам и автодополнение (это не будет работать, если вы получаете словарь вместо модели Pydantic): |
|||
|
|||
<img src="/img/tutorial/body/image03.png"> |
|||
|
|||
Также вы будете получать ошибки в случае несоответствия типов: |
|||
|
|||
<img src="/img/tutorial/body/image04.png"> |
|||
|
|||
Это не случайно, весь фреймворк построен вокруг такого дизайна. |
|||
|
|||
И это все тщательно протестировано еще на этапе разработки дизайна, до реализации, чтобы это работало со всеми редакторами. |
|||
|
|||
Для поддержки этого даже были внесены некоторые изменения в сам Pydantic. |
|||
|
|||
На всех предыдущих скриншотах используется <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>. |
|||
|
|||
Но у вас будет такая же поддержка и с <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>, и вообще с любым редактором Python: |
|||
|
|||
<img src="/img/tutorial/body/image05.png"> |
|||
|
|||
!!! tip "Подсказка" |
|||
Если вы используете <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> в качестве редактора, то вам стоит попробовать плагин <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>. |
|||
|
|||
Он улучшает поддержку редактором моделей Pydantic в части: |
|||
|
|||
* автодополнения, |
|||
* проверки типов, |
|||
* рефакторинга, |
|||
* поиска, |
|||
* инспектирования. |
|||
|
|||
## Использование модели |
|||
|
|||
Внутри функции вам доступны все атрибуты объекта модели напрямую: |
|||
|
|||
```Python hl_lines="21" |
|||
{!../../../docs_src/body/tutorial002.py!} |
|||
``` |
|||
|
|||
## Тело запроса + параметры пути |
|||
|
|||
Вы можете одновременно объявлять параметры пути и тело запроса. |
|||
|
|||
**FastAPI** распознает, какие параметры функции соответствуют параметрам пути и должны быть **получены из пути**, а какие параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**. |
|||
|
|||
```Python hl_lines="17-18" |
|||
{!../../../docs_src/body/tutorial003.py!} |
|||
``` |
|||
|
|||
## Тело запроса + параметры пути + параметры запроса |
|||
|
|||
Вы также можете одновременно объявить параметры для **пути**, **запроса** и **тела запроса**. |
|||
|
|||
**FastAPI** распознает каждый из них и возьмет данные из правильного источника. |
|||
|
|||
```Python hl_lines="18" |
|||
{!../../../docs_src/body/tutorial004.py!} |
|||
``` |
|||
|
|||
Параметры функции распознаются следующим образом: |
|||
|
|||
* Если параметр также указан в **пути**, то он будет использоваться как параметр пути. |
|||
* Если аннотация типа параметра содержит **примитивный тип** (`int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**. |
|||
* Если аннотация типа параметра представляет собой **модель Pydantic**, он будет интерпретирован как параметр **тела запроса**. |
|||
|
|||
!!! note "Заметка" |
|||
FastAPI понимает, что значение параметра `q` не является обязательным, потому что имеет значение по умолчанию `= None`. |
|||
|
|||
Аннотация `Optional` в `Optional[str]` не используется FastAPI, но помогает вашему редактору лучше понимать ваш код и обнаруживать ошибки. |
|||
|
|||
## Без Pydantic |
|||
|
|||
Если вы не хотите использовать модели Pydantic, вы все еще можете использовать параметры **тела запроса**. Читайте в документации раздел [Тело - Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. |
@ -0,0 +1,112 @@ |
|||
# Отладка |
|||
|
|||
Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm. |
|||
|
|||
## Вызов `uvicorn` |
|||
|
|||
В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую: |
|||
|
|||
```Python hl_lines="1 15" |
|||
{!../../../docs_src/debugging/tutorial001.py!} |
|||
``` |
|||
|
|||
### Описание `__name__ == "__main__"` |
|||
|
|||
Главная цель использования `__name__ == "__main__"` в том, чтобы код выполнялся при запуске файла с помощью: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python myapp.py |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
но не вызывался, когда другой файл импортирует это, например:: |
|||
|
|||
```Python |
|||
from myapp import app |
|||
``` |
|||
|
|||
#### Больше деталей |
|||
|
|||
Давайте назовём ваш файл `myapp.py`. |
|||
|
|||
Если вы запустите его с помощью: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python myapp.py |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
то встроенная переменная `__name__`, автоматически создаваемая Python в вашем файле, будет иметь значение строкового типа `"__main__"`. |
|||
|
|||
Тогда выполнится условие и эта часть кода: |
|||
|
|||
```Python |
|||
uvicorn.run(app, host="0.0.0.0", port=8000) |
|||
``` |
|||
|
|||
будет запущена. |
|||
|
|||
--- |
|||
|
|||
Но этого не произойдет, если вы импортируете этот модуль (файл). |
|||
|
|||
Таким образом, если у вас есть файл `importer.py` с таким импортом: |
|||
|
|||
```Python |
|||
from myapp import app |
|||
|
|||
# Some more code |
|||
``` |
|||
|
|||
то автоматическая создаваемая внутри файла `myapp.py` переменная `__name__` будет иметь значение отличающееся от `"__main__"`. |
|||
|
|||
Следовательно, строка: |
|||
|
|||
```Python |
|||
uvicorn.run(app, host="0.0.0.0", port=8000) |
|||
``` |
|||
|
|||
не будет выполнена. |
|||
|
|||
!!! Информация |
|||
Для получения дополнительной информации, ознакомьтесь с <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">официальной документацией Python</a>. |
|||
|
|||
## Запуск вашего кода с помощью отладчика |
|||
|
|||
Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика. |
|||
|
|||
--- |
|||
|
|||
Например, в Visual Studio Code вы можете выполнить следующие шаги: |
|||
|
|||
* Перейдите на панель "Debug". |
|||
* Выберите "Add configuration...". |
|||
* Выберите "Python" |
|||
* Запустите отладчик "`Python: Current File (Integrated Terminal)`". |
|||
|
|||
Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д. |
|||
|
|||
Вот как это может выглядеть: |
|||
|
|||
<img src="/img/tutorial/debugging/image01.png"> |
|||
|
|||
--- |
|||
|
|||
Если используете Pycharm, вы можете выполнить следующие шаги: |
|||
|
|||
* Открыть "Run" меню. |
|||
* Выбрать опцию "Debug...". |
|||
* Затем в появившемся контекстном меню. |
|||
* Выбрать файл для отладки (в данном случае, `main.py`). |
|||
|
|||
Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д. |
|||
|
|||
Вот как это может выглядеть: |
|||
|
|||
<img src="/img/tutorial/debugging/image02.png"> |
@ -0,0 +1,333 @@ |
|||
# Первые шаги |
|||
|
|||
Самый простой FastAPI файл может выглядеть так: |
|||
|
|||
```Python |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Скопируйте в файл `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) |
|||
<span style="color: green;">INFO</span>: Started reloader process [28720] |
|||
<span style="color: green;">INFO</span>: Started server process [28722] |
|||
<span style="color: green;">INFO</span>: Waiting for application startup. |
|||
<span style="color: green;">INFO</span>: Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! note "Технические детали" |
|||
Команда `uvicorn main:app` обращается к: |
|||
|
|||
* `main`: файл `main.py` (модуль Python). |
|||
* `app`: объект, созданный внутри файла `main.py` в строке `app = FastAPI()`. |
|||
* `--reload`: перезапускает сервер после изменения кода. Используйте только для разработки. |
|||
|
|||
В окне вывода появится следующая строка: |
|||
|
|||
```hl_lines="4" |
|||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
Эта строка показывает URL-адрес, по которому приложение доступно на локальной машине. |
|||
|
|||
### Проверьте |
|||
|
|||
Откройте браузер по адресу: <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>. |
|||
|
|||
Вы увидите JSON-ответ следующего вида: |
|||
|
|||
```JSON |
|||
{"message": "Hello World"} |
|||
``` |
|||
|
|||
### Интерактивная документация API |
|||
|
|||
Перейдите по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
Вы увидите автоматически сгенерированную, интерактивную документацию по API (предоставленную <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): |
|||
|
|||
 |
|||
|
|||
### Альтернативная документация API |
|||
|
|||
Теперь перейдите по адресу <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. |
|||
|
|||
Вы увидите альтернативную автоматически сгенерированную документацию (предоставленную <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): |
|||
|
|||
 |
|||
|
|||
### OpenAPI |
|||
|
|||
**FastAPI** генерирует "схему" всего API, используя стандарт **OpenAPI**. |
|||
|
|||
#### "Схема" |
|||
|
|||
"Схема" - это определение или описание чего-либо. Не код, реализующий это, а только абстрактное описание. |
|||
|
|||
#### API "схема" |
|||
|
|||
<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> - это спецификация, которая определяет, как описывать схему API. |
|||
|
|||
Определение схемы содержит пути (paths) API, их параметры и т.п. |
|||
|
|||
#### "Схема" данных |
|||
|
|||
Термин "схема" также может относиться к формату или структуре некоторых данных, например, JSON. |
|||
|
|||
Тогда, подразумеваются атрибуты JSON, их типы данных и т.п. |
|||
|
|||
#### OpenAPI и JSON Schema |
|||
|
|||
OpenAPI описывает схему API. Эта схема содержит определения (или "схемы") данных, отправляемых и получаемых API. Для описания структуры данных в JSON используется стандарт **JSON Schema**. |
|||
|
|||
#### Рассмотрим `openapi.json` |
|||
|
|||
Если Вас интересует, как выглядит исходная схема OpenAPI, то FastAPI автоматически генерирует JSON-схему со всеми описаниями API. |
|||
|
|||
Можете посмотреть здесь: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>. |
|||
|
|||
Вы увидите примерно такой JSON: |
|||
|
|||
```JSON |
|||
{ |
|||
"openapi": "3.0.2", |
|||
"info": { |
|||
"title": "FastAPI", |
|||
"version": "0.1.0" |
|||
}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
|
|||
|
|||
|
|||
... |
|||
``` |
|||
|
|||
#### Для чего нужен OpenAPI |
|||
|
|||
Схема OpenAPI является основой для обеих систем интерактивной документации. |
|||
|
|||
Существуют десятки альтернативных инструментов, основанных на OpenAPI. Вы можете легко добавить любой из них к **FastAPI** приложению. |
|||
|
|||
Вы также можете использовать OpenAPI для автоматической генерации кода для клиентов, которые взаимодействуют с API. Например, для фронтенд-, мобильных или IoT-приложений. |
|||
|
|||
## Рассмотрим поэтапно |
|||
|
|||
### Шаг 1: импортируйте `FastAPI` |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
`FastAPI` это класс в Python, который предоставляет всю функциональность для API. |
|||
|
|||
!!! note "Технические детали" |
|||
`FastAPI` это класс, который наследуется непосредственно от `Starlette`. |
|||
|
|||
Вы можете использовать всю функциональность <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> в `FastAPI`. |
|||
|
|||
### Шаг 2: создайте экземпляр `FastAPI` |
|||
|
|||
```Python hl_lines="3" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Переменная `app` является экземпляром класса `FastAPI`. |
|||
|
|||
Это единая точка входа для создания и взаимодействия с API. |
|||
|
|||
Именно к этой переменной `app` обращается `uvicorn` в команде: |
|||
|
|||
<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> |
|||
|
|||
Если создать такое приложение: |
|||
|
|||
```Python hl_lines="3" |
|||
{!../../../docs_src/first_steps/tutorial002.py!} |
|||
``` |
|||
|
|||
И поместить его в `main.py`, тогда вызов `uvicorn` будет таким: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:my_awesome_api --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
### Шаг 3: определите *операцию пути (path operation)* |
|||
|
|||
#### Путь (path) |
|||
|
|||
"Путь" это часть URL, после первого символа `/`, следующего за именем домена. |
|||
|
|||
Для URL: |
|||
|
|||
``` |
|||
https://example.com/items/foo |
|||
``` |
|||
|
|||
...путь выглядит так: |
|||
|
|||
``` |
|||
/items/foo |
|||
``` |
|||
|
|||
!!! info "Дополнительная иформация" |
|||
Термин "path" также часто называется "endpoint" или "route". |
|||
|
|||
При создании API, "путь" является основным способом разделения "задач" и "ресурсов". |
|||
|
|||
#### Операция (operation) |
|||
|
|||
"Операция" это один из "методов" HTTP. |
|||
|
|||
Таких, как: |
|||
|
|||
* `POST` |
|||
* `GET` |
|||
* `PUT` |
|||
* `DELETE` |
|||
|
|||
...и более экзотических: |
|||
|
|||
* `OPTIONS` |
|||
* `HEAD` |
|||
* `PATCH` |
|||
* `TRACE` |
|||
|
|||
По протоколу HTTP можно обращаться к каждому пути, используя один (или несколько) из этих "методов". |
|||
|
|||
--- |
|||
|
|||
При создании API принято использовать конкретные HTTP-методы для выполнения определенных действий. |
|||
|
|||
Обычно используют: |
|||
|
|||
* `POST`: создать данные. |
|||
* `GET`: прочитать. |
|||
* `PUT`: изменить (обновить). |
|||
* `DELETE`: удалить. |
|||
|
|||
В OpenAPI каждый HTTP метод называется "**операция**". |
|||
|
|||
Мы также будем придерживаться этого термина. |
|||
|
|||
#### Определите *декоратор операции пути (path operation decorator)* |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Декоратор `@app.get("/")` указывает **FastAPI**, что функция, прямо под ним, отвечает за обработку запросов, поступающих по адресу: |
|||
|
|||
* путь `/` |
|||
* использующих <abbr title="HTTP GET метод"><code>get</code> операцию</abbr> |
|||
|
|||
!!! info "`@decorator` Дополнительная информация" |
|||
Синтаксис `@something` в Python называется "декоратор". |
|||
|
|||
Вы помещаете его над функцией. Как красивую декоративную шляпу (думаю, что оттуда и происходит этот термин). |
|||
|
|||
"Декоратор" принимает функцию ниже и выполняет с ней какое-то действие. |
|||
|
|||
В нашем случае, этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` и **операции** `get`. |
|||
|
|||
Это и есть "**декоратор операции пути**". |
|||
|
|||
Можно также использовать операции: |
|||
|
|||
* `@app.post()` |
|||
* `@app.put()` |
|||
* `@app.delete()` |
|||
|
|||
И более экзотические: |
|||
|
|||
* `@app.options()` |
|||
* `@app.head()` |
|||
* `@app.patch()` |
|||
* `@app.trace()` |
|||
|
|||
!!! tip "Подсказка" |
|||
Вы можете использовать каждую операцию (HTTP-метод) по своему усмотрению. |
|||
|
|||
**FastAPI** не навязывает определенного значения для каждого метода. |
|||
|
|||
Информация здесь представлена как рекомендация, а не требование. |
|||
|
|||
Например, при использовании GraphQL обычно все действия выполняются только с помощью POST операций. |
|||
|
|||
### Шаг 4: определите **функцию операции пути** |
|||
|
|||
Вот "**функция операции пути**": |
|||
|
|||
* **путь**: `/`. |
|||
* **операция**: `get`. |
|||
* **функция**: функция ниже "декоратора" (ниже `@app.get("/")`). |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Это обычная Python функция. |
|||
|
|||
**FastAPI** будет вызывать её каждый раз при получении `GET` запроса к URL "`/`". |
|||
|
|||
В данном случае это асинхронная функция. |
|||
|
|||
--- |
|||
|
|||
Вы также можете определить ее как обычную функцию вместо `async def`: |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/first_steps/tutorial003.py!} |
|||
``` |
|||
|
|||
!!! note "Технические детали" |
|||
Если не знаете в чём разница, посмотрите [Конкурентность: *"Нет времени?"*](../async.md#in-a-hurry){.internal-link target=_blank}. |
|||
|
|||
### Шаг 5: верните результат |
|||
|
|||
```Python hl_lines="8" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д. |
|||
|
|||
Также можно вернуть модели Pydantic (рассмотрим это позже). |
|||
|
|||
Многие объекты и модели будут автоматически преобразованы в JSON (включая ORM). Пробуйте использовать другие объекты, которые предпочтительней для Вас, вероятно, они уже поддерживаются. |
|||
|
|||
## Резюме |
|||
|
|||
* Импортируем `FastAPI`. |
|||
* Создаём экземпляр `app`. |
|||
* Пишем **декоратор операции пути** (такой как `@app.get("/")`). |
|||
* Пишем **функцию операции пути** (`def root(): ...`). |
|||
* Запускаем сервер в режиме разработки (`uvicorn main:app --reload`). |
@ -0,0 +1,80 @@ |
|||
# Учебник - Руководство пользователя - Введение |
|||
|
|||
В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций. |
|||
|
|||
Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API. |
|||
|
|||
Он также создан для использования в качестве будущего справочника. |
|||
|
|||
Так что вы можете вернуться и посмотреть именно то, что вам нужно. |
|||
|
|||
## Запустите код |
|||
|
|||
Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python). |
|||
|
|||
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами: |
|||
|
|||
<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) |
|||
<span style="color: green;">INFO</span>: Started reloader process [28720] |
|||
<span style="color: green;">INFO</span>: Started server process [28722] |
|||
<span style="color: green;">INFO</span>: Waiting for application startup. |
|||
<span style="color: green;">INFO</span>: Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
**НАСТОЯТЕЛЬНО рекомендуется**, чтобы вы написали или скопировали код, отредактировали его и запустили локально. |
|||
|
|||
Использование кода в вашем редакторе — это то, что действительно показывает вам преимущества FastAPI, видя, как мало кода вам нужно написать, все проверки типов, автодополнение и т.д. |
|||
|
|||
--- |
|||
|
|||
## Установка FastAPI |
|||
|
|||
Первый шаг — установить FastAPI. |
|||
|
|||
Для руководства вы, возможно, захотите установить его со всеми дополнительными зависимостями и функциями: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "fastapi[all]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
...это также включает `uvicorn`, который вы можете использовать в качестве сервера, который запускает ваш код. |
|||
|
|||
!!! note "Технические детали" |
|||
Вы также можете установить его по частям. |
|||
|
|||
Это то, что вы, вероятно, сделаете, когда захотите развернуть свое приложение в рабочей среде: |
|||
|
|||
``` |
|||
pip install fastapi |
|||
``` |
|||
|
|||
Также установите `uvicorn` для работы в качестве сервера: |
|||
|
|||
``` |
|||
pip install "uvicorn[standard]" |
|||
``` |
|||
|
|||
И то же самое для каждой из необязательных зависимостей, которые вы хотите использовать. |
|||
|
|||
## Продвинутое руководство пользователя |
|||
|
|||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**. |
|||
|
|||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям. |
|||
|
|||
Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас). |
|||
|
|||
Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**. |
@ -0,0 +1,292 @@ |
|||
# Path-параметры и валидация числовых данных |
|||
|
|||
Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров. |
|||
|
|||
## Импорт Path |
|||
|
|||
Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="1 3" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="1 3" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="3-4" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="3" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! info "Информация" |
|||
Поддержка `Annotated` была добавлена в FastAPI начиная с версии 0.95.0 (и с этой версии рекомендуется использовать этот подход). |
|||
|
|||
Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`. |
|||
|
|||
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`. |
|||
|
|||
## Определите метаданные |
|||
|
|||
Вы можете указать все те же параметры, что и для `Query`. |
|||
|
|||
Например, чтобы указать значение метаданных `title` для path-параметра `item_id`, вы можете написать: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! note "Примечание" |
|||
Path-параметр всегда является обязательным, поскольку он составляет часть пути. |
|||
|
|||
Поэтому следует объявить его с помощью `...`, чтобы обозначить, что этот параметр обязательный. |
|||
|
|||
Тем не менее, даже если вы объявите его как `None` или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным. |
|||
|
|||
## Задайте нужный вам порядок параметров |
|||
|
|||
!!! tip "Подсказка" |
|||
Это не имеет большого значения, если вы используете `Annotated`. |
|||
|
|||
Допустим, вы хотите объявить query-параметр `q` как обязательный параметр типа `str`. |
|||
|
|||
И если вам больше ничего не нужно указывать для этого параметра, то нет необходимости использовать `Query`. |
|||
|
|||
Но вам по-прежнему нужно использовать `Path` для path-параметра `item_id`. И если по какой-либо причине вы не хотите использовать `Annotated`, то могут возникнуть небольшие сложности. |
|||
|
|||
Если вы поместите параметр со значением по умолчанию перед другим параметром, у которого нет значения по умолчанию, то Python укажет на ошибку. |
|||
|
|||
Но вы можете изменить порядок параметров, чтобы параметр без значения по умолчанию (query-параметр `q`) шёл первым. |
|||
|
|||
Это не имеет значения для **FastAPI**. Он распознает параметры по их названиям, типам и значениям по умолчанию (`Query`, `Path`, и т.д.), ему не важен их порядок. |
|||
|
|||
Поэтому вы можете определить функцию так: |
|||
|
|||
=== "Python 3.6 без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial002.py!} |
|||
``` |
|||
|
|||
Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете `Query()` или `Path()` в качестве значения по умолчанию для параметра функции. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial002_an.py!} |
|||
``` |
|||
|
|||
## Задайте нужный вам порядок параметров, полезные приёмы |
|||
|
|||
!!! tip "Подсказка" |
|||
Это не имеет большого значения, если вы используете `Annotated`. |
|||
|
|||
Здесь описан **небольшой приём**, который может оказаться удобным, хотя часто он вам не понадобится. |
|||
|
|||
Если вы хотите: |
|||
|
|||
* объявить query-параметр `q` без `Query` и без значения по умолчанию |
|||
* объявить path-параметр `item_id` с помощью `Path` |
|||
* указать их в другом порядке |
|||
* не использовать `Annotated` |
|||
|
|||
...то вы можете использовать специальную возможность синтаксиса Python. |
|||
|
|||
Передайте `*` в качестве первого параметра функции. |
|||
|
|||
Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>, даже если у них нет значений по умолчанию. |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} |
|||
``` |
|||
|
|||
### Лучше с `Annotated` |
|||
|
|||
Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, то у вас не возникнет подобной проблемы и вам не придётся использовать `*`. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial003_an.py!} |
|||
``` |
|||
|
|||
## Валидация числовых данных: больше или равно |
|||
|
|||
С помощью `Query` и `Path` (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных. |
|||
|
|||
В этом примере при указании `ge=1`, параметр `item_id` должен быть больше или равен `1` ("`g`reater than or `e`qual"). |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial004_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial004.py!} |
|||
``` |
|||
|
|||
## Валидация числовых данных: больше и меньше или равно |
|||
|
|||
То же самое применимо к: |
|||
|
|||
* `gt`: больше (`g`reater `t`han) |
|||
* `le`: меньше или равно (`l`ess than or `e`qual) |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial005_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial005.py!} |
|||
``` |
|||
|
|||
## Валидация числовых данных: числа с плавающей точкой, больше и меньше |
|||
|
|||
Валидация также применима к значениям типа `float`. |
|||
|
|||
В этом случае становится важной возможность добавить ограничение <abbr title="greater than"><code>gt</code></abbr>, вместо <abbr title="greater than or equal"><code>ge</code></abbr>, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`. |
|||
|
|||
Таким образом, `0.5` будет корректным значением. А `0.0` или `0` — нет. |
|||
|
|||
То же самое справедливо и для <abbr title="less than"><code>lt</code></abbr>. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="13" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="12" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial006_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial006.py!} |
|||
``` |
|||
|
|||
## Резюме |
|||
|
|||
С помощью `Query`, `Path` (и других классов, которые мы пока не затронули) вы можете добавлять метаданные и строковую валидацию тем же способом, как и в главе [Query-параметры и валидация строк](query-params-str-validations.md){.internal-link target=_blank}. |
|||
|
|||
А также вы можете добавить валидацию числовых данных: |
|||
|
|||
* `gt`: больше (`g`reater `t`han) |
|||
* `ge`: больше или равно (`g`reater than or `e`qual) |
|||
* `lt`: меньше (`l`ess `t`han) |
|||
* `le`: меньше или равно (`l`ess than or `e`qual) |
|||
|
|||
!!! info "Информация" |
|||
`Query`, `Path` и другие классы, которые мы разберём позже, являются наследниками общего класса `Param`. |
|||
|
|||
Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее. |
|||
|
|||
!!! note "Технические детали" |
|||
`Query`, `Path` и другие "классы", которые вы импортируете из `fastapi`, на самом деле являются функциями, которые при вызове возвращают экземпляры одноимённых классов. |
|||
|
|||
Объект `Query`, который вы импортируете, является функцией. И при вызове она возвращает экземпляр одноимённого класса `Query`. |
|||
|
|||
Использование функций (вместо использования классов напрямую) нужно для того, чтобы ваш редактор не подсвечивал ошибки, связанные с их типами. |
|||
|
|||
Таким образом вы можете использовать привычный вам редактор и инструменты разработки, не добавляя дополнительных конфигураций для игнорирования подобных ошибок. |
@ -0,0 +1,251 @@ |
|||
# Path-параметры |
|||
|
|||
Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python: |
|||
|
|||
```Python hl_lines="6-7" |
|||
{!../../../docs_src/path_params/tutorial001.py!} |
|||
``` |
|||
|
|||
Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`. |
|||
|
|||
Если запустите этот пример и перейдёте по адресу: <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то увидите ответ: |
|||
|
|||
```JSON |
|||
{"item_id":"foo"} |
|||
``` |
|||
|
|||
## Параметры пути с типами |
|||
|
|||
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python. |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/path_params/tutorial002.py!} |
|||
``` |
|||
|
|||
Здесь, `item_id` объявлен типом `int`. |
|||
|
|||
!!! check "Заметка" |
|||
Это обеспечит поддержку редактора внутри функции (проверка ошибок, автодополнение и т.п.). |
|||
|
|||
## <abbr title="Или сериализация, парсинг">Преобразование</abbr> данных |
|||
|
|||
Если запустите этот пример и перейдёте по адресу: <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, то увидите ответ: |
|||
|
|||
```JSON |
|||
{"item_id":3} |
|||
``` |
|||
|
|||
!!! check "Заметка" |
|||
Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`. |
|||
|
|||
Используя определения типов, **FastAPI** выполняет автоматический <abbr title="преобразование строк из HTTP-запроса в типы данных Python">"парсинг"</abbr> запросов. |
|||
|
|||
## <abbr title="Или валидация">Проверка</abbr> данных |
|||
|
|||
Если откроете браузер по адресу <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то увидите интересную HTTP-ошибку: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"path", |
|||
"item_id" |
|||
], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`. |
|||
|
|||
Та же ошибка возникнет, если вместо `int` передать `float` , например: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a> |
|||
|
|||
!!! check "Заметка" |
|||
**FastAPI** обеспечивает проверку типов, используя всё те же определения типов. |
|||
|
|||
Обратите внимание, что в тексте ошибки явно указано место не прошедшее проверку. |
|||
|
|||
Это очень полезно при разработке и отладке кода, который взаимодействует с API. |
|||
|
|||
## Документация |
|||
|
|||
И теперь, когда откроете браузер по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, то увидите вот такую автоматически сгенерированную документацию API: |
|||
|
|||
<img src="/img/tutorial/path-params/image01.png"> |
|||
|
|||
!!! check "Заметка" |
|||
Ещё раз, просто используя определения типов, **FastAPI** обеспечивает автоматическую интерактивную документацию (с интеграцией Swagger UI). |
|||
|
|||
Обратите внимание, что параметр пути объявлен целочисленным. |
|||
|
|||
## Преимущества стандартизации, альтернативная документация |
|||
|
|||
Поскольку сгенерированная схема соответствует стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a>, её можно использовать со множеством совместимых инструментов. |
|||
|
|||
Именно поэтому, FastAPI сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу: <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. |
|||
|
|||
<img src="/img/tutorial/path-params/image02.png"> |
|||
|
|||
По той же причине, есть множество совместимых инструментов, включая инструменты генерации кода для многих языков. |
|||
|
|||
## Pydantic |
|||
|
|||
Вся проверка данных выполняется под капотом с помощью <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>. Поэтому вы можете быть уверены в качестве обработки данных. |
|||
|
|||
Вы можете использовать в аннотациях как простые типы данных, вроде `str`, `float`, `bool`, так и более сложные типы. |
|||
|
|||
Некоторые из них рассматриваются в следующих главах данного руководства. |
|||
|
|||
## Порядок имеет значение |
|||
|
|||
При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным. |
|||
|
|||
Например, `/users/me`. Предположим, что это путь для получения данных о текущем пользователе. |
|||
|
|||
У вас также может быть путь `/users/{user_id}`, чтобы получить данные о конкретном пользователе по его ID. |
|||
|
|||
Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`: |
|||
|
|||
|
|||
```Python hl_lines="6 11" |
|||
{!../../../docs_src/path_params/tutorial003.py!} |
|||
``` |
|||
|
|||
Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`. |
|||
|
|||
Аналогично, вы не можете переопределить операцию с путем: |
|||
|
|||
```Python hl_lines="6 11" |
|||
{!../../../docs_src/path_params/tutorial003b.py!} |
|||
``` |
|||
|
|||
Первый будет выполняться всегда, так как путь совпадает первым. |
|||
|
|||
## Предопределенные значения |
|||
|
|||
Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление <abbr title="Enumeration">`Enum`</abbr> Python. |
|||
|
|||
### Создание класса `Enum` |
|||
|
|||
Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`. |
|||
|
|||
Мы наследуемся от `str`, чтобы документация API могла понять, что значения должны быть типа `string` и отображалась правильно. |
|||
|
|||
Затем создайте атрибуты класса с фиксированными допустимыми значениями: |
|||
|
|||
```Python hl_lines="1 6-9" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
!!! info "Дополнительная информация" |
|||
<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">Перечисления (enum) доступны в Python</a> начиная с версии 3.4. |
|||
|
|||
!!! tip "Подсказка" |
|||
Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия <abbr title="Технически, это архитектуры моделей глубокого обучения">моделей</abbr> машинного обучения. |
|||
|
|||
### Определение *параметра пути* |
|||
|
|||
Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее: |
|||
|
|||
```Python hl_lines="16" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
### Проверьте документацию |
|||
|
|||
Поскольку доступные значения *параметра пути* определены заранее, интерактивная документация может наглядно их отображать: |
|||
|
|||
<img src="/img/tutorial/path-params/image03.png"> |
|||
|
|||
### Работа с *перечислениями* в Python |
|||
|
|||
Значение *параметра пути* будет *элементом перечисления*. |
|||
|
|||
#### Сравнение *элементов перечисления* |
|||
|
|||
Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`: |
|||
|
|||
```Python hl_lines="17" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
#### Получение *значения перечисления* |
|||
|
|||
Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`: |
|||
|
|||
```Python hl_lines="20" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
!!! tip "Подсказка" |
|||
Значение `"lenet"` также можно получить с помощью `ModelName.lenet.value`. |
|||
|
|||
#### Возврат *элементов перечисления* |
|||
|
|||
Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело JSON (например в `dict`). |
|||
|
|||
Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту: |
|||
|
|||
```Python hl_lines="18 21 23" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
Вы отправите клиенту такой JSON-ответ: |
|||
|
|||
```JSON |
|||
{ |
|||
"model_name": "alexnet", |
|||
"message": "Deep Learning FTW!" |
|||
} |
|||
``` |
|||
|
|||
## Path-параметры, содержащие пути |
|||
|
|||
Предположим, что есть *операция пути* с путем `/files/{file_path}`. |
|||
|
|||
Но вам нужно, чтобы `file_path` сам содержал *путь*, например, `home/johndoe/myfile.txt`. |
|||
|
|||
Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`. |
|||
|
|||
### Поддержка OpenAPI |
|||
|
|||
OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать. |
|||
|
|||
Тем не менее это можно сделать в **FastAPI**, используя один из внутренних инструментов Starlette. |
|||
|
|||
Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь. |
|||
|
|||
### Конвертер пути |
|||
|
|||
Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде: |
|||
|
|||
``` |
|||
/files/{file_path:path} |
|||
``` |
|||
|
|||
В этом случае `file_path` - это имя параметра, а часть `:path`, указывает, что параметр должен соответствовать любому *пути*. |
|||
|
|||
Можете использовать так: |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/path_params/tutorial004.py!} |
|||
``` |
|||
|
|||
!!! tip "Подсказка" |
|||
Возможно, вам понадобится, чтобы параметр содержал `/home/johndoe/myfile.txt` с ведущим слэшем (`/`). |
|||
|
|||
В этом случае URL будет таким: `/files//home/johndoe/myfile.txt`, с двойным слэшем (`//`) между `files` и `home`. |
|||
|
|||
## Резюме |
|||
Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете: |
|||
|
|||
* Поддержку редактора (проверку ошибок, автозаполнение и т.п.) |
|||
* "<abbr title="преобразование строк из HTTP-запроса в типы данных Python">Парсинг</abbr>" данных |
|||
* Валидацию данных |
|||
* Автоматическую документацию API с указанием типов параметров. |
|||
|
|||
И объявлять типы достаточно один раз. |
|||
|
|||
Это, вероятно, является главным заметным преимуществом **FastAPI** по сравнению с альтернативными фреймворками (кроме <abbr title="не считая оптимизаций">сырой</abbr> производительности). |
@ -0,0 +1,225 @@ |
|||
# Query-параметры |
|||
|
|||
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры. |
|||
|
|||
```Python hl_lines="9" |
|||
{!../../../docs_src/query_params/tutorial001.py!} |
|||
``` |
|||
|
|||
Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`. |
|||
|
|||
Например, в этом URL-адресе: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=0&limit=10 |
|||
``` |
|||
|
|||
...параметры запроса такие: |
|||
|
|||
* `skip`: со значением `0` |
|||
* `limit`: со значением `10` |
|||
|
|||
Будучи частью URL-адреса, они "по умолчанию" являются строками. |
|||
|
|||
Но когда вы объявляете их с использованием аннотаций (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему. |
|||
|
|||
Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам: |
|||
|
|||
* Поддержка от редактора кода (очевидно) |
|||
* <abbr title="преобразование строки, полученной из HTTP запроса в Python данные">"Парсинг"</abbr> данных |
|||
* Проверка на соответствие данных (Валидация) |
|||
* Автоматическая документация |
|||
|
|||
## Значения по умолчанию |
|||
|
|||
Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию. |
|||
|
|||
В примере выше значения по умолчанию равны `skip=0` и `limit=10`. |
|||
|
|||
Таким образом, результат перехода по URL-адресу: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/ |
|||
``` |
|||
|
|||
будет таким же, как если перейти используя параметры по умолчанию: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=0&limit=10 |
|||
``` |
|||
|
|||
Но если вы введёте, например: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=20 |
|||
``` |
|||
|
|||
Значения параметров в вашей функции будут: |
|||
|
|||
* `skip=20`: потому что вы установили это в URL-адресе |
|||
* `limit=10`: т.к это было значение по умолчанию |
|||
|
|||
## Необязательные параметры |
|||
|
|||
Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/query_params/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/query_params/tutorial002.py!} |
|||
``` |
|||
|
|||
В этом случае, параметр `q` будет не обязательным и будет иметь значение `None` по умолчанию. |
|||
|
|||
!!! Важно |
|||
Также обратите внимание, что **FastAPI** достаточно умён чтобы заметить, что параметр `item_id` является path-параметром, а `q` нет, поэтому, это параметр запроса. |
|||
|
|||
## Преобразование типа параметра запроса |
|||
|
|||
Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/query_params/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/query_params/tutorial003.py!} |
|||
``` |
|||
|
|||
В этом случае, если вы сделаете запрос: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=1 |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=True |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=true |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=on |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=yes |
|||
``` |
|||
|
|||
или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`. |
|||
|
|||
|
|||
## Смешивание query-параметров и path-параметров |
|||
|
|||
Вы можете объявлять несколько query-параметров и path-параметров одновременно,**FastAPI** сам разберётся, что чем является. |
|||
|
|||
И вы не обязаны объявлять их в каком-либо определенном порядке. |
|||
|
|||
Они будут обнаружены по именам: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="6 8" |
|||
{!> ../../../docs_src/query_params/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="8 10" |
|||
{!> ../../../docs_src/query_params/tutorial004.py!} |
|||
``` |
|||
|
|||
## Обязательные query-параметры |
|||
|
|||
Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то это значение не является обязательным. |
|||
|
|||
Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`. |
|||
|
|||
Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию: |
|||
|
|||
```Python hl_lines="6-7" |
|||
{!../../../docs_src/query_params/tutorial005.py!} |
|||
``` |
|||
|
|||
Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`. |
|||
|
|||
Если вы откроете в браузере URL-адрес, например: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo-item |
|||
``` |
|||
|
|||
...без добавления обязательного параметра `needy`, вы увидите подобного рода ошибку: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"query", |
|||
"needy" |
|||
], |
|||
"msg": "field required", |
|||
"type": "value_error.missing" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
Поскольку `needy` является обязательным параметром, вам необходимо указать его в URL-адресе: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy |
|||
``` |
|||
|
|||
...это будет работать: |
|||
|
|||
```JSON |
|||
{ |
|||
"item_id": "foo-item", |
|||
"needy": "sooooneedy" |
|||
} |
|||
``` |
|||
|
|||
Конечно, вы можете определить некоторые параметры как обязательные, некоторые - со значением по умполчанию, а некоторые - полностью необязательные: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/query_params/tutorial006_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/query_params/tutorial006.py!} |
|||
``` |
|||
|
|||
В этом примере, у нас есть 3 параметра запроса: |
|||
|
|||
* `needy`, обязательный `str`. |
|||
* `skip`, типа `int` и со значением по умолчанию `0`. |
|||
* `limit`, необязательный `int`. |
|||
|
|||
!!! подсказка |
|||
Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}. |
@ -0,0 +1,189 @@ |
|||
# Объявление примера запроса данных |
|||
|
|||
Вы можете объявлять примеры данных, которые ваше приложение может получать. |
|||
|
|||
Вот несколько способов, как это можно сделать. |
|||
|
|||
## Pydantic `schema_extra` |
|||
|
|||
Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в <a href="https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic документации: Настройка схемы</a>: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="13-21" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="15-23" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial001.py!} |
|||
``` |
|||
|
|||
Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API. |
|||
|
|||
!!! tip Подсказка |
|||
Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации. |
|||
|
|||
Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д. |
|||
|
|||
## Дополнительные аргументы поля `Field` |
|||
|
|||
При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию. |
|||
|
|||
Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="2 8-11" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="4 10-13" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial002.py!} |
|||
``` |
|||
|
|||
!!! warning Внимание |
|||
Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации. |
|||
|
|||
## Использование `example` и `examples` в OpenAPI |
|||
|
|||
При использовании любой из этих функций: |
|||
|
|||
* `Path()` |
|||
* `Query()` |
|||
* `Header()` |
|||
* `Cookie()` |
|||
* `Body()` |
|||
* `Form()` |
|||
* `File()` |
|||
|
|||
вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**. |
|||
|
|||
### Параметр `Body` с аргументом `example` |
|||
|
|||
Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="22-27" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="22-27" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="23-28" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="18-23" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="20-25" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003.py!} |
|||
``` |
|||
|
|||
### Аргумент "example" в UI документации |
|||
|
|||
С любым из вышеуказанных методов это будет выглядеть так в `/docs`: |
|||
|
|||
<img src="/img/tutorial/body-fields/image01.png"> |
|||
|
|||
### `Body` с аргументом `examples` |
|||
|
|||
В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**. |
|||
|
|||
Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией. |
|||
|
|||
Каждый конкретный пример типа `dict` в аргументе `examples` может содержать: |
|||
|
|||
* `summary`: Краткое описание для примера. |
|||
* `description`: Полное описание, которое может содержать текст в формате Markdown. |
|||
* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`. |
|||
* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`. |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="23-49" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="23-49" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="24-50" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="19-45" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="21-47" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004.py!} |
|||
``` |
|||
|
|||
### Аргумент "examples" в UI документации |
|||
|
|||
С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так: |
|||
|
|||
<img src="/img/tutorial/body-fields/image02.png"> |
|||
|
|||
## Технические Детали |
|||
|
|||
!!! warning Внимание |
|||
Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**. |
|||
|
|||
Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить. |
|||
|
|||
Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic. |
|||
|
|||
И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации. |
|||
|
|||
Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`. |
|||
|
|||
Таким образом, OpenAPI 3.0.3 определяет своё собственное поле <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI). |
|||
|
|||
Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации. |
|||
|
|||
Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему). |
|||
|
|||
Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object" class="external-link" target="_blank">определение OpenAPI, к объекту `Parameter Object` (в спецификации)</a>. |
|||
|
|||
И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject" class="external-link" target="_blank"> определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации)</a>. |
|||
|
|||
С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы. |
@ -0,0 +1,40 @@ |
|||
# Статические Файлы |
|||
|
|||
Вы можете предоставлять статические файлы автоматически из директории, используя `StaticFiles`. |
|||
|
|||
## Использование `StaticFiles` |
|||
|
|||
* Импортируйте `StaticFiles`. |
|||
* "Примонтируйте" экземпляр `StaticFiles()` с указанием определенной директории. |
|||
|
|||
```Python hl_lines="2 6" |
|||
{!../../../docs_src/static_files/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! заметка "Технические детали" |
|||
Вы также можете использовать `from starlette.staticfiles import StaticFiles`. |
|||
|
|||
**FastAPI** предоставляет `starlette.staticfiles` под псевдонимом `fastapi.staticfiles`, просто для вашего удобства, как разработчика. Но на самом деле это берётся напрямую из библиотеки Starlette. |
|||
|
|||
### Что такое "Монтирование" |
|||
|
|||
"Монтирование" означает добавление полноценного "независимого" приложения в определенную директорию, которое затем обрабатывает все подпути. |
|||
|
|||
Это отличается от использования `APIRouter`, так как примонтированное приложение является полностью независимым. |
|||
OpenAPI и документация из вашего главного приложения не будет содержать ничего из примонтированного приложения, и т.д. |
|||
|
|||
Вы можете прочитать больше об этом в **Расширенном руководстве пользователя**. |
|||
|
|||
## Детали |
|||
|
|||
Первый параметр `"/static"` относится к подпути, по которому это "подприложение" будет "примонтировано". Таким образом, любой путь начинающийся со `"/static"` будет обработан этим приложением. |
|||
|
|||
Параметр `directory="static"` относится к имени директории, которая содержит ваши статические файлы. |
|||
|
|||
`name="static"` даёт имя маршруту, которое может быть использовано внутри **FastAPI**. |
|||
|
|||
Все эти параметры могут отличаться от "`static`", настройте их в соответствии с вашими нуждами и конкретными деталями вашего собственного приложения. |
|||
|
|||
## Больше информации |
|||
|
|||
Для получения дополнительной информации о деталях и настройках ознакомьтесь с <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">Документацией Starlette о статических файлах</a>. |
@ -0,0 +1,212 @@ |
|||
# Тестирование |
|||
|
|||
Благодаря <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>, тестировать приложения **FastAPI** легко и приятно. |
|||
|
|||
Тестирование основано на библиотеке <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, которая в свою очередь основана на библиотеке Requests, так что все действия знакомы и интуитивно понятны. |
|||
|
|||
Используя эти инструменты, Вы можете напрямую задействовать <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> с **FastAPI**. |
|||
|
|||
## Использование класса `TestClient` |
|||
|
|||
!!! info "Информация" |
|||
Для использования класса `TestClient` необходимо установить библиотеку <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>. |
|||
|
|||
Например, так: `pip install httpx`. |
|||
|
|||
Импортируйте `TestClient`. |
|||
|
|||
Создайте объект `TestClient`, передав ему в качестве параметра Ваше приложение **FastAPI**. |
|||
|
|||
Создайте функцию, название которой должно начинаться с `test_` (это стандарт из соглашений `pytest`). |
|||
|
|||
Используйте объект `TestClient` так же, как Вы используете `httpx`. |
|||
|
|||
Напишите простое утверждение с `assert` дабы проверить истинность Python-выражения (это тоже стандарт `pytest`). |
|||
|
|||
```Python hl_lines="2 12 15-18" |
|||
{!../../../docs_src/app_testing/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! tip "Подсказка" |
|||
Обратите внимание, что тестирующая функция является обычной `def`, а не асинхронной `async def`. |
|||
|
|||
И вызов клиента также осуществляется без `await`. |
|||
|
|||
Это позволяет вам использовать `pytest` без лишних усложнений. |
|||
|
|||
!!! note "Технические детали" |
|||
Также можно написать `from starlette.testclient import TestClient`. |
|||
|
|||
**FastAPI** предоставляет тот же самый `starlette.testclient` как `fastapi.testclient`. Это всего лишь небольшое удобство для Вас, как разработчика. |
|||
|
|||
!!! tip "Подсказка" |
|||
Если для тестирования Вам, помимо запросов к приложению FastAPI, необходимо вызывать асинхронные функции (например, для подключения к базе данных с помощью асинхронного драйвера), то ознакомьтесь со страницей [Асинхронное тестирование](../advanced/async-tests.md){.internal-link target=_blank} в расширенном руководстве. |
|||
|
|||
## Разделение тестов и приложения |
|||
|
|||
В реальном приложении Вы, вероятно, разместите тесты в отдельном файле. |
|||
|
|||
Кроме того, Ваше приложение **FastAPI** может состоять из нескольких файлов, модулей и т.п. |
|||
|
|||
### Файл приложения **FastAPI** |
|||
|
|||
Допустим, структура файлов Вашего приложения похожа на ту, что описана на странице [Более крупные приложения](./bigger-applications.md){.internal-link target=_blank}: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ └── main.py |
|||
``` |
|||
|
|||
Здесь файл `main.py` является "точкой входа" в Ваше приложение и содержит инициализацию Вашего приложения **FastAPI**: |
|||
|
|||
|
|||
```Python |
|||
{!../../../docs_src/app_testing/main.py!} |
|||
``` |
|||
|
|||
### Файл тестов |
|||
|
|||
Также у Вас может быть файл `test_main.py` содержащий тесты. Можно разместить тестовый файл и файл приложения в одной директории (в директориях для Python-кода желательно размещать и файл `__init__.py`): |
|||
|
|||
``` hl_lines="5" |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
│ └── test_main.py |
|||
``` |
|||
|
|||
Так как оба файла находятся в одной директории, для импорта объекта приложения из файла `main` в файл `test_main` Вы можете использовать относительный импорт: |
|||
|
|||
```Python hl_lines="3" |
|||
{!../../../docs_src/app_testing/test_main.py!} |
|||
``` |
|||
|
|||
...и писать дальше тесты, как и раньше. |
|||
|
|||
## Тестирование: расширенный пример |
|||
|
|||
Теперь давайте расширим наш пример и добавим деталей, чтоб посмотреть, как тестировать различные части приложения. |
|||
|
|||
### Расширенный файл приложения **FastAPI** |
|||
|
|||
Мы продолжим работу с той же файловой структурой, что и ранее: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
│ └── test_main.py |
|||
``` |
|||
|
|||
Предположим, что в файле `main.py` с приложением **FastAPI** есть несколько **операций пути**. |
|||
|
|||
В нём описана операция `GET`, которая может вернуть ошибку. |
|||
|
|||
Ещё есть операция `POST` и она тоже может вернуть ошибку. |
|||
|
|||
Обе *операции пути* требуют наличия в запросе заголовка `X-Token`. |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/app_testing/app_b_an/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
По возможности используйте версию с `Annotated`. |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/app_testing/app_b_py310/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
По возможности используйте версию с `Annotated`. |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/app_testing/app_b/main.py!} |
|||
``` |
|||
|
|||
### Расширенный файл тестов |
|||
|
|||
Теперь обновим файл `test_main.py`, добавив в него тестов: |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/app_testing/app_b/test_main.py!} |
|||
``` |
|||
|
|||
Если Вы не знаете, как передать информацию в запросе, можете воспользоваться поисковиком (погуглить) и задать вопрос: "Как передать информацию в запросе с помощью `httpx`", можно даже спросить: "Как передать информацию в запросе с помощью `requests`", поскольку дизайн HTTPX основан на дизайне Requests. |
|||
|
|||
Затем Вы просто применяете найденные ответы в тестах. |
|||
|
|||
Например: |
|||
|
|||
* Передаёте *path*-параметры или *query*-параметры, вписав их непосредственно в строку URL. |
|||
* Передаёте JSON в теле запроса, передав Python-объект (например: `dict`) через именованный параметр `json`. |
|||
* Если же Вам необходимо отправить *форму с данными* вместо JSON, то используйте параметр `data` вместо `json`. |
|||
* Для передачи *заголовков*, передайте объект `dict` через параметр `headers`. |
|||
* Для передачи *cookies* также передайте `dict`, но через параметр `cookies`. |
|||
|
|||
Для получения дополнительной информации о передаче данных на бэкенд с помощью `httpx` или `TestClient` ознакомьтесь с <a href="https://www.python-httpx.org" class="external-link" target="_blank">документацией HTTPX</a>. |
|||
|
|||
!!! info "Информация" |
|||
Обратите внимание, что `TestClient` принимает данные, которые можно конвертировать в JSON, но не модели Pydantic. |
|||
|
|||
Если в Ваших тестах есть модели Pydantic и Вы хотите отправить их в тестируемое приложение, то можете использовать функцию `jsonable_encoder`, описанную на странице [Кодировщик совместимый с JSON](encoder.md){.internal-link target=_blank}. |
|||
|
|||
## Запуск тестов |
|||
|
|||
Далее Вам нужно установить `pytest`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pytest |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Он автоматически найдёт все файлы и тесты, выполнит их и предоставит Вам отчёт о результатах тестирования. |
|||
|
|||
Запустите тесты командой `pytest` и увидите результат: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pytest |
|||
|
|||
================ test session starts ================ |
|||
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 |
|||
rootdir: /home/user/code/superawesome-cli/app |
|||
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 |
|||
collected 6 items |
|||
|
|||
---> 100% |
|||
|
|||
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span> |
|||
|
|||
<span style="color: green;">================= 1 passed in 0.03s =================</span> |
|||
``` |
|||
|
|||
</div> |
@ -0,0 +1,31 @@ |
|||
# 响应 - 更改状态码 |
|||
|
|||
你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。 |
|||
|
|||
但在某些情况下,你需要返回一个不同于默认值的状态码。 |
|||
|
|||
## 使用场景 |
|||
|
|||
例如,假设你想默认返回一个HTTP状态码为“OK”`200`。 |
|||
|
|||
但如果数据不存在,你想创建它,并返回一个HTTP状态码为“CREATED”`201`。 |
|||
|
|||
但你仍然希望能够使用`response_model`过滤和转换你返回的数据。 |
|||
|
|||
对于这些情况,你可以使用一个`Response`参数。 |
|||
|
|||
## 使用 `Response` 参数 |
|||
|
|||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。 |
|||
|
|||
然后你可以在这个*临时*响应对象中设置`status_code`。 |
|||
|
|||
```Python hl_lines="1 9 12" |
|||
{!../../../docs_src/response_change_status_code/tutorial001.py!} |
|||
``` |
|||
|
|||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 |
|||
|
|||
**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 |
|||
|
|||
你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。 |
@ -0,0 +1,39 @@ |
|||
# 响应头 |
|||
|
|||
## 使用 `Response` 参数 |
|||
|
|||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。 |
|||
|
|||
然后你可以在这个*临时*响应对象中设置头部。 |
|||
```Python hl_lines="1 7-8" |
|||
{!../../../docs_src/response_headers/tutorial002.py!} |
|||
``` |
|||
|
|||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 |
|||
|
|||
**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 |
|||
|
|||
你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。 |
|||
|
|||
## 直接返回 `Response` |
|||
|
|||
你也可以在直接返回`Response`时添加头部。 |
|||
|
|||
按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: |
|||
```Python hl_lines="10-12" |
|||
{!../../../docs_src/response_headers/tutorial001.py!} |
|||
``` |
|||
|
|||
|
|||
!!! 注意 "技术细节" |
|||
你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。 |
|||
|
|||
**FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。 |
|||
|
|||
由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。 |
|||
|
|||
## 自定义头部 |
|||
|
|||
请注意,可以使用'X-'前缀添加自定义专有头部。 |
|||
|
|||
但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在<a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette的CORS文档</a>中记录的`expose_headers`参数。 |
@ -0,0 +1,39 @@ |
|||
# 静态文件 |
|||
|
|||
您可以使用 `StaticFiles`从目录中自动提供静态文件。 |
|||
|
|||
## 使用`StaticFiles` |
|||
|
|||
* 导入`StaticFiles`。 |
|||
* "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。 |
|||
|
|||
```Python hl_lines="2 6" |
|||
{!../../../docs_src/static_files/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! note "技术细节" |
|||
你也可以用 `from starlette.staticfiles import StaticFiles`。 |
|||
|
|||
**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。 |
|||
|
|||
### 什么是"挂载"(Mounting) |
|||
|
|||
"挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。 |
|||
|
|||
这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。 |
|||
|
|||
你可以在**高级用户指南**中了解更多。 |
|||
|
|||
## 细节 |
|||
|
|||
这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。 |
|||
|
|||
`directory="static"` 指向包含你的静态文件的目录名字。 |
|||
|
|||
`name="static"` 提供了一个能被**FastAPI**内部使用的名字。 |
|||
|
|||
所有这些参数可以不同于"`static`",根据你应用的需要和具体细节调整它们。 |
|||
|
|||
## 更多信息 |
|||
|
|||
更多细节和选择查阅 <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">Starlette's docs about Static Files</a>. |
@ -0,0 +1,8 @@ |
|||
-e . |
|||
mkdocs >=1.1.2,<2.0.0 |
|||
mkdocs-material >=8.1.4,<9.0.0 |
|||
mdx-include >=1.4.1,<2.0.0 |
|||
mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 |
|||
typer-cli >=0.0.13,<0.0.14 |
|||
typer[all] >=0.6.1,<0.8.0 |
|||
pyyaml >=5.3.1,<7.0.0 |
@ -0,0 +1,25 @@ |
|||
-e . |
|||
pytest >=7.1.3,<8.0.0 |
|||
coverage[toml] >= 6.5.0,< 8.0 |
|||
mypy ==1.3.0 |
|||
ruff ==0.0.272 |
|||
black == 23.3.0 |
|||
httpx >=0.23.0,<0.24.0 |
|||
email_validator >=1.1.1,<2.0.0 |
|||
# TODO: once removing databases from tutorial, upgrade SQLAlchemy |
|||
# probably when including SQLModel |
|||
sqlalchemy >=1.3.18,<1.4.43 |
|||
peewee >=3.13.3,<4.0.0 |
|||
databases[sqlite] >=0.3.2,<0.7.0 |
|||
orjson >=3.2.1,<4.0.0 |
|||
ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0 |
|||
python-multipart >=0.0.5,<0.0.7 |
|||
flask >=1.1.2,<3.0.0 |
|||
anyio[trio] >=3.2.1,<4.0.0 |
|||
python-jose[cryptography] >=3.3.0,<4.0.0 |
|||
pyyaml >=5.3.1,<7.0.0 |
|||
passlib[bcrypt] >=1.7.2,<2.0.0 |
|||
|
|||
# types |
|||
types-ujson ==5.7.0.1 |
|||
types-orjson ==3.6.2 |
@ -0,0 +1,5 @@ |
|||
-e .[all] |
|||
-r requirements-tests.txt |
|||
-r requirements-docs.txt |
|||
uvicorn[standard] >=0.12.0,<0.21.0 |
|||
pre-commit >=2.17.0,<3.0.0 |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue