Глава 3.
Обзор FastAPI 1
Что такое FastAPI
Как и любой другой веб-фреймворк, FastAPI помогает создавать веб-
прило- жения. Каждый фреймворк призван облегчить выполнение
некоторых опе- раций за счет особенностей, допущений и настроек
по умолчанию. Как следует из названия, FastAPI предназначен для
разработки веб-интерфейсов API, хотя можно использовать его и
для традиционных приложений с веб- контентом.
На сайте FastAPI заявлены такие его преимущества:
высокая производительность — в некоторых случаях он работает
так же бы- стро, как Node.js и Go, что необычно для фреймворков
Python;
ускоренный процесс разработки — никаких острых углов или
странностей;
повышение качества кода — подсказки типов и модели помогают
уменьшить количество ошибок;
автоматически генерируемая документация и тестовые
страницы — это гораздо проще, чем вручную редактировать
описания OpenAPI.
В FastAPI используются:
подсказки типов Python;
пакет Starlette для веб-машин, включая поддержку асинхронности;
пакет Pydantic для определения и проверки данных;
специальная интеграция, позволяющая использовать и
расширять возмож- ности других фреймворков.
Такое сочетание создает приятную среду для разработки веб-
приложений, осо- бенно RESTful-веб-сервисов.
Приложение FastAPI
Напишем маленькое приложение FastAPI — веб-сервис с одной
конечной точ- кой. Пока что будем работать на так называемом
веб-уровне, где обрабатыва- ются только веб-запросы и ответы.
Сначала установите основные необходимые пакеты Python:
фреймворк FastAPI (https://fastapi.tiangolo.com) — pip install fastapi;
веб-сервер Uvicorn (https://www.uvicorn.org) — pip install uvicorn;
Глава 3. Обзор FastAPI 2
текстовый веб-клиент HTTPie (https://httpie.io) — pip install httpie;
пакет синхронного веб-клиента Requests
(https://requests.readthedocs.io) — pip install requests;
пакет синхронного/асинхронного веб-клиента HTTPX
(https://www.python- httpx.org) — pip install httpx.
Станем тенью веб-разработчика-интроверта в примере 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
можно двумя способами — извне или изнутри.
Глава 3. Обзор FastAPI 3
Чтобы запустить 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. Обзор FastAPI 4
Как написано в предисловии, то, что вы вводите, выделено
полужирным моно- ширинным шрифтом, а вывод представлен в формате
обычного моноширинного шрифта.
В примерах 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. Обзор FastAPI 5
и вывести только тело запроса.
Глава 3. Обзор FastAPI 6
Пример 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
(заголовки и тело ответа), а другие — только тело.
HTTP-запросы
Пример 3.9 включает только один конкретный запрос: GET на URL /hi на
сервер
localhost, порт 8000.
Веб-запросы «бегают» по разным частям HTTP-запроса, а FastAPI
позволяет получить к ним беспрепятственный доступ. В примере
3.10 показан HTTP- запрос из образца запроса в примере 3.9,
отправленный командой http на веб-сервер.
Пример 3.10. HTTP-запрос
GET /hi HTTP/1.1
Глава 3. Обзор FastAPI 7
Accept: /
Accept-Encoding: gzip, deflate
Глава 3. Обзор FastAPI 8
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-заголовке.
Глава 3. Обзор FastAPI 9
Путь URL
Отредактируйте файл 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
Глава 3. Обзор FastAPI 10
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
Глава 3. Обзор FastAPI 11
Функция конечной точки снова определяется как 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. Обзор FastAPI 12
В примере 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. Обзор FastAPI 13
Пример 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. Обзор FastAPI 14
теле запроса.
Глава 3. Обзор FastAPI 15
Пример 3.23. Проверка примера 3.21 с помощью Requests
>>> import requests
>>> r = requests.post("http://localhost:8000/hi", json={"who": "Mom"})
>>> r.json()
'Hello? Mom?'
HTTP-заголовок
Наконец, попробуем передать аргумент приветствия в качестве 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. Обзор FastAPI 16
Пример 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.
Глава 3. Обзор FastAPI 17
Строки запросов обычно применяются для предоставления
дополнительных аргументов, таких как пагинация.
Тело запроса обычно используется для больших объемов
вводимых данных, например целых или частичных моделей.
Во всех случаях, если вы предоставите подсказки типов в
определениях дан- ных, ваши аргументы будут автоматически
проверены библиотекой Pydantic. Это гарантирует их наличие и
правильность.
HTTP-ответы
По умолчанию 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. Обзор FastAPI 18
Пример 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;
Глава 3. Обзор FastAPI 19
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.fixtur
e def data():
Глава 3. Обзор FastAPI 20
return datetime.datetime.now()
Глава 3. Обзор FastAPI 21
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
Типы моделей и response_model
В программе могут быть разные классы с одинаковыми полями, но
один из них будет специализирован для ввода данных
пользователем, другой — для выво- да, а третий — для внутреннего
применения. Причины возникновения таких вариантов могут быть
следующими.
Удаление из выходных данных некоторой конфиденциальной
информа- ции — например, деидентификация личных
медицинских данных, если вы столкнулись с требованиями
закона о мобильности и подотчетности медицинского
страхования (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)
Глава 3. Обзор FastAPI 22
from datetime import
datetime from pydantic
import BaseClass
Глава 3. Обзор FastAPI 23
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
Глава 3. Обзор FastAPI 24
преобразует его в TagOut.
Глава 3. Обзор FastAPI 25
Автоматизированная документация
Здесь предполагается, что вы используете веб-приложение из
примера 3.21 — версию, отправляющую параметр who в тело HTTP-
запроса с помощью запроса POST к http://localhost:8000/hi.
Необходимо убедить браузер посетить URL-адрес
http://localhost:8000/docs.
Вы увидите что-то похожее на рис. 3.1 (я обрезал следующие
снимки экрана, чтобы подчеркнуть определенные области).
Рис. 3.1. Сгенерированная страница документации
Откуда это взялось?
FastAPI генерирует спецификацию OpenAPI из вашего кода и включает
эту стра- ницу для отображения и тестирования всех ваших
конечных точек. Это лишь один из ингредиентов «секретного
соуса».
Глава 3. Обзор FastAPI 26
Нажмите стрелку вниз в правой части зеленого поля, чтобы открыть
его для тестирования (рис. 3.2).
Рис. 3.2. Открытие страницы документации
Нажмите кнопку Try it out (Попробовать) справа. Теперь вы увидите
область, которая позволит ввести значение в разделе тела запроса (рис.
3.3).
Рис. 3.3. Страница ввода данных
Щелкните левой кнопкой мыши на надписи "string". Измените ее
на "Cousin Eddie" (Кузен Эдди) (она должна быть заключена в
двойные кавычки). Затем нажмите нижнюю синюю кнопку Execute
(Выполнить).
Теперь посмотрите на раздел Responses (Ответы) под кнопкой
Execute (Выпол- нить) (рис. 3.4).
Глава 3. Обзор FastAPI 27
Рис. 3.4. Страница ответа
В поле Response body (Тело ответа) указано, что кузен Эдди объявился.
Этот процесс представляет собой еще один способ протестировать
сайт (помимо предыдущих примеров с использованием браузера,
HTTPie и Requests).
Кстати, в поле Curl в окне Responses (Ответы) видно, что применение
инструмента curl для тестирования командной строки вместо HTTPie
потребовало бы больше ввода. Здесь поможет автоматическое
кодирование JSON в HTTPie.
Эта автоматизированная документация на самом деле
представляет собой так называемую большую пушистую
сделку. Когда ваш веб-сервис раз- растется до сотен конечных
точек, полезно иметь постоянно обновляемую страницу
документации и тестирования.
Глава 3. Обзор FastAPI 28
Комплексные данные
В этих примерах показано, как передать конечной точке только одну строку.
У многих конечных точек, особенно GET или DELETE, может быть несколько
простых аргументов, таких как строки и числа, или не быть их вовсе. Но при
создании (POST) или изменении (PUT или PATCH) ресурса нам обычно
требуются более сложные структуры данных. В главе 5 показано, как FastAPI
использует библиотеку Pydantic и модели данных для их чистой реализации.
Заключение
В этой главе мы задействовали FastAPI для создания веб-сайта с одной конеч-
ной точкой. Протестировали ее с помощью нескольких веб-клиентов:
браузера, текстовой программы HTTPie, пакета Requests Python и пакета HTTPX
Python. Начиная с простого вызова GET, аргументы запроса передавались на
сервер через путь URL, параметр запроса и HTTP-заголовок. Затем тело HTTP-
запроса при- менялось для отправки данных в конечную точку POST. Позже
было показано, как возвращать различные типы HTTP-ответов. Наконец,
автоматически сгене- рированная страница с формами предоставила
четвертому тестовому клиенту как документацию, так и действующие
формы.