committed by
GitHub
10 changed files with 1361 additions and 1 deletions
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 23 KiB |
@ -0,0 +1,700 @@ |
|||
# FastAPI и Docker-контейнеры |
|||
|
|||
При развёртывании приложений FastAPI, часто начинают с создания **образа контейнера на основе Linux**. Обычно для этого используют <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Затем можно развернуть такой контейнер на сервере одним из нескольких способов. |
|||
|
|||
Использование контейнеров на основе Linux имеет ряд преимуществ, включая **безопасность**, **воспроизводимость**, **простоту** и прочие. |
|||
|
|||
!!! tip "Подсказка" |
|||
Торопитесь или уже знакомы с этой технологией? Перепрыгните на раздел [Создать Docker-образ для FastAPI 👇](#docker-fastapi) |
|||
|
|||
<details> |
|||
<summary>Развернуть Dockerfile 👀</summary> |
|||
|
|||
```Dockerfile |
|||
FROM python:3.9 |
|||
|
|||
WORKDIR /code |
|||
|
|||
COPY ./requirements.txt /code/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
COPY ./app /code/app |
|||
|
|||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
|
|||
# Если используете прокси-сервер, такой как Nginx или Traefik, добавьте --proxy-headers |
|||
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] |
|||
``` |
|||
|
|||
</details> |
|||
|
|||
## Что такое "контейнер" |
|||
|
|||
Контейнеризация - это **легковесный** способ упаковать приложение, включая все его зависимости и необходимые файлы, чтобы изолировать его от других контейнеров (других приложений и компонентов) работающих на этой же системе. |
|||
|
|||
Контейнеры, основанные на Linux, запускаются используя ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это значит, что они очень легковесные (по сравнению с полноценными виртуальными машинами, полностью эмулирующими работу операционной системы). |
|||
|
|||
Благодаря этому, контейнеры потребляют **малое количество ресурсов**, сравнимое с процессом запущенным напрямую (виртуальная машина потребует гораздо больше ресурсов). |
|||
|
|||
Контейнеры также имеют собственные запущенные **изолированные** процессы (но часто только один процесс), файловую систему и сеть, что упрощает развёртывание, разработку, управление доступом и т.п. |
|||
|
|||
## Что такое "образ контейнера" |
|||
|
|||
Для запуска **контейнера** нужен **образ контейнера**. |
|||
|
|||
Образ контейнера - это **замороженная** версия всех файлов, переменных окружения, программ и команд по умолчанию, необходимых для работы приложения. **Замороженный** - означает, что **образ** не запущен и не выполняется, это всего лишь упакованные вместе файлы и метаданные. |
|||
|
|||
В отличие от **образа контейнера**, хранящего неизменное содержимое, под термином **контейнер** подразумевают запущенный образ, то есть объёкт, который **исполняется**. |
|||
|
|||
Когда **контейнер** запущен (на основании **образа**), он может создавать и изменять файлы, переменные окружения и т.д. Эти изменения будут существовать только внутри контейнера, но не будут сохраняться в образе контейнера (не будут сохранены на диск). |
|||
|
|||
Образ контейнера можно сравнить с файлом, содержащем **программу**, например, как файл `main.py`. |
|||
|
|||
И **контейнер** (в отличие от **образа**) - это на самом деле выполняемый экземпляр образа, примерно как **процесс**. По факту, контейнер запущен только когда запущены его процессы (чаще, всего один процесс) и остановлен, когда запущенных процессов нет. |
|||
|
|||
## Образы контейнеров |
|||
|
|||
Docker является одним оз основных инструментов для создания **образов** и **контейнеров** и управления ими. |
|||
|
|||
Существует общедоступный <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> с подготовленными **официальными образами** многих инструментов, окружений, баз данных и приложений. |
|||
|
|||
К примеру, есть официальный <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">образ Python</a>. |
|||
|
|||
Также там представлены и другие полезные образы, такие как базы данных: |
|||
|
|||
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a> |
|||
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a> |
|||
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a> |
|||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a> |
|||
|
|||
и т.п. |
|||
|
|||
Использование подготовленных образов значительно упрощает **комбинирование** и использование разных инструментов. Например, Вы можете попытаться использовать новую базу данных. В большинстве случаев можно использовать **официальный образ** и всего лишь указать переменные окружения. |
|||
|
|||
Таким образом, Вы можете изучить, что такое контейнеризация и Docker, и использовать полученные знания с разными инструментами и компонентами. |
|||
|
|||
Так, Вы можете запустить одновременно **множество контейнеров** с базой данных, Python-приложением, веб-сервером, React-приложением и соединить их вместе через внутреннюю сеть. |
|||
|
|||
Все системы управления контейнерами (такие, как Docker или Kubernetes) имеют встроенные возможности для организации такого сетевого взаимодействия. |
|||
|
|||
## Контейнеры и процессы |
|||
|
|||
Обычно **образ контейнера** содержит метаданные предустановленной программы или команду, которую следует выполнить при запуске **контейнера**. Также он может содержать параметры, передаваемые предустановленной программе. Похоже на то, как если бы Вы запускали такую программу через терминал. |
|||
|
|||
Когда **контейнер** запущен, он будет выполнять прописанные в нём команды и программы. Но Вы можете изменить его так, чтоб он выполнял другие команды и программы. |
|||
|
|||
Контейнер буде работать до тех пор, пока выполняется его **главный процесс** (команда или программа). |
|||
|
|||
В контейнере обычно выполняется **только один процесс**, но от его имени можно запустить другие процессы, тогда в этом же в контейнере будет выполняться **множество процессов**. |
|||
|
|||
Контейнер не считается запущенным, если в нём **не выполняется хотя бы один процесс**. Если главный процесс остановлен, значит и контейнер остановлен. |
|||
|
|||
## Создать Docker-образ для FastAPI |
|||
|
|||
Что ж, давайте ужё создадим что-нибудь! 🚀 |
|||
|
|||
Я покажу Вам, как собирать **Docker-образ** для FastAPI **с нуля**, основываясь на **официальном образе Python**. |
|||
|
|||
Такой подход сгодится для **большинства случаев**, например: |
|||
|
|||
* Использование с **Kubernetes** или аналогичным инструментом |
|||
* Запуск в **Raspberry Pi** |
|||
* Использование в облачных сервисах, запускающих образы контейнеров для Вас и т.п. |
|||
|
|||
### Установить зависимости |
|||
|
|||
Обычно Вашему приложению необходимы **дополнительные библиотеки**, список которых находится в отдельном файле. |
|||
|
|||
На название и содержание такого файла влияет выбранный Вами инструмент **установки** этих библиотек (зависимостей). |
|||
|
|||
Чаще всего это простой файл `requirements.txt` с построчным перечислением библиотек и их версий. |
|||
|
|||
При этом Вы, для выбора версий, будете использовать те же идеи, что упомянуты на странице [О версиях FastAPI](./versions.md){.internal-link target=_blank}. |
|||
|
|||
Ваш файл `requirements.txt` может выглядеть как-то так: |
|||
|
|||
``` |
|||
fastapi>=0.68.0,<0.69.0 |
|||
pydantic>=1.8.0,<2.0.0 |
|||
uvicorn>=0.15.0,<0.16.0 |
|||
``` |
|||
|
|||
Устанавливать зависимости проще всего с помощью `pip`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install -r requirements.txt |
|||
---> 100% |
|||
Successfully installed fastapi pydantic uvicorn |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! info "Информация" |
|||
Существуют и другие инструменты управления зависимостями. |
|||
|
|||
В этом же разделе, но позже, я покажу Вам пример использования Poetry. 👇 |
|||
|
|||
### Создать приложение **FastAPI** |
|||
|
|||
* Создайте директорию `app` и перейдите в неё. |
|||
* Создайте пустой файл `__init__.py`. |
|||
* Создайте файл `main.py` и заполните его: |
|||
|
|||
```Python |
|||
from typing import Union |
|||
|
|||
from fastapi import FastAPI |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
@app.get("/") |
|||
def read_root(): |
|||
return {"Hello": "World"} |
|||
|
|||
|
|||
@app.get("/items/{item_id}") |
|||
def read_item(item_id: int, q: Union[str, None] = None): |
|||
return {"item_id": item_id, "q": q} |
|||
``` |
|||
|
|||
### Dockerfile |
|||
|
|||
В этой же директории создайте файл `Dockerfile` и заполните его: |
|||
|
|||
```{ .dockerfile .annotate } |
|||
# (1) |
|||
FROM python:3.9 |
|||
|
|||
# (2) |
|||
WORKDIR /code |
|||
|
|||
# (3) |
|||
COPY ./requirements.txt /code/requirements.txt |
|||
|
|||
# (4) |
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
# (5) |
|||
COPY ./app /code/app |
|||
|
|||
# (6) |
|||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
1. Начните с официального образа Python, который будет основой для образа приложения. |
|||
|
|||
2. Укажите, что в дальнейшем команды запускаемые в контейнере, будут выполняться в директории `/code`. |
|||
|
|||
Инструкция создаст эту директорию внутри контейнера и мы поместим в неё файл `requirements.txt` и директорию `app`. |
|||
|
|||
3. Скопируете файл с зависимостями из текущей директории в `/code`. |
|||
|
|||
Сначала копируйте **только** файл с зависимостями. |
|||
|
|||
Этот файл **изменяется довольно редко**, Docker ищет изменения при постройке образа и если не находит, то использует **кэш**, в котором хранятся предыдущии версии сборки образа. |
|||
|
|||
4. Установите библиотеки перечисленные в файле с зависимостями. |
|||
|
|||
Опция `--no-cache-dir` указывает `pip` не сохранять загружаемые библиотеки на локальной машине для использования их в случае повторной загрузки. В контейнере, в случае пересборки этого шага, они всё равно будут удалены. |
|||
|
|||
!!! note "Заметка" |
|||
Опция `--no-cache-dir` нужна только для `pip`, она никак не влияет на Docker или контейнеры. |
|||
|
|||
Опция `--upgrade` указывает `pip` обновить библиотеки, емли они уже установлены. |
|||
|
|||
Ка и в предыдущем шаге с копированием файла, этот шаг также будет использовать **кэш Docker** в случае отсутствия изменений. |
|||
|
|||
Использрвание кэша, особенно на этом шаге, позволит Вам **сэкономить** кучу времени при повторной сборке образа, так как зависимости будут сохранены в кеше, а не **загружаться и устанавливаться каждый раз**. |
|||
|
|||
5. Скопируйте директорию `./app` внутрь директории `/code` (в контейнере). |
|||
|
|||
Так как в этой директории расположен код, который **часто изменяется**, то использование **кэша** на этом шаге будет наименее эффективно, а значит лучше поместить этот шаг **ближе к концу** `Dockerfile`, дабы не терять выгоду от оптимизации предыдущих шагов. |
|||
|
|||
6. Укажите **команду**, запускающую сервер `uvicorn`. |
|||
|
|||
`CMD` принимает список строк, разделённых запятыми, но при выполнении объединит их через пробел, собрав из них одну команду, которую Вы могли бы написать в терминале. |
|||
|
|||
Эта команда будет выполнена в **текущей рабочей директории**, а именно в директории `/code`, котоая указана командой `WORKDIR /code`. |
|||
|
|||
Так как команда выполняется внутрии директории `/code`, в которую мы поместили папку `./app` с приложением, то **Uvicorn** сможет найти и **импортировать** объект `app` из файла `app.main`. |
|||
|
|||
!!! tip "Подсказка" |
|||
Если ткнёте на кружок с плюсом, то увидите пояснения. 👆 |
|||
|
|||
На данном этапе структура проекта должны выглядеть так: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ └── main.py |
|||
├── Dockerfile |
|||
└── requirements.txt |
|||
``` |
|||
|
|||
#### Использование прокси-сервера |
|||
|
|||
Если Вы запускаете контейнер за прокси-сервером завершения TLS (балансирующего нагрузку), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`, которая укажет Uvicorn, что он работает позади прокси-сервера и может доверять заголовкам отправляемым им. |
|||
|
|||
```Dockerfile |
|||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
#### Кэш Docker'а |
|||
|
|||
В нашем `Dockerfile` использована полезная хитрость, когда сначала копируется **только файл с зависимостями**, а не вся папка с кодом приложения. |
|||
|
|||
```Dockerfile |
|||
COPY ./requirements.txt /code/requirements.txt |
|||
``` |
|||
|
|||
Docker и подобные ему инструменты **создают** образы контейнеров **пошагово**, добавляя **один слой над другим**, начиная с первой строки `Dockerfile` и добавляя файлы, создаваемые при выполнении каждой инструкции из `Dockerfile`. |
|||
|
|||
При создании образа используется **внутренний кэш** и если в файлах нет изменений с момента последней сборки образа, то будет **переиспользован** ранее созданный слой образа, а не повторное копирование файлов и создание слоя с нуля. |
|||
Заметьте, что так как слой следующего шага зависит от слоя предыдущего, то изменения внесённые в промежуточный слой, также повлияют на последующие. |
|||
|
|||
Избегание копирования файлов не обязательно улучшит ситуацию, но использование кэша на одном шаге, позволит **использовать кэш и на следующих шагах**. Например, можно использовать кэш при установке зависимостей: |
|||
|
|||
```Dockerfile |
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
``` |
|||
|
|||
Файл со списком зависимостей **изменяется довольно редко**. Так что выполнив команду копирования только этого файла, Docker сможет **использовать кэш** на этом шаге. |
|||
|
|||
А затем **использовать кэш и на следующем шаге**, загружающем и устанавливающем зависимости. И вот тут-то мы и **сэкономим много времени**. ✨ ...а не будем томиться в тягостном ожидании. 😪😆 |
|||
|
|||
Для загрузки и установки необходимых библиотек **может понадобиться несколько минут**, но использование **кэша** занимает несколько **секунд** максимум. |
|||
|
|||
И так как во время разработки Вы будете часто пересобирать контейнер для проверки работоспособности внесённых изменений, то сэкономленные минуты сложатся в часы, а то и дни. |
|||
|
|||
Так как папка с кодом приложения **изменяется чаще всего**, то мы расположили её в конце `Dockerfile`, ведь после внесённых в код изменений кэш не будет использован на этом и следующих шагах. |
|||
|
|||
```Dockerfile |
|||
COPY ./app /code/app |
|||
``` |
|||
|
|||
### Создать Docker-образ |
|||
|
|||
Теперь, когда все файлы на своих местах, давайте создадим образ контейнера. |
|||
|
|||
* Перейдите в директорию проекта (в ту, где расположены `Dockerfile` и папка `app` с приложением). |
|||
* Создай образ приложения FastAPI: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ docker build -t myimage . |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! tip "Подсказка" |
|||
Обратите внимание, что в конце написана точка - `.`, это то же самое что и `./`, тем самым мы указываем Docker директорию, из которой нужно выполнять сборку образа контейнера. |
|||
|
|||
В данном случае это та же самая директория (`.`). |
|||
|
|||
### Запуск Docker-контейнера |
|||
|
|||
* Запустите контейнер, основанный на Вашем образе: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ docker run -d --name mycontainer -p 80:80 myimage |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## Проверка |
|||
|
|||
Вы можете проверить, что Ваш Docker-контейнер работает перейдя по ссылке: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> или <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (или похожей, которую использует Ваш Docker-хост). |
|||
|
|||
Там Вы увидите: |
|||
|
|||
```JSON |
|||
{"item_id": 5, "q": "somequery"} |
|||
``` |
|||
|
|||
## Интерактивная документация API |
|||
|
|||
Теперь перейдите по ссылке <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> или <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (или похожей, которую использует Ваш Docker-хост). |
|||
|
|||
Здесь Вы увидите автоматическую интерактивную документацию API (предоставляемую <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): |
|||
|
|||
 |
|||
|
|||
## Альтернативная документация API |
|||
|
|||
Также Вы можете перейти по ссылке <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> or <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (или похожей, которую использует Ваш Docker-хост). |
|||
|
|||
Здесь Вы увидите альтернативную автоматическую документацию API (предоставляемую <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): |
|||
|
|||
 |
|||
|
|||
## Создание Docker-образа на основе однофайлового приложения FastAPI |
|||
|
|||
Если Ваше приложение FastAPI помещено в один файл, например, `main.py` и структура Ваших файлов похожа на эту: |
|||
|
|||
``` |
|||
. |
|||
├── Dockerfile |
|||
├── main.py |
|||
└── requirements.txt |
|||
``` |
|||
|
|||
Вам нужно изменить в `Dockerfile` соответствующие пути копирования файлов: |
|||
|
|||
```{ .dockerfile .annotate hl_lines="10 13" } |
|||
FROM python:3.9 |
|||
|
|||
WORKDIR /code |
|||
|
|||
COPY ./requirements.txt /code/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
# (1) |
|||
COPY ./main.py /code/ |
|||
|
|||
# (2) |
|||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
1. Скопируйте непосредственно файл `main.py` в директорию `/code` (не указывайте `./app`). |
|||
|
|||
2. При запуске Uvicorn укажите ему, что объект `app` нужно импортировать из файла `main` (вместо импортирования из `app.main`). |
|||
|
|||
Настройте Uvicorn на использование `main` вместо `app.main` для импорта объекта `app`. |
|||
|
|||
## Концепции развёртывания |
|||
|
|||
Давайте вспомним о [Концепциях развёртывания](./concepts.md){.internal-link target=_blank} и применим их к контейнерам. |
|||
|
|||
Контейнеры - это, в основном, инструмент упрощающий **сборку и развёртывание** приложения и они не обязыают к применению какой-то определённой **концепции развёртывания**, а значит мы можем выбирать нужную стратегию. |
|||
|
|||
**Хорошая новость** в том, что независимо от выбранной стратегии, мы всё равно можем покрыть все концепции развёртывания. 🎉 |
|||
|
|||
Рассмотрим эти **концепции развёртывания** применительно к контейнерам: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения |
|||
|
|||
## Использование более безопасного протокола HTTPS |
|||
|
|||
Если мы определимся, что **образ контейнера** будет содержать только приложение FastAPI, то работу с HTTPS можно организовать **снаружи** контейнера при помощи другого инструмента. |
|||
|
|||
Это может быть другой контейнер, в котором есть, например, <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, работающий с **HTTPS** и **самостоятельно** обновляющий **сертификаты**. |
|||
|
|||
!!! tip "Подсказка" |
|||
Traefik совместим с Docker, Kubernetes и им подобными инструментами. Он очень прост в установке и настройке использования HTTPS для Ваших контейнеров. |
|||
|
|||
В качестве альтернативы, работу с HTTPS можно доверить облачному провайдеру, если он предоставляет такую услугу. |
|||
|
|||
## Настройки запуска и перезагрузки приложения |
|||
|
|||
Обычно **запуском контейнера с приложением** занимается какой-то отдельный инструмент. |
|||
|
|||
Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный провайдер** и т.п. |
|||
|
|||
В большинстве случаев это простейшие настройки запуска и перезагрузки приложения (при падении). Например, команде запуска Docker-контейнера можно добавить опцию `--restart`. |
|||
|
|||
Управление запуском и перезагрузкой приложений без использования контейнеров - весьма затруднительно. Но при **работе с контейнерами** - это всего лишь функционал доступный по умолчанию. ✨ |
|||
|
|||
## Запуск нескольких экземпляров приложения - Указание количества процессов |
|||
|
|||
Если у Вас есть <abbr title="Несколько серверов настроенных для совместной работы.">кластер</abbr> машин под управлением **Kubernetes**, Docker Swarm Mode, Nomad или аналогичной сложной системой оркестрации контейнеров, скорее всего, вместо использования менеджера процессов (типа Gunicorn и его воркеры) в каждом контейнере, Вы захотите **управлять количеством запущенных экземпляров приложения** на **уровне кластера**. |
|||
|
|||
В любую из этих систем управления контейнерами обычно встроен способ управления **количеством запущенных контейнеров** для распределения **нагрузки** от входящих запросов на **уровне кластера**. |
|||
|
|||
В такой ситуации Вы, вероятно, захотите создать **образ Docker**, как [описано выше](#dockerfile), с установленными зависимостями и запускающий **один процесс Uvicorn** вместо того, чтобы запускать Gunicorn управляющий несколькими воркерами Uvicorn. |
|||
|
|||
### Балансировщик нагрузки |
|||
|
|||
Обычно при использовании контейнеров один компонент **прослушивает главный порт**. Это может быть контейнер содержащий **прокси-сервер завершения работы TLS** для работы с **HTTPS** или что-то подобное. |
|||
|
|||
Поскольку этот компонент **принимает запросы** и равномерно **распределяет** их между компонентами, его также называют **балансировщиком нагрузки**. |
|||
|
|||
!!! tip "Подсказка" |
|||
**Прокси-сервер завершения работы TLS** одновременно может быть **балансировщиком нагрузки**. |
|||
|
|||
Система оркестрации, которую Вы используете для запуска и управления контейнерами, имеет встроенный инструмент **сетевого взаимодействия** (например, для передачи HTTP-запросов) между контейнерами с Вашими приложениями и **балансировщиком нагрузки** (который также может быть **прокси-сервером**). |
|||
|
|||
### Один балансировщик - Множество контейнеров |
|||
|
|||
При работе с **Kubernetes** или аналогичными системами оркестрации использование их внутреннней сети позволяет иметь один **балансировщик нагрузки**, который прослушивает **главный** порт и передаёт запросы **множеству запущенных контейнеров** с Вашими приложениями. |
|||
|
|||
В каждом из контейнеров обычно работает **только один процесс** (например, процесс Uvicorn управляющий Вашим приложением FastAPI). Контейнеры могут быть **идентичными**, запущенными на основе одного и того же образа, но у каждого будут свои отдельные процесс, память и т.п. Таким образом мы получаем преимущества **распараллеливания** работы по **разным ядрам** процессора или даже **разным машинам**. |
|||
|
|||
Система управления контейнерами с **балансировщиком нагрузки** будет **распределять запросы** к контейнерам с приложениями **по очереди**. То есть каждый запрос будет обработан одним из множества **одинаковых контейнеров** с одним и тем же приложением. |
|||
|
|||
**Балансировщик нагрузки** может обрабатывать запросы к *разным* приложениям, расположенным в Вашем кластере (например, если у них разные домены или префиксы пути) и передавать запросы нужному контейнеру с требуемым приложением. |
|||
|
|||
### Один процесс на контейнер |
|||
|
|||
В этом варианте **в одном контейнере будет запущен только один процесс (Uvicorn)**, а управление изменением количества запущенных копий приложения происходит на уровне кластера. |
|||
|
|||
Здесь **не нужен** менеджер процессов типа Gunicorn, управляющий процессами Uvicorn, или же Uvicorn, управляющий другими процессами Uvicorn. Достаточно **только одного процесса Uvicorn** на контейнер (но запуск нескольких процессов не запрещён). |
|||
|
|||
Использование менеджера процессов (Gunicorn или Uvicorn) внутри контейнера только добавляет **излишнее усложнение**, так как управление следует осуществлять системой оркестрации. |
|||
|
|||
### <a name="special-cases"></a>Множество процессов внутри контейнера для особых случаев</a> |
|||
|
|||
Безусловно, бывают **особые случаи**, когда может понадобится внутри контейнера запускать **менеджер процессов Gunicorn**, управляющий несколькими **процессами Uvicorn**. |
|||
|
|||
Для таких случаев Вы можете использовать **официальный Docker-образ** (прим. пер: - *здесь и далее на этой странице, если Вы встретите сочетание "официальный Docker-образ" без уточнений, то автор имеет в виду именно предоставляемый им образ*), где в качестве менеджера процессов используется **Gunicorn**, запускающий несколько **процессов Uvicorn** и некоторые настройки по умолчанию, автоматически устанавливающие количество запущенных процессов в зависимости от количества ядер Вашего процессора. Я расскажу Вам об этом подробнее тут: [Официальный Docker-образ со встроенными Gunicorn и Uvicorn](#docker-gunicorn-uvicorn). |
|||
|
|||
Некоторые примеры подобных случаев: |
|||
|
|||
#### Простое приложение |
|||
|
|||
Вы можете использовать менеджер процессов внутри контейнера, если Ваше приложение **настолько простое**, что у Вас нет необходимости (по крайней мере, пока нет) в тщательных настройках количества процессов и Вам достаточно имеющихся настроек по умолчанию (если используется официальный Docker-образ) для запуска приложения **только на одном сервере**, а не в кластере. |
|||
|
|||
#### Docker Compose |
|||
|
|||
С помощью **Docker Compose** можно разворачивать несколько контейнеров на **одном сервере** (не кластере), но при это у Вас не будет простого способа управления количеством запущенных контейнеров с одновременным сохранением общей сети и **балансировки нагрузки**. |
|||
|
|||
В этом случае можно использовать **менеджер процессов**, управляющий **несколькими процессами**, внутри **одного контейнера**. |
|||
|
|||
#### Prometheus и прочие причины |
|||
|
|||
У Вас могут быть и **другие причины**, когда использование **множества процессов** внутри **одного контейнера** будет проще, нежели запуск **нескольких контейнеров** с **единственным процессом** в каждом из них. |
|||
|
|||
Например (в зависимости от конфигурации), у Вас могут быть инструменты подобные экспортёру Prometheus, которые должны иметь доступ к **каждому запросу** приходящему в контейнер. |
|||
|
|||
Если у Вас будет **несколько контейнеров**, то Prometheus, по умолчанию, **при сборе метрик** получит их **только с одного контейнера**, который обрабатывает конкретный запрос, вместо **сбора метрик** со всех работающих контейнеров. |
|||
|
|||
В таком случае может быть проще иметь **один контейнер** со **множеством процессов**, с нужным инструментом (таким как экспортёр Prometheus) в этом же контейнере и собирающем метрики со всех внутренних процессов этого контейнера. |
|||
|
|||
--- |
|||
|
|||
Самое главное - **ни одно** из перечисленных правил не является **высеченным в камне** и Вы не обязаны слепо их повторять. Вы можете использовать эти идеи при **рассмотрении Вашего конкретного случая** и самостоятельно решать, какая из концепции подходит лучше: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения |
|||
|
|||
## Управление памятью |
|||
|
|||
При **запуске одного процесса на контейнер** Вы получаете относительно понятный, стабильный и ограниченный объём памяти, потребляемый одним контейнером. |
|||
|
|||
Вы можете установить аналогичные ограничения по памяти при конфигурировании своей системы управления контейнерами (например, **Kubernetes**). Таким образом система сможет **изменять количество контейнеров** на **доступных ей машинах** приводя в соответствие количество памяти нужной контейнерам с количеством памяти доступной в кластере (наборе доступных машин). |
|||
|
|||
Если у Вас **простенькое** приложение, вероятно у Вас не будет **необходимости** устанавливать жёсткие ограничения на выделяемую ему память. Но если приложение **использует много памяти** (например, оно использует модели **машинного обучения**), Вам следует проверить, как много памяти ему требуется и отрегулировать **количество контейнеров** запущенных на **каждой машине** (может быть даже добавить машин в кластер). |
|||
|
|||
Если Вы запускаете **несколько процессов в контейнере**, то должны быть уверены, что эти процессы не **займут памяти больше**, чем доступно для контейнера. |
|||
|
|||
## Подготовительные шаги при запуске контейнеров |
|||
|
|||
Есть два основных подхода, которые Вы можете использовать при запуске контейнеров (Docker, Kubernetes и т.п.). |
|||
|
|||
### Множество контейнеров |
|||
|
|||
Когда Вы запускаете **множество контейнеров**, в каждом из которых работает **только один процесс** (например, в кластере **Kubernetes**), может возникнуть необходимость иметь **отдельный контейнер**, который осуществит **предварительные шаги перед запуском** остальных контейнеров (например, применяет миграции к базе данных). |
|||
|
|||
!!! info "Информация" |
|||
При использовании Kubernetes, это может быть <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Инициализирующий контейнер</a>. |
|||
|
|||
При отсутствии такой необходимости (допустим, не нужно применять миграции к базе данных, а только проверить, что она готова принимать соединения), Вы можете проводить такую проверку в каждом контейнере перед запуском его основного процесса и запускать все контейнеры **одновременно**. |
|||
|
|||
### Только один контейнер |
|||
|
|||
Если у Вас несложное приложение для работы которого достаточно **одного контейнера**, но в котором работает **несколько процессов** (или один процесс), то прохождение предварительных шагов можно осуществить в этом же контейнере до запуска основного процесса. Официальный Docker-образ поддерживает такие действия. |
|||
|
|||
## Официальный Docker-образ с Gunicorn и Uvicorn |
|||
|
|||
Я подготовил для Вас Docker-образ, в который включён Gunicorn управляющий процессами (воркерами) Uvicorn, в соответствии с концепциями рассмотренными в предыдущей главе: [Рабочие процессы сервера (воркеры) - Gunicorn совместно с Uvicorn](./server-workers.md){.internal-link target=_blank}. |
|||
|
|||
Этот образ может быть полезен для ситуаций описанных тут: [Множество процессов внутри контейнера для особых случаев](#special-cases). |
|||
|
|||
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. |
|||
|
|||
!!! warning "Предупреждение" |
|||
Скорее всего у Вас **нет необходимости** в использовании этого образа или подобного ему и лучше создать свой образ с нуля как описано тут: [Создать Docker-образ для FastAPI](#docker-fastapi). |
|||
|
|||
В этом образе есть **автоматический** механизм подстройки для запуска **необходимого количества процессов** в соответствии с доступным количеством ядер процессора. |
|||
|
|||
В нём установлены **разумные значения по умолчанию**, но можно изменять и обновлять конфигурацию с помощью **переменных окружения** или конфигурационных файлов. |
|||
|
|||
Он также поддерживает прохождение <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**Подготовительных шагов при запуске контейнеров**</a> при помощи скрипта. |
|||
|
|||
!!! tip "Подсказка" |
|||
Для просмотра всех возможных настроек перейдите на страницу этого Docker-образа: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. |
|||
|
|||
### Количество процессов в официальном Docker-образе |
|||
|
|||
**Количество процессов** в этом образе **вычисляется автоматически** и зависит от доступного количества **ядер** центрального процессора. |
|||
|
|||
Это означает, что он будет пытаться **выжать** из процессора как можно больше **производительности**. |
|||
|
|||
Но Вы можете изменять и обновлять конфигурацию с помощью **переменных окружения** и т.п. |
|||
|
|||
Поскольку количество процессов зависит от процессора, на котором работает контейнер, **объём потребляемой памяти** также будет зависеть от этого. |
|||
|
|||
А значит, если Вашему приложению требуется много оперативной памяти (например, оно использует модели машинного обучения) и Ваш сервер имеет центральный процессор с большим количеством ядер, но **не слишком большим объёмом оперативной памяти**, то может дойти до того, что контейнер попытается занять памяти больше, чем доступно, из-за чего будет падение производительности (или сервер вовсе упадёт). 🚨 |
|||
|
|||
|
|||
### Написание `Dockerfile` |
|||
|
|||
Итак, теперь мы можем написать `Dockerfile` основанный на этом официальном Docker-образе: |
|||
|
|||
```Dockerfile |
|||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 |
|||
|
|||
COPY ./requirements.txt /app/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt |
|||
|
|||
COPY ./app /app |
|||
``` |
|||
|
|||
### Большие приложения |
|||
|
|||
Если Вы успели ознакомиться с разделом [Приложения содержащие много файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}, состоящие из множества файлов, Ваш Dockerfile может выглядеть так: |
|||
|
|||
```Dockerfile hl_lines="7" |
|||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 |
|||
|
|||
COPY ./requirements.txt /app/requirements.txt |
|||
|
|||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt |
|||
|
|||
COPY ./app /app/app |
|||
``` |
|||
|
|||
### Как им пользоваться |
|||
|
|||
Если Вы используете **Kubernetes** (или что-то вроде того), скорее всего Вам **не нужно** использовать официальный Docker-образ (или другой похожий) в качестве основы, так как управление **количеством запущенных контейнеров** должно быть настроено на уровне кластера. В таком случае лучше **создать образ с нуля**, как описано в разделе Создать [Docker-образ для FastAPI](#docker-fastapi). |
|||
|
|||
Официальный образ может быть полезен в отдельных случаях, описанных выше в разделе [Множество процессов внутри контейнера для особых случаев](#special-cases). Например, если Ваше приложение **достаточно простое**, не требует запуска в кластере и способно уместиться в один контейнер, то его настройки по умолчанию будут работать довольно хорошо. Или же Вы развертываете его с помощью **Docker Compose**, работаете на одном сервере и т. д |
|||
|
|||
## Развёртывание образа контейнера |
|||
|
|||
После создания образа контейнера существует несколько способов его развёртывания. |
|||
|
|||
Например: |
|||
|
|||
* С использованием **Docker Compose** при развёртывании на одном сервере |
|||
* С использованием **Kubernetes** в кластере |
|||
* С использованием режима Docker Swarm в кластере |
|||
* С использованием других инструментов, таких как Nomad |
|||
* С использованием облачного сервиса, который будет управлять разворачиванием Вашего контейнера |
|||
|
|||
## Docker-образ и Poetry |
|||
|
|||
Если Вы пользуетесь <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> для управления зависимостями Вашего проекта, то можете использовать многоэтапную сборку образа: |
|||
|
|||
```{ .dockerfile .annotate } |
|||
# (1) |
|||
FROM python:3.9 as requirements-stage |
|||
|
|||
# (2) |
|||
WORKDIR /tmp |
|||
|
|||
# (3) |
|||
RUN pip install poetry |
|||
|
|||
# (4) |
|||
COPY ./pyproject.toml ./poetry.lock* /tmp/ |
|||
|
|||
# (5) |
|||
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes |
|||
|
|||
# (6) |
|||
FROM python:3.9 |
|||
|
|||
# (7) |
|||
WORKDIR /code |
|||
|
|||
# (8) |
|||
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt |
|||
|
|||
# (9) |
|||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt |
|||
|
|||
# (10) |
|||
COPY ./app /code/app |
|||
|
|||
# (11) |
|||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
1. Это первый этап, которому мы дадим имя `requirements-stage`. |
|||
|
|||
2. Установите директорию `/tmp` в качестве рабочей директории. |
|||
|
|||
В ней будет создан файл `requirements.txt` |
|||
|
|||
3. На этом шаге установите Poetry. |
|||
|
|||
4. Скопируйте файлы `pyproject.toml` и `poetry.lock` в директорию `/tmp`. |
|||
|
|||
Поскольку название файла написано как `./poetry.lock*` (с `*` в конце), то ничего не сломается, если такой файл не будет найден. |
|||
|
|||
5. Создайте файл `requirements.txt`. |
|||
|
|||
6. Это второй (и последний) этап сборки, который и создаст окончательный образ контейнера. |
|||
|
|||
7. Установите директорию `/code` в качестве рабочей. |
|||
|
|||
8. Скопируйте файл `requirements.txt` в директорию `/code`. |
|||
|
|||
Этот файл находится в образе, созданном на предыдущем этапе, которому мы дали имя requirements-stage, потому при копировании нужно написать `--from-requirements-stage`. |
|||
|
|||
9. Установите зависимости, указанные в файле `requirements.txt`. |
|||
|
|||
10. Скопируйте папку `app` в папку `/code`. |
|||
|
|||
11. Запустите `uvicorn`, указав ему использовать объект `app`, расположенный в `app.main`. |
|||
|
|||
!!! tip "Подсказка" |
|||
Если ткнёте на кружок с плюсом, то увидите объяснения, что происходит в этой строке. |
|||
|
|||
**Этапы сборки Docker-образа** являются частью `Dockerfile` и работают как **временные образы контейнеров**. Они нужны только для создания файлов, используемых в дальнейших этапах. |
|||
|
|||
Первый этап был нужен только для **установки Poetry** и **создания файла `requirements.txt`**, в которым прописаны зависимости Вашего проекта, взятые из файла `pyproject.toml`. |
|||
|
|||
На **следующем этапе** `pip` будет использовать файл `requirements.txt`. |
|||
|
|||
В итоговом образе будет содержаться **только последний этап сборки**, предыдущие этапы будут отброшены. |
|||
|
|||
При использовании Poetry, имеет смысл использовать **многоэтапную сборку Docker-образа**, потому что на самом деле Вам не нужен Poetry и его зависимости в окончательном образе контейнера, Вам **нужен только** сгенерированный файл `requirements.txt` для установки зависимостей Вашего проекта. |
|||
|
|||
А на последнем этапе, придерживаясь описанных ранее правил, создаётся итоговый образ |
|||
|
|||
### Использование прокси-сервера завершения TLS и Poetry |
|||
|
|||
И снова повторюсь, если используете прокси-сервер (балансировщик нагрузки), такой, как Nginx или Traefik, добавьте в команду запуска опцию `--proxy-headers`: |
|||
|
|||
```Dockerfile |
|||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] |
|||
``` |
|||
|
|||
## Резюме |
|||
|
|||
При помощи систем контейнеризации (таких, как **Docker** и **Kubernetes**), становится довольно просто обрабатывать все **концепции развертывания**: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения |
|||
|
|||
В большинстве случаев Вам, вероятно, не нужно использовать какой-либо базовый образ, **лучше создать образ контейнера с нуля** на основе официального Docker-образа Python. |
|||
|
|||
Позаботившись о **порядке написания** инструкций в `Dockerfile`, Вы сможете использовать **кэш Docker'а**, **минимизировав время сборки**, максимально повысив свою производительность (и избежать скуки). 😎 |
|||
|
|||
В некоторых особых случаях вы можете использовать официальный образ Docker для FastAPI. 🤓 |
@ -0,0 +1,96 @@ |
|||
# Параметри Cookie |
|||
|
|||
Ви можете визначити параметри Cookie таким же чином, як визначаються параметри `Query` і `Path`. |
|||
|
|||
## Імпорт `Cookie` |
|||
|
|||
Спочатку імпортуйте `Cookie`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="3" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="3" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="3" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="3" |
|||
{!> ../../../docs_src/cookie_params/tutorial001.py!} |
|||
``` |
|||
|
|||
## Визначення параметрів `Cookie` |
|||
|
|||
Потім визначте параметри cookie, використовуючи таку ж конструкцію як для `Path` і `Query`. |
|||
|
|||
Перше значення це значення за замовчуванням, ви можете також передати всі додаткові параметри валідації чи анотації: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/cookie_params/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! note "Технічні Деталі" |
|||
`Cookie` це "сестра" класів `Path` і `Query`. Вони наслідуються від одного батьківського класу `Param`. |
|||
Але пам'ятайте, що коли ви імпортуєте `Query`, `Path`, `Cookie` та інше з `fastapi`, це фактично функції, що повертають спеціальні класи. |
|||
|
|||
!!! info |
|||
Для визначення cookies ви маєте використовувати `Cookie`, тому що в іншому випадку параметри будуть інтерпритовані, як параметри запиту. |
|||
|
|||
## Підсумки |
|||
|
|||
Визначайте cookies за допомогою `Cookie`, використовуючи той же спільний шаблон, що і `Query` та `Path`. |
@ -0,0 +1,545 @@ |
|||
# Giới thiệu kiểu dữ liệu Python |
|||
|
|||
Python hỗ trợ tùy chọn "type hints" (còn được gọi là "type annotations"). |
|||
|
|||
Những **"type hints"** hay chú thích là một cú pháp đặc biệt cho phép khai báo <abbr title="ví dụ: str, int, float, bool"> kiểu dữ liệu</abbr> của một biến. |
|||
|
|||
Bằng việc khai báo kiểu dữ liệu cho các biến của bạn, các trình soạn thảo và các công cụ có thể hỗ trợ bạn tốt hơn. |
|||
|
|||
Đây chỉ là một **hướng dẫn nhanh** về gợi ý kiểu dữ liệu trong Python. Nó chỉ bao gồm những điều cần thiết tối thiểu để sử dụng chúng với **FastAPI**... đó thực sự là rất ít. |
|||
|
|||
**FastAPI** hoàn toàn được dựa trên những gợi ý kiểu dữ liệu, chúng mang đến nhiều ưu điểm và lợi ích. |
|||
|
|||
Nhưng thậm chí nếu bạn không bao giờ sử dụng **FastAPI**, bạn sẽ được lợi từ việc học một ít về chúng. |
|||
|
|||
!!! note |
|||
Nếu bạn là một chuyên gia về Python, và bạn đã biết mọi thứ về gợi ý kiểu dữ liệu, bỏ qua và đi tới chương tiếp theo. |
|||
|
|||
## Động lực |
|||
|
|||
Hãy bắt đầu với một ví dụ đơn giản: |
|||
|
|||
```Python |
|||
{!../../../docs_src/python_types/tutorial001.py!} |
|||
``` |
|||
|
|||
Kết quả khi gọi chương trình này: |
|||
|
|||
``` |
|||
John Doe |
|||
``` |
|||
|
|||
Hàm thực hiện như sau: |
|||
|
|||
* Lấy một `first_name` và `last_name`. |
|||
* Chuyển đổi kí tự đầu tiên của mỗi biến sang kiểu chữ hoa với `title()`. |
|||
* <abbr title="Đặt chúng lại với nhau thành một. Với các nội dung lần lượt.">Nối</abbr> chúng lại với nhau bằng một kí tự trắng ở giữa. |
|||
|
|||
```Python hl_lines="2" |
|||
{!../../../docs_src/python_types/tutorial001.py!} |
|||
``` |
|||
|
|||
### Sửa đổi |
|||
|
|||
Nó là một chương trình rất đơn giản. |
|||
|
|||
Nhưng bây giờ hình dung rằng bạn đang viết nó từ đầu. |
|||
|
|||
Tại một vài thời điểm, bạn sẽ bắt đầu định nghĩa hàm, bạn có các tham số... |
|||
|
|||
Nhưng sau đó bạn phải gọi "phương thức chuyển đổi kí tự đầu tiên sang kiểu chữ hoa". |
|||
|
|||
Có phải là `upper`? Có phải là `uppercase`? `first_uppercase`? `capitalize`? |
|||
|
|||
Sau đó, bạn thử hỏi người bạn cũ của mình, autocompletion của trình soạn thảo. |
|||
|
|||
Bạn gõ tham số đầu tiên của hàm, `first_name`, sau đó một dấu chấm (`.`) và sau đó ấn `Ctrl+Space` để kích hoạt bộ hoàn thành. |
|||
|
|||
Nhưng đáng buồn, bạn không nhận được điều gì hữu ích cả: |
|||
|
|||
<img src="/img/python-types/image01.png"> |
|||
|
|||
### Thêm kiểu dữ liệu |
|||
|
|||
Hãy sửa một dòng từ phiên bản trước. |
|||
|
|||
Chúng ta sẽ thay đổi chính xác đoạn này, tham số của hàm, từ: |
|||
|
|||
```Python |
|||
first_name, last_name |
|||
``` |
|||
|
|||
sang: |
|||
|
|||
```Python |
|||
first_name: str, last_name: str |
|||
``` |
|||
|
|||
Chính là nó. |
|||
|
|||
Những thứ đó là "type hints": |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/python_types/tutorial002.py!} |
|||
``` |
|||
|
|||
Đó không giống như khai báo những giá trị mặc định giống như: |
|||
|
|||
```Python |
|||
first_name="john", last_name="doe" |
|||
``` |
|||
|
|||
Nó là một thứ khác. |
|||
|
|||
Chúng ta sử dụng dấu hai chấm (`:`), không phải dấu bằng (`=`). |
|||
|
|||
Và việc thêm gợi ý kiểu dữ liệu không làm thay đổi những gì xảy ra so với khi chưa thêm chúng. |
|||
|
|||
But now, imagine you are again in the middle of creating that function, but with type hints. |
|||
|
|||
Tại cùng một điểm, bạn thử kích hoạt autocomplete với `Ctrl+Space` và bạn thấy: |
|||
|
|||
<img src="/img/python-types/image02.png"> |
|||
|
|||
Với cái đó, bạn có thể cuộn, nhìn thấy các lựa chọn, cho đến khi bạn tìm thấy một "tiếng chuông": |
|||
|
|||
<img src="/img/python-types/image03.png"> |
|||
|
|||
## Động lực nhiều hơn |
|||
|
|||
Kiểm tra hàm này, nó đã có gợi ý kiểu dữ liệu: |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/python_types/tutorial003.py!} |
|||
``` |
|||
|
|||
Bởi vì trình soạn thảo biết kiểu dữ liệu của các biến, bạn không chỉ có được completion, bạn cũng được kiểm tra lỗi: |
|||
|
|||
<img src="/img/python-types/image04.png"> |
|||
|
|||
Bây giờ bạn biết rằng bạn phải sửa nó, chuyển `age` sang một xâu với `str(age)`: |
|||
|
|||
```Python hl_lines="2" |
|||
{!../../../docs_src/python_types/tutorial004.py!} |
|||
``` |
|||
|
|||
## Khai báo các kiểu dữ liệu |
|||
|
|||
Bạn mới chỉ nhìn thấy những nơi chủ yếu để đặt khai báo kiểu dữ liệu. Như là các tham số của hàm. |
|||
|
|||
Đây cũng là nơi chủ yếu để bạn sử dụng chúng với **FastAPI**. |
|||
|
|||
### Kiểu dữ liệu đơn giản |
|||
|
|||
Bạn có thể khai báo tất cả các kiểu dữ liệu chuẩn của Python, không chỉ là `str`. |
|||
|
|||
Bạn có thể sử dụng, ví dụ: |
|||
|
|||
* `int` |
|||
* `float` |
|||
* `bool` |
|||
* `bytes` |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/python_types/tutorial005.py!} |
|||
``` |
|||
|
|||
### Các kiểu dữ liệu tổng quát với tham số kiểu dữ liệu |
|||
|
|||
Có một vài cấu trúc dữ liệu có thể chứa các giá trị khác nhau như `dict`, `list`, `set` và `tuple`. Và những giá trị nội tại cũng có thể có kiểu dữ liệu của chúng. |
|||
|
|||
Những kiểu dữ liệu nội bộ này được gọi là những kiểu dữ liệu "**tổng quát**". Và có khả năng khai báo chúng, thậm chí với các kiểu dữ liệu nội bộ của chúng. |
|||
|
|||
Để khai báo những kiểu dữ liệu và những kiểu dữ liệu nội bộ đó, bạn có thể sử dụng mô đun chuẩn của Python là `typing`. Nó có hỗ trợ những gợi ý kiểu dữ liệu này. |
|||
|
|||
#### Những phiên bản mới hơn của Python |
|||
|
|||
Cú pháp sử dụng `typing` **tương thích** với tất cả các phiên bản, từ Python 3.6 tới những phiên bản cuối cùng, bao gồm Python 3.9, Python 3.10,... |
|||
|
|||
As Python advances, **những phiên bản mới** mang tới sự hỗ trợ được cải tiến cho những chú thích kiểu dữ liệu và trong nhiều trường hợp bạn thậm chí sẽ không cần import và sử dụng mô đun `typing` để khai báo chú thích kiểu dữ liệu. |
|||
|
|||
Nếu bạn có thể chọn một phiên bản Python gần đây hơn cho dự án của bạn, ban sẽ có được những ưu điểm của những cải tiến đơn giản đó. |
|||
|
|||
Trong tất cả các tài liệu tồn tại những ví dụ tương thích với mỗi phiên bản Python (khi có một sự khác nhau). |
|||
|
|||
Cho ví dụ "**Python 3.6+**" có nghĩa là nó tương thích với Python 3.7 hoặc lớn hơn (bao gồm 3.7, 3.8, 3.9, 3.10,...). và "**Python 3.9+**" nghĩa là nó tương thích với Python 3.9 trở lên (bao gồm 3.10,...). |
|||
|
|||
Nếu bạn có thể sử dụng **phiên bản cuối cùng của Python**, sử dụng những ví dụ cho phiên bản cuối, những cái đó sẽ có **cú pháp đơn giản và tốt nhât**, ví dụ, "**Python 3.10+**". |
|||
|
|||
#### List |
|||
|
|||
Ví dụ, hãy định nghĩa một biến là `list` các `str`. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
Khai báo biến với cùng dấu hai chấm (`:`). |
|||
|
|||
Tương tự kiểu dữ liệu `list`. |
|||
|
|||
Như danh sách là một kiểu dữ liệu chứa một vài kiểu dữ liệu có sẵn, bạn đặt chúng trong các dấu ngoặc vuông: |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial006_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
Từ `typing`, import `List` (với chữ cái `L` viết hoa): |
|||
|
|||
``` Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial006.py!} |
|||
``` |
|||
|
|||
Khai báo biến với cùng dấu hai chấm (`:`). |
|||
|
|||
Tương tự như kiểu dữ liệu, `List` bạn import từ `typing`. |
|||
|
|||
Như danh sách là một kiểu dữ liệu chứa các kiểu dữ liệu có sẵn, bạn đặt chúng bên trong dấu ngoặc vuông: |
|||
|
|||
```Python hl_lines="4" |
|||
{!> ../../../docs_src/python_types/tutorial006.py!} |
|||
``` |
|||
|
|||
!!! info |
|||
Các kiểu dữ liệu có sẵn bên trong dấu ngoặc vuông được gọi là "tham số kiểu dữ liệu". |
|||
|
|||
Trong trường hợp này, `str` là tham số kiểu dữ liệu được truyền tới `List` (hoặc `list` trong Python 3.9 trở lên). |
|||
|
|||
Có nghĩa là: "biến `items` là một `list`, và mỗi phần tử trong danh sách này là một `str`". |
|||
|
|||
!!! tip |
|||
Nếu bạn sử dụng Python 3.9 hoặc lớn hơn, bạn không phải import `List` từ `typing`, bạn có thể sử dụng `list` để thay thế. |
|||
|
|||
Bằng cách này, trình soạn thảo của bạn có thể hỗ trợ trong khi xử lí các phần tử trong danh sách: |
|||
|
|||
<img src="/img/python-types/image05.png"> |
|||
|
|||
Đa phần đều không thể đạt được nếu không có các kiểu dữ liệu. |
|||
|
|||
Chú ý rằng, biến `item` là một trong các phần tử trong danh sách `items`. |
|||
|
|||
Và do vậy, trình soạn thảo biết nó là một `str`, và cung cấp sự hỗ trợ cho nó. |
|||
|
|||
#### Tuple and Set |
|||
|
|||
Bạn sẽ làm điều tương tự để khai báo các `tuple` và các `set`: |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial007_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial007.py!} |
|||
``` |
|||
|
|||
Điều này có nghĩa là: |
|||
|
|||
* Biến `items_t` là một `tuple` với 3 phần tử, một `int`, một `int` nữa, và một `str`. |
|||
* Biến `items_s` là một `set`, và mỗi phần tử của nó có kiểu `bytes`. |
|||
|
|||
#### Dict |
|||
|
|||
Để định nghĩa một `dict`, bạn truyền 2 tham số kiểu dữ liệu, phân cách bởi dấu phẩy. |
|||
|
|||
Tham số kiểu dữ liệu đầu tiên dành cho khóa của `dict`. |
|||
|
|||
Tham số kiểu dữ liệu thứ hai dành cho giá trị của `dict`. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial008_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial008.py!} |
|||
``` |
|||
|
|||
Điều này có nghĩa là: |
|||
|
|||
* Biến `prices` là một `dict`: |
|||
* Khóa của `dict` này là kiểu `str` (đó là tên của mỗi vật phẩm). |
|||
* Giá trị của `dict` này là kiểu `float` (đó là giá của mỗi vật phẩm). |
|||
|
|||
#### Union |
|||
|
|||
Bạn có thể khai báo rằng một biến có thể là **một vài kiểu dữ liệu" bất kì, ví dụ, một `int` hoặc một `str`. |
|||
|
|||
Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể sử dụng kiểu `Union` từ `typing` và đặt trong dấu ngoặc vuông những giá trị được chấp nhận. |
|||
|
|||
In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</abbr>. |
|||
|
|||
Trong Python 3.10 cũng có một **cú pháp mới** mà bạn có thể đặt những kiểu giá trị khả thi phân cách bởi một dấu <abbr title='cũng được gọi là "toán tử nhị phân"'>sổ dọc (`|`)</abbr>. |
|||
|
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial008b_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial008b.py!} |
|||
``` |
|||
|
|||
Trong cả hai trường hợp có nghĩa là `item` có thể là một `int` hoặc `str`. |
|||
|
|||
#### Khả năng `None` |
|||
|
|||
Bạn có thể khai báo một giá trị có thể có một kiểu dữ liệu, giống như `str`, nhưng nó cũng có thể là `None`. |
|||
|
|||
Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể khai báo nó bằng các import và sử dụng `Optional` từ mô đun `typing`. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!../../../docs_src/python_types/tutorial009.py!} |
|||
``` |
|||
|
|||
Sử dụng `Optional[str]` thay cho `str` sẽ cho phép trình soạn thảo giúp bạn phát hiện các lỗi mà bạn có thể gặp như một giá trị luôn là một `str`, trong khi thực tế nó rất có thể là `None`. |
|||
|
|||
`Optional[Something]` là một cách viết ngắn gọn của `Union[Something, None]`, chúng là tương đương nhau. |
|||
|
|||
Điều này cũng có nghĩa là trong Python 3.10, bạn có thể sử dụng `Something | None`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial009_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial009.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ alternative" |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial009b.py!} |
|||
``` |
|||
|
|||
#### Sử dụng `Union` hay `Optional` |
|||
|
|||
If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view: |
|||
|
|||
Nếu bạn đang sử dụng phiên bản Python dưới 3.10, đây là một mẹo từ ý kiến rất "chủ quan" của tôi: |
|||
|
|||
* 🚨 Tránh sử dụng `Optional[SomeType]` |
|||
* Thay vào đó ✨ **sử dụng `Union[SomeType, None]`** ✨. |
|||
|
|||
Cả hai là tương đương và bên dưới chúng giống nhau, nhưng tôi sẽ đễ xuất `Union` thay cho `Optional` vì từ "**tùy chọn**" có vẻ ngầm định giá trị là tùy chọn, và nó thực sự có nghĩa rằng "nó có thể là `None`", do đó nó không phải là tùy chọn và nó vẫn được yêu cầu. |
|||
|
|||
Tôi nghĩ `Union[SomeType, None]` là rõ ràng hơn về ý nghĩa của nó. |
|||
|
|||
Nó chỉ là về các từ và tên. Nhưng những từ đó có thể ảnh hưởng cách bạn và những đồng đội của bạn suy nghĩ về code. |
|||
|
|||
Cho một ví dụ, hãy để ý hàm này: |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!../../../docs_src/python_types/tutorial009c.py!} |
|||
``` |
|||
|
|||
Tham số `name` được định nghĩa là `Optional[str]`, nhưng nó **không phải là tùy chọn**, bạn không thể gọi hàm mà không có tham số: |
|||
|
|||
```Python |
|||
say_hi() # Oh, no, this throws an error! 😱 |
|||
``` |
|||
|
|||
Tham số `name` **vẫn được yêu cầu** (không phải là *tùy chọn*) vì nó không có giá trị mặc định. Trong khi đó, `name` chấp nhận `None` như là giá trị: |
|||
|
|||
```Python |
|||
say_hi(name=None) # This works, None is valid 🎉 |
|||
``` |
|||
|
|||
Tin tốt là, khi bạn sử dụng Python 3.10, bạn sẽ không phải lo lắng về điều đó, bạn sẽ có thể sử dụng `|` để định nghĩa hợp của các kiểu dữ liệu một cách đơn giản: |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!../../../docs_src/python_types/tutorial009c_py310.py!} |
|||
``` |
|||
|
|||
Và sau đó, bạn sẽ không phải lo rằng những cái tên như `Optional` và `Union`. 😎 |
|||
|
|||
|
|||
#### Những kiểu dữ liệu tổng quát |
|||
|
|||
Những kiểu dữ liệu này lấy tham số kiểu dữ liệu trong dấu ngoặc vuông được gọi là **Kiểu dữ liệu tổng quát**, cho ví dụ: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
Bạn có thể sử dụng các kiểu dữ liệu có sẵn như là kiểu dữ liệu tổng quát (với ngoặc vuông và kiểu dữ liệu bên trong): |
|||
|
|||
* `list` |
|||
* `tuple` |
|||
* `set` |
|||
* `dict` |
|||
|
|||
Và tương tự với Python 3.6, từ mô đun `typing`: |
|||
|
|||
* `Union` |
|||
* `Optional` (tương tự như Python 3.6) |
|||
* ...và các kiểu dữ liệu khác. |
|||
|
|||
Trong Python 3.10, thay vì sử dụng `Union` và `Optional`, bạn có thể sử dụng <abbr title='cũng gọi là "toán tử nhị phân", nhưng ý nghĩa không liên quan ở đây'>sổ dọc ('|')</abbr> để khai báo hợp của các kiểu dữ liệu, điều đó tốt hơn và đơn giản hơn nhiều. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
Bạn có thể sử dụng các kiểu dữ liệu có sẵn tương tự như (với ngoặc vuông và kiểu dữ liệu bên trong): |
|||
|
|||
* `list` |
|||
* `tuple` |
|||
* `set` |
|||
* `dict` |
|||
|
|||
Và tương tự với Python 3.6, từ mô đun `typing`: |
|||
|
|||
* `Union` |
|||
* `Optional` |
|||
* ...and others. |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
* `List` |
|||
* `Tuple` |
|||
* `Set` |
|||
* `Dict` |
|||
* `Union` |
|||
* `Optional` |
|||
* ...và các kiểu khác. |
|||
|
|||
### Lớp như kiểu dữ liệu |
|||
|
|||
Bạn cũng có thể khai báo một lớp như là kiểu dữ liệu của một biến. |
|||
|
|||
Hãy nói rằng bạn muốn có một lớp `Person` với một tên: |
|||
|
|||
```Python hl_lines="1-3" |
|||
{!../../../docs_src/python_types/tutorial010.py!} |
|||
``` |
|||
|
|||
Sau đó bạn có thể khai báo một biến có kiểu là `Person`: |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/python_types/tutorial010.py!} |
|||
``` |
|||
|
|||
Và lại một lần nữa, bạn có được tất cả sự hỗ trợ từ trình soạn thảo: |
|||
|
|||
<img src="/img/python-types/image06.png"> |
|||
|
|||
Lưu ý rằng, điều này có nghĩa rằng "`one_person`" là một **thực thể** của lớp `Person`. |
|||
|
|||
Nó không có nghĩa "`one_person`" là một **lớp** gọi là `Person`. |
|||
|
|||
## Pydantic models |
|||
|
|||
<a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> là một thư viện Python để validate dữ liệu hiệu năng cao. |
|||
|
|||
Bạn có thể khai báo "hình dạng" của dữa liệu như là các lớp với các thuộc tính. |
|||
|
|||
Và mỗi thuộc tính có một kiểu dữ liệu. |
|||
|
|||
Sau đó bạn tạo một thực thể của lớp đó với một vài giá trị và nó sẽ validate các giá trị, chuyển đổi chúng sang kiểu dữ liệu phù hợp (nếu đó là trường hợp) và cho bạn một object với toàn bộ dữ liệu. |
|||
|
|||
Và bạn nhận được tất cả sự hỗ trợ của trình soạn thảo với object kết quả đó. |
|||
|
|||
Một ví dụ từ tài liệu chính thức của Pydantic: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/python_types/tutorial011_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/python_types/tutorial011_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/python_types/tutorial011.py!} |
|||
``` |
|||
|
|||
!!! info |
|||
Để học nhiều hơn về <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic, tham khảo tài liệu của nó</a>. |
|||
|
|||
**FastAPI** được dựa hoàn toàn trên Pydantic. |
|||
|
|||
Bạn sẽ thấy nhiều ví dụ thực tế hơn trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}. |
|||
|
|||
!!! tip |
|||
Pydantic có một hành vi đặc biệt khi bạn sử dụng `Optional` hoặc `Union[Something, None]` mà không có giá trị mặc dịnh, bạn có thể đọc nhiều hơn về nó trong tài liệu của Pydantic về <a href="https://pydantic-docs.helpmanual.io/usage/models/#required-optional-fields" class="external-link" target="_blank">Required Optional fields</a>. |
|||
|
|||
|
|||
## Type Hints với Metadata Annotations |
|||
|
|||
Python cũng có một tính năng cho phép đặt **metadata bổ sung** trong những gợi ý kiểu dữ liệu này bằng cách sử dụng `Annotated`. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
Trong Python 3.9, `Annotated` là một phần của thư viện chuẩn, do đó bạn có thể import nó từ `typing`. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial013_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
Ở phiên bản dưới Python 3.9, bạn import `Annotated` từ `typing_extensions`. |
|||
|
|||
Nó đã được cài đặt sẵng cùng với **FastAPI**. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial013.py!} |
|||
``` |
|||
|
|||
Python bản thân nó không làm bất kì điều gì với `Annotated`. Với các trình soạn thảo và các công cụ khác, kiểu dữ liệu vẫn là `str`. |
|||
|
|||
Nhưng bạn có thể sử dụng `Annotated` để cung cấp cho **FastAPI** metadata bổ sung về cách mà bạn muốn ứng dụng của bạn xử lí. |
|||
|
|||
Điều quan trọng cần nhớ là ***tham số kiểu dữ liệu* đầu tiên** bạn truyền tới `Annotated` là **kiểu giá trị thực sự**. Phần còn lại chỉ là metadata cho các công cụ khác. |
|||
|
|||
Bây giờ, bạn chỉ cần biết rằng `Annotated` tồn tại, và nó là tiêu chuẩn của Python. 😎 |
|||
|
|||
|
|||
Sau đó, bạn sẽ thấy sự **mạnh mẽ** mà nó có thể làm. |
|||
|
|||
!!! tip |
|||
Thực tế, cái này là **tiêu chuẩn của Python**, nghĩa là bạn vẫn sẽ có được **trải nghiệm phát triển tốt nhất có thể** với trình soạn thảo của bạn, với các công cụ bạn sử dụng để phân tích và tái cấu trúc code của bạn, etc. ✨ |
|||
|
|||
Và code của bạn sẽ tương thích với nhiều công cụ và thư viện khác của Python. 🚀 |
|||
|
|||
## Các gợi ý kiểu dữ liệu trong **FastAPI** |
|||
|
|||
**FastAPI** lấy các ưu điểm của các gợi ý kiểu dữ liệu để thực hiện một số thứ. |
|||
|
|||
Với **FastAPI**, bạn khai báo các tham số với gợi ý kiểu và bạn có được: |
|||
|
|||
* **Sự hỗ trợ từ các trình soạn thảo**. |
|||
* **Kiểm tra kiểu dữ liệu (type checking)**. |
|||
|
|||
...và **FastAPI** sử dụng các khia báo để: |
|||
|
|||
* **Định nghĩa các yêu cầu**: từ tham số đường dẫn của request, tham số query, headers, bodies, các phụ thuộc (dependencies),... |
|||
* **Chuyển dổi dữ liệu*: từ request sang kiểu dữ liệu được yêu cầu. |
|||
* **Kiểm tra tính đúng đắn của dữ liệu**: tới từ mỗi request: |
|||
* Sinh **lỗi tự động** để trả về máy khác khi dữ liệu không hợp lệ. |
|||
* **Tài liệu hóa** API sử dụng OpenAPI: |
|||
* cái mà sau được được sử dụng bởi tài liệu tương tác người dùng. |
|||
|
|||
Điều này có thể nghe trừu tượng. Đừng lo lắng. Bạn sẽ thấy tất cả chúng trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}. |
|||
|
|||
Điều quan trọng là bằng việc sử dụng các kiểu dữ liệu chuẩn của Python (thay vì thêm các lớp, decorators,...), **FastAPI** sẽ thực hiện nhiều công việc cho bạn. |
|||
|
|||
!!! info |
|||
Nếu bạn đã đi qua toàn bộ các hướng dẫn và quay trở lại để tìm hiểu nhiều hơn về các kiểu dữ liệu, một tài nguyên tốt như <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">"cheat sheet" từ `mypy`</a>. |
Loading…
Reference in new issue