80 changed files with 3770 additions and 529 deletions
@ -20,7 +20,7 @@ jobs: |
|||
steps: |
|||
- uses: tiangolo/[email protected] |
|||
with: |
|||
token: ${{ secrets.GITHUB_TOKEN }} |
|||
token: ${{ secrets.FASTAPI_ISSUE_MANAGER }} |
|||
config: > |
|||
{ |
|||
"answered": { |
|||
|
@ -18,7 +18,7 @@ jobs: |
|||
- name: Download Artifact Docs |
|||
uses: dawidd6/[email protected] |
|||
with: |
|||
github_token: ${{ secrets.GITHUB_TOKEN }} |
|||
github_token: ${{ secrets.FASTAPI_PREVIEW_DOCS_DOWNLOAD_ARTIFACTS }} |
|||
workflow: build-docs.yml |
|||
run_id: ${{ github.event.workflow_run.id }} |
|||
name: docs-zip |
|||
@ -34,7 +34,7 @@ jobs: |
|||
with: |
|||
publish-dir: './site' |
|||
production-deploy: false |
|||
github-token: ${{ secrets.GITHUB_TOKEN }} |
|||
github-token: ${{ secrets.FASTAPI_PREVIEW_DOCS_NETLIFY }} |
|||
enable-commit-comment: false |
|||
env: |
|||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |
|||
@ -42,5 +42,5 @@ jobs: |
|||
- name: Comment Deploy |
|||
uses: ./.github/actions/comment-docs-preview-in-pr |
|||
with: |
|||
token: ${{ secrets.GITHUB_TOKEN }} |
|||
token: ${{ secrets.FASTAPI_PREVIEW_DOCS_COMMENT_DEPLOY }} |
|||
deploy_url: "${{ steps.netlify.outputs.deploy-url }}" |
|||
|
@ -22,6 +22,7 @@ jobs: |
|||
|
|||
- uses: dawidd6/[email protected] |
|||
with: |
|||
github_token: ${{ secrets.FASTAPI_SMOKESHOW_DOWNLOAD_ARTIFACTS }} |
|||
workflow: test.yml |
|||
commit: ${{ github.event.workflow_run.head_sha }} |
|||
|
|||
@ -30,6 +31,6 @@ jobs: |
|||
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} |
|||
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100 |
|||
SMOKESHOW_GITHUB_CONTEXT: coverage |
|||
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
|||
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.FASTAPI_SMOKESHOW_UPLOAD }} |
|||
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} |
|||
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} |
|||
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 5.6 KiB |
@ -0,0 +1,311 @@ |
|||
# Концепции развёртывания |
|||
|
|||
Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых Вы можете выбрать **наиболее подходящий** способ. |
|||
|
|||
Самые важные из них: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения. |
|||
|
|||
Рассмотрим ниже влияние каждого из них на процесс **развёртывания**. |
|||
|
|||
Наша конечная цель - **обслуживать клиентов Вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀 |
|||
|
|||
Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у Вас сложится **интуитивное понимание**, какой способ выбрать при развертывании Вашего API в различных окружениях, возможно, даже **ещё не существующих**. |
|||
|
|||
Ознакомившись с этими концепциями, Вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**. |
|||
|
|||
В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI. |
|||
|
|||
А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡 |
|||
|
|||
## Использование более безопасного протокола HTTPS |
|||
|
|||
В [предыдущей главе об HTTPS](./https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для Вашего API. |
|||
|
|||
Также мы заметили, что обычно для работы с HTTPS Вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**. |
|||
|
|||
И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия. |
|||
|
|||
### Примеры инструментов для работы с HTTPS |
|||
|
|||
Вот некоторые инструменты, которые Вы можете применять как прокси-серверы: |
|||
|
|||
* Traefik |
|||
* С автоматическим обновлением сертификатов ✨ |
|||
* Caddy |
|||
* С автоматическим обновлением сертификатов ✨ |
|||
* Nginx |
|||
* С дополнительным компонентом типа Certbot для обновления сертификатов |
|||
* HAProxy |
|||
* С дополнительным компонентом типа Certbot для обновления сертификатов |
|||
* Kubernetes с Ingress Controller похожим на Nginx |
|||
* С дополнительным компонентом типа cert-manager для обновления сертификатов |
|||
* Использование услуг облачного провайдера (читайте ниже 👇) |
|||
|
|||
В последнем варианте Вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера. |
|||
|
|||
В дальнейшем я покажу Вам некоторые конкретные примеры их применения. |
|||
|
|||
--- |
|||
|
|||
Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn). |
|||
|
|||
## Программа и процесс |
|||
|
|||
Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними. |
|||
|
|||
### Что такое программа |
|||
|
|||
Термином **программа** обычно описывают множество вещей: |
|||
|
|||
* **Код**, который Вы написали, в нашем случае **Python-файлы**. |
|||
* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`. |
|||
* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**. |
|||
|
|||
### Что такое процесс |
|||
|
|||
Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца): |
|||
|
|||
* Конкретная программа, **запущенная** операционной системой. |
|||
* Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой. |
|||
* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**. |
|||
* Процесс может быть **прерван** (или "убит") Вами или Вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**. |
|||
* Каждое приложение, которое Вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов. |
|||
* И **одна программа** может запустить **несколько параллельных процессов**. |
|||
|
|||
Если Вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) Вашей операционной системы, то увидите множество работающих процессов. |
|||
|
|||
Вполне вероятно, что Вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы. |
|||
|
|||
<img class="shadow" src="/img/deployment/concepts/image01.png"> |
|||
|
|||
--- |
|||
|
|||
Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания. |
|||
|
|||
## Настройки запуска приложения |
|||
|
|||
В большинстве случаев когда Вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у Вас могут быть причины, чтоб оно запускалось только при определённых условиях. |
|||
|
|||
### Удалённый сервер |
|||
|
|||
Когда Вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как Вы делаете при локальной разработке. |
|||
|
|||
Это рабочий способ и он полезен **во время разработки**. |
|||
|
|||
Но если Вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**. |
|||
|
|||
И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), Вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱 |
|||
|
|||
### Автоматический запуск программ |
|||
|
|||
Вероятно Вы пожелаете, чтоб Ваша серверная программа (такая как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI). |
|||
|
|||
### Отдельная программа |
|||
|
|||
Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных |
|||
|
|||
### Примеры инструментов, управляющих запуском программ |
|||
|
|||
Вот несколько примеров, которые могут справиться с такой задачей: |
|||
|
|||
* Docker |
|||
* Kubernetes |
|||
* Docker Compose |
|||
* Docker в режиме Swarm |
|||
* Systemd |
|||
* Supervisor |
|||
* Использование услуг облачного провайдера |
|||
* Прочие... |
|||
|
|||
Я покажу Вам некоторые примеры их использования в следующих главах. |
|||
|
|||
## Перезапуск |
|||
|
|||
Вы, вероятно, также пожелаете, чтоб Ваше приложение **перезапускалось**, если в нём произошёл сбой. |
|||
|
|||
### Мы ошибаемся |
|||
|
|||
Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛 |
|||
|
|||
И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅). |
|||
|
|||
### Небольшие ошибки обрабатываются автоматически |
|||
|
|||
Когда Вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡 |
|||
|
|||
Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами. |
|||
|
|||
### Большие ошибки - Падение приложений |
|||
|
|||
Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥 |
|||
|
|||
Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок. |
|||
|
|||
### Перезапуск после падения |
|||
|
|||
Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз... |
|||
|
|||
!!! tip "Заметка" |
|||
... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, Вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания. |
|||
|
|||
Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново. |
|||
|
|||
Возможно Вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск Вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в Вашем коде внутри приложения, не может быть выполнено в принципе. |
|||
|
|||
### Примеры инструментов для автоматического перезапуска |
|||
|
|||
В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы. |
|||
|
|||
В качестве примера можно взять те же: |
|||
|
|||
* Docker |
|||
* Kubernetes |
|||
* Docker Compose |
|||
* Docker в режиме Swarm |
|||
* Systemd |
|||
* Supervisor |
|||
* Использование услуг облачного провайдера |
|||
* Прочие... |
|||
|
|||
## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память |
|||
|
|||
Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно. |
|||
|
|||
Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов. |
|||
|
|||
### Множество процессов - Воркеры (Workers) |
|||
|
|||
Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то Вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами. |
|||
|
|||
**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**. |
|||
|
|||
### Процессы и порты́ |
|||
|
|||
Помните ли Вы, как на странице [Об HTTPS](./https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта? |
|||
|
|||
С тех пор ничего не изменилось. |
|||
|
|||
Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу. |
|||
|
|||
### У каждого процесса своя память |
|||
|
|||
Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера. |
|||
|
|||
Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения Вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти. |
|||
|
|||
### Память сервера |
|||
|
|||
Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда Вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если Вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате Вашему API потребуется **4 ГБ оперативной памяти (RAM)**. |
|||
|
|||
И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨 |
|||
|
|||
### Множество процессов - Пример |
|||
|
|||
В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**. |
|||
|
|||
Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам. |
|||
|
|||
Каждый из этих процессов будет запускать Ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память. |
|||
|
|||
<img src="/img/deployment/concepts/process-ram.svg"> |
|||
|
|||
Безусловно, на этом же сервере будут работать и **другие процессы**, которые не относятся к Вашему приложению. |
|||
|
|||
Интересная деталь - обычно в течение времени процент **использования центрального процессора (CPU)** каждым процессом может очень сильно **изменяться**, но объём занимаемой **оперативной памяти (RAM)** остаётся относительно **стабильным**. |
|||
|
|||
Если у Вас есть API, который каждый раз выполняет сопоставимый объем вычислений, и у Вас много клиентов, то **загрузка процессора**, вероятно, *также будет стабильной* (вместо того, чтобы постоянно быстро увеличиваться и уменьшаться). |
|||
|
|||
### Примеры стратегий и инструментов для запуска нескольких экземпляров приложения |
|||
|
|||
Существует несколько подходов для достижения целей репликации и я расскажу Вам больше о конкретных стратегиях в следующих главах, например, когда речь пойдет о Docker и контейнерах. |
|||
|
|||
Основное ограничение при этом - только **один** компонент может работать с определённым **портом публичного IP**. И должен быть способ **передачи** данных между этим компонентом и копиями **процессов/воркеров**. |
|||
|
|||
Вот некоторые возможные комбинации и стратегии: |
|||
|
|||
* **Gunicorn** управляющий **воркерами Uvicorn** |
|||
* Gunicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **множества работающих процессов Uvicorn**. |
|||
* **Uvicorn** управляющий **воркерами Uvicorn** |
|||
* Один процесс Uvicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Он будет запускать **множество работающих процессов Uvicorn**. |
|||
* **Kubernetes** и аналогичные **контейнерные системы** |
|||
* Какой-то компонент в **Kubernetes** будет слушать **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**. |
|||
* **Облачные сервисы**, которые позаботятся обо всём за Вас |
|||
* Возможно, что облачный сервис умеет **управлять запуском дополнительных экземпляров приложения**. Вероятно, он потребует, чтоб Вы указали - какой **процесс** или **образ** следует клонировать. Скорее всего, Вы укажете **один процесс Uvicorn** и облачный сервис будет запускать его копии при необходимости. |
|||
|
|||
!!! tip "Заметка" |
|||
Если Вы не знаете, что такое **контейнеры**, Docker или Kubernetes, не переживайте. |
|||
|
|||
Я поведаю Вам о контейнерах, образах, Docker, Kubernetes и т.п. в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}. |
|||
|
|||
## Шаги, предшествующие запуску |
|||
|
|||
Часто бывает, что Вам необходимо произвести какие-то подготовительные шаги **перед запуском** своего приложения. |
|||
|
|||
Например, запустить **миграции базы данных**. |
|||
|
|||
Но в большинстве случаев такие действия достаточно произвести **однократно**. |
|||
|
|||
Поэтому Вам нужен будет **один процесс**, выполняющий эти **подготовительные шаги** до запуска приложения. |
|||
|
|||
Также Вам нужно будет убедиться, что этот процесс выполнил подготовительные шаги *даже* если впоследствии Вы запустите **несколько процессов** (несколько воркеров) самого приложения. Если бы эти шаги выполнялись в каждом **клонированном процессе**, они бы **дублировали** работу, пытаясь выполнить её **параллельно**. И если бы эта работа была бы чем-то деликатным, вроде миграции базы данных, то это может вызвать конфликты между ними. |
|||
|
|||
Безусловно, возможны случаи, когда нет проблем при выполнении предварительной подготовки параллельно или несколько раз. Тогда Вам повезло, работать с ними намного проще. |
|||
|
|||
!!! tip "Заметка" |
|||
Имейте в виду, что в некоторых случаях запуск Вашего приложения **может не требовать каких-либо предварительных шагов вовсе**. |
|||
|
|||
Что ж, тогда Вам не нужно беспокоиться об этом. 🤷 |
|||
|
|||
### Примеры стратегий запуска предварительных шагов |
|||
|
|||
Существует **сильная зависимость** от того, как Вы **развёртываете свою систему**, запускаете программы, обрабатываете перезапуски и т.д. |
|||
|
|||
Вот некоторые возможные идеи: |
|||
|
|||
* При использовании Kubernetes нужно предусмотреть "инициализирующий контейнер", запускаемый до контейнера с приложением. |
|||
* Bash-скрипт, выполняющий предварительные шаги, а затем запускающий приложение. |
|||
* При этом Вам всё ещё нужно найти способ - как запускать/перезапускать *такой* bash-скрипт, обнаруживать ошибки и т.п. |
|||
|
|||
!!! tip "Заметка" |
|||
Я приведу Вам больше конкретных примеров работы с контейнерами в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}. |
|||
|
|||
## Утилизация ресурсов |
|||
|
|||
Ваш сервер располагает ресурсами, которые Ваши программы могут потреблять или **утилизировать**, а именно - время работы центрального процессора и объём оперативной памяти. |
|||
|
|||
Как много системных ресурсов Вы предполагаете потребить/утилизировать? Если не задумываться, то можно ответить - "немного", но на самом деле Вы, вероятно, пожелаете использовать **максимально возможное количество**. |
|||
|
|||
Если Вы платите за содержание трёх серверов, но используете лишь малую часть системных ресурсов каждого из них, то Вы **выбрасываете деньги на ветер**, а также **впустую тратите электроэнергию** и т.п. |
|||
|
|||
В таком случае было бы лучше обойтись двумя серверами, но более полно утилизировать их ресурсы (центральный процессор, оперативную память, жёсткий диск, сети передачи данных и т.д). |
|||
|
|||
С другой стороны, если Вы располагаете только двумя серверами и используете **на 100% их процессоры и память**, но какой-либо процесс запросит дополнительную память, то операционная система сервера будет использовать жёсткий диск для расширения оперативной памяти (а диск работает в тысячи раз медленнее), а то вовсе **упадёт**. Или если какому-то процессу понадобится произвести вычисления, то ему придётся подождать, пока процессор освободится. |
|||
|
|||
В такой ситуации лучше подключить **ещё один сервер** и перераспределить процессы между серверами, чтоб всем **хватало памяти и процессорного времени**. |
|||
|
|||
Также есть вероятность, что по какой-то причине возник **всплеск** запросов к Вашему API. Возможно, это был вирус, боты или другие сервисы начали пользоваться им. И для таких происшествий Вы можете захотеть иметь дополнительные ресурсы. |
|||
|
|||
При настройке логики развёртываний, Вы можете указать **целевое значение** утилизации ресурсов, допустим, **от 50% до 90%**. Обычно эти метрики и используют. |
|||
|
|||
Вы можете использовать простые инструменты, такие как `htop`, для отслеживания загрузки центрального процессора и оперативной памяти сервера, в том числе каждым процессом. Или более сложные системы мониторинга нескольких серверов. |
|||
|
|||
## Резюме |
|||
|
|||
Вы прочитали некоторые из основных концепций, которые необходимо иметь в виду при принятии решения о развертывании приложений: |
|||
|
|||
* Использование более безопасного протокола HTTPS |
|||
* Настройки запуска приложения |
|||
* Перезагрузка приложения |
|||
* Запуск нескольких экземпляров приложения |
|||
* Управление памятью |
|||
* Использование перечисленных функций перед запуском приложения. |
|||
|
|||
Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓 |
|||
|
|||
В следующих разделах я приведу более конкретные примеры возможных стратегий, которым Вы можете следовать. 🚀 |
@ -0,0 +1,309 @@ |
|||
# Body - Множество параметров |
|||
|
|||
Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры обьявления тела запроса. |
|||
|
|||
## Обьединение `Path`, `Query` и параметров тела запроса |
|||
|
|||
Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать. |
|||
|
|||
Вы также можете объявить параметры тела запроса как необязательные, установив значение по умолчанию, равное `None`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="18-20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="18-20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="19-21" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="17-19" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="19-21" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! Заметка |
|||
Заметьте, что в данном случае параметр `item`, который будет взят из тела запроса, необязателен. Так как было установлено значение `None` по умолчанию. |
|||
|
|||
## Несколько параметров тела запроса |
|||
|
|||
В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON-тело с параметрами, соответствующими атрибутам `Item`, например: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
|
|||
Но вы также можете объявить множество параметров тела запроса, например `item` и `user`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="22" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial002.py!} |
|||
``` |
|||
|
|||
В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются моделями Pydantic). |
|||
|
|||
Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
!!! Внимание |
|||
Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предпологается, что он находится внутри тела с ключом `item`. |
|||
|
|||
|
|||
**FastAPI** сделает автоматические преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`. |
|||
|
|||
Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах. |
|||
|
|||
## Отдельные значения в теле запроса |
|||
|
|||
Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`. |
|||
|
|||
Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`. |
|||
|
|||
Если вы объявите его без указания, какой именно объект (Path, Query, Body и .т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр. |
|||
|
|||
Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="23" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="23" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="24" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="20" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="22" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial003.py!} |
|||
``` |
|||
|
|||
В этом случае, **FastAPI** будет ожидать тело запроса в формате: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
}, |
|||
"importance": 5 |
|||
} |
|||
``` |
|||
|
|||
И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д. |
|||
|
|||
## Множество body и query параметров |
|||
|
|||
Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам. |
|||
|
|||
Поскольку по умолчанию, отдельные значения интерпретируются как query-параметры, вам не нужно явно добавлять `Query`, вы можете просто сделать так: |
|||
|
|||
```Python |
|||
q: Union[str, None] = None |
|||
``` |
|||
|
|||
Или в Python 3.10 и выше: |
|||
|
|||
```Python |
|||
q: str | None = None |
|||
``` |
|||
|
|||
Например: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="27" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="27" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="28" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="25" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="27" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial004.py!} |
|||
``` |
|||
|
|||
!!! Информация |
|||
`Body` также имеет все те же дополнительные параметры валидации и метаданных, как у `Query`,`Path` и других, которые вы увидите позже. |
|||
|
|||
## Добавление одного body-параметра |
|||
|
|||
Предположим, у вас есть только один body-параметр `item` из Pydantic модели `Item`. |
|||
|
|||
По умолчанию, **FastAPI** ожидает получить тело запроса напрямую. |
|||
|
|||
Но если вы хотите чтобы он ожидал JSON с ключом `item` с содержимым модели внутри, также как это происходит при объявлении дополнительных body-параметров, вы можете использовать специальный параметр `embed` у типа `Body`: |
|||
|
|||
```Python |
|||
item: Item = Body(embed=True) |
|||
``` |
|||
|
|||
так же, как в этом примере: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="18" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="15" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! Заметка |
|||
Рекомендуется использовать `Annotated` версию, если это возможно. |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/body_multiple_params/tutorial005.py!} |
|||
``` |
|||
|
|||
В этом случае **FastAPI** будет ожидать тело запроса в формате: |
|||
|
|||
```JSON hl_lines="2" |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
вместо этого: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
|
|||
## Резюме |
|||
|
|||
Вы можете добавлять несколько body-параметров вашей *функции операции пути*, несмотря даже на то, что запрос может содержать только одно тело. |
|||
|
|||
Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*. |
|||
|
|||
Вы также можете объявить отдельные значения для получения в рамках тела запроса. |
|||
|
|||
И вы можете настроить **FastAPI** таким образом, чтобы включить тело запроса в ключ, даже если объявлен только один параметр. |
@ -0,0 +1,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,165 @@ |
|||
# Тело запроса |
|||
|
|||
Когда вам необходимо отправить данные из клиента (допустим, браузера) в ваш API, вы отправляете их как **тело запроса**. |
|||
|
|||
Тело **запроса** --- это данные, отправляемые клиентом в ваш API. Тело **ответа** --- это данные, которые ваш API отправляет клиенту. |
|||
|
|||
Ваш API почти всегда отправляет тело **ответа**. Но клиентам не обязательно всегда отправлять тело **запроса**. |
|||
|
|||
Чтобы объявить тело **запроса**, необходимо использовать модели <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>, со всей их мощью и преимуществами. |
|||
|
|||
!!! info "Информация" |
|||
Чтобы отправить данные, необходимо использовать один из методов: `POST` (обычно), `PUT`, `DELETE` или `PATCH`. |
|||
|
|||
Отправка тела с запросом `GET` имеет неопределенное поведение в спецификациях, тем не менее, оно поддерживается FastAPI только для очень сложных/экстремальных случаев использования. |
|||
|
|||
Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании метода GET, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса. |
|||
|
|||
## Импортирование `BaseModel` из Pydantic |
|||
|
|||
Первое, что вам необходимо сделать, это импортировать `BaseModel` из пакета `pydantic`: |
|||
|
|||
```Python hl_lines="4" |
|||
{!../../../docs_src/body/tutorial001.py!} |
|||
``` |
|||
|
|||
## Создание вашей собственной модели |
|||
|
|||
После этого вы описываете вашу модель данных как класс, наследующий от `BaseModel`. |
|||
|
|||
Используйте аннотации типов Python для всех атрибутов: |
|||
|
|||
```Python hl_lines="7-11" |
|||
{!../../../docs_src/body/tutorial001.py!} |
|||
``` |
|||
|
|||
Также как и при описании параметров запроса, когда атрибут модели имеет значение по умолчанию, он является необязательным. Иначе он обязателен. Используйте `None`, чтобы сделать его необязательным без использования конкретных значений по умолчанию. |
|||
|
|||
Например, модель выше описывает вот такой JSON "объект" (или словарь Python): |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "An optional description", |
|||
"price": 45.2, |
|||
"tax": 3.5 |
|||
} |
|||
``` |
|||
|
|||
...поскольку `description` и `tax` являются необязательными (с `None` в качестве значения по умолчанию), вот такой JSON "объект" также подходит: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"price": 45.2 |
|||
} |
|||
``` |
|||
|
|||
## Объявление как параметра функции |
|||
|
|||
Чтобы добавить параметр к вашему *обработчику*, объявите его также, как вы объявляли параметры пути или параметры запроса: |
|||
|
|||
```Python hl_lines="18" |
|||
{!../../../docs_src/body/tutorial001.py!} |
|||
``` |
|||
|
|||
...и укажите созданную модель в качестве типа параметра, `Item`. |
|||
|
|||
## Результаты |
|||
|
|||
Всего лишь с помощью аннотации типов Python, **FastAPI**: |
|||
|
|||
* Читает тело запроса как JSON. |
|||
* Приводит к соответствующим типам (если есть необходимость). |
|||
* Проверяет корректность данных. |
|||
* Если данные некорректны, будет возращена читаемая и понятная ошибка, показывающая что именно и в каком месте некорректно в данных. |
|||
* Складывает полученные данные в параметр `item`. |
|||
* Поскольку внутри функции вы объявили его с типом `Item`, то теперь у вас есть поддержка со стороны редактора (автодополнение и т.п.) для всех атрибутов и их типов. |
|||
* Генерирует декларативное описание модели в виде <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a>, так что вы можете его использовать где угодно, если это имеет значение для вашего проекта. |
|||
* Эти схемы являются частью сгенерированной схемы OpenAPI и используются для автоматического документирования <abbr title="Пользовательских интерфейсов (User Interfaces)">UI</abbr>. |
|||
|
|||
## Автоматическое документирование |
|||
|
|||
Схема JSON ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображена в интерактивной документации API: |
|||
|
|||
<img src="/img/tutorial/body/image01.png"> |
|||
|
|||
Также она будет указана в документации по API внутри каждой *операции пути*, в которой используются: |
|||
|
|||
<img src="/img/tutorial/body/image02.png"> |
|||
|
|||
## Поддержка редактора |
|||
|
|||
В вашем редакторе внутри вашей функции у вас будут подсказки по типам и автодополнение (это не будет работать, если вы получаете словарь вместо модели Pydantic): |
|||
|
|||
<img src="/img/tutorial/body/image03.png"> |
|||
|
|||
Также вы будете получать ошибки в случае несоответствия типов: |
|||
|
|||
<img src="/img/tutorial/body/image04.png"> |
|||
|
|||
Это не случайно, весь фреймворк построен вокруг такого дизайна. |
|||
|
|||
И это все тщательно протестировано еще на этапе разработки дизайна, до реализации, чтобы это работало со всеми редакторами. |
|||
|
|||
Для поддержки этого даже были внесены некоторые изменения в сам Pydantic. |
|||
|
|||
На всех предыдущих скриншотах используется <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>. |
|||
|
|||
Но у вас будет такая же поддержка и с <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>, и вообще с любым редактором Python: |
|||
|
|||
<img src="/img/tutorial/body/image05.png"> |
|||
|
|||
!!! tip "Подсказка" |
|||
Если вы используете <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> в качестве редактора, то вам стоит попробовать плагин <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>. |
|||
|
|||
Он улучшает поддержку редактором моделей Pydantic в части: |
|||
|
|||
* автодополнения, |
|||
* проверки типов, |
|||
* рефакторинга, |
|||
* поиска, |
|||
* инспектирования. |
|||
|
|||
## Использование модели |
|||
|
|||
Внутри функции вам доступны все атрибуты объекта модели напрямую: |
|||
|
|||
```Python hl_lines="21" |
|||
{!../../../docs_src/body/tutorial002.py!} |
|||
``` |
|||
|
|||
## Тело запроса + параметры пути |
|||
|
|||
Вы можете одновременно объявлять параметры пути и тело запроса. |
|||
|
|||
**FastAPI** распознает, какие параметры функции соответствуют параметрам пути и должны быть **получены из пути**, а какие параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**. |
|||
|
|||
```Python hl_lines="17-18" |
|||
{!../../../docs_src/body/tutorial003.py!} |
|||
``` |
|||
|
|||
## Тело запроса + параметры пути + параметры запроса |
|||
|
|||
Вы также можете одновременно объявить параметры для **пути**, **запроса** и **тела запроса**. |
|||
|
|||
**FastAPI** распознает каждый из них и возьмет данные из правильного источника. |
|||
|
|||
```Python hl_lines="18" |
|||
{!../../../docs_src/body/tutorial004.py!} |
|||
``` |
|||
|
|||
Параметры функции распознаются следующим образом: |
|||
|
|||
* Если параметр также указан в **пути**, то он будет использоваться как параметр пути. |
|||
* Если аннотация типа параметра содержит **примитивный тип** (`int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**. |
|||
* Если аннотация типа параметра представляет собой **модель Pydantic**, он будет интерпретирован как параметр **тела запроса**. |
|||
|
|||
!!! note "Заметка" |
|||
FastAPI понимает, что значение параметра `q` не является обязательным, потому что имеет значение по умолчанию `= None`. |
|||
|
|||
Аннотация `Optional` в `Optional[str]` не используется FastAPI, но помогает вашему редактору лучше понимать ваш код и обнаруживать ошибки. |
|||
|
|||
## Без Pydantic |
|||
|
|||
Если вы не хотите использовать модели Pydantic, вы все еще можете использовать параметры **тела запроса**. Читайте в документации раздел [Тело - Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. |
@ -0,0 +1,112 @@ |
|||
# Отладка |
|||
|
|||
Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm. |
|||
|
|||
## Вызов `uvicorn` |
|||
|
|||
В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую: |
|||
|
|||
```Python hl_lines="1 15" |
|||
{!../../../docs_src/debugging/tutorial001.py!} |
|||
``` |
|||
|
|||
### Описание `__name__ == "__main__"` |
|||
|
|||
Главная цель использования `__name__ == "__main__"` в том, чтобы код выполнялся при запуске файла с помощью: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python myapp.py |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
но не вызывался, когда другой файл импортирует это, например:: |
|||
|
|||
```Python |
|||
from myapp import app |
|||
``` |
|||
|
|||
#### Больше деталей |
|||
|
|||
Давайте назовём ваш файл `myapp.py`. |
|||
|
|||
Если вы запустите его с помощью: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python myapp.py |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
то встроенная переменная `__name__`, автоматически создаваемая Python в вашем файле, будет иметь значение строкового типа `"__main__"`. |
|||
|
|||
Тогда выполнится условие и эта часть кода: |
|||
|
|||
```Python |
|||
uvicorn.run(app, host="0.0.0.0", port=8000) |
|||
``` |
|||
|
|||
будет запущена. |
|||
|
|||
--- |
|||
|
|||
Но этого не произойдет, если вы импортируете этот модуль (файл). |
|||
|
|||
Таким образом, если у вас есть файл `importer.py` с таким импортом: |
|||
|
|||
```Python |
|||
from myapp import app |
|||
|
|||
# Some more code |
|||
``` |
|||
|
|||
то автоматическая создаваемая внутри файла `myapp.py` переменная `__name__` будет иметь значение отличающееся от `"__main__"`. |
|||
|
|||
Следовательно, строка: |
|||
|
|||
```Python |
|||
uvicorn.run(app, host="0.0.0.0", port=8000) |
|||
``` |
|||
|
|||
не будет выполнена. |
|||
|
|||
!!! Информация |
|||
Для получения дополнительной информации, ознакомьтесь с <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">официальной документацией Python</a>. |
|||
|
|||
## Запуск вашего кода с помощью отладчика |
|||
|
|||
Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика. |
|||
|
|||
--- |
|||
|
|||
Например, в Visual Studio Code вы можете выполнить следующие шаги: |
|||
|
|||
* Перейдите на панель "Debug". |
|||
* Выберите "Add configuration...". |
|||
* Выберите "Python" |
|||
* Запустите отладчик "`Python: Current File (Integrated Terminal)`". |
|||
|
|||
Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д. |
|||
|
|||
Вот как это может выглядеть: |
|||
|
|||
<img src="/img/tutorial/debugging/image01.png"> |
|||
|
|||
--- |
|||
|
|||
Если используете Pycharm, вы можете выполнить следующие шаги: |
|||
|
|||
* Открыть "Run" меню. |
|||
* Выбрать опцию "Debug...". |
|||
* Затем в появившемся контекстном меню. |
|||
* Выбрать файл для отладки (в данном случае, `main.py`). |
|||
|
|||
Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д. |
|||
|
|||
Вот как это может выглядеть: |
|||
|
|||
<img src="/img/tutorial/debugging/image02.png"> |
@ -0,0 +1,333 @@ |
|||
# Первые шаги |
|||
|
|||
Самый простой FastAPI файл может выглядеть так: |
|||
|
|||
```Python |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Скопируйте в файл `main.py`. |
|||
|
|||
Запустите сервер в режиме реального времени: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
<span style="color: green;">INFO</span>: Started reloader process [28720] |
|||
<span style="color: green;">INFO</span>: Started server process [28722] |
|||
<span style="color: green;">INFO</span>: Waiting for application startup. |
|||
<span style="color: green;">INFO</span>: Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! note "Технические детали" |
|||
Команда `uvicorn main:app` обращается к: |
|||
|
|||
* `main`: файл `main.py` (модуль Python). |
|||
* `app`: объект, созданный внутри файла `main.py` в строке `app = FastAPI()`. |
|||
* `--reload`: перезапускает сервер после изменения кода. Используйте только для разработки. |
|||
|
|||
В окне вывода появится следующая строка: |
|||
|
|||
```hl_lines="4" |
|||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
Эта строка показывает URL-адрес, по которому приложение доступно на локальной машине. |
|||
|
|||
### Проверьте |
|||
|
|||
Откройте браузер по адресу: <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>. |
|||
|
|||
Вы увидите JSON-ответ следующего вида: |
|||
|
|||
```JSON |
|||
{"message": "Hello World"} |
|||
``` |
|||
|
|||
### Интерактивная документация API |
|||
|
|||
Перейдите по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
Вы увидите автоматически сгенерированную, интерактивную документацию по API (предоставленную <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>): |
|||
|
|||
 |
|||
|
|||
### Альтернативная документация API |
|||
|
|||
Теперь перейдите по адресу <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. |
|||
|
|||
Вы увидите альтернативную автоматически сгенерированную документацию (предоставленную <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>): |
|||
|
|||
 |
|||
|
|||
### OpenAPI |
|||
|
|||
**FastAPI** генерирует "схему" всего API, используя стандарт **OpenAPI**. |
|||
|
|||
#### "Схема" |
|||
|
|||
"Схема" - это определение или описание чего-либо. Не код, реализующий это, а только абстрактное описание. |
|||
|
|||
#### API "схема" |
|||
|
|||
<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> - это спецификация, которая определяет, как описывать схему API. |
|||
|
|||
Определение схемы содержит пути (paths) API, их параметры и т.п. |
|||
|
|||
#### "Схема" данных |
|||
|
|||
Термин "схема" также может относиться к формату или структуре некоторых данных, например, JSON. |
|||
|
|||
Тогда, подразумеваются атрибуты JSON, их типы данных и т.п. |
|||
|
|||
#### OpenAPI и JSON Schema |
|||
|
|||
OpenAPI описывает схему API. Эта схема содержит определения (или "схемы") данных, отправляемых и получаемых API. Для описания структуры данных в JSON используется стандарт **JSON Schema**. |
|||
|
|||
#### Рассмотрим `openapi.json` |
|||
|
|||
Если Вас интересует, как выглядит исходная схема OpenAPI, то FastAPI автоматически генерирует JSON-схему со всеми описаниями API. |
|||
|
|||
Можете посмотреть здесь: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>. |
|||
|
|||
Вы увидите примерно такой JSON: |
|||
|
|||
```JSON |
|||
{ |
|||
"openapi": "3.0.2", |
|||
"info": { |
|||
"title": "FastAPI", |
|||
"version": "0.1.0" |
|||
}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
|
|||
|
|||
|
|||
... |
|||
``` |
|||
|
|||
#### Для чего нужен OpenAPI |
|||
|
|||
Схема OpenAPI является основой для обеих систем интерактивной документации. |
|||
|
|||
Существуют десятки альтернативных инструментов, основанных на OpenAPI. Вы можете легко добавить любой из них к **FastAPI** приложению. |
|||
|
|||
Вы также можете использовать OpenAPI для автоматической генерации кода для клиентов, которые взаимодействуют с API. Например, для фронтенд-, мобильных или IoT-приложений. |
|||
|
|||
## Рассмотрим поэтапно |
|||
|
|||
### Шаг 1: импортируйте `FastAPI` |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
`FastAPI` это класс в Python, который предоставляет всю функциональность для API. |
|||
|
|||
!!! note "Технические детали" |
|||
`FastAPI` это класс, который наследуется непосредственно от `Starlette`. |
|||
|
|||
Вы можете использовать всю функциональность <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> в `FastAPI`. |
|||
|
|||
### Шаг 2: создайте экземпляр `FastAPI` |
|||
|
|||
```Python hl_lines="3" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Переменная `app` является экземпляром класса `FastAPI`. |
|||
|
|||
Это единая точка входа для создания и взаимодействия с API. |
|||
|
|||
Именно к этой переменной `app` обращается `uvicorn` в команде: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Если создать такое приложение: |
|||
|
|||
```Python hl_lines="3" |
|||
{!../../../docs_src/first_steps/tutorial002.py!} |
|||
``` |
|||
|
|||
И поместить его в `main.py`, тогда вызов `uvicorn` будет таким: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:my_awesome_api --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
### Шаг 3: определите *операцию пути (path operation)* |
|||
|
|||
#### Путь (path) |
|||
|
|||
"Путь" это часть URL, после первого символа `/`, следующего за именем домена. |
|||
|
|||
Для URL: |
|||
|
|||
``` |
|||
https://example.com/items/foo |
|||
``` |
|||
|
|||
...путь выглядит так: |
|||
|
|||
``` |
|||
/items/foo |
|||
``` |
|||
|
|||
!!! info "Дополнительная иформация" |
|||
Термин "path" также часто называется "endpoint" или "route". |
|||
|
|||
При создании API, "путь" является основным способом разделения "задач" и "ресурсов". |
|||
|
|||
#### Операция (operation) |
|||
|
|||
"Операция" это один из "методов" HTTP. |
|||
|
|||
Таких, как: |
|||
|
|||
* `POST` |
|||
* `GET` |
|||
* `PUT` |
|||
* `DELETE` |
|||
|
|||
...и более экзотических: |
|||
|
|||
* `OPTIONS` |
|||
* `HEAD` |
|||
* `PATCH` |
|||
* `TRACE` |
|||
|
|||
По протоколу HTTP можно обращаться к каждому пути, используя один (или несколько) из этих "методов". |
|||
|
|||
--- |
|||
|
|||
При создании API принято использовать конкретные HTTP-методы для выполнения определенных действий. |
|||
|
|||
Обычно используют: |
|||
|
|||
* `POST`: создать данные. |
|||
* `GET`: прочитать. |
|||
* `PUT`: изменить (обновить). |
|||
* `DELETE`: удалить. |
|||
|
|||
В OpenAPI каждый HTTP метод называется "**операция**". |
|||
|
|||
Мы также будем придерживаться этого термина. |
|||
|
|||
#### Определите *декоратор операции пути (path operation decorator)* |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Декоратор `@app.get("/")` указывает **FastAPI**, что функция, прямо под ним, отвечает за обработку запросов, поступающих по адресу: |
|||
|
|||
* путь `/` |
|||
* использующих <abbr title="HTTP GET метод"><code>get</code> операцию</abbr> |
|||
|
|||
!!! info "`@decorator` Дополнительная информация" |
|||
Синтаксис `@something` в Python называется "декоратор". |
|||
|
|||
Вы помещаете его над функцией. Как красивую декоративную шляпу (думаю, что оттуда и происходит этот термин). |
|||
|
|||
"Декоратор" принимает функцию ниже и выполняет с ней какое-то действие. |
|||
|
|||
В нашем случае, этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` и **операции** `get`. |
|||
|
|||
Это и есть "**декоратор операции пути**". |
|||
|
|||
Можно также использовать операции: |
|||
|
|||
* `@app.post()` |
|||
* `@app.put()` |
|||
* `@app.delete()` |
|||
|
|||
И более экзотические: |
|||
|
|||
* `@app.options()` |
|||
* `@app.head()` |
|||
* `@app.patch()` |
|||
* `@app.trace()` |
|||
|
|||
!!! tip "Подсказка" |
|||
Вы можете использовать каждую операцию (HTTP-метод) по своему усмотрению. |
|||
|
|||
**FastAPI** не навязывает определенного значения для каждого метода. |
|||
|
|||
Информация здесь представлена как рекомендация, а не требование. |
|||
|
|||
Например, при использовании GraphQL обычно все действия выполняются только с помощью POST операций. |
|||
|
|||
### Шаг 4: определите **функцию операции пути** |
|||
|
|||
Вот "**функция операции пути**": |
|||
|
|||
* **путь**: `/`. |
|||
* **операция**: `get`. |
|||
* **функция**: функция ниже "декоратора" (ниже `@app.get("/")`). |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Это обычная Python функция. |
|||
|
|||
**FastAPI** будет вызывать её каждый раз при получении `GET` запроса к URL "`/`". |
|||
|
|||
В данном случае это асинхронная функция. |
|||
|
|||
--- |
|||
|
|||
Вы также можете определить ее как обычную функцию вместо `async def`: |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/first_steps/tutorial003.py!} |
|||
``` |
|||
|
|||
!!! note "Технические детали" |
|||
Если не знаете в чём разница, посмотрите [Конкурентность: *"Нет времени?"*](../async.md#in-a-hurry){.internal-link target=_blank}. |
|||
|
|||
### Шаг 5: верните результат |
|||
|
|||
```Python hl_lines="8" |
|||
{!../../../docs_src/first_steps/tutorial001.py!} |
|||
``` |
|||
|
|||
Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д. |
|||
|
|||
Также можно вернуть модели Pydantic (рассмотрим это позже). |
|||
|
|||
Многие объекты и модели будут автоматически преобразованы в JSON (включая ORM). Пробуйте использовать другие объекты, которые предпочтительней для Вас, вероятно, они уже поддерживаются. |
|||
|
|||
## Резюме |
|||
|
|||
* Импортируем `FastAPI`. |
|||
* Создаём экземпляр `app`. |
|||
* Пишем **декоратор операции пути** (такой как `@app.get("/")`). |
|||
* Пишем **функцию операции пути** (`def root(): ...`). |
|||
* Запускаем сервер в режиме разработки (`uvicorn main:app --reload`). |
@ -0,0 +1,80 @@ |
|||
# Учебник - Руководство пользователя - Введение |
|||
|
|||
В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций. |
|||
|
|||
Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API. |
|||
|
|||
Он также создан для использования в качестве будущего справочника. |
|||
|
|||
Так что вы можете вернуться и посмотреть именно то, что вам нужно. |
|||
|
|||
## Запустите код |
|||
|
|||
Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python). |
|||
|
|||
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
<span style="color: green;">INFO</span>: Started reloader process [28720] |
|||
<span style="color: green;">INFO</span>: Started server process [28722] |
|||
<span style="color: green;">INFO</span>: Waiting for application startup. |
|||
<span style="color: green;">INFO</span>: Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
**НАСТОЯТЕЛЬНО рекомендуется**, чтобы вы написали или скопировали код, отредактировали его и запустили локально. |
|||
|
|||
Использование кода в вашем редакторе — это то, что действительно показывает вам преимущества FastAPI, видя, как мало кода вам нужно написать, все проверки типов, автодополнение и т.д. |
|||
|
|||
--- |
|||
|
|||
## Установка FastAPI |
|||
|
|||
Первый шаг — установить FastAPI. |
|||
|
|||
Для руководства вы, возможно, захотите установить его со всеми дополнительными зависимостями и функциями: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "fastapi[all]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
...это также включает `uvicorn`, который вы можете использовать в качестве сервера, который запускает ваш код. |
|||
|
|||
!!! note "Технические детали" |
|||
Вы также можете установить его по частям. |
|||
|
|||
Это то, что вы, вероятно, сделаете, когда захотите развернуть свое приложение в рабочей среде: |
|||
|
|||
``` |
|||
pip install fastapi |
|||
``` |
|||
|
|||
Также установите `uvicorn` для работы в качестве сервера: |
|||
|
|||
``` |
|||
pip install "uvicorn[standard]" |
|||
``` |
|||
|
|||
И то же самое для каждой из необязательных зависимостей, которые вы хотите использовать. |
|||
|
|||
## Продвинутое руководство пользователя |
|||
|
|||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**. |
|||
|
|||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям. |
|||
|
|||
Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас). |
|||
|
|||
Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**. |
@ -0,0 +1,292 @@ |
|||
# Path-параметры и валидация числовых данных |
|||
|
|||
Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров. |
|||
|
|||
## Импорт Path |
|||
|
|||
Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="1 3" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="1 3" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="3-4" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="3" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! info "Информация" |
|||
Поддержка `Annotated` была добавлена в FastAPI начиная с версии 0.95.0 (и с этой версии рекомендуется использовать этот подход). |
|||
|
|||
Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`. |
|||
|
|||
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`. |
|||
|
|||
## Определите метаданные |
|||
|
|||
Вы можете указать все те же параметры, что и для `Query`. |
|||
|
|||
Например, чтобы указать значение метаданных `title` для path-параметра `item_id`, вы можете написать: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! note "Примечание" |
|||
Path-параметр всегда является обязательным, поскольку он составляет часть пути. |
|||
|
|||
Поэтому следует объявить его с помощью `...`, чтобы обозначить, что этот параметр обязательный. |
|||
|
|||
Тем не менее, даже если вы объявите его как `None` или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным. |
|||
|
|||
## Задайте нужный вам порядок параметров |
|||
|
|||
!!! tip "Подсказка" |
|||
Это не имеет большого значения, если вы используете `Annotated`. |
|||
|
|||
Допустим, вы хотите объявить query-параметр `q` как обязательный параметр типа `str`. |
|||
|
|||
И если вам больше ничего не нужно указывать для этого параметра, то нет необходимости использовать `Query`. |
|||
|
|||
Но вам по-прежнему нужно использовать `Path` для path-параметра `item_id`. И если по какой-либо причине вы не хотите использовать `Annotated`, то могут возникнуть небольшие сложности. |
|||
|
|||
Если вы поместите параметр со значением по умолчанию перед другим параметром, у которого нет значения по умолчанию, то Python укажет на ошибку. |
|||
|
|||
Но вы можете изменить порядок параметров, чтобы параметр без значения по умолчанию (query-параметр `q`) шёл первым. |
|||
|
|||
Это не имеет значения для **FastAPI**. Он распознает параметры по их названиям, типам и значениям по умолчанию (`Query`, `Path`, и т.д.), ему не важен их порядок. |
|||
|
|||
Поэтому вы можете определить функцию так: |
|||
|
|||
=== "Python 3.6 без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial002.py!} |
|||
``` |
|||
|
|||
Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете `Query()` или `Path()` в качестве значения по умолчанию для параметра функции. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial002_an.py!} |
|||
``` |
|||
|
|||
## Задайте нужный вам порядок параметров, полезные приёмы |
|||
|
|||
!!! tip "Подсказка" |
|||
Это не имеет большого значения, если вы используете `Annotated`. |
|||
|
|||
Здесь описан **небольшой приём**, который может оказаться удобным, хотя часто он вам не понадобится. |
|||
|
|||
Если вы хотите: |
|||
|
|||
* объявить query-параметр `q` без `Query` и без значения по умолчанию |
|||
* объявить path-параметр `item_id` с помощью `Path` |
|||
* указать их в другом порядке |
|||
* не использовать `Annotated` |
|||
|
|||
...то вы можете использовать специальную возможность синтаксиса Python. |
|||
|
|||
Передайте `*` в качестве первого параметра функции. |
|||
|
|||
Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>, даже если у них нет значений по умолчанию. |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} |
|||
``` |
|||
|
|||
### Лучше с `Annotated` |
|||
|
|||
Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, то у вас не возникнет подобной проблемы и вам не придётся использовать `*`. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial003_an.py!} |
|||
``` |
|||
|
|||
## Валидация числовых данных: больше или равно |
|||
|
|||
С помощью `Query` и `Path` (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных. |
|||
|
|||
В этом примере при указании `ge=1`, параметр `item_id` должен быть больше или равен `1` ("`g`reater than or `e`qual"). |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial004_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial004.py!} |
|||
``` |
|||
|
|||
## Валидация числовых данных: больше и меньше или равно |
|||
|
|||
То же самое применимо к: |
|||
|
|||
* `gt`: больше (`g`reater `t`han) |
|||
* `le`: меньше или равно (`l`ess than or `e`qual) |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial005_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial005.py!} |
|||
``` |
|||
|
|||
## Валидация числовых данных: числа с плавающей точкой, больше и меньше |
|||
|
|||
Валидация также применима к значениям типа `float`. |
|||
|
|||
В этом случае становится важной возможность добавить ограничение <abbr title="greater than"><code>gt</code></abbr>, вместо <abbr title="greater than or equal"><code>ge</code></abbr>, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`. |
|||
|
|||
Таким образом, `0.5` будет корректным значением. А `0.0` или `0` — нет. |
|||
|
|||
То же самое справедливо и для <abbr title="less than"><code>lt</code></abbr>. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="13" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="12" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial006_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ без Annotated" |
|||
|
|||
!!! tip "Подсказка" |
|||
Рекомендуется использовать версию с `Annotated` если возможно. |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/path_params_numeric_validations/tutorial006.py!} |
|||
``` |
|||
|
|||
## Резюме |
|||
|
|||
С помощью `Query`, `Path` (и других классов, которые мы пока не затронули) вы можете добавлять метаданные и строковую валидацию тем же способом, как и в главе [Query-параметры и валидация строк](query-params-str-validations.md){.internal-link target=_blank}. |
|||
|
|||
А также вы можете добавить валидацию числовых данных: |
|||
|
|||
* `gt`: больше (`g`reater `t`han) |
|||
* `ge`: больше или равно (`g`reater than or `e`qual) |
|||
* `lt`: меньше (`l`ess `t`han) |
|||
* `le`: меньше или равно (`l`ess than or `e`qual) |
|||
|
|||
!!! info "Информация" |
|||
`Query`, `Path` и другие классы, которые мы разберём позже, являются наследниками общего класса `Param`. |
|||
|
|||
Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее. |
|||
|
|||
!!! note "Технические детали" |
|||
`Query`, `Path` и другие "классы", которые вы импортируете из `fastapi`, на самом деле являются функциями, которые при вызове возвращают экземпляры одноимённых классов. |
|||
|
|||
Объект `Query`, который вы импортируете, является функцией. И при вызове она возвращает экземпляр одноимённого класса `Query`. |
|||
|
|||
Использование функций (вместо использования классов напрямую) нужно для того, чтобы ваш редактор не подсвечивал ошибки, связанные с их типами. |
|||
|
|||
Таким образом вы можете использовать привычный вам редактор и инструменты разработки, не добавляя дополнительных конфигураций для игнорирования подобных ошибок. |
@ -0,0 +1,251 @@ |
|||
# Path-параметры |
|||
|
|||
Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python: |
|||
|
|||
```Python hl_lines="6-7" |
|||
{!../../../docs_src/path_params/tutorial001.py!} |
|||
``` |
|||
|
|||
Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`. |
|||
|
|||
Если запустите этот пример и перейдёте по адресу: <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то увидите ответ: |
|||
|
|||
```JSON |
|||
{"item_id":"foo"} |
|||
``` |
|||
|
|||
## Параметры пути с типами |
|||
|
|||
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python. |
|||
|
|||
```Python hl_lines="7" |
|||
{!../../../docs_src/path_params/tutorial002.py!} |
|||
``` |
|||
|
|||
Здесь, `item_id` объявлен типом `int`. |
|||
|
|||
!!! check "Заметка" |
|||
Это обеспечит поддержку редактора внутри функции (проверка ошибок, автодополнение и т.п.). |
|||
|
|||
## <abbr title="Или сериализация, парсинг">Преобразование</abbr> данных |
|||
|
|||
Если запустите этот пример и перейдёте по адресу: <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, то увидите ответ: |
|||
|
|||
```JSON |
|||
{"item_id":3} |
|||
``` |
|||
|
|||
!!! check "Заметка" |
|||
Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`. |
|||
|
|||
Используя определения типов, **FastAPI** выполняет автоматический <abbr title="преобразование строк из HTTP-запроса в типы данных Python">"парсинг"</abbr> запросов. |
|||
|
|||
## <abbr title="Или валидация">Проверка</abbr> данных |
|||
|
|||
Если откроете браузер по адресу <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то увидите интересную HTTP-ошибку: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"path", |
|||
"item_id" |
|||
], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`. |
|||
|
|||
Та же ошибка возникнет, если вместо `int` передать `float` , например: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a> |
|||
|
|||
!!! check "Заметка" |
|||
**FastAPI** обеспечивает проверку типов, используя всё те же определения типов. |
|||
|
|||
Обратите внимание, что в тексте ошибки явно указано место не прошедшее проверку. |
|||
|
|||
Это очень полезно при разработке и отладке кода, который взаимодействует с API. |
|||
|
|||
## Документация |
|||
|
|||
И теперь, когда откроете браузер по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, то увидите вот такую автоматически сгенерированную документацию API: |
|||
|
|||
<img src="/img/tutorial/path-params/image01.png"> |
|||
|
|||
!!! check "Заметка" |
|||
Ещё раз, просто используя определения типов, **FastAPI** обеспечивает автоматическую интерактивную документацию (с интеграцией Swagger UI). |
|||
|
|||
Обратите внимание, что параметр пути объявлен целочисленным. |
|||
|
|||
## Преимущества стандартизации, альтернативная документация |
|||
|
|||
Поскольку сгенерированная схема соответствует стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a>, её можно использовать со множеством совместимых инструментов. |
|||
|
|||
Именно поэтому, FastAPI сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу: <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>. |
|||
|
|||
<img src="/img/tutorial/path-params/image02.png"> |
|||
|
|||
По той же причине, есть множество совместимых инструментов, включая инструменты генерации кода для многих языков. |
|||
|
|||
## Pydantic |
|||
|
|||
Вся проверка данных выполняется под капотом с помощью <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>. Поэтому вы можете быть уверены в качестве обработки данных. |
|||
|
|||
Вы можете использовать в аннотациях как простые типы данных, вроде `str`, `float`, `bool`, так и более сложные типы. |
|||
|
|||
Некоторые из них рассматриваются в следующих главах данного руководства. |
|||
|
|||
## Порядок имеет значение |
|||
|
|||
При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным. |
|||
|
|||
Например, `/users/me`. Предположим, что это путь для получения данных о текущем пользователе. |
|||
|
|||
У вас также может быть путь `/users/{user_id}`, чтобы получить данные о конкретном пользователе по его ID. |
|||
|
|||
Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`: |
|||
|
|||
|
|||
```Python hl_lines="6 11" |
|||
{!../../../docs_src/path_params/tutorial003.py!} |
|||
``` |
|||
|
|||
Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`. |
|||
|
|||
Аналогично, вы не можете переопределить операцию с путем: |
|||
|
|||
```Python hl_lines="6 11" |
|||
{!../../../docs_src/path_params/tutorial003b.py!} |
|||
``` |
|||
|
|||
Первый будет выполняться всегда, так как путь совпадает первым. |
|||
|
|||
## Предопределенные значения |
|||
|
|||
Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление <abbr title="Enumeration">`Enum`</abbr> Python. |
|||
|
|||
### Создание класса `Enum` |
|||
|
|||
Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`. |
|||
|
|||
Мы наследуемся от `str`, чтобы документация API могла понять, что значения должны быть типа `string` и отображалась правильно. |
|||
|
|||
Затем создайте атрибуты класса с фиксированными допустимыми значениями: |
|||
|
|||
```Python hl_lines="1 6-9" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
!!! info "Дополнительная информация" |
|||
<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">Перечисления (enum) доступны в Python</a> начиная с версии 3.4. |
|||
|
|||
!!! tip "Подсказка" |
|||
Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия <abbr title="Технически, это архитектуры моделей глубокого обучения">моделей</abbr> машинного обучения. |
|||
|
|||
### Определение *параметра пути* |
|||
|
|||
Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее: |
|||
|
|||
```Python hl_lines="16" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
### Проверьте документацию |
|||
|
|||
Поскольку доступные значения *параметра пути* определены заранее, интерактивная документация может наглядно их отображать: |
|||
|
|||
<img src="/img/tutorial/path-params/image03.png"> |
|||
|
|||
### Работа с *перечислениями* в Python |
|||
|
|||
Значение *параметра пути* будет *элементом перечисления*. |
|||
|
|||
#### Сравнение *элементов перечисления* |
|||
|
|||
Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`: |
|||
|
|||
```Python hl_lines="17" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
#### Получение *значения перечисления* |
|||
|
|||
Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`: |
|||
|
|||
```Python hl_lines="20" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
|
|||
!!! tip "Подсказка" |
|||
Значение `"lenet"` также можно получить с помощью `ModelName.lenet.value`. |
|||
|
|||
#### Возврат *элементов перечисления* |
|||
|
|||
Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело JSON (например в `dict`). |
|||
|
|||
Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту: |
|||
|
|||
```Python hl_lines="18 21 23" |
|||
{!../../../docs_src/path_params/tutorial005.py!} |
|||
``` |
|||
Вы отправите клиенту такой JSON-ответ: |
|||
|
|||
```JSON |
|||
{ |
|||
"model_name": "alexnet", |
|||
"message": "Deep Learning FTW!" |
|||
} |
|||
``` |
|||
|
|||
## Path-параметры, содержащие пути |
|||
|
|||
Предположим, что есть *операция пути* с путем `/files/{file_path}`. |
|||
|
|||
Но вам нужно, чтобы `file_path` сам содержал *путь*, например, `home/johndoe/myfile.txt`. |
|||
|
|||
Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`. |
|||
|
|||
### Поддержка OpenAPI |
|||
|
|||
OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать. |
|||
|
|||
Тем не менее это можно сделать в **FastAPI**, используя один из внутренних инструментов Starlette. |
|||
|
|||
Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь. |
|||
|
|||
### Конвертер пути |
|||
|
|||
Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде: |
|||
|
|||
``` |
|||
/files/{file_path:path} |
|||
``` |
|||
|
|||
В этом случае `file_path` - это имя параметра, а часть `:path`, указывает, что параметр должен соответствовать любому *пути*. |
|||
|
|||
Можете использовать так: |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/path_params/tutorial004.py!} |
|||
``` |
|||
|
|||
!!! tip "Подсказка" |
|||
Возможно, вам понадобится, чтобы параметр содержал `/home/johndoe/myfile.txt` с ведущим слэшем (`/`). |
|||
|
|||
В этом случае URL будет таким: `/files//home/johndoe/myfile.txt`, с двойным слэшем (`//`) между `files` и `home`. |
|||
|
|||
## Резюме |
|||
Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете: |
|||
|
|||
* Поддержку редактора (проверку ошибок, автозаполнение и т.п.) |
|||
* "<abbr title="преобразование строк из HTTP-запроса в типы данных Python">Парсинг</abbr>" данных |
|||
* Валидацию данных |
|||
* Автоматическую документацию API с указанием типов параметров. |
|||
|
|||
И объявлять типы достаточно один раз. |
|||
|
|||
Это, вероятно, является главным заметным преимуществом **FastAPI** по сравнению с альтернативными фреймворками (кроме <abbr title="не считая оптимизаций">сырой</abbr> производительности). |
@ -0,0 +1,225 @@ |
|||
# Query-параметры |
|||
|
|||
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры. |
|||
|
|||
```Python hl_lines="9" |
|||
{!../../../docs_src/query_params/tutorial001.py!} |
|||
``` |
|||
|
|||
Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`. |
|||
|
|||
Например, в этом URL-адресе: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=0&limit=10 |
|||
``` |
|||
|
|||
...параметры запроса такие: |
|||
|
|||
* `skip`: со значением `0` |
|||
* `limit`: со значением `10` |
|||
|
|||
Будучи частью URL-адреса, они "по умолчанию" являются строками. |
|||
|
|||
Но когда вы объявляете их с использованием аннотаций (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему. |
|||
|
|||
Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам: |
|||
|
|||
* Поддержка от редактора кода (очевидно) |
|||
* <abbr title="преобразование строки, полученной из HTTP запроса в Python данные">"Парсинг"</abbr> данных |
|||
* Проверка на соответствие данных (Валидация) |
|||
* Автоматическая документация |
|||
|
|||
## Значения по умолчанию |
|||
|
|||
Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию. |
|||
|
|||
В примере выше значения по умолчанию равны `skip=0` и `limit=10`. |
|||
|
|||
Таким образом, результат перехода по URL-адресу: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/ |
|||
``` |
|||
|
|||
будет таким же, как если перейти используя параметры по умолчанию: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=0&limit=10 |
|||
``` |
|||
|
|||
Но если вы введёте, например: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=20 |
|||
``` |
|||
|
|||
Значения параметров в вашей функции будут: |
|||
|
|||
* `skip=20`: потому что вы установили это в URL-адресе |
|||
* `limit=10`: т.к это было значение по умолчанию |
|||
|
|||
## Необязательные параметры |
|||
|
|||
Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/query_params/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/query_params/tutorial002.py!} |
|||
``` |
|||
|
|||
В этом случае, параметр `q` будет не обязательным и будет иметь значение `None` по умолчанию. |
|||
|
|||
!!! Важно |
|||
Также обратите внимание, что **FastAPI** достаточно умён чтобы заметить, что параметр `item_id` является path-параметром, а `q` нет, поэтому, это параметр запроса. |
|||
|
|||
## Преобразование типа параметра запроса |
|||
|
|||
Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="7" |
|||
{!> ../../../docs_src/query_params/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/query_params/tutorial003.py!} |
|||
``` |
|||
|
|||
В этом случае, если вы сделаете запрос: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=1 |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=True |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=true |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=on |
|||
``` |
|||
|
|||
или |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=yes |
|||
``` |
|||
|
|||
или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`. |
|||
|
|||
|
|||
## Смешивание query-параметров и path-параметров |
|||
|
|||
Вы можете объявлять несколько query-параметров и path-параметров одновременно,**FastAPI** сам разберётся, что чем является. |
|||
|
|||
И вы не обязаны объявлять их в каком-либо определенном порядке. |
|||
|
|||
Они будут обнаружены по именам: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="6 8" |
|||
{!> ../../../docs_src/query_params/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="8 10" |
|||
{!> ../../../docs_src/query_params/tutorial004.py!} |
|||
``` |
|||
|
|||
## Обязательные query-параметры |
|||
|
|||
Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то это значение не является обязательным. |
|||
|
|||
Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`. |
|||
|
|||
Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию: |
|||
|
|||
```Python hl_lines="6-7" |
|||
{!../../../docs_src/query_params/tutorial005.py!} |
|||
``` |
|||
|
|||
Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`. |
|||
|
|||
Если вы откроете в браузере URL-адрес, например: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo-item |
|||
``` |
|||
|
|||
...без добавления обязательного параметра `needy`, вы увидите подобного рода ошибку: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"query", |
|||
"needy" |
|||
], |
|||
"msg": "field required", |
|||
"type": "value_error.missing" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
Поскольку `needy` является обязательным параметром, вам необходимо указать его в URL-адресе: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy |
|||
``` |
|||
|
|||
...это будет работать: |
|||
|
|||
```JSON |
|||
{ |
|||
"item_id": "foo-item", |
|||
"needy": "sooooneedy" |
|||
} |
|||
``` |
|||
|
|||
Конечно, вы можете определить некоторые параметры как обязательные, некоторые - со значением по умполчанию, а некоторые - полностью необязательные: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/query_params/tutorial006_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/query_params/tutorial006.py!} |
|||
``` |
|||
|
|||
В этом примере, у нас есть 3 параметра запроса: |
|||
|
|||
* `needy`, обязательный `str`. |
|||
* `skip`, типа `int` и со значением по умолчанию `0`. |
|||
* `limit`, необязательный `int`. |
|||
|
|||
!!! подсказка |
|||
Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}. |
@ -0,0 +1,189 @@ |
|||
# Объявление примера запроса данных |
|||
|
|||
Вы можете объявлять примеры данных, которые ваше приложение может получать. |
|||
|
|||
Вот несколько способов, как это можно сделать. |
|||
|
|||
## Pydantic `schema_extra` |
|||
|
|||
Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в <a href="https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic документации: Настройка схемы</a>: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="13-21" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="15-23" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial001.py!} |
|||
``` |
|||
|
|||
Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API. |
|||
|
|||
!!! tip Подсказка |
|||
Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации. |
|||
|
|||
Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д. |
|||
|
|||
## Дополнительные аргументы поля `Field` |
|||
|
|||
При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию. |
|||
|
|||
Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="2 8-11" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="4 10-13" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial002.py!} |
|||
``` |
|||
|
|||
!!! warning Внимание |
|||
Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации. |
|||
|
|||
## Использование `example` и `examples` в OpenAPI |
|||
|
|||
При использовании любой из этих функций: |
|||
|
|||
* `Path()` |
|||
* `Query()` |
|||
* `Header()` |
|||
* `Cookie()` |
|||
* `Body()` |
|||
* `Form()` |
|||
* `File()` |
|||
|
|||
вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**. |
|||
|
|||
### Параметр `Body` с аргументом `example` |
|||
|
|||
Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="22-27" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="22-27" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="23-28" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="18-23" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="20-25" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial003.py!} |
|||
``` |
|||
|
|||
### Аргумент "example" в UI документации |
|||
|
|||
С любым из вышеуказанных методов это будет выглядеть так в `/docs`: |
|||
|
|||
<img src="/img/tutorial/body-fields/image01.png"> |
|||
|
|||
### `Body` с аргументом `examples` |
|||
|
|||
В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**. |
|||
|
|||
Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией. |
|||
|
|||
Каждый конкретный пример типа `dict` в аргументе `examples` может содержать: |
|||
|
|||
* `summary`: Краткое описание для примера. |
|||
* `description`: Полное описание, которое может содержать текст в формате Markdown. |
|||
* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`. |
|||
* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`. |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="23-49" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="23-49" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="24-50" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="19-45" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip Заметка |
|||
Рекомендуется использовать версию с `Annotated`, если это возможно. |
|||
|
|||
```Python hl_lines="21-47" |
|||
{!> ../../../docs_src/schema_extra_example/tutorial004.py!} |
|||
``` |
|||
|
|||
### Аргумент "examples" в UI документации |
|||
|
|||
С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так: |
|||
|
|||
<img src="/img/tutorial/body-fields/image02.png"> |
|||
|
|||
## Технические Детали |
|||
|
|||
!!! warning Внимание |
|||
Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**. |
|||
|
|||
Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить. |
|||
|
|||
Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic. |
|||
|
|||
И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации. |
|||
|
|||
Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`. |
|||
|
|||
Таким образом, OpenAPI 3.0.3 определяет своё собственное поле <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI). |
|||
|
|||
Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации. |
|||
|
|||
Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему). |
|||
|
|||
Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object" class="external-link" target="_blank">определение OpenAPI, к объекту `Parameter Object` (в спецификации)</a>. |
|||
|
|||
И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject" class="external-link" target="_blank"> определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации)</a>. |
|||
|
|||
С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы. |
@ -0,0 +1,40 @@ |
|||
# Статические Файлы |
|||
|
|||
Вы можете предоставлять статические файлы автоматически из директории, используя `StaticFiles`. |
|||
|
|||
## Использование `StaticFiles` |
|||
|
|||
* Импортируйте `StaticFiles`. |
|||
* "Примонтируйте" экземпляр `StaticFiles()` с указанием определенной директории. |
|||
|
|||
```Python hl_lines="2 6" |
|||
{!../../../docs_src/static_files/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! заметка "Технические детали" |
|||
Вы также можете использовать `from starlette.staticfiles import StaticFiles`. |
|||
|
|||
**FastAPI** предоставляет `starlette.staticfiles` под псевдонимом `fastapi.staticfiles`, просто для вашего удобства, как разработчика. Но на самом деле это берётся напрямую из библиотеки Starlette. |
|||
|
|||
### Что такое "Монтирование" |
|||
|
|||
"Монтирование" означает добавление полноценного "независимого" приложения в определенную директорию, которое затем обрабатывает все подпути. |
|||
|
|||
Это отличается от использования `APIRouter`, так как примонтированное приложение является полностью независимым. |
|||
OpenAPI и документация из вашего главного приложения не будет содержать ничего из примонтированного приложения, и т.д. |
|||
|
|||
Вы можете прочитать больше об этом в **Расширенном руководстве пользователя**. |
|||
|
|||
## Детали |
|||
|
|||
Первый параметр `"/static"` относится к подпути, по которому это "подприложение" будет "примонтировано". Таким образом, любой путь начинающийся со `"/static"` будет обработан этим приложением. |
|||
|
|||
Параметр `directory="static"` относится к имени директории, которая содержит ваши статические файлы. |
|||
|
|||
`name="static"` даёт имя маршруту, которое может быть использовано внутри **FastAPI**. |
|||
|
|||
Все эти параметры могут отличаться от "`static`", настройте их в соответствии с вашими нуждами и конкретными деталями вашего собственного приложения. |
|||
|
|||
## Больше информации |
|||
|
|||
Для получения дополнительной информации о деталях и настройках ознакомьтесь с <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">Документацией Starlette о статических файлах</a>. |
@ -0,0 +1,31 @@ |
|||
# 响应 - 更改状态码 |
|||
|
|||
你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。 |
|||
|
|||
但在某些情况下,你需要返回一个不同于默认值的状态码。 |
|||
|
|||
## 使用场景 |
|||
|
|||
例如,假设你想默认返回一个HTTP状态码为“OK”`200`。 |
|||
|
|||
但如果数据不存在,你想创建它,并返回一个HTTP状态码为“CREATED”`201`。 |
|||
|
|||
但你仍然希望能够使用`response_model`过滤和转换你返回的数据。 |
|||
|
|||
对于这些情况,你可以使用一个`Response`参数。 |
|||
|
|||
## 使用 `Response` 参数 |
|||
|
|||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。 |
|||
|
|||
然后你可以在这个*临时*响应对象中设置`status_code`。 |
|||
|
|||
```Python hl_lines="1 9 12" |
|||
{!../../../docs_src/response_change_status_code/tutorial001.py!} |
|||
``` |
|||
|
|||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 |
|||
|
|||
**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 |
|||
|
|||
你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。 |
@ -0,0 +1,39 @@ |
|||
# 响应头 |
|||
|
|||
## 使用 `Response` 参数 |
|||
|
|||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。 |
|||
|
|||
然后你可以在这个*临时*响应对象中设置头部。 |
|||
```Python hl_lines="1 7-8" |
|||
{!../../../docs_src/response_headers/tutorial002.py!} |
|||
``` |
|||
|
|||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 |
|||
|
|||
**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 |
|||
|
|||
你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。 |
|||
|
|||
## 直接返回 `Response` |
|||
|
|||
你也可以在直接返回`Response`时添加头部。 |
|||
|
|||
按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: |
|||
```Python hl_lines="10-12" |
|||
{!../../../docs_src/response_headers/tutorial001.py!} |
|||
``` |
|||
|
|||
|
|||
!!! 注意 "技术细节" |
|||
你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。 |
|||
|
|||
**FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。 |
|||
|
|||
由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。 |
|||
|
|||
## 自定义头部 |
|||
|
|||
请注意,可以使用'X-'前缀添加自定义专有头部。 |
|||
|
|||
但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在<a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette的CORS文档</a>中记录的`expose_headers`参数。 |
@ -0,0 +1,39 @@ |
|||
# 静态文件 |
|||
|
|||
您可以使用 `StaticFiles`从目录中自动提供静态文件。 |
|||
|
|||
## 使用`StaticFiles` |
|||
|
|||
* 导入`StaticFiles`。 |
|||
* "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。 |
|||
|
|||
```Python hl_lines="2 6" |
|||
{!../../../docs_src/static_files/tutorial001.py!} |
|||
``` |
|||
|
|||
!!! note "技术细节" |
|||
你也可以用 `from starlette.staticfiles import StaticFiles`。 |
|||
|
|||
**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。 |
|||
|
|||
### 什么是"挂载"(Mounting) |
|||
|
|||
"挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。 |
|||
|
|||
这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。 |
|||
|
|||
你可以在**高级用户指南**中了解更多。 |
|||
|
|||
## 细节 |
|||
|
|||
这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。 |
|||
|
|||
`directory="static"` 指向包含你的静态文件的目录名字。 |
|||
|
|||
`name="static"` 提供了一个能被**FastAPI**内部使用的名字。 |
|||
|
|||
所有这些参数可以不同于"`static`",根据你应用的需要和具体细节调整它们。 |
|||
|
|||
## 更多信息 |
|||
|
|||
更多细节和选择查阅 <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">Starlette's docs about Static Files</a>. |
@ -0,0 +1,8 @@ |
|||
-e . |
|||
mkdocs >=1.1.2,<2.0.0 |
|||
mkdocs-material >=8.1.4,<9.0.0 |
|||
mdx-include >=1.4.1,<2.0.0 |
|||
mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 |
|||
typer-cli >=0.0.13,<0.0.14 |
|||
typer[all] >=0.6.1,<0.8.0 |
|||
pyyaml >=5.3.1,<7.0.0 |
@ -0,0 +1,25 @@ |
|||
-e . |
|||
pytest >=7.1.3,<8.0.0 |
|||
coverage[toml] >= 6.5.0,< 8.0 |
|||
mypy ==1.3.0 |
|||
ruff ==0.0.272 |
|||
black == 23.3.0 |
|||
httpx >=0.23.0,<0.24.0 |
|||
email_validator >=1.1.1,<2.0.0 |
|||
# TODO: once removing databases from tutorial, upgrade SQLAlchemy |
|||
# probably when including SQLModel |
|||
sqlalchemy >=1.3.18,<1.4.43 |
|||
peewee >=3.13.3,<4.0.0 |
|||
databases[sqlite] >=0.3.2,<0.7.0 |
|||
orjson >=3.2.1,<4.0.0 |
|||
ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0 |
|||
python-multipart >=0.0.5,<0.0.7 |
|||
flask >=1.1.2,<3.0.0 |
|||
anyio[trio] >=3.2.1,<4.0.0 |
|||
python-jose[cryptography] >=3.3.0,<4.0.0 |
|||
pyyaml >=5.3.1,<7.0.0 |
|||
passlib[bcrypt] >=1.7.2,<2.0.0 |
|||
|
|||
# types |
|||
types-ujson ==5.7.0.1 |
|||
types-orjson ==3.6.2 |
@ -0,0 +1,5 @@ |
|||
-e .[all] |
|||
-r requirements-tests.txt |
|||
-r requirements-docs.txt |
|||
uvicorn[standard] >=0.12.0,<0.21.0 |
|||
pre-commit >=2.17.0,<3.0.0 |
@ -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 |
@ -0,0 +1,73 @@ |
|||
import json |
|||
from typing import List |
|||
|
|||
from fastapi import APIRouter, Depends, FastAPI, WebSocket |
|||
from fastapi.testclient import TestClient |
|||
from typing_extensions import Annotated |
|||
|
|||
|
|||
def dependency_list() -> List[str]: |
|||
return [] |
|||
|
|||
|
|||
DepList = Annotated[List[str], Depends(dependency_list)] |
|||
|
|||
|
|||
def create_dependency(name: str): |
|||
def fun(deps: DepList): |
|||
deps.append(name) |
|||
|
|||
return Depends(fun) |
|||
|
|||
|
|||
router = APIRouter(dependencies=[create_dependency("router")]) |
|||
prefix_router = APIRouter(dependencies=[create_dependency("prefix_router")]) |
|||
app = FastAPI(dependencies=[create_dependency("app")]) |
|||
|
|||
|
|||
@app.websocket("/", dependencies=[create_dependency("index")]) |
|||
async def index(websocket: WebSocket, deps: DepList): |
|||
await websocket.accept() |
|||
await websocket.send_text(json.dumps(deps)) |
|||
await websocket.close() |
|||
|
|||
|
|||
@router.websocket("/router", dependencies=[create_dependency("routerindex")]) |
|||
async def routerindex(websocket: WebSocket, deps: DepList): |
|||
await websocket.accept() |
|||
await websocket.send_text(json.dumps(deps)) |
|||
await websocket.close() |
|||
|
|||
|
|||
@prefix_router.websocket("/", dependencies=[create_dependency("routerprefixindex")]) |
|||
async def routerprefixindex(websocket: WebSocket, deps: DepList): |
|||
await websocket.accept() |
|||
await websocket.send_text(json.dumps(deps)) |
|||
await websocket.close() |
|||
|
|||
|
|||
app.include_router(router, dependencies=[create_dependency("router2")]) |
|||
app.include_router( |
|||
prefix_router, prefix="/prefix", dependencies=[create_dependency("prefix_router2")] |
|||
) |
|||
|
|||
|
|||
def test_index(): |
|||
client = TestClient(app) |
|||
with client.websocket_connect("/") as websocket: |
|||
data = json.loads(websocket.receive_text()) |
|||
assert data == ["app", "index"] |
|||
|
|||
|
|||
def test_routerindex(): |
|||
client = TestClient(app) |
|||
with client.websocket_connect("/router") as websocket: |
|||
data = json.loads(websocket.receive_text()) |
|||
assert data == ["app", "router2", "router", "routerindex"] |
|||
|
|||
|
|||
def test_routerprefixindex(): |
|||
client = TestClient(app) |
|||
with client.websocket_connect("/prefix/") as websocket: |
|||
data = json.loads(websocket.receive_text()) |
|||
assert data == ["app", "prefix_router2", "prefix_router", "routerprefixindex"] |
Loading…
Reference in new issue