80 changed files with 3770 additions and 529 deletions
@ -20,7 +20,7 @@ jobs: |
|||||
steps: |
steps: |
||||
- uses: tiangolo/[email protected] |
- uses: tiangolo/[email protected] |
||||
with: |
with: |
||||
token: ${{ secrets.GITHUB_TOKEN }} |
token: ${{ secrets.FASTAPI_ISSUE_MANAGER }} |
||||
config: > |
config: > |
||||
{ |
{ |
||||
"answered": { |
"answered": { |
||||
|
@ -18,7 +18,7 @@ jobs: |
|||||
- name: Download Artifact Docs |
- name: Download Artifact Docs |
||||
uses: dawidd6/[email protected] |
uses: dawidd6/[email protected] |
||||
with: |
with: |
||||
github_token: ${{ secrets.GITHUB_TOKEN }} |
github_token: ${{ secrets.FASTAPI_PREVIEW_DOCS_DOWNLOAD_ARTIFACTS }} |
||||
workflow: build-docs.yml |
workflow: build-docs.yml |
||||
run_id: ${{ github.event.workflow_run.id }} |
run_id: ${{ github.event.workflow_run.id }} |
||||
name: docs-zip |
name: docs-zip |
||||
@ -34,7 +34,7 @@ jobs: |
|||||
with: |
with: |
||||
publish-dir: './site' |
publish-dir: './site' |
||||
production-deploy: false |
production-deploy: false |
||||
github-token: ${{ secrets.GITHUB_TOKEN }} |
github-token: ${{ secrets.FASTAPI_PREVIEW_DOCS_NETLIFY }} |
||||
enable-commit-comment: false |
enable-commit-comment: false |
||||
env: |
env: |
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} |
||||
@ -42,5 +42,5 @@ jobs: |
|||||
- name: Comment Deploy |
- name: Comment Deploy |
||||
uses: ./.github/actions/comment-docs-preview-in-pr |
uses: ./.github/actions/comment-docs-preview-in-pr |
||||
with: |
with: |
||||
token: ${{ secrets.GITHUB_TOKEN }} |
token: ${{ secrets.FASTAPI_PREVIEW_DOCS_COMMENT_DEPLOY }} |
||||
deploy_url: "${{ steps.netlify.outputs.deploy-url }}" |
deploy_url: "${{ steps.netlify.outputs.deploy-url }}" |
||||
|
@ -22,6 +22,7 @@ jobs: |
|||||
|
|
||||
- uses: dawidd6/[email protected] |
- uses: dawidd6/[email protected] |
||||
with: |
with: |
||||
|
github_token: ${{ secrets.FASTAPI_SMOKESHOW_DOWNLOAD_ARTIFACTS }} |
||||
workflow: test.yml |
workflow: test.yml |
||||
commit: ${{ github.event.workflow_run.head_sha }} |
commit: ${{ github.event.workflow_run.head_sha }} |
||||
|
|
||||
@ -30,6 +31,6 @@ jobs: |
|||||
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} |
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} |
||||
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100 |
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100 |
||||
SMOKESHOW_GITHUB_CONTEXT: coverage |
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_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} |
||||
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} |
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