committed by
GitHub
59 changed files with 2255 additions and 48 deletions
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,200 @@ |
|||||
|
# Cechy |
||||
|
|
||||
|
## Cechy FastAPI |
||||
|
|
||||
|
**FastAPI** zapewnia Ci następujące korzyści: |
||||
|
|
||||
|
### Oparcie o standardy open |
||||
|
|
||||
|
* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> do tworzenia API, w tym deklaracji <abbr title="znane również jako: paths, endpoints, routes">ścieżek</abbr> <abbr title="znane również jako metody HTTP, takie jak POST, GET, PUT, DELETE">operacji</abbr>, parametrów, <abbr title="po angielsku: body requests">ciał zapytań</abbr>, bezpieczeństwa, itp. |
||||
|
* Automatyczna dokumentacja modelu danych za pomocą <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (ponieważ OpenAPI bazuje na JSON Schema). |
||||
|
* Zaprojektowane z myślą o zgodności z powyższymi standardami zamiast dodawania ich obsługi po fakcie. |
||||
|
* Możliwość automatycznego **generowania kodu klienta** w wielu językach. |
||||
|
|
||||
|
### Automatyczna dokumentacja |
||||
|
|
||||
|
Interaktywna dokumentacja i webowe interfejsy do eksploracji API. Z racji tego, że framework bazuje na OpenAPI, istnieje wiele opcji, z czego 2 są domyślnie dołączone. |
||||
|
|
||||
|
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>, z interaktywnym interfejsem - odpytuj i testuj swoje API bezpośrednio z przeglądarki. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
* Alternatywna dokumentacja API z <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### Nowoczesny Python |
||||
|
|
||||
|
Wszystko opiera się na standardowych deklaracjach typu **Python 3.6** (dzięki Pydantic). Brak nowej składni do uczenia. Po prostu standardowy, współczesny Python. |
||||
|
|
||||
|
Jeśli potrzebujesz szybkiego przypomnienia jak używać deklaracji typów w Pythonie (nawet jeśli nie używasz FastAPI), sprawdź krótki samouczek: [Python Types](python-types.md){.internal-link target=_blank}. |
||||
|
|
||||
|
Wystarczy, że napiszesz standardowe deklaracje typów Pythona: |
||||
|
|
||||
|
```Python |
||||
|
from datetime import date |
||||
|
|
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
# Zadeklaruj parametr jako str |
||||
|
# i uzyskaj wsparcie edytora wewnątrz funkcji |
||||
|
def main(user_id: str): |
||||
|
return user_id |
||||
|
|
||||
|
|
||||
|
# Model Pydantic |
||||
|
class User(BaseModel): |
||||
|
id: int |
||||
|
name: str |
||||
|
joined: date |
||||
|
``` |
||||
|
|
||||
|
A one będą mogły zostać później użyte w następujący sposób: |
||||
|
|
||||
|
```Python |
||||
|
my_user: User = User(id=3, name="John Doe", joined="2018-07-19") |
||||
|
|
||||
|
second_user_data = { |
||||
|
"id": 4, |
||||
|
"name": "Mary", |
||||
|
"joined": "2018-11-30", |
||||
|
} |
||||
|
|
||||
|
my_second_user: User = User(**second_user_data) |
||||
|
``` |
||||
|
|
||||
|
!!! info |
||||
|
`**second_user_data` oznacza: |
||||
|
|
||||
|
Przekaż klucze i wartości słownika `second_user_data` bezpośrednio jako argumenty klucz-wartość, co jest równoznaczne z: `User(id=4, name="Mary", joined="2018-11-30")` |
||||
|
|
||||
|
### Wsparcie edytora |
||||
|
|
||||
|
Cały framework został zaprojektowany tak, aby był łatwy i intuicyjny w użyciu. Wszystkie pomysły zostały przetestowane na wielu edytorach jeszcze przed rozpoczęciem procesu tworzenia, aby zapewnić najlepsze wrażenia programistyczne. |
||||
|
|
||||
|
Ostatnia ankieta <abbr title="coroczna ankieta przeprowadza w środowisku programistów języka Python">Python developer survey</abbr> jasno wskazuje, że <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">najczęściej używaną funkcjonalnością jest autouzupełnianie w edytorze</a>. |
||||
|
|
||||
|
Cała struktura frameworku **FastAPI** jest na tym oparta. Autouzupełnianie działa wszędzie. |
||||
|
|
||||
|
Rzadko będziesz musiał wracać do dokumentacji. |
||||
|
|
||||
|
Oto, jak twój edytor może Ci pomóc: |
||||
|
|
||||
|
* <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>: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Otrzymasz uzupełnienie nawet w miejscach, w których normalnie uzupełnienia nie ma. Na przykład klucz "price" w treści JSON (który mógł być zagnieżdżony), który pochodzi z zapytania. |
||||
|
|
||||
|
Koniec z wpisywaniem błędnych nazw kluczy, przechodzeniem tam i z powrotem w dokumentacji lub przewijaniem w górę i w dół, aby sprawdzić, czy w końcu użyłeś nazwy `username` czy `user_name`. |
||||
|
|
||||
|
### Zwięzłość |
||||
|
|
||||
|
Wszystko posiada sensowne **domyślne wartości**. Wszędzie znajdziesz opcjonalne konfiguracje. Wszystkie parametry możesz dostroić, aby zrobić to co potrzebujesz do zdefiniowania API. |
||||
|
|
||||
|
Ale domyślnie wszystko **"po prostu działa"**. |
||||
|
|
||||
|
### Walidacja |
||||
|
|
||||
|
* Walidacja większości (lub wszystkich?) **typów danych** Pythona, w tym: |
||||
|
* Obiektów JSON (`dict`). |
||||
|
* Tablic JSON (`list`) ze zdefiniowanym typem elementów. |
||||
|
* Pól tekstowych (`str`) z określeniem minimalnej i maksymalnej długości. |
||||
|
* Liczb (`int`, `float`) z wartościami minimalnymi, maksymalnymi, itp. |
||||
|
|
||||
|
* Walidacja bardziej egzotycznych typów danych, takich jak: |
||||
|
* URL. |
||||
|
* Email. |
||||
|
* UUID. |
||||
|
* ...i inne. |
||||
|
|
||||
|
Cała walidacja jest obsługiwana przez ugruntowaną i solidną bibliotekę **Pydantic**. |
||||
|
|
||||
|
### Bezpieczeństwo i uwierzytelnianie |
||||
|
|
||||
|
Bezpieczeństwo i uwierzytelnianie jest zintegrowane. Bez żadnych kompromisów z bazami czy modelami danych. |
||||
|
|
||||
|
Wszystkie schematy bezpieczeństwa zdefiniowane w OpenAPI, w tym: |
||||
|
|
||||
|
* Podstawowy protokół HTTP. |
||||
|
* **OAuth2** (również z **tokenami JWT**). Sprawdź samouczek [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. |
||||
|
* Klucze API w: |
||||
|
* Nagłówkach. |
||||
|
* Parametrach zapytań. |
||||
|
* Ciasteczkach, itp. |
||||
|
|
||||
|
Plus wszystkie funkcje bezpieczeństwa Starlette (włączając w to **<abbr title="po angielsku: session cookies">ciasteczka sesyjne</abbr>**). |
||||
|
|
||||
|
Wszystko zbudowane jako narzędzia i komponenty wielokrotnego użytku, które można łatwo zintegrować z systemami, magazynami oraz bazami danych - relacyjnymi, NoSQL, itp. |
||||
|
|
||||
|
### Wstrzykiwanie Zależności |
||||
|
|
||||
|
FastAPI zawiera niezwykle łatwy w użyciu, ale niezwykle potężny system <abbr title='Po angielsku: Dependency Injection. Znane również jako "components", "resources", "services", "providers"'><strong>Wstrzykiwania Zależności</strong></abbr>. |
||||
|
|
||||
|
* Nawet zależności mogą mieć zależności, tworząc hierarchię lub **"graf" zależności**. |
||||
|
* Wszystko jest **obsługiwane automatycznie** przez framework. |
||||
|
* Wszystkie zależności mogą wymagać danych w żądaniach oraz rozszerzać ograniczenia i automatyczną dokumentację **<abbr title="po angielsku: path operations">operacji na ścieżce</abbr>**. |
||||
|
* **Automatyczna walidacja** parametrów *operacji na ścieżce* zdefiniowanych w zależnościach. |
||||
|
* Obsługa złożonych systemów uwierzytelniania użytkowników, **połączeń z bazami danych**, itp. |
||||
|
* Bazy danych, front end, itp. **bez kompromisów**, ale wciąż łatwe do integracji. |
||||
|
|
||||
|
### Nieograniczone "wtyczki" |
||||
|
|
||||
|
Lub ujmując to inaczej - brak potrzeby wtyczek. Importuj i używaj kod, który potrzebujesz. |
||||
|
|
||||
|
Każda integracja została zaprojektowana tak, aby była tak prosta w użyciu (z zależnościami), że możesz utworzyć "wtyczkę" dla swojej aplikacji w 2 liniach kodu, używając tej samej struktury i składni, które są używane w *operacjach na ścieżce*. |
||||
|
|
||||
|
### Testy |
||||
|
|
||||
|
* 100% <abbr title="Ilość kodu, który jest automatycznie testowany">pokrycia kodu testami</abbr>. |
||||
|
* 100% <abbr title="Deklaracje typów Python - dzięki nim twój edytor i zewnętrzne narzędzia mogą zapewnić Ci lepsze wsparcie ">adnotacji typów</abbr>. |
||||
|
* Używany w aplikacjach produkcyjnych. |
||||
|
|
||||
|
## Cechy Starlette |
||||
|
|
||||
|
**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) <a href="https://www.starlette.io/" class="external-link" target="_blank"><strong>Starlette</strong></a>. Tak więc każdy dodatkowy kod Starlette, który posiadasz, również będzie działał. |
||||
|
|
||||
|
`FastAPI` jest w rzeczywistości podklasą `Starlette`, więc jeśli już znasz lub używasz Starlette, większość funkcji będzie działać w ten sam sposób. |
||||
|
|
||||
|
Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Starlette** (ponieważ FastAPI to po prostu Starlette na sterydach): |
||||
|
|
||||
|
* Bardzo imponująca wydajność. Jest to <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">jeden z najszybszych dostępnych frameworków Pythona, na równi z **NodeJS** i **Go**</a>. |
||||
|
* Wsparcie dla **WebSocket**. |
||||
|
* <abbr title='Zadania wykonywane w tle, bez zatrzymywania żądań, w tym samym procesie. Po angielsku: In-process background tasks'>Zadania w tle</abbr>. |
||||
|
* Eventy startup i shutdown. |
||||
|
* Klient testowy zbudowany na bazie biblioteki `requests`. |
||||
|
* **CORS**, GZip, pliki statyczne, streamy. |
||||
|
* Obsługa **sesji i ciasteczek**. |
||||
|
* 100% pokrycie testami. |
||||
|
* 100% adnotacji typów. |
||||
|
|
||||
|
## Cechy Pydantic |
||||
|
|
||||
|
**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) <a href="https://pydantic-docs.helpmanual.io" class="external-link" target="_blank"><strong>Pydantic</strong></a>. Tak więc każdy dodatkowy kod Pydantic, który posiadasz, również będzie działał. |
||||
|
|
||||
|
Wliczając w to zewnętrzne biblioteki, również oparte o Pydantic, takie jak <abbr title="Mapowanie obiektowo-relacyjne. Po angielsku: Object-Relational Mapper">ORM</abbr>, <abbr title="Object-Document Mapper">ODM</abbr> dla baz danych. |
||||
|
|
||||
|
Oznacza to, że w wielu przypadkach możesz przekazać ten sam obiekt, który otrzymasz z żądania **bezpośrednio do bazy danych**, ponieważ wszystko jest walidowane automatycznie. |
||||
|
|
||||
|
Działa to również w drugą stronę, w wielu przypadkach możesz po prostu przekazać obiekt otrzymany z bazy danych **bezpośrednio do klienta**. |
||||
|
|
||||
|
Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Pydantic** (ponieważ FastAPI bazuje na Pydantic do obsługi wszystkich danych): |
||||
|
|
||||
|
* **Bez prania mózgu**: |
||||
|
* Brak nowego mikrojęzyka do definiowania schematu, którego trzeba się nauczyć. |
||||
|
* Jeśli znasz adnotacje typów Pythona to wiesz jak używać Pydantic. |
||||
|
* Dobrze współpracuje z Twoim **<abbr title='Skrót od "Integrated Development Environment", podobne do edytora kodu'>IDE</abbr>/<abbr title="Program, który sprawdza Twój kod pod kątem błędów">linterem</abbr>/mózgiem**: |
||||
|
* Ponieważ struktury danych Pydantic to po prostu instancje klas, które definiujesz; autouzupełnianie, linting, mypy i twoja intuicja powinny działać poprawnie z Twoimi zwalidowanymi danymi. |
||||
|
* **Szybkość**: |
||||
|
* w <a href="https://pydantic-docs.helpmanual.io/benchmarks/" class="external-link" target="_blank">benchmarkach</a> Pydantic jest szybszy niż wszystkie inne testowane biblioteki. |
||||
|
* Walidacja **złożonych struktur**: |
||||
|
* Wykorzystanie hierarchicznych modeli Pydantic, Pythonowego modułu `typing` zawierającego `List`, `Dict`, itp. |
||||
|
* Walidatory umożliwiają jasne i łatwe definiowanie, sprawdzanie złożonych struktur danych oraz dokumentowanie ich jako JSON Schema. |
||||
|
* Możesz mieć głęboko **zagnieżdżone obiekty JSON** i wszystkie je poddać walidacji i adnotować. |
||||
|
* **Rozszerzalność**: |
||||
|
* Pydantic umożliwia zdefiniowanie niestandardowych typów danych lub rozszerzenie walidacji o metody na modelu, na których użyty jest dekorator walidatora. |
||||
|
* 100% pokrycie testami. |
@ -0,0 +1,382 @@ |
|||||
|
# Body - Вложенные модели |
||||
|
|
||||
|
С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic). |
||||
|
|
||||
|
## Определение полей содержащих списки |
||||
|
|
||||
|
Вы можете определять атрибут как подтип. Например, тип `list` в Python: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="12" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="14" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен. |
||||
|
|
||||
|
## Определение полей содержащих список с определением типов его элементов |
||||
|
|
||||
|
Однако в Python есть способ объявления списков с указанием типов для вложенных элементов: |
||||
|
|
||||
|
### Импортируйте `List` из модуля typing |
||||
|
|
||||
|
В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡 |
||||
|
|
||||
|
Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python: |
||||
|
|
||||
|
```Python hl_lines="1" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
### Объявление `list` с указанием типов для вложенных элементов |
||||
|
|
||||
|
Объявление типов для элементов (внутренних типов) вложенных в такие типы как `list`, `dict`, `tuple`: |
||||
|
|
||||
|
* Если у вас Python версии ниже чем 3.9, импортируйте их аналог из модуля `typing` |
||||
|
* Передайте внутренний(ие) тип(ы) как "параметры типа", используя квадратные скобки: `[` и `]` |
||||
|
|
||||
|
В Python версии 3.9 это будет выглядеть так: |
||||
|
|
||||
|
```Python |
||||
|
my_list: list[str] |
||||
|
``` |
||||
|
|
||||
|
В версиях Python до 3.9 это будет выглядеть так: |
||||
|
|
||||
|
```Python |
||||
|
from typing import List |
||||
|
|
||||
|
my_list: List[str] |
||||
|
``` |
||||
|
|
||||
|
Это всё стандартный синтаксис Python для объявления типов. |
||||
|
|
||||
|
Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами. |
||||
|
|
||||
|
Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк": |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="12" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial002_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="14" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="14" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
## Типы множеств |
||||
|
|
||||
|
Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками. |
||||
|
|
||||
|
И в Python есть специальный тип данных для множеств уникальных элементов - `set`. |
||||
|
|
||||
|
Тогда мы может обьявить поле `tags` как множество строк: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="12" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="14" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="1 14" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
С помощью этого, даже если вы получите запрос с повторяющимися данными, они будут преобразованы в множество уникальных элементов. |
||||
|
|
||||
|
И когда вы выводите эти данные, даже если исходный набор содержал дубликаты, они будут выведены в виде множества уникальных элементов. |
||||
|
|
||||
|
И они также будут соответствующим образом аннотированы / задокументированы. |
||||
|
|
||||
|
## Вложенные Модели |
||||
|
|
||||
|
У каждого атрибута Pydantic-модели есть тип. |
||||
|
|
||||
|
Но этот тип может сам быть другой моделью Pydantic. |
||||
|
|
||||
|
Таким образом вы можете объявлять глубоко вложенные JSON "объекты" с определёнными именами атрибутов, типами и валидацией. |
||||
|
|
||||
|
Всё это может быть произвольно вложенным. |
||||
|
|
||||
|
### Определение подмодели |
||||
|
|
||||
|
Например, мы можем определить модель `Image`: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="7-9" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="9-11" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="9-11" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
### Использование вложенной модели в качестве типа |
||||
|
|
||||
|
Также мы можем использовать эту модель как тип атрибута: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="18" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="20" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="20" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
Это означает, что **FastAPI** будет ожидать тело запроса, аналогичное этому: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"name": "Foo", |
||||
|
"description": "The pretender", |
||||
|
"price": 42.0, |
||||
|
"tax": 3.2, |
||||
|
"tags": ["rock", "metal", "bar"], |
||||
|
"image": { |
||||
|
"url": "http://example.com/baz.jpg", |
||||
|
"name": "The Foo live" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите: |
||||
|
|
||||
|
* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей |
||||
|
* Преобразование данных |
||||
|
* Валидацию данных |
||||
|
* Автоматическую документацию |
||||
|
|
||||
|
## Особые типы и валидация |
||||
|
|
||||
|
Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`. |
||||
|
|
||||
|
Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией <a href="https://pydantic-docs.helpmanual.io/usage/types/" class="external-link" target="_blank">по необычным типам Pydantic</a>. Вы увидите некоторые примеры в следующей главе. |
||||
|
|
||||
|
Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="2 8" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial005_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="4 10" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="4 10" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial005.py!} |
||||
|
``` |
||||
|
|
||||
|
Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI. |
||||
|
|
||||
|
## Атрибуты, содержащие списки подмоделей |
||||
|
|
||||
|
Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="18" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial006_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="20" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="20" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial006.py!} |
||||
|
``` |
||||
|
|
||||
|
Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате: |
||||
|
|
||||
|
```JSON hl_lines="11" |
||||
|
{ |
||||
|
"name": "Foo", |
||||
|
"description": "The pretender", |
||||
|
"price": 42.0, |
||||
|
"tax": 3.2, |
||||
|
"tags": [ |
||||
|
"rock", |
||||
|
"metal", |
||||
|
"bar" |
||||
|
], |
||||
|
"images": [ |
||||
|
{ |
||||
|
"url": "http://example.com/baz.jpg", |
||||
|
"name": "The Foo live" |
||||
|
}, |
||||
|
{ |
||||
|
"url": "http://example.com/dave.jpg", |
||||
|
"name": "The Baz" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
!!! info "Информация" |
||||
|
Заметьте, что теперь у ключа `images` есть список объектов изображений. |
||||
|
|
||||
|
## Глубоко вложенные модели |
||||
|
|
||||
|
Вы можете определять модели с произвольным уровнем вложенности: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="7 12 18 21 25" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial007_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="9 14 20 23 27" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial007_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="9 14 20 23 27" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial007.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! info "Информация" |
||||
|
Заметьте, что у объекта `Offer` есть список объектов `Item`, которые, в свою очередь, могут содержать необязательный список объектов `Image` |
||||
|
|
||||
|
## Тела с чистыми списками элементов |
||||
|
|
||||
|
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic: |
||||
|
|
||||
|
```Python |
||||
|
images: List[Image] |
||||
|
``` |
||||
|
|
||||
|
в Python 3.9 и выше: |
||||
|
|
||||
|
```Python |
||||
|
images: list[Image] |
||||
|
``` |
||||
|
|
||||
|
например так: |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="13" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="15" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial008.py!} |
||||
|
``` |
||||
|
|
||||
|
## Универсальная поддержка редактора |
||||
|
|
||||
|
И вы получаете поддержку редактора везде. |
||||
|
|
||||
|
Даже для элементов внутри списков: |
||||
|
|
||||
|
<img src="/img/tutorial/body-nested-models/image01.png"> |
||||
|
|
||||
|
Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic. |
||||
|
|
||||
|
Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON. |
||||
|
|
||||
|
## Тела запросов с произвольными словарями (`dict` ) |
||||
|
|
||||
|
Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных. |
||||
|
|
||||
|
Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic). |
||||
|
|
||||
|
Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`. |
||||
|
|
||||
|
Именно это мы сейчас и увидим здесь. |
||||
|
|
||||
|
В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`: |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="7" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial009_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="9" |
||||
|
{!> ../../../docs_src/body_nested_models/tutorial009.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! tip "Совет" |
||||
|
Имейте в виду, что JSON поддерживает только ключи типа `str`. |
||||
|
|
||||
|
Но Pydantic обеспечивает автоматическое преобразование данных. |
||||
|
|
||||
|
Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные. |
||||
|
|
||||
|
А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`. |
||||
|
|
||||
|
## Резюме |
||||
|
|
||||
|
С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода. |
||||
|
|
||||
|
И дополнительно вы получаете: |
||||
|
|
||||
|
* Поддержку редактора (автодополнение доступно везде!) |
||||
|
* Преобразование данных (также известно как парсинг / сериализация) |
||||
|
* Валидацию данных |
||||
|
* Документацию схемы данных |
||||
|
* Автоматическую генерацию документации |
@ -0,0 +1,84 @@ |
|||||
|
# CORS (Cross-Origin Resource Sharing) |
||||
|
|
||||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Понятие CORS или "Cross-Origin Resource Sharing"</a> относится к ситуациям, при которых запущенный в браузере фронтенд содержит JavaScript-код, который взаимодействует с бэкендом, находящимся на другом "источнике" ("origin"). |
||||
|
|
||||
|
## Источник |
||||
|
|
||||
|
Источник - это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`). |
||||
|
|
||||
|
Поэтому это три разных источника: |
||||
|
|
||||
|
* `http://localhost` |
||||
|
* `https://localhost` |
||||
|
* `http://localhost:8080` |
||||
|
|
||||
|
Даже если они все расположены в `localhost`, они используют разные протоколы и порты, а значит, являются разными источниками. |
||||
|
|
||||
|
## Шаги |
||||
|
|
||||
|
Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`). |
||||
|
|
||||
|
Затем браузер отправит бэкенду HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие заголовки для авторизации взаимодействия с другим источником (`http://localhost:8080`), то браузер разрешит JavaScript-коду на фронтенде отправить запрос на этот бэкенд. |
||||
|
|
||||
|
Чтобы это работало, у бэкенда должен быть список "разрешённых источников" ("allowed origins"). |
||||
|
|
||||
|
В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд работал корректно. |
||||
|
|
||||
|
## Подстановочный символ `"*"` |
||||
|
|
||||
|
В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники. |
||||
|
|
||||
|
Но тогда не будут разрешены некоторые виды взаимодействия, включая всё связанное с учётными данными: куки, заголовки Authorization с Bearer-токенами наподобие тех, которые мы использовали ранее и т.п. |
||||
|
|
||||
|
Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников. |
||||
|
|
||||
|
## Использование `CORSMiddleware` |
||||
|
|
||||
|
Вы можете настроить этот механизм в вашем **FastAPI** приложении, используя `CORSMiddleware`. |
||||
|
|
||||
|
* Импортируйте `CORSMiddleware`. |
||||
|
* Создайте список разрешённых источников (в виде строк). |
||||
|
* Добавьте его как "middleware" к вашему **FastAPI** приложению. |
||||
|
|
||||
|
Вы также можете указать, разрешает ли ваш бэкенд использование: |
||||
|
|
||||
|
* Учётных данных (включая заголовки Authorization, куки и т.п.). |
||||
|
* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`. |
||||
|
* Отдельных HTTP-заголовков или всех вместе, используя `"*"`. |
||||
|
|
||||
|
```Python hl_lines="2 6-11 13-19" |
||||
|
{!../../../docs_src/cors/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
`CORSMiddleware` использует для параметров "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте. |
||||
|
|
||||
|
Поддерживаются следующие аргументы: |
||||
|
|
||||
|
* `allow_origins` - Список источников, на которые разрешено выполнять кросс-доменные запросы. Например, `['https://example.org', 'https://www.example.org']`. Можно использовать `['*']`, чтобы разрешить любые источники. |
||||
|
* `allow_origin_regex` - Регулярное выражение для определения источников, на которые разрешено выполнять кросс-доменные запросы. Например, `'https://.*\.example\.org'`. |
||||
|
* `allow_methods` - Список HTTP-методов, которые разрешены для кросс-доменных запросов. По умолчанию равно `['GET']`. Можно использовать `['*']`, чтобы разрешить все стандартные методы. |
||||
|
* `allow_headers` - Список HTTP-заголовков, которые должны поддерживаться при кросс-доменных запросах. По умолчанию равно `[]`. Можно использовать `['*']`, чтобы разрешить все заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` и `Content-Type` всегда разрешены для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простых CORS-запросов</a>. |
||||
|
* `allow_credentials` - указывает, что куки разрешены в кросс-доменных запросах. По умолчанию равно `False`. Также, `allow_origins` нельзя присвоить `['*']`, если разрешено использование учётных данных. В таком случае должен быть указан список источников. |
||||
|
* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию равно `[]`. |
||||
|
* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию равно `600`. |
||||
|
|
||||
|
`CORSMiddleware` отвечает на два типа HTTP-запросов... |
||||
|
|
||||
|
### CORS-запросы с предварительной проверкой |
||||
|
|
||||
|
Это любые `OPTIONS` запросы с заголовками `Origin` и `Access-Control-Request-Method`. |
||||
|
|
||||
|
В этом случае middleware перехватит входящий запрос и отправит соответствующие CORS-заголовки в ответе, а также ответ `200` или `400` в информационных целях. |
||||
|
|
||||
|
### Простые запросы |
||||
|
|
||||
|
Любые запросы с заголовком `Origin`. В этом случае middleware передаст запрос дальше как обычно, но добавит соответствующие CORS-заголовки к ответу. |
||||
|
|
||||
|
## Больше информации |
||||
|
|
||||
|
Для получения более подробной информации о <abbr title="Cross-Origin Resource Sharing">CORS</abbr>, обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Документации CORS от Mozilla</a>. |
||||
|
|
||||
|
!!! note "Технические детали" |
||||
|
Вы также можете использовать `from starlette.middleware.cors import CORSMiddleware`. |
||||
|
|
||||
|
**FastAPI** предоставляет несколько middleware в `fastapi.middleware` только для вашего удобства как разработчика. Но большинство доступных middleware взяты напрямую из Starlette. |
@ -0,0 +1,252 @@ |
|||||
|
# Дополнительные модели |
||||
|
|
||||
|
В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей. |
||||
|
|
||||
|
Это особенно применимо в случае моделей пользователя, потому что: |
||||
|
|
||||
|
* **Модель для ввода** должна иметь возможность содержать пароль. |
||||
|
* **Модель для вывода** не должна содержать пароль. |
||||
|
* **Модель для базы данных**, возможно, должна содержать хэшированный пароль. |
||||
|
|
||||
|
!!! danger "Внимание" |
||||
|
Никогда не храните пароли пользователей в чистом виде. Всегда храните "безопасный хэш", который вы затем сможете проверить. |
||||
|
|
||||
|
Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. |
||||
|
|
||||
|
## Множественные модели |
||||
|
|
||||
|
Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" |
||||
|
{!> ../../../docs_src/extra_models/tutorial001_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" |
||||
|
{!> ../../../docs_src/extra_models/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
### Про `**user_in.dict()` |
||||
|
|
||||
|
#### `.dict()` из Pydantic |
||||
|
|
||||
|
`user_in` - это Pydantic-модель класса `UserIn`. |
||||
|
|
||||
|
У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели. |
||||
|
|
||||
|
Поэтому, если мы создадим Pydantic-объект `user_in` таким способом: |
||||
|
|
||||
|
```Python |
||||
|
user_in = UserIn(username="john", password="secret", email="john.doe@example.com") |
||||
|
``` |
||||
|
|
||||
|
и затем вызовем: |
||||
|
|
||||
|
```Python |
||||
|
user_dict = user_in.dict() |
||||
|
``` |
||||
|
|
||||
|
то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели). |
||||
|
|
||||
|
И если мы вызовем: |
||||
|
|
||||
|
```Python |
||||
|
print(user_dict) |
||||
|
``` |
||||
|
|
||||
|
мы можем получить `dict` с такими данными: |
||||
|
|
||||
|
```Python |
||||
|
{ |
||||
|
'username': 'john', |
||||
|
'password': 'secret', |
||||
|
'email': 'john.doe@example.com', |
||||
|
'full_name': None, |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### Распаковка `dict` |
||||
|
|
||||
|
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение. |
||||
|
|
||||
|
Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода: |
||||
|
|
||||
|
```Python |
||||
|
UserInDB(**user_dict) |
||||
|
``` |
||||
|
|
||||
|
Будет работать так же, как примерно такой код: |
||||
|
|
||||
|
```Python |
||||
|
UserInDB( |
||||
|
username="john", |
||||
|
password="secret", |
||||
|
email="john.doe@example.com", |
||||
|
full_name=None, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так: |
||||
|
|
||||
|
```Python |
||||
|
UserInDB( |
||||
|
username = user_dict["username"], |
||||
|
password = user_dict["password"], |
||||
|
email = user_dict["email"], |
||||
|
full_name = user_dict["full_name"], |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
#### Pydantic-модель из содержимого другой модели |
||||
|
|
||||
|
Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код: |
||||
|
|
||||
|
```Python |
||||
|
user_dict = user_in.dict() |
||||
|
UserInDB(**user_dict) |
||||
|
``` |
||||
|
|
||||
|
будет равнозначен такому: |
||||
|
|
||||
|
```Python |
||||
|
UserInDB(**user_in.dict()) |
||||
|
``` |
||||
|
|
||||
|
...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`. |
||||
|
|
||||
|
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели. |
||||
|
|
||||
|
#### Распаковка `dict` и дополнительные именованные аргументы |
||||
|
|
||||
|
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь: |
||||
|
|
||||
|
```Python |
||||
|
UserInDB(**user_in.dict(), hashed_password=hashed_password) |
||||
|
``` |
||||
|
|
||||
|
... то мы получим что-то подобное: |
||||
|
|
||||
|
```Python |
||||
|
UserInDB( |
||||
|
username = user_dict["username"], |
||||
|
password = user_dict["password"], |
||||
|
email = user_dict["email"], |
||||
|
full_name = user_dict["full_name"], |
||||
|
hashed_password = hashed_password, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
!!! warning "Предупреждение" |
||||
|
Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность. |
||||
|
|
||||
|
## Сократите дублирование |
||||
|
|
||||
|
Сокращение дублирования кода - это одна из главных идей **FastAPI**. |
||||
|
|
||||
|
Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д. |
||||
|
|
||||
|
А все описанные выше модели используют много общих данных и дублируют названия атрибутов и типов. |
||||
|
|
||||
|
Мы можем это улучшить. |
||||
|
|
||||
|
Мы можем определить модель `UserBase`, которая будет базовой для остальных моделей. И затем мы можем создать подклассы этой модели, которые будут наследовать её атрибуты (объявления типов, валидацию, и т.п.). |
||||
|
|
||||
|
Все операции конвертации, валидации, документации, и т.п. будут по-прежнему работать нормально. |
||||
|
|
||||
|
В этом случае мы можем определить только различия между моделями (с `password` в чистом виде, с `hashed_password` и без пароля): |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="7 13-14 17-18 21-22" |
||||
|
{!> ../../../docs_src/extra_models/tutorial002_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="9 15-16 19-20 23-24" |
||||
|
{!> ../../../docs_src/extra_models/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
## `Union` или `anyOf` |
||||
|
|
||||
|
Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них. |
||||
|
|
||||
|
Он будет определён в OpenAPI как `anyOf`. |
||||
|
|
||||
|
Для этого используйте стандартные аннотации типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>: |
||||
|
|
||||
|
!!! note "Примечание" |
||||
|
При объявлении <a href="https://pydantic-docs.helpmanual.io/usage/types/#unions" class="external-link" target="_blank">`Union`</a>, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`. |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="1 14-15 18-20 33" |
||||
|
{!> ../../../docs_src/extra_models/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="1 14-15 18-20 33" |
||||
|
{!> ../../../docs_src/extra_models/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
### `Union` в Python 3.10 |
||||
|
|
||||
|
В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`. |
||||
|
|
||||
|
Поскольку мы передаём его как **значение аргумента** вместо того, чтобы поместить его в **аннотацию типа**, нам придётся использовать `Union` даже в Python 3.10. |
||||
|
|
||||
|
Если оно было бы указано в аннотации типа, то мы могли бы использовать вертикальную черту как в примере: |
||||
|
|
||||
|
```Python |
||||
|
some_variable: PlaneItem | CarItem |
||||
|
``` |
||||
|
|
||||
|
Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа. |
||||
|
|
||||
|
## Список моделей |
||||
|
|
||||
|
Таким же образом вы можете определять ответы как списки объектов. |
||||
|
|
||||
|
Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше): |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="18" |
||||
|
{!> ../../../docs_src/extra_models/tutorial004_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="1 20" |
||||
|
{!> ../../../docs_src/extra_models/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
## Ответ с произвольным `dict` |
||||
|
|
||||
|
Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей. |
||||
|
|
||||
|
Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели). |
||||
|
|
||||
|
В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше): |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="6" |
||||
|
{!> ../../../docs_src/extra_models/tutorial005_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="1 8" |
||||
|
{!> ../../../docs_src/extra_models/tutorial005.py!} |
||||
|
``` |
||||
|
|
||||
|
## Резюме |
||||
|
|
||||
|
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них. |
||||
|
|
||||
|
Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля. |
@ -0,0 +1,111 @@ |
|||||
|
# URL-адреса метаданных и документации |
||||
|
|
||||
|
Вы можете настроить несколько конфигураций метаданных в вашем **FastAPI** приложении. |
||||
|
|
||||
|
## Метаданные для API |
||||
|
|
||||
|
Вы можете задать следующие поля, которые используются в спецификации OpenAPI и в UI автоматической документации API: |
||||
|
|
||||
|
| Параметр | Тип | Описание | |
||||
|
|------------|--|-------------| |
||||
|
| `title` | `str` | Заголовок API. | |
||||
|
| `description` | `str` | Краткое описание API. Может быть использован Markdown. | |
||||
|
| `version` | `string` | Версия API. Версия вашего собственного приложения, а не OpenAPI. К примеру `2.5.0`. | |
||||
|
| `terms_of_service` | `str` | Ссылка к условиям пользования API. Если указано, то это должен быть URL-адрес. | |
||||
|
| `contact` | `dict` | Контактная информация для открытого API. Может содержать несколько полей. <details><summary>поля <code>contact</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>Идентификационное имя контактного лица/организации.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL указывающий на контактную информацию. ДОЛЖЕН быть в формате URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Email адрес контактного лица/организации. ДОЛЖЕН быть в формате email адреса.</td></tr></tbody></table></details> | |
||||
|
| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей. <details><summary>поля <code>license_info</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБЯЗАТЕЛЬНО</strong> (если установлен параметр <code>license_info</code>). Название лицензии, используемой для API</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.</td></tr></tbody></table></details> | |
||||
|
|
||||
|
Вы можете задать их следующим образом: |
||||
|
|
||||
|
```Python hl_lines="3-16 19-31" |
||||
|
{!../../../docs_src/metadata/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! tip "Подсказка" |
||||
|
Вы можете использовать Markdown в поле `description`, и оно будет отображено в выводе. |
||||
|
|
||||
|
С этой конфигурацией автоматическая документация API будут выглядеть так: |
||||
|
|
||||
|
<img src="/img/tutorial/metadata/image01.png"> |
||||
|
|
||||
|
## Метаданные для тегов |
||||
|
|
||||
|
Вы также можете добавить дополнительные метаданные для различных тегов, используемых для группировки ваших операций пути с помощью параметра `openapi_tags`. |
||||
|
|
||||
|
Он принимает список, содержащий один словарь для каждого тега. |
||||
|
|
||||
|
Каждый словарь может содержать в себе: |
||||
|
|
||||
|
* `name` (**обязательно**): `str`-значение с тем же именем тега, которое вы используете в параметре `tags` в ваших *операциях пути* и `APIRouter`ах. |
||||
|
* `description`: `str`-значение с кратким описанием для тега. Может содержать Markdown и будет отображаться в UI документации. |
||||
|
* `externalDocs`: `dict`-значение описывающее внешнюю документацию. Включает в себя: |
||||
|
* `description`: `str`-значение с кратким описанием для внешней документации. |
||||
|
* `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации. |
||||
|
|
||||
|
### Создание метаданных для тегов |
||||
|
|
||||
|
Давайте попробуем сделать это на примере с тегами для `users` и `items`. |
||||
|
|
||||
|
Создайте метаданные для ваших тегов и передайте их в параметре `openapi_tags`: |
||||
|
|
||||
|
```Python hl_lines="3-16 18" |
||||
|
{!../../../docs_src/metadata/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
Помните, что вы можете использовать Markdown внутри описания, к примеру "login" будет отображен жирным шрифтом (**login**) и "fancy" будет отображаться курсивом (_fancy_). |
||||
|
|
||||
|
!!! tip "Подсказка" |
||||
|
Вам необязательно добавлять метаданные для всех используемых тегов |
||||
|
|
||||
|
### Используйте собственные теги |
||||
|
Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги: |
||||
|
|
||||
|
```Python hl_lines="21 26" |
||||
|
{!../../../docs_src/metadata/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! info "Дополнительная информация" |
||||
|
Узнайте больше о тегах в [Конфигурации операции пути](../path-operation-configuration/#tags){.internal-link target=_blank}. |
||||
|
|
||||
|
### Проверьте документацию |
||||
|
|
||||
|
Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию: |
||||
|
|
||||
|
<img src="/img/tutorial/metadata/image02.png"> |
||||
|
|
||||
|
### Порядок расположения тегов |
||||
|
|
||||
|
Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в документах UI |
||||
|
|
||||
|
К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке. |
||||
|
|
||||
|
## URL-адреса OpenAPI |
||||
|
|
||||
|
По умолчанию схема OpenAPI отображена по адресу `/openapi.json`. |
||||
|
|
||||
|
Но вы можете изменить это с помощью параметра `openapi_url`. |
||||
|
|
||||
|
К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../../docs_src/metadata/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые его использует. |
||||
|
|
||||
|
## URL-адреса документации |
||||
|
|
||||
|
Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, среди которых |
||||
|
|
||||
|
* **Swagger UI**: отображаемый по адресу `/docs`. |
||||
|
* Вы можете задать его URL с помощью параметра `docs_url`. |
||||
|
* Вы можете отключить это с помощью настройки `docs_url=None`. |
||||
|
* **ReDoc**: отображаемый по адресу `/redoc`. |
||||
|
* Вы можете задать его URL с помощью параметра `redoc_url`. |
||||
|
* Вы можете отключить это с помощью настройки `redoc_url=None`. |
||||
|
|
||||
|
К примеру, чтобы задать отображение Swagger UI по адресу `/documentation` и отключить ReDoc: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../../docs_src/metadata/tutorial003.py!} |
||||
|
``` |
@ -0,0 +1,179 @@ |
|||||
|
# Конфигурация операций пути |
||||
|
|
||||
|
Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки. |
||||
|
|
||||
|
!!! warning "Внимание" |
||||
|
Помните, что эти параметры передаются непосредственно *декоратору операций пути*, а не вашей *функции-обработчику операций пути*. |
||||
|
|
||||
|
## Коды состояния |
||||
|
|
||||
|
Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*. |
||||
|
|
||||
|
Вы можете передать только `int`-значение кода, например `404`. |
||||
|
|
||||
|
Но если вы не помните, для чего нужен каждый числовой код, вы можете использовать сокращенные константы в параметре `status`: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="1 15" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="3 17" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="3 17" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
Этот код состояния будет использован в ответе и будет добавлен в схему OpenAPI. |
||||
|
|
||||
|
!!! note "Технические детали" |
||||
|
Вы также можете использовать `from starlette import status`. |
||||
|
|
||||
|
**FastAPI** предоставляет тот же `starlette.status` под псевдонимом `fastapi.status` для удобства разработчика. Но его источник - это непосредственно Starlette. |
||||
|
|
||||
|
## Теги |
||||
|
|
||||
|
Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка): |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="15 20 25" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="17 22 27" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="17 22 27" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
Они будут добавлены в схему OpenAPI и будут использованы в автоматической документации интерфейса: |
||||
|
|
||||
|
<img src="/img/tutorial/path-operation-configuration/image01.png"> |
||||
|
|
||||
|
### Теги с перечислениями |
||||
|
|
||||
|
Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*. |
||||
|
|
||||
|
В этих случаях, имеет смысл хранить теги в классе `Enum`. |
||||
|
|
||||
|
**FastAPI** поддерживает это так же, как и в случае с обычными строками: |
||||
|
|
||||
|
```Python hl_lines="1 8-10 13 18" |
||||
|
{!../../../docs_src/path_operation_configuration/tutorial002b.py!} |
||||
|
``` |
||||
|
|
||||
|
## Краткое и развёрнутое содержание |
||||
|
|
||||
|
Вы можете добавить параметры `summary` и `description`: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="18-19" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="20-21" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="20-21" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
## Описание из строк документации |
||||
|
|
||||
|
Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции <abbr title="многострочный текст, первое выражение внутри функции (не присвоенный какой-либо переменной), используемый для документации">строки документации</abbr> и **FastAPI** прочитает её отсюда. |
||||
|
|
||||
|
Вы можете использовать <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> в строке документации, и он будет интерпретирован и отображён корректно (с учетом отступа в строке документации). |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="17-25" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="19-27" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="19-27" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
Он будет использован в интерактивной документации: |
||||
|
|
||||
|
<img src="/img/tutorial/path-operation-configuration/image02.png"> |
||||
|
|
||||
|
## Описание ответа |
||||
|
|
||||
|
Вы можете указать описание ответа с помощью параметра `response_description`: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="19" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="21" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="21" |
||||
|
{!> ../../../docs_src/path_operation_configuration/tutorial005.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! info "Дополнительная информация" |
||||
|
Помните, что `response_description` относится конкретно к ответу, а `description` относится к *операции пути* в целом. |
||||
|
|
||||
|
!!! check "Технические детали" |
||||
|
OpenAPI указывает, что каждой *операции пути* необходимо описание ответа. |
||||
|
|
||||
|
Если вдруг вы не укажете его, то **FastAPI** автоматически сгенерирует это описание с текстом "Successful response". |
||||
|
|
||||
|
<img src="/img/tutorial/path-operation-configuration/image03.png"> |
||||
|
|
||||
|
## Обозначение *операции пути* как устаревшей |
||||
|
|
||||
|
Если вам необходимо пометить *операцию пути* как <abbr title="устаревшее, не рекомендовано к использованию">устаревшую</abbr>, при этом не удаляя её, передайте параметр `deprecated`: |
||||
|
|
||||
|
```Python hl_lines="16" |
||||
|
{!../../../docs_src/path_operation_configuration/tutorial006.py!} |
||||
|
``` |
||||
|
|
||||
|
Он будет четко помечен как устаревший в интерактивной документации: |
||||
|
|
||||
|
<img src="/img/tutorial/path-operation-configuration/image04.png"> |
||||
|
|
||||
|
Проверьте, как будут выглядеть устаревшие и не устаревшие *операции пути*: |
||||
|
|
||||
|
<img src="/img/tutorial/path-operation-configuration/image05.png"> |
||||
|
|
||||
|
## Резюме |
||||
|
|
||||
|
Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*. |
@ -0,0 +1,16 @@ |
|||||
|
# 高级安全 - 介绍 |
||||
|
|
||||
|
## 附加特性 |
||||
|
|
||||
|
除 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性. |
||||
|
|
||||
|
!!! tip "小贴士" |
||||
|
接下来的章节 **并不一定是 "高级的"**. |
||||
|
|
||||
|
而且对于你的使用场景来说,解决方案很可能就在其中。 |
||||
|
|
||||
|
## 先阅读教程 |
||||
|
|
||||
|
接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank}. |
||||
|
|
||||
|
它们都基于相同的概念,但支持一些额外的功能. |
@ -0,0 +1,433 @@ |
|||||
|
# 设置和环境变量 |
||||
|
|
||||
|
在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。 |
||||
|
|
||||
|
这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。 |
||||
|
|
||||
|
因此,通常会将它们提供为由应用程序读取的环境变量。 |
||||
|
|
||||
|
## 环境变量 |
||||
|
|
||||
|
!!! tip |
||||
|
如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。 |
||||
|
|
||||
|
环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。 |
||||
|
|
||||
|
您可以在 shell 中创建和使用环境变量,而无需使用 Python: |
||||
|
|
||||
|
=== "Linux、macOS、Windows Bash" |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
// 您可以创建一个名为 MY_NAME 的环境变量 |
||||
|
$ export MY_NAME="Wade Wilson" |
||||
|
|
||||
|
// 然后您可以与其他程序一起使用它,例如 |
||||
|
$ echo "Hello $MY_NAME" |
||||
|
|
||||
|
Hello Wade Wilson |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
=== "Windows PowerShell" |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
// 创建一个名为 MY_NAME 的环境变量 |
||||
|
$ $Env:MY_NAME = "Wade Wilson" |
||||
|
|
||||
|
// 与其他程序一起使用它,例如 |
||||
|
$ echo "Hello $Env:MY_NAME" |
||||
|
|
||||
|
Hello Wade Wilson |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
### 在 Python 中读取环境变量 |
||||
|
|
||||
|
您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。 |
||||
|
|
||||
|
例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
import os |
||||
|
|
||||
|
name = os.getenv("MY_NAME", "World") |
||||
|
print(f"Hello {name} from Python") |
||||
|
``` |
||||
|
|
||||
|
!!! tip |
||||
|
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的第二个参数是要返回的默认值。 |
||||
|
|
||||
|
如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。 |
||||
|
|
||||
|
然后,您可以调用该 Python 程序: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
// 这里我们还没有设置环境变量 |
||||
|
$ python main.py |
||||
|
|
||||
|
// 因为我们没有设置环境变量,所以我们得到默认值 |
||||
|
|
||||
|
Hello World from Python |
||||
|
|
||||
|
// 但是如果我们先创建一个环境变量 |
||||
|
$ export MY_NAME="Wade Wilson" |
||||
|
|
||||
|
// 然后再次调用程序 |
||||
|
$ python main.py |
||||
|
|
||||
|
// 现在它可以读取环境变量 |
||||
|
|
||||
|
Hello Wade Wilson from Python |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。 |
||||
|
|
||||
|
|
||||
|
|
||||
|
您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。 |
||||
|
|
||||
|
要做到这一点,在程序本身之前的同一行创建它: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
// 在此程序调用行中创建一个名为 MY_NAME 的环境变量 |
||||
|
$ MY_NAME="Wade Wilson" python main.py |
||||
|
|
||||
|
// 现在它可以读取环境变量 |
||||
|
|
||||
|
Hello Wade Wilson from Python |
||||
|
|
||||
|
// 之后环境变量不再存在 |
||||
|
$ python main.py |
||||
|
|
||||
|
Hello World from Python |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
!!! tip |
||||
|
您可以在 <a href="https://12factor.net/config" class="external-link" target="_blank">Twelve-Factor App: Config</a> 中阅读更多相关信息。 |
||||
|
|
||||
|
### 类型和验证 |
||||
|
|
||||
|
这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。 |
||||
|
|
||||
|
这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。 |
||||
|
|
||||
|
## Pydantic 的 `Settings` |
||||
|
|
||||
|
幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即<a href="https://pydantic-docs.helpmanual.io/usage/settings/" class="external-link" target="_blank">Pydantic: Settings management</a>。 |
||||
|
|
||||
|
### 创建 `Settings` 对象 |
||||
|
|
||||
|
从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。 |
||||
|
|
||||
|
与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。 |
||||
|
|
||||
|
您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。 |
||||
|
|
||||
|
```Python hl_lines="2 5-8 11" |
||||
|
{!../../../docs_src/settings/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! tip |
||||
|
如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。 |
||||
|
|
||||
|
然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。 |
||||
|
|
||||
|
然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。 |
||||
|
|
||||
|
### 使用 `settings` |
||||
|
|
||||
|
然后,您可以在应用程序中使用新的 `settings` 对象: |
||||
|
|
||||
|
```Python hl_lines="18-20" |
||||
|
{!../../../docs_src/settings/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
### 运行服务器 |
||||
|
|
||||
|
接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app |
||||
|
|
||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
!!! tip |
||||
|
要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。 |
||||
|
|
||||
|
然后,`admin_email` 设置将为 `"deadpool@example.com"`。 |
||||
|
|
||||
|
`app_name` 将为 `"ChimichangApp"`。 |
||||
|
|
||||
|
而 `items_per_user` 将保持其默认值为 `50`。 |
||||
|
|
||||
|
## 在另一个模块中设置 |
||||
|
|
||||
|
您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。 |
||||
|
|
||||
|
例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容: |
||||
|
|
||||
|
```Python |
||||
|
{!../../../docs_src/settings/app01/config.py!} |
||||
|
``` |
||||
|
|
||||
|
然后在一个名为 `main.py` 的文件中使用它: |
||||
|
|
||||
|
```Python hl_lines="3 11-13" |
||||
|
{!../../../docs_src/settings/app01/main.py!} |
||||
|
``` |
||||
|
!!! tip |
||||
|
您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 |
||||
|
|
||||
|
## 在依赖项中使用设置 |
||||
|
|
||||
|
在某些情况下,从依赖项中提供设置可能比在所有地方都使用全局对象 `settings` 更有用。 |
||||
|
|
||||
|
这在测试期间尤其有用,因为很容易用自定义设置覆盖依赖项。 |
||||
|
|
||||
|
### 配置文件 |
||||
|
|
||||
|
根据前面的示例,您的 `config.py` 文件可能如下所示: |
||||
|
|
||||
|
```Python hl_lines="10" |
||||
|
{!../../../docs_src/settings/app02/config.py!} |
||||
|
``` |
||||
|
|
||||
|
请注意,现在我们不创建默认实例 `settings = Settings()`。 |
||||
|
|
||||
|
### 主应用程序文件 |
||||
|
|
||||
|
现在我们创建一个依赖项,返回一个新的 `config.Settings()`。 |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="6 12-13" |
||||
|
{!> ../../../docs_src/settings/app02_an_py39/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="6 12-13" |
||||
|
{!> ../../../docs_src/settings/app02_an/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+ 非注解版本" |
||||
|
|
||||
|
!!! tip |
||||
|
如果可能,请尽量使用 `Annotated` 版本。 |
||||
|
|
||||
|
```Python hl_lines="5 11-12" |
||||
|
{!> ../../../docs_src/settings/app02/main.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! tip |
||||
|
我们稍后会讨论 `@lru_cache()`。 |
||||
|
|
||||
|
目前,您可以将 `get_settings()` 视为普通函数。 |
||||
|
|
||||
|
然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。 |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="17 19-21" |
||||
|
{!> ../../../docs_src/settings/app02_an_py39/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="17 19-21" |
||||
|
{!> ../../../docs_src/settings/app02_an/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+ 非注解版本" |
||||
|
|
||||
|
!!! tip |
||||
|
如果可能,请尽量使用 `Annotated` 版本。 |
||||
|
|
||||
|
```Python hl_lines="16 18-20" |
||||
|
{!> ../../../docs_src/settings/app02/main.py!} |
||||
|
``` |
||||
|
|
||||
|
### 设置和测试 |
||||
|
|
||||
|
然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象: |
||||
|
|
||||
|
```Python hl_lines="9-10 13 21" |
||||
|
{!../../../docs_src/settings/app02/test_main.py!} |
||||
|
``` |
||||
|
|
||||
|
在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。 |
||||
|
|
||||
|
然后,我们可以测试它是否被使用。 |
||||
|
|
||||
|
## 从 `.env` 文件中读取设置 |
||||
|
|
||||
|
如果您有许多可能经常更改的设置,可能在不同的环境中,将它们放在一个文件中,然后从该文件中读取它们,就像它们是环境变量一样,可能非常有用。 |
||||
|
|
||||
|
这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。 |
||||
|
|
||||
|
!!! tip |
||||
|
以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。 |
||||
|
|
||||
|
但是,dotenv 文件实际上不一定要具有确切的文件名。 |
||||
|
|
||||
|
Pydantic 支持使用外部库从这些类型的文件中读取。您可以在<a href="https://pydantic-docs.helpmanual.io/usage/settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic 设置: Dotenv (.env) 支持</a>中阅读更多相关信息。 |
||||
|
|
||||
|
!!! tip |
||||
|
要使其工作,您需要执行 `pip install python-dotenv`。 |
||||
|
|
||||
|
### `.env` 文件 |
||||
|
|
||||
|
您可以使用以下内容创建一个名为 `.env` 的文件: |
||||
|
|
||||
|
```bash |
||||
|
ADMIN_EMAIL="deadpool@example.com" |
||||
|
APP_NAME="ChimichangApp" |
||||
|
``` |
||||
|
|
||||
|
### 从 `.env` 文件中读取设置 |
||||
|
|
||||
|
然后,您可以使用以下方式更新您的 `config.py`: |
||||
|
|
||||
|
```Python hl_lines="9-10" |
||||
|
{!../../../docs_src/settings/app03/config.py!} |
||||
|
``` |
||||
|
|
||||
|
在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。 |
||||
|
|
||||
|
!!! tip |
||||
|
`Config` 类仅用于 Pydantic 配置。您可以在<a href="https://pydantic-docs.helpmanual.io/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>中阅读更多相关信息。 |
||||
|
|
||||
|
### 使用 `lru_cache` 仅创建一次 `Settings` |
||||
|
|
||||
|
从磁盘中读取文件通常是一项耗时的(慢)操作,因此您可能希望仅在首次读取后并重复使用相同的设置对象,而不是为每个请求都读取它。 |
||||
|
|
||||
|
但是,每次执行以下操作: |
||||
|
|
||||
|
```Python |
||||
|
Settings() |
||||
|
``` |
||||
|
|
||||
|
都会创建一个新的 `Settings` 对象,并且在创建时会再次读取 `.env` 文件。 |
||||
|
|
||||
|
如果依赖项函数只是这样的: |
||||
|
|
||||
|
```Python |
||||
|
def get_settings(): |
||||
|
return Settings() |
||||
|
``` |
||||
|
|
||||
|
我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️ |
||||
|
|
||||
|
但是,由于我们在顶部使用了 `@lru_cache()` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️ |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="1 11" |
||||
|
{!> ../../../docs_src/settings/app03_an_py39/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="1 11" |
||||
|
{!> ../../../docs_src/settings/app03_an/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+ 非注解版本" |
||||
|
|
||||
|
!!! tip |
||||
|
如果可能,请尽量使用 `Annotated` 版本。 |
||||
|
|
||||
|
```Python hl_lines="1 10" |
||||
|
{!> ../../../docs_src/settings/app03/main.py!} |
||||
|
``` |
||||
|
|
||||
|
然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。 |
||||
|
|
||||
|
#### `lru_cache` 技术细节 |
||||
|
|
||||
|
`@lru_cache()` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。 |
||||
|
|
||||
|
因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。 |
||||
|
|
||||
|
例如,如果您有一个函数: |
||||
|
```Python |
||||
|
@lru_cache() |
||||
|
def say_hi(name: str, salutation: str = "Ms."): |
||||
|
return f"Hello {salutation} {name}" |
||||
|
``` |
||||
|
|
||||
|
您的程序可以像这样执行: |
||||
|
|
||||
|
```mermaid |
||||
|
sequenceDiagram |
||||
|
|
||||
|
participant code as Code |
||||
|
participant function as say_hi() |
||||
|
participant execute as Execute function |
||||
|
|
||||
|
rect rgba(0, 255, 0, .1) |
||||
|
code ->> function: say_hi(name="Camila") |
||||
|
function ->> execute: 执行函数代码 |
||||
|
execute ->> code: 返回结果 |
||||
|
end |
||||
|
|
||||
|
rect rgba(0, 255, 255, .1) |
||||
|
code ->> function: say_hi(name="Camila") |
||||
|
function ->> code: 返回存储的结果 |
||||
|
end |
||||
|
|
||||
|
rect rgba(0, 255, 0, .1) |
||||
|
code ->> function: say_hi(name="Rick") |
||||
|
function ->> execute: 执行函数代码 |
||||
|
execute ->> code: 返回结果 |
||||
|
end |
||||
|
|
||||
|
rect rgba(0, 255, 0, .1) |
||||
|
code ->> function: say_hi(name="Rick", salutation="Mr.") |
||||
|
function ->> execute: 执行函数代码 |
||||
|
execute ->> code: 返回结果 |
||||
|
end |
||||
|
|
||||
|
rect rgba(0, 255, 255, .1) |
||||
|
code ->> function: say_hi(name="Rick") |
||||
|
function ->> code: 返回存储的结果 |
||||
|
end |
||||
|
|
||||
|
rect rgba(0, 255, 255, .1) |
||||
|
code ->> function: say_hi(name="Camila") |
||||
|
function ->> code: 返回存储的结果 |
||||
|
end |
||||
|
``` |
||||
|
|
||||
|
对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。 |
||||
|
|
||||
|
这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。 |
||||
|
|
||||
|
`@lru_cache()` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在<a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python 文档中了解有关 `@lru_cache()` 的更多信息</a>。 |
||||
|
|
||||
|
## 小结 |
||||
|
|
||||
|
您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。 |
||||
|
|
||||
|
* 通过使用依赖项,您可以简化测试。 |
||||
|
* 您可以使用 `.env` 文件。 |
||||
|
* 使用 `@lru_cache()` 可以避免为每个请求重复读取 dotenv 文件,同时允许您在测试时进行覆盖。 |
@ -0,0 +1,214 @@ |
|||||
|
# WebSockets |
||||
|
|
||||
|
您可以在 **FastAPI** 中使用 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)。 |
||||
|
|
||||
|
## 安装 `WebSockets` |
||||
|
|
||||
|
首先,您需要安装 `WebSockets`: |
||||
|
|
||||
|
```console |
||||
|
$ pip install websockets |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
## WebSockets 客户端 |
||||
|
|
||||
|
### 在生产环境中 |
||||
|
|
||||
|
在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。 |
||||
|
|
||||
|
要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。 |
||||
|
|
||||
|
或者,您可能有一个原生移动应用程序,直接使用原生代码与 WebSocket 后端通信。 |
||||
|
|
||||
|
或者,您可能有其他与 WebSocket 终端通信的方式。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。 |
||||
|
|
||||
|
当然,这并不是最优的做法,您不应该在生产环境中使用它。 |
||||
|
|
||||
|
在生产环境中,您应该选择上述任一选项。 |
||||
|
|
||||
|
但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: |
||||
|
|
||||
|
```Python hl_lines="2 6-38 41-43" |
||||
|
{!../../../docs_src/websockets/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
## 创建 `websocket` |
||||
|
|
||||
|
在您的 **FastAPI** 应用程序中,创建一个 `websocket`: |
||||
|
|
||||
|
```Python hl_lines="1 46-47" |
||||
|
{!../../../docs_src/websockets/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! note "技术细节" |
||||
|
您也可以使用 `from starlette.websockets import WebSocket`。 |
||||
|
|
||||
|
**FastAPI** 直接提供了相同的 `WebSocket`,只是为了方便开发人员。但它直接来自 Starlette。 |
||||
|
|
||||
|
## 等待消息并发送消息 |
||||
|
|
||||
|
在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 |
||||
|
|
||||
|
```Python hl_lines="48-52" |
||||
|
{!../../../docs_src/websockets/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
您可以接收和发送二进制、文本和 JSON 数据。 |
||||
|
|
||||
|
## 尝试一下 |
||||
|
|
||||
|
如果您的文件名为 `main.py`,请使用以下命令运行应用程序: |
||||
|
|
||||
|
```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) |
||||
|
``` |
||||
|
|
||||
|
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。 |
||||
|
|
||||
|
您将看到一个简单的页面,如下所示: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image01.png"> |
||||
|
|
||||
|
您可以在输入框中输入消息并发送: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image02.png"> |
||||
|
|
||||
|
您的 **FastAPI** 应用程序将回复: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image03.png"> |
||||
|
|
||||
|
您可以发送(和接收)多条消息: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image04.png"> |
||||
|
|
||||
|
所有这些消息都将使用同一个 WebSocket 连 |
||||
|
|
||||
|
接。 |
||||
|
|
||||
|
## 使用 `Depends` 和其他依赖项 |
||||
|
|
||||
|
在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容: |
||||
|
|
||||
|
* `Depends` |
||||
|
* `Security` |
||||
|
* `Cookie` |
||||
|
* `Header` |
||||
|
* `Path` |
||||
|
* `Query` |
||||
|
|
||||
|
它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同: |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python hl_lines="68-69 82" |
||||
|
{!> ../../../docs_src/websockets/tutorial002_an_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="68-69 82" |
||||
|
{!> ../../../docs_src/websockets/tutorial002_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="69-70 83" |
||||
|
{!> ../../../docs_src/websockets/tutorial002_an.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.10+ 非带注解版本" |
||||
|
|
||||
|
!!! tip |
||||
|
如果可能,请尽量使用 `Annotated` 版本。 |
||||
|
|
||||
|
```Python hl_lines="66-67 79" |
||||
|
{!> ../../../docs_src/websockets/tutorial002_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+ 非带注解版本" |
||||
|
|
||||
|
!!! tip |
||||
|
如果可能,请尽量使用 `Annotated` 版本。 |
||||
|
|
||||
|
```Python hl_lines="68-69 81" |
||||
|
{!> ../../../docs_src/websockets/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! info |
||||
|
由于这是一个 WebSocket,抛出 `HTTPException` 并不是很合理,而是抛出 `WebSocketException`。 |
||||
|
|
||||
|
您可以使用<a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">规范中定义的有效代码</a>。 |
||||
|
|
||||
|
### 尝试带有依赖项的 WebSockets |
||||
|
|
||||
|
如果您的文件名为 `main.py`,请使用以下命令运行应用程序: |
||||
|
|
||||
|
```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) |
||||
|
``` |
||||
|
|
||||
|
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。 |
||||
|
|
||||
|
在页面中,您可以设置: |
||||
|
|
||||
|
* "Item ID",用于路径。 |
||||
|
* "Token",作为查询参数。 |
||||
|
|
||||
|
!!! tip |
||||
|
注意,查询参数 `token` 将由依赖项处理。 |
||||
|
|
||||
|
通过这样,您可以连接 WebSocket,然后发送和接收消息: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image05.png"> |
||||
|
|
||||
|
## 处理断开连接和多个客户端 |
||||
|
|
||||
|
当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python hl_lines="79-81" |
||||
|
{!> ../../../docs_src/websockets/tutorial003_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python hl_lines="81-83" |
||||
|
{!> ../../../docs_src/websockets/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
尝试以下操作: |
||||
|
|
||||
|
* 使用多个浏览器选项卡打开应用程序。 |
||||
|
* 从这些选项卡中发送消息。 |
||||
|
* 然后关闭其中一个选项卡。 |
||||
|
|
||||
|
这将引发 `WebSocketDisconnect` 异常,并且所有其他客户端都会收到类似以下的消息: |
||||
|
|
||||
|
``` |
||||
|
Client #1596980209979 left the chat |
||||
|
``` |
||||
|
|
||||
|
!!! tip |
||||
|
上面的应用程序是一个最小和简单的示例,用于演示如何处理和向多个 WebSocket 连接广播消息。 |
||||
|
|
||||
|
但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。 |
||||
|
|
||||
|
如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。 |
||||
|
|
||||
|
## 更多信息 |
||||
|
|
||||
|
要了解更多选项,请查看 Starlette 的文档: |
||||
|
|
||||
|
* [WebSocket 类](https://www.starlette.io/websockets/) |
||||
|
* [基于类的 WebSocket 处理](https://www.starlette.io/endpoints/#websocketendpoint)。 |
@ -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>, 而HTTPX又是基于Requests设计的,所以很相似且易懂。 |
||||
|
|
||||
|
有了它,你可以直接与**FastAPI**一起使用 <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>。 |
||||
|
|
||||
|
## 使用 `TestClient` |
||||
|
|
||||
|
!!! 信息 |
||||
|
要使用 `TestClient`,先要安装 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>. |
||||
|
|
||||
|
例:`pip install httpx`. |
||||
|
|
||||
|
导入 `TestClient`. |
||||
|
|
||||
|
通过传入你的**FastAPI**应用创建一个 `TestClient` 。 |
||||
|
|
||||
|
创建名字以 `test_` 开头的函数(这是标准的 `pytest` 约定)。 |
||||
|
|
||||
|
像使用 `httpx` 那样使用 `TestClient` 对象。 |
||||
|
|
||||
|
为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。 |
||||
|
|
||||
|
```Python hl_lines="2 12 15-18" |
||||
|
{!../../../docs_src/app_testing/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! 提示 |
||||
|
注意测试函数是普通的 `def`,不是 `async def`。 |
||||
|
|
||||
|
还有client的调用也是普通的调用,不是用 `await`。 |
||||
|
|
||||
|
这让你可以直接使用 `pytest` 而不会遇到麻烦。 |
||||
|
|
||||
|
!!! note "技术细节" |
||||
|
你也可以用 `from starlette.testclient import TestClient`。 |
||||
|
|
||||
|
**FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。但它直接来自Starlette。 |
||||
|
|
||||
|
!!! 提示 |
||||
|
除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。 |
||||
|
|
||||
|
## 分离测试 |
||||
|
|
||||
|
在实际应用中,你可能会把你的测试放在另一个文件里。 |
||||
|
|
||||
|
您的**FastAPI**应用程序也可能由一些文件/模块组成等等。 |
||||
|
|
||||
|
### **FastAPI** app 文件 |
||||
|
|
||||
|
假设你有一个像 [更大的应用](./bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ └── main.py |
||||
|
``` |
||||
|
|
||||
|
在 `main.py` 文件中你有一个 **FastAPI** app: |
||||
|
|
||||
|
|
||||
|
```Python |
||||
|
{!../../../docs_src/app_testing/main.py!} |
||||
|
``` |
||||
|
|
||||
|
### 测试文件 |
||||
|
|
||||
|
然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件): |
||||
|
|
||||
|
``` hl_lines="5" |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
│ └── test_main.py |
||||
|
``` |
||||
|
|
||||
|
因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../../docs_src/app_testing/test_main.py!} |
||||
|
``` |
||||
|
|
||||
|
...然后测试代码和之前一样的。 |
||||
|
|
||||
|
## 测试:扩展示例 |
||||
|
|
||||
|
现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。 |
||||
|
|
||||
|
### 扩展后的 **FastAPI** app 文件 |
||||
|
|
||||
|
让我们继续之前的文件结构: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
│ └── test_main.py |
||||
|
``` |
||||
|
|
||||
|
假设现在包含**FastAPI** app的文件 `main.py` 有些其他**路径操作**。 |
||||
|
|
||||
|
有个 `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+ non-Annotated" |
||||
|
|
||||
|
!!! tip |
||||
|
Prefer to use the `Annotated` version if possible. |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_py310/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+ non-Annotated" |
||||
|
|
||||
|
!!! tip |
||||
|
Prefer to use the `Annotated` version if possible. |
||||
|
|
||||
|
```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的设计的。 |
||||
|
|
||||
|
接着只需在测试中同样操作。 |
||||
|
|
||||
|
示例: |
||||
|
|
||||
|
* 传一个*路径* 或*查询* 参数,添加到URL上。 |
||||
|
* 传一个JSON体,传一个Python对象(例如一个`dict`)到参数 `json`。 |
||||
|
* 如果你需要发送 *Form Data* 而不是 JSON,使用 `data` 参数。 |
||||
|
* 要发送 *headers*,传 `dict` 给 `headers` 参数。 |
||||
|
* 对于 *cookies*,传 `dict` 给 `cookies` 参数。 |
||||
|
|
||||
|
关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX 文档</a>. |
||||
|
|
||||
|
!!! 信息 |
||||
|
注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。 |
||||
|
|
||||
|
如果你在测试中有一个Pydantic模型,并且你想在测试时发送它的数据给应用,你可以使用在[JSON Compatible Encoder](encoder.md){.internal-link target=_blank}介绍的`jsonable_encoder` 。 |
||||
|
|
||||
|
## 运行起来 |
||||
|
|
||||
|
之后,你只需要安装 `pytest`: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install pytest |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
他会自动检测文件和测试,执行测试,然后向你报告结果。 |
||||
|
|
||||
|
执行测试: |
||||
|
|
||||
|
<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> |
@ -1,5 +1,5 @@ |
|||||
-e .[all] |
-e .[all] |
||||
-r requirements-tests.txt |
-r requirements-tests.txt |
||||
-r requirements-docs.txt |
-r requirements-docs.txt |
||||
uvicorn[standard] >=0.12.0,<0.21.0 |
uvicorn[standard] >=0.12.0,<0.23.0 |
||||
pre-commit >=2.17.0,<3.0.0 |
pre-commit >=2.17.0,<4.0.0 |
||||
|
@ -0,0 +1,40 @@ |
|||||
|
from fastapi import APIRouter, FastAPI |
||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
|
||||
|
def test_redirect_slashes_enabled(): |
||||
|
app = FastAPI() |
||||
|
router = APIRouter() |
||||
|
|
||||
|
@router.get("/hello/") |
||||
|
def hello_page() -> str: |
||||
|
return "Hello, World!" |
||||
|
|
||||
|
app.include_router(router) |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
response = client.get("/hello/", follow_redirects=False) |
||||
|
assert response.status_code == 200 |
||||
|
|
||||
|
response = client.get("/hello", follow_redirects=False) |
||||
|
assert response.status_code == 307 |
||||
|
|
||||
|
|
||||
|
def test_redirect_slashes_disabled(): |
||||
|
app = FastAPI(redirect_slashes=False) |
||||
|
router = APIRouter() |
||||
|
|
||||
|
@router.get("/hello/") |
||||
|
def hello_page() -> str: |
||||
|
return "Hello, World!" |
||||
|
|
||||
|
app.include_router(router) |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
response = client.get("/hello/", follow_redirects=False) |
||||
|
assert response.status_code == 200 |
||||
|
|
||||
|
response = client.get("/hello", follow_redirects=False) |
||||
|
assert response.status_code == 404 |
Loading…
Reference in new issue