FastAPI — это современный быстрый (высокопроизводительный) веб-фреймворк для создания API на Python 3.6+, основанный на стандартных подсказках типов Python.
Себастьян Рамирес, создатель FastAPI
FastAPI (https://fastapi.tiangolo.com) был представлен в 2018 году Себастьяном Рамиресом (https://tiangolo.com). Во многих смыслах это более современный, чем большинство веб-фреймворков Python, и он использует добавленный в Python 3 за последние несколько лет функционал. Эта глава представляет собой краткий обзор основных возможностей FastAPI с акцентом на первом из интересующих вас вопросов: как обрабатывать веб-запросы и ответы?
Как и любой другой веб-фреймворк, FastAPI помогает создавать веб-приложения. Каждый фреймворк призван облегчить выполнение некоторых операций за счет особенностей, допущений и настроек по умолчанию. Как следует из названия, FastAPI предназначен для разработки веб-интерфейсов API, хотя можно использовать его и для традиционных приложений с веб-контентом.
На сайте FastAPI заявлены такие его преимущества:
• высокая производительность — в некоторых случаях он работает так же быстро, как Node.js и Go, что необычно для фреймворков Python;
• ускоренный процесс разработки — никаких острых углов или странностей;
• повышение качества кода — подсказки типов и модели помогают уменьшить количество ошибок;
• автоматически генерируемая документация и тестовые страницы — это гораздо проще, чем вручную редактировать описания OpenAPI.
В FastAPI используются:
• подсказки типов Python;
• пакет Starlette для веб-машин, включая поддержку асинхронности;
• пакет Pydantic для определения и проверки данных;
• специальная интеграция, позволяющая использовать и расширять возможности других фреймворков.
Такое сочетание создает приятную среду для разработки веб-приложений, особенно RESTful-веб-сервисов.
Напишем маленькое приложение FastAPI — веб-сервис с одной конечной точкой. Пока что будем работать на так называемом веб-уровне, где обрабатываются только веб-запросы и ответы. Сначала установите основные необходимые пакеты Python:
• фреймворк FastAPI (https://fastapi.tiangolo.com) — pip install fastapi;
• веб-сервер Uvicorn (https://www.uvicorn.org) — pip install uvicorn;
• текстовый веб-клиент HTTPie (https://httpie.io) — pip install httpie;
• пакет синхронного веб-клиента Requests (https://requests.readthedocs.io) — pip install requests;
• пакет синхронного/асинхронного веб-клиента HTTPX (https://www.python-httpx.org) — pip install httpx.
Хотя curl (https://curl.se) — это самый известный текстовый веб-клиент, я считаю, что HTTPie проще в использовании. Кроме того, по умолчанию он задействует кодирование и декодирование JSON, что лучше подходит для FastAPI. Далее в этой главе вы увидите снимок экрана, содержащий синтаксис командной строки curl, необходимой для доступа к определенной конечной точке.
Станем тенью веб-разработчика-интроверта в примере 3.1 и сохраним этот код в файле hello.py.
Пример 3.1. Робкая конечная точка (hello.py)
from fastapi import FastAPI
app = FastAPI()
@app.get("/hi")
def greet():
return "Hello? World?"
Нужно обратить внимание на следующие моменты.
• app — это объект FastAPI верхнего уровня, представляющий все веб-приложение.
• @app.get("/hi") — это декоратор пути. Он сообщает FastAPI следующее:
• запрос к URL-адресу "/hi" на этом сервере должен быть направлен на следующую функцию;
• этот декоратор применяется только к HTTP-глаголу GET. Также можно ответить на URL-запросы "/hi", отправленные другими HTTP-глаголами (PUT, POST и т.д.), каждый с отдельной функцией.
• def greet() представляет собой функцию пути — основную точку контакта с HTTP-запросами и ответами. В этом примере у нее нет аргументов, но следующие разделы показывают, что в недрах FastAPI скрывается гораздо больше.
Следующим шагом будет запуск этого веб-приложения на веб-сервере. Сам FastAPI не включает в себя веб-сервер, но рекомендует использовать Uvicorn. Запустить Uvicorn и веб-приложение FastAPI можно двумя способами — извне или изнутри.
Чтобы запустить Uvicorn извне, через командную строку, смотрите пример 3.2.
Пример 3.2. Запуск Uvicorn с помощью командной строки
$ uvicorn hello:app --reload
Слово hello дает ссылку на файл hello.py, а слово app — это имя переменной FastAPI в этом файле.
Кроме того, вы можете запустить Uvicorn внутри самого приложения, как показано в примере 3.3.
Пример 3.3. Запуск Uvicorn внутри приложения
from fastapi import FastAPI
app = FastAPI()
@app.get("/hi")
def greet():
return "Hello? World?"
if __name__ == "__main__":
import uvicorn
uvicorn.run("hello:app", reload=True)
В любом случае параметр reload указывает Uvicorn перезапустить веб-сервер, если содержимое файла hello.py изменится. В этой главе мы будем часто использовать автоматическую перезагрузку.
По умолчанию будет задействоваться порт 8000 вашей машины под названием localhost. И у внешнего, и у внутреннего методов есть аргументы host и port, но, возможно, вы предпочитаете что-то другое.
Теперь у сервера есть единственная конечная точка (/hi), и он готов к приему запросов. Протестируем его с помощью нескольких веб-клиентов.
• В браузере введите URL-адрес в строку вверху окна.
• Для текстового веб-клиента HTTPie введите показанную ниже, в примере 3.7, команду (символ $ означает командную строку, используемую в вашей системной оболочке).
• Для запросов или HTTPX применяйте Python в интерактивном режиме и набирайте текст после >>>.
Как написано в предисловии, то, что вы вводите, выделено полужирным моноширинным шрифтом, а вывод представлен в формате обычного моноширинного шрифта.
В примерах 3.4–3.7 показаны различные способы тестирования новой конечной точки /hi веб-сервера.
Пример 3.4. Проверка /hi в браузере
http://localhost:8000/hi
Пример 3.5. Проверка /hi с помощью Requests
>>> import requests
>>> r = requests.get("http://localhost:8000/hi")
>>> r.json()
'Hello? World?'
Пример 3.6. Проверка /hi с помощью HTTPX, практически идентичная работе с Requests
>>> import httpx
>>> r = httpx.get("http://localhost:8000/hi")
>>> r.json()
'Hello? World?'
Неважно, используете ли вы Requests или HTTPX для тестирования маршрутов FastAPI. Но в главе 13 показаны случаи, когда HTTPX полезен при выполнении других асинхронных вызовов. Поэтому в остальных примерах в этой главе задействуются именно Requests.
Пример 3.7. Проверка /hi с помощью HTTPie
$ http localhost:8000/hi
HTTP/1.1 200 OK
content-length: 15
content-type: application/json
date: Thu, 30 Jun 2022 07:38:27 GMT
server: uvicorn
"Hello? World?"
В примере 3.8 используйте аргумент -b, чтобы пропустить заголовки ответа и вывести только тело запроса.
Пример 3.8. Проверка /hi с помощью HTTPie с выводом только тела ответа
$ http -b localhost:8000/hi
"Hello? World?"
Пример 3.9 позволяет получить полные заголовки запроса, а также ответ с помощью аргумента -v.
Пример 3.9. Проверка /hi с помощью HTTPie с получением всех данных
$ http -v localhost:8000/hi
GET /hi HTTP/1.1
Accept: /
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/3.2.1
HTTP/1.1 200 OK
content-length: 15
content-type: application/json
date: Thu, 30 Jun 2022 08:05:06 GMT
server: uvicorn
"Hello? World?"
Одни примеры, приводимые в книге, показывают стандартный вывод HTTPie (заголовки и тело ответа), а другие — только тело.
Пример 3.9 включает только один конкретный запрос: GET на URL /hi на сервер localhost, порт 8000.
Веб-запросы «бегают» по разным частям HTTP-запроса, а FastAPI позволяет получить к ним беспрепятственный доступ. В примере 3.10 показан HTTP-запрос из образца запроса в примере 3.9, отправленный командой http на веб-сервер.
Пример 3.10. HTTP-запрос
GET /hi HTTP/1.1
Accept: /
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/3.2.1
Этот запрос содержит:
• глагол-оператор (GET) и путь (/hi);
• все параметры запроса (текст после любого символа ?, в данном случае отсутствует);
• другие HTTP-заголовки;
• содержимое тела запроса (отсутствует).
FastAPI разложит их по удобным определениям:
• Header — HTTP-заголовки;
• Path — URL-адрес;
• Query — параметры запроса (после символа ? в конце URL);
• Body — тело HTTP-сообщения.
То, как FastAPI предоставляет данные из различных частей HTTP-запросов, — одна из его лучших особенностей и улучшение по сравнению с тем, как это делают большинство веб-фреймворков Python. Все необходимые аргументы можно объявить и предоставить непосредственно внутри функции пути, используя определения из предыдущего списка (Path, Query и т.д.), а также с помощью написанных вами функций. Для этого применяется техника, называемая внедрением зависимостей. Ее мы рассмотрим по ходу повествования и расширим описание в главе 6.
Сделаем предыдущее приложение более личным, добавив параметр who, адресующий важный вопрос «Hello?» кому-то. Попробуем разные способы передачи этого нового параметра:
• в пути URL;
• в качестве параметра запроса после символа ? в URL;
• в теле HTTP-сообщения;
• в HTTP-заголовке.
Отредактируйте файл hello.py в примере 3.11.
Пример 3.11. Возврат пути к приветствию
from fastapi import FastAPI
app = FastAPI()
@app.get("/hi/{who}")
def greet(who):
return f"Hello? {who}?"
Как только вы сохраните изменения в редакторе, Uvicorn должен перезапуститься. (В противном случае нам пришлось бы создавать файл hello2.py и так далее и каждый раз заново запускать Uvicorn.) Если вы допустили опечатку, продолжайте пытаться вводить код, пока не исправите ее, и Uvicorn не доставит вам хлопот.
Добавление слова {who} в URL-адрес (после выражения @app.get) приказывает FastAPI извлечь переменную под названием who в указанном местоположении в URL. Затем FastAPI присваивает ее аргументу who в следующей функции greet(). Это показывает координацию между декоратором пути и функцией пути.
Не следует здесь использовать f-строку Python для измененной строки URL ("/hi/{who}"). Фигурные скобки применяются самим FastAPI для сопоставления частей URL в качестве параметров пути.
В примерах 3.12–3.14 проверьте эту доработанную конечную точку с помощью различных методов, рассмотренных ранее.
Пример 3.12. Проверка /hi/Mom в браузере
localhost:8000/hi/Mom
Пример 3.13. Проверка /hi/Mom с помощью HTTPie
$ http localhost:8000/hi/Mom
HTTP/1.1 200 OK
content-length: 13
content-type: application/json
date: Thu, 30 Jun 2022 08:09:02 GMT
server: uvicorn
"Hello? Mom?"
Пример 3.14. Проверка /hi/Mom с помощью Requests
>>> import requests
>>> r = requests.get("http://localhost:8000/hi/Mom")
>>> r.json()
'Hello? Mom?'
Во всех случаях отправляемая как часть URL строка "Mom" передается в функцию пути greet() как переменная who и возвращается как часть ответа. Каждый раз ответом будет строка JSON "Hello? Mom?" (с одинарными или двойными кавычками в зависимости от того, какой тестовый клиент вы использовали).
Параметры запроса — это строки name=value после символа ? в URL-адресе, разделенные символами &. Отредактируйте файл hello.py в примере 3.15.
Пример 3.15. Возврат параметра запроса приветствия
from fastapi import FastAPI
app = FastAPI()
@app.get("/hi")
def greet(who):
return f"Hello? {who}?"
Функция конечной точки снова определяется как greet(who), но выражение {who} на этот раз отсутствует в URL-адресе в предыдущей строке декоратора, поэтому FastAPI предполагает, что слово who — это параметр запроса. Проверьте код в примерах 3.16 и 3.17.
Пример 3.16. Проверка примера 3.15 с помощью браузера
localhost:8000/hi?who=Mom
Пример 3.17. Проверка примера 3.15 с помощью HTTPie
$ http -b localhost:8000/hi?who=Mom
"Hello? Mom?"
В примере 3.18 можно вызвать HTTPie с аргументом параметра запроса (обратите внимание на оператор ==).
Пример 3.18. Проверка примера 3.15 с помощью HTTPie и параметров
$ http -b localhost:8000/hi who==Mom
"Hello? Mom?"
У вас может быть несколько таких аргументов для HTTPie, и их удобнее вводить через пробел.
В примерах 3.19 и 3.20 показаны те же альтернативы для веб-клиента Requests.
Пример 3.19. Проверка примера 3.15 с помощью Requests
>>> import requests
>>> r = requests.get("http://localhost:8000/hi?who=Mom")
>>> r.json()
'Hello? Mom?'
Пример 3.20. Проверка примера 3.15 с помощью Requests и параметров
>>> import requests
>>> params = {"who": "Mom"}
>>> r = requests.get("http://localhost:8000/hi", params=params)
>>> r.json()
'Hello? Mom?'
Во всех случаях вы предоставляете строку "Mom" новым способом, передаете ее в функцию пути и доводите до конечного ответа.
Можно предоставить конечной точке GET путь или параметры запроса, но не значения из тела запроса. В HTTP запрос GET должен быть идемпотентным. Идемпотентность — вычислительный термин, означающий «задай один и тот же вопрос — получи один и тот же ответ». HTTP-запрос GET должен только выполнять возврат данных. Тело запроса используется для отправки данных на сервер при создании (POST) или обновлении (PUT или PATCH). В главе 9 показан способ обойти эту проблему.
Итак, в примере 3.21 изменим конечную точку с GET на POST. (Технически мы ничего не создаем, так что метод POST не является кошерным, но если владыки RESTful подадут на нас в суд, то оцените крутое здание суда.)
Пример 3.21. Возврат тела приветствия
from fastapi import FastAPI, Body
app = FastAPI()
@app.post("/hi")
def greet(who:str = Body(embed=True)):
return f"Hello? {who}?"
Выражение Body(embed=True) требуется для того, чтобы сообщить FastAPI, что на этот раз мы получаем значение who из тела запроса в формате JSON. Часть выражения embed в скобках означает, что ответ должен выглядеть как {"who": "Mom"}, а не просто "Mom".
В примере 3.22 попробуйте протестировать HTTPie, используя аргумент -v для отображения сгенерированного тела запроса (обратите внимание на единственный параметр = для указания данных тела в формате JSON).
Пример 3.22. Проверка примера 3.21 с помощью HTTPie
$ http -v localhost:8000/hi who=Mom
POST /hi HTTP/1.1
Accept: application/json, /;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 14
Content-Type: application/json
Host: localhost:8000
User-Agent: HTTPie/3.2.1
{
"who": "Mom"
}
HTTP/1.1 200 OK
content-length: 13
content-type: application/json
date: Thu, 30 Jun 2022 08:37:00 GMT
server: uvicorn
"Hello? Mom?"
И наконец, проверьте пример 3.23 с помощью Requests, использующего свой аргумент json для передачи закодированных в формате JSON данных в теле запроса.
Пример 3.23. Проверка примера 3.21 с помощью Requests
>>> import requests
>>> r = requests.post("http://localhost:8000/hi", json={"who": "Mom"})
>>> r.json()
'Hello? Mom?'
Наконец, попробуем передать аргумент приветствия в качестве HTTP-заголовка в примере 3.24.
Пример 3.24. Возврат заголовка приветствия
from fastapi import FastAPI, Header
app = FastAPI()
@app.post("/hi")
def greet(who:str = Header()):
return f"Hello? {who}?"
Проверим это с помощью HTTPie в примере 3.25. Для определения HTTP-заголовка используется выражение name:value.
Пример 3.25. Проверка примера 3.24 с помощью HTTPie
$ http -v localhost:8000/hi who:Mom
GET /hi HTTP/1.1
Accept: */\*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/3.2.1
who: Mom
HTTP/1.1 200 OK
content-length: 13
content-type: application/json
date: Mon, 16 Jan 2023 05:14:46 GMT
server: uvicorn
"Hello? Mom?"
FastAPI переводит ключи HTTP-заголовков в нижний регистр и преобразует дефис (-) в нижнее подчеркивание (_). Поэтому вы можете вывести значение заголовка HTTP User-Agent, как показано в примерах 3.26 и 3.27.
Пример 3.26. Возврат заголовка User-Agent (hello.py)
from fastapi import FastAPI, Header
app = FastAPI()
@app.post("/agent")
def get_agent(user_agent:str = Header()):
return user_agent
Пример 3.27. Возврат заголовка User-Agent с помощью HTTPie
$ http -v localhost:8000/agent
GET /agent HTTP/1.1
Accept: */\*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/3.2.1
HTTP/1.1 200 OK
content-length: 14
content-type: application/json
date: Mon, 16 Jan 2023 05:21:35 GMT
server: uvicorn
"HTTPie/3.2.1"
В одной функции пути можно использовать более одного из этих методов. То есть вы можете получать данные из URL, параметров запроса, тела HTTP, HTTP-заголовков, cookie-файлов и т.д. Можете написать собственные функции зависимости, которые будут обрабатывать и объединять их особым образом, например, для пагинации или аутентификации. Некоторые из них вы увидите в главе 6 и различных главах части III.
Вот несколько рекомендаций, которые могут помочь сделать правильный выбор.
• При передаче аргументов в URL стандартной практикой стало следование рекомендациям RESTful.
• Строки запросов обычно применяются для предоставления дополнительных аргументов, таких как пагинация.
• Тело запроса обычно используется для больших объемов вводимых данных, например целых или частичных моделей.
Во всех случаях, если вы предоставите подсказки типов в определениях данных, ваши аргументы будут автоматически проверены библиотекой Pydantic. Это гарантирует их наличие и правильность.
По умолчанию FastAPI преобразует все, что вы возвращаете из своей функции конечной точки, в формат JSON. HTTP-ответ содержит строку заголовка Content-type: application/json. Поэтому, несмотря на то что функция greet() первоначально возвращает строку "Hello? World?", FastAPI преобразует ее в формат JSON. Это одно из значений по умолчанию, выбранных FastAPI для упрощения разработки API.
В этом случае строка Python "Hello? World?" будет преобразована в свой эквивалент строки в формате JSON "Hello? World?", который представляет собой ту же самую строку. Но все, что вы возвращаете, преобразуется в формат JSON, будь то встроенные типы Python или модели Pydantic.
По умолчанию FastAPI возвращает код состояния 200. Исключения вызывают коды группы 4xx.
В декораторе пути необходимо указать возвращаемый в случае успеха код состояния HTTP (исключения будут генерировать собственные коды и переопределять это значение). Добавьте код из примера 3.28 куда-нибудь в свой файл hello.py (чтобы не показывать весь файл снова и снова) и проверьте его в примере 3.29.
Пример 3.28. Указание кода состояния HTTP (добавьте в файл hello.py)
@app.get("/happy")
def happy(status_code=200):
return ":)"
Пример 3.29. Указание кода состояния HTTP
$ http localhost:8000/happy
HTTP/1.1 200 OK
content-length: 4
content-type: application/json
date: Sun, 05 Feb 2023 04:37:32 GMT
server: uvicorn
":)"
Можно вводить заголовки HTTP-ответов, как в примере 3.30 (вам не нужно возвращать сообщения response).
Пример 3.30. Установка HTTP-заголовков (добавьте в файл hello.py)
from fastapi import Response
@app.get("/header/{name}/{value}")
def header(name: str, value: str, response:Response):
response.headers[name] = value
return "normal body"
Посмотрим, получилось ли (пример 3.31).
Пример 3.31. Проверка HTTP-заголовков ответа
$ http localhost:8000/header/marco/polo
HTTP/1.1 200 OK
content-length: 13
content-type: application/json
date: Wed, 31 May 2023 17:47:38 GMT
marco: polo
server: uvicorn
"normal body"
Типы ответов (импортируйте эти классы из модуля fastapi.responses) бывают следующие:
• JSONResponse (по умолчанию);
• HTMLResponse;
• PlainTextResponse;
• RedirectResponse;
• FileResponse;
• StreamingResponse.
О двух последних я расскажу подробнее в главе 15.
Для других форматов вывода, известных также как MIME-типы или медиатипы, можно использовать общий класс Response, требующий следующие сущности:
• content — строка или байт;
• media_type — строка MIME-типа;
• status_code — целочисленный код состояния HTTP;
• headers — словарь (dict) строк.
Функция пути может возвращать что угодно, и по умолчанию (используя JSONResponse) FastAPI преобразует ее в строку JSON и возвращает с соответствующими заголовками HTTP-ответа Content-Length и Content-Type. Сюда входит любой класс модели Pydantic.
Но как это происходит? Если вы пользовались библиотекой Python JSON, то наверняка видели, что она вызывает исключение при предоставлении некоторых типов данных, таких как datetime. FastAPI задействует встроенную функцию jsonable_encoder() для преобразования любой структуры данных в JSON-подобную структуру данных Python, а затем вызывает обычную функцию json.dumps() для превращения этой структуры в JSON-строку. В примере 3.32 показан тест, выполняемый с помощью фреймворка pytest.
Пример 3.32. Используйте функцию jsonable_encoder(), чтобы избежать казусов в JSON
import datetime
import pytest
from fastapi.encoders import jsonable_encoder
import json
@pytest.fixture
def data():
return datetime.datetime.now()
def test_json_dump(data):
with pytest.raises(Exception):
_ = json.dumps(data)
def test_encoder(data):
out = jsonable_encoder(data)
assert out
json_out = json.dumps(out)
assert json_out
В программе могут быть разные классы с одинаковыми полями, но один из них будет специализирован для ввода данных пользователем, другой — для вывода, а третий — для внутреннего применения. Причины возникновения таких вариантов могут быть следующими.
• Удаление из выходных данных некоторой конфиденциальной информации — например, деидентификация личных медицинских данных, если вы столкнулись с требованиями закона о мобильности и подотчетности медицинского страхования (Health Insurance Portability and Accountability Act, HIPAA).
• Добавление поля для ввода пользователем, например даты и времени создания.
В примере 3.33 показаны три связанных класса для нестандартного случая.
• TagIn — это класс, определяющий, что должен предоставить пользователь (в данном случае просто строку под названием tag).
• Tag создается из класса TagIn и добавляет два поля: created (когда объект Tag был создан) и secret (внутренняя строка, которая может храниться в базе данных, но никогда не должна оказаться в широком доступе).
• TagOut — это класс, определяющий, что может быть возвращено пользователю (конечная точка определенного или неопределенного поиска). Он содержит поле tag из исходного объекта TagIn и производный от него объект Tag, а также поле created, созданное для объекта Tag, но не содержит поля secret.
Пример 3.33. Варианты моделей (model/tag.py)
from datetime import datetime
from pydantic import BaseClass
class TagIn(BaseClass):
tag: str
class Tag(BaseClass):
tag: str
created: datetime
secret: str
class TagOut(BaseClass):
tag: str
created: datetime
Из функции пути FastAPI можно возвращать типы данных, отличные от стандартного JSON, разными способами. Один из них — использовать аргумент response_model в декораторе пути, чтобы указать FastAPI вернуть что-то другое. FastAPI отбросит все поля возвращаемого объекта, не указанные в определенном аргументом response_model объекте.
Представьте, что в примере 3.34 вы написали новый сервисный модуль service/tag.py с функциями create() и get(), которые дают этому веб-модулю возможность вызывать что-то. Эти детали нижнего уровня здесь не имеют значения. Важными моментами являются функция пути get_one() в нижней части и выражение response_model=TagOut в декораторе пути. Это автоматически заменяет внутренний объект Tag очищенным объектом TagOut.
Пример 3.34. Возврат другого типа ответа с помощью аргумента response_model (web/tag.py)
import datetime
from model.tag import TagIn, Tag, TagOut
import service.tag as service
@app.post('/')
def create(tag_in: TagIn) -> TagIn:
tag: Tag = Tag(tag=tag_in.tag, created=datetime.utcnow(),
secret="shhhh")
service.create(tag)
return tag_in
@app.get('/{tag_str}', response_model=TagOut)
def get_one(tag_str: str) -> TagOut:
tag: Tag = service.get(tag_str)
return tag
Несмотря на то что мы вернули объект Tag, response_model преобразует его в TagOut.
Здесь предполагается, что вы используете веб-приложение из примера 3.21 — версию, отправляющую параметр who в тело HTTP-запроса с помощью запроса POST к http://localhost:8000/hi.
Необходимо убедить браузер посетить URL-адрес http://localhost:8000/docs.
Вы увидите что-то похожее на рис. 3.1 (я обрезал следующие снимки экрана, чтобы подчеркнуть определенные области).
Рис. 3.1. Сгенерированная страница документации
Откуда это взялось?
FastAPI генерирует спецификацию OpenAPI из вашего кода и включает эту страницу для отображения и тестирования всех ваших конечных точек. Это лишь один из ингредиентов «секретного соуса».
Нажмите стрелку вниз в правой части зеленого поля, чтобы открыть его для тестирования (рис. 3.2).
Рис. 3.2. Открытие страницы документации
Нажмите кнопку Try it out (Попробовать) справа. Теперь вы увидите область, которая позволит ввести значение в разделе тела запроса (рис. 3.3).
Рис. 3.3. Страница ввода данных
Щелкните левой кнопкой мыши на надписи "string". Измените ее на "Cousin Eddie" (Кузен Эдди) (она должна быть заключена в двойные кавычки). Затем нажмите нижнюю синюю кнопку Execute (Выполнить).
Теперь посмотрите на раздел Responses (Ответы) под кнопкой Execute (Выполнить) (рис. 3.4).
Рис. 3.4. Страница ответа
В поле Response body (Тело ответа) указано, что кузен Эдди объявился.
Этот процесс представляет собой еще один способ протестировать сайт (помимо предыдущих примеров с использованием браузера, HTTPie и Requests).
Кстати, в поле Curl в окне Responses (Ответы) видно, что применение инструмента curl для тестирования командной строки вместо HTTPie потребовало бы больше ввода. Здесь поможет автоматическое кодирование JSON в HTTPie.
Эта автоматизированная документация на самом деле представляет собой так называемую большую пушистую сделку. Когда ваш веб-сервис разрастется до сотен конечных точек, полезно иметь постоянно обновляемую страницу документации и тестирования.
В этих примерах показано, как передать конечной точке только одну строку. У многих конечных точек, особенно GET или DELETE, может быть несколько простых аргументов, таких как строки и числа, или не быть их вовсе. Но при создании (POST) или изменении (PUT или PATCH) ресурса нам обычно требуются более сложные структуры данных. В главе 5 показано, как FastAPI использует библиотеку Pydantic и модели данных для их чистой реализации.
В этой главе мы задействовали FastAPI для создания веб-сайта с одной конечной точкой. Протестировали ее с помощью нескольких веб-клиентов: браузера, текстовой программы HTTPie, пакета Requests Python и пакета HTTPX Python. Начиная с простого вызова GET, аргументы запроса передавались на сервер через путь URL, параметр запроса и HTTP-заголовок. Затем тело HTTP-запроса применялось для отправки данных в конечную точку POST. Позже было показано, как возвращать различные типы HTTP-ответов. Наконец, автоматически сгенерированная страница с формами предоставила четвертому тестовому клиенту как документацию, так и действующие формы.
Этот обзор FastAPI будет расширен в главе 8.