Книга: FastAPI: веб-разработка на Python
Назад: Часть II. Обзор FastAPI
Дальше: Глава 4. Асинхронность, конкурентность и обзор библиотеки Starlette

Глава 3. Обзор FastAPI

FastAPI — это современный быстрый (высокопроизводительный) веб-фреймворк для создания API на Python 3.6+, основанный на стандартных подсказках типов Python.

Себастьян Рамирес, создатель FastAPI

Обзор

FastAPI (https://fastapi.tiangolo.com) был представлен в 2018 году Себастьяном Рами­ресом (https://tiangolo.com). Во многих смыслах это более современный, чем большинство веб-фреймворков Python, и он использует добавленный в Python 3 за последние несколько лет функционал. Эта глава представляет собой краткий обзор основных возможностей FastAPI с акцентом на первом из интересующих вас вопросов: как обрабатывать веб-запросы и ответы?

Что такое 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;

• текстовый веб-клиент 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 (заголовки и тело ответа), а другие — только тело.

HTTP-запросы

Пример 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-заголовке.

Путь 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

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-заголовок

Наконец, попробуем передать аргумент приветствия в качестве 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. Это гарантирует их наличие и правильность.

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.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

Типы моделей и 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)

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.

Назад: Часть II. Обзор FastAPI
Дальше: Глава 4. Асинхронность, конкурентность и обзор библиотеки Starlette