committed by
GitHub
15 changed files with 991 additions and 13 deletions
@ -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="[email protected]") |
|||
``` |
|||
|
|||
и затем вызовем: |
|||
|
|||
```Python |
|||
user_dict = user_in.dict() |
|||
``` |
|||
|
|||
то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели). |
|||
|
|||
И если мы вызовем: |
|||
|
|||
```Python |
|||
print(user_dict) |
|||
``` |
|||
|
|||
мы можем получить `dict` с такими данными: |
|||
|
|||
```Python |
|||
{ |
|||
'username': 'john', |
|||
'password': 'secret', |
|||
'email': '[email protected]', |
|||
'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="[email protected]", |
|||
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,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