[Перевод] 5 неочевидных возможностей FastAPI: упрощаем работу с бэкендом на Python

03f67deb9225beaf10fa5903e56f95f3.jpg

API (Application Programming Interface) — технология, позволяющая соединить функциональность разных компьютерных программ. API можно сравнить с официантом, который получает от клиента заказ из ограниченных пунктов меню, передает его на кухню («системе»), а после приготовления, возвращает готовые «блюда» заказчику.

Почти все, с чем мы сталкиваемся в интернете, имеет отношение к API, а точнее к версиям этого программного интерфейса, использующим для работы HTTP-запросы. Когда мы хотим узнать прогноз погоды, интерфейс браузера или мобильного приложения вызывает API Яндекс.Погоды или API Gismeteo. Когда прокладываем кратчайший маршрут из одного места в другое, Яндекс.Карты вызывают соответствующее API.

Пользовательские API-интерфейсы могут быть реализованы на Python с использованием нескольких фреймворков. В этой статье остановимся на особенностях работы с одним из самых популярных вариантов — платформой FastAPI, библиотеки которой активно используют такие технологические гиганты, как Microsoft, Netflix, Uber. Речь пойдет о некоторых расширенных функциях FastAPI, которые могут использовать в своих проектах те разработчики, у кого уже есть базовые знания о фреймворке.

Почему FastAPI

FastAPI — современная, высокопроизводительная веб-инфраструктура для создания готовых к эксплуатации HTTP API-серверов с помощью Python 3+. Производительность FastAPI можно сравнить с NodeJS и Go, поэтому он считается одним из самых быстрых фреймворков Python. Структура разработки очень похожа на Flask, который де-факто является базовым вариантом для всех, кто начинает веб-разработку с использованием Python.

FastAPI прост в использовании, хорошо задокументирован и обладает встроенными валидацией, сериализацией и асинхронностью «из коробки». Фреймворк предлагает все стандартные функции для создания API, но не ограничивается этим. Инструмент обладает большой гибкостью и множеством полезных функций (например, поддержкой обратного монтирования WSGI), которые остаются вне поля внимания большинства пользователей.

Включите Flask, Django, Dash или любой другой WSGI

FastAPI — веб-фреймворк, использующий клиент-серверный протокол ASGI (Asynchronous Server Gateway Interface), который дает доступ к функциям параллельного выполнения кода. Но, с помощью связки совместимых по рабочим классам HTTP-сервера Gunicorn и ASGI-сервера Uvicorn, FastAPI также поддерживает протокол WSGI (Web Server Gateway Interface) с последовательной обработкой запросов.

FastAPI позволяет монтировать любые WSGI-приложения, например, на фреймворках Dash, Flask, Django или CherryPy, внутри приложения FastAPI. На корневом уровне у вас может быть основное приложение FastAPI, дополненное WSGI-приложениями (например, на Flask), которые будут обрабатывать все запросы для этого конкретного пути.

Один из вариантов практической реализации этой функциональности — монтирование plotly-dash в качестве промежуточного программного обеспечения WSGI. Dash — популярный веб-фреймворк для визуализации данных на базе графиков plotly, включающий набор компонентов для работы с HTML и Bootstrap.

Я смонтировал сервер dash для маршрутизации панели инструментов моего веб-сайта. Это позволило отделить логику взаимодействия дашборда с основным приложением FastAPI, а также обеспечило гибкость отключения панели мониторинга в любое время, когда нагрузка на сервер возрастает. 

Ниже приведен пример абстрактной реализации монтирования промежуточного ПО WSGI, которое я сделал для одного из проектов.

Файл dashboard.py с кодом сервера Plotly dash:

from dash import html
from dash import dcc
import dash_bootstrap_components as dbc
import dash


dash_app = dash.Dash(__name__,
                     requests_pathname_prefix='/dashboard/', title='Kaustubh Demo')

header = dbc.Row(
    dbc.Col(
        [
            html.Div(style={"height": 30}),
            html.H1("Demo", className="text-center"),
        ]
    ),
    className="mb-4",
)

dash_app.layout = dbc.Container(
    [
        header,
    ],
    fluid=True,
    className="bg-light",
)

Чтобы включить Dash-сервер в качестве независимого приложения в FastAPI, просто оберните объект приложения WSGI в класс WSGIMiddleware и передайте этот обернутый объект в качестве параметра в функции монтирования корневого объекта FastAPI. 

Функция монтирования также принимает путь, по которому следует использовать это приложение. Например, в приведенном ниже файле main.py, сервер дашборда plotly-dash в FastAPI будет смонтирован по пути »/dashboard»:

from fastapi import FastAPI
from fastapi.middleware.wsgi import WSGIMiddleware
from demo_dash import dash_app
import uvicorn

app = FastAPI()
app.mount("/dashboard", WSGIMiddleware(dash_app.server))

@app.get('/')
def index():
    return "Hello"

if __name__ == "__main__":
    uvicorn.run("main:app", host='127.0.0.1', port=8000, reload=True)

При реализации подобного сценария все запросы к панели управления будут перенаправлены на сервер dash.

Примеры логов из моего проекта, где показано перенаправление маршрута к панели управления на сервер Dash.Примеры логов из моего проекта, где показано перенаправление маршрута к панели управления на сервер Dash.

Смонтируйте отдельные приложения FastAPI для разных маршрутов

Правила монтирования приложений WSGI позволяют монтировать внутри одного приложения FastAPI несколько других самостоятельных приложений. Каждое подобное субприложение FastAPI будет иметь свою документацию, работать независимо от других приложений и будет обрабатывать свои запросы, зависящие от пути. 

Чтобы реализовать подобную возможность, просто создайте файл первого субприложения (apiv1.py), как показано ниже:

from fastapi import FastAPI


apiv1 = FastAPI()

@apiv1.get('/returnName')
def index():
    return {"Name": "Kaustubh demo"}

Теперь создайте файл второго субприложения (apiv2.py):

from fastapi import FastAPI


apiv2 = FastAPI()

@apiv2.get('/returnLocation')
def index():
    return {"Location": "India"}

Теперь мы можем смонтировать оба этих субприложения в главном приложении (master.py) с помощью импорта объектов:

from fastapi import FastAPI
from apiv1 import apiv1
from apiv2 import apiv2
import uvicorn

app = FastAPI()
app.mount("/api/v1", apiv1)
app.mount("/api/v2", apiv2)


@app.get('/')
def index():
    return "Hello"

if __name__ == "__main__":
    uvicorn.run("master:app", host='127.0.0.1', port=8000, reload=True)

Если вы делаете запросы к эндпоинтам соответствующих путей, запрос будет обрабатываться этими субприложениями.

Здесь оба запроса обслуживались соответствующими приложениями.Здесь оба запроса обслуживались соответствующими приложениями.

Удобство метода заключается в том, что он не требует промежуточного программного обеспечения. 

Разделите маршруты FastAPI на разные файлы

Обычно по мере того, как приложение укрупняется, управлять всеми маршрутами в одном месте становится неудобно и менее эффективно. Иногда могут происходить перекрытия или маршруты дублируются, последствия чего «всплывают» значительно позже. А можете ли вы представить систему, в которой есть возможность группировать маршруты в разные файловые структуры и легко ими управлять?

FastAPI имеет собственную систему API-маршрутизации. API-роуты (APIRouters) можно рассматривать как мини-приложения FastAPI, которые являются частью более крупного приложения. Это позволяет разбить большие маршруты приложений на небольшие блоки API-роутов и смонтировать их в основном приложении.

Обратите внимание! Эти API-роуты не являются независимыми, как те, что мы видели в двух предыдущих разделах, а входят в основное приложение как составная часть. Поэтому все маршруты от API-роутов будут перечислены в основной документации приложения.

API-роуты могут иметь отдельные префиксы для операций пути, тегов, зависимостей и ответов. Чтобы реализовать это, нужно импортировать класс APIRouter из FastAPI, а затем использовать его объект для создания маршрутов, как в обычном приложении FastAPI.

Для примера предположим, что мы создаем систему управления библиотекой и хотим обрабатывать данные книг и новелл (рассказов) отдельно. 

Реализуем это через применение APIRouter в файле book.py (для книг):

from fastapi import APIRouter


bookroute = APIRouter()

@bookroute.get('/info')
def books():
    return {"detail": "This book info is from the book APIRouter",
    "name": "Hello",
    "ISBN": "32DS3"}

А этот код — пример использования APIRouter в файле novel.py (для новелл):

from fastapi import APIRouter


novelroute = APIRouter()

@novelroute.get('/info')
def novels():
    return {"detail": "This novel info is from the novel APIRouter",
    "name": "I am good",
    "publication": "Kaustubh"}

Чтобы включить оба маршрутизатора в основное приложение, импортируем объекты APIRouter и передадим их в функцию include_router основного объекта приложения FastAPI. Мы также добавим префиксы для этих маршрутизаторов, чтобы одни и те же эндпоинты на обоих маршрутизаторах не конфликтовали.

Пример кода для включения двух API-роутов (book.py и novel.py) в основное приложение:

from fastapi import FastAPI
from book import bookroute
from novel import novelroute
import uvicorn

app = FastAPI()
app.include_router(bookroute, prefix="/book")
app.include_router(novelroute, prefix="/novel")


@app.get('/')
def index():
    return "Hello"

if __name__ == "__main__":
    uvicorn.run("demo:app", host='127.0.0.1', port=8000, reload=True)

Если вы обратитесь к эндпоинтам книг »/book/info» и новелл »/novel/info», то получите разные ответы в зависимости от того, как обработались эти входные данные в API-роутах.

Эндпоинт книги в APIRouter.Эндпоинт книги в APIRouter.Эндпоинт новеллы в APIRouter.Эндпоинт новеллы в APIRouter.

Таким образом, может быть несколько API-роутов для обработки параметров, в зависимости от того, какой тип операции нужно выполнить для разных групп эндпоинтов.

Добавьте шаблоны Jinja и статические файлы

Возможности FastAPI не ограничиваются лишь созданием API. С помощью фреймворка можно также обрабатывать статические файлы (например, HTML, CSS, JS) и включать механизм шаблонов модуля Jinja, как это реализовано в Flask. Потенциально это означает, что с помощью FastAPI можно создать полноценный веб-сайт.

Возьмем для примера Flask. В этом фреймворке предусмотрена стандартная папка «templates», где можно хранить все файлы HTML с шаблонизатором Jinja. В основном приложении для возврата этих шаблонов с данными для заполнения можно напрямую использовать функцию render_template.

В FastAPI процесс остается примерно тем же, но с небольшими изменениями. Рассмотрим его по шагам:

  1. Для начала нужно создать объект для шаблонов Jinja с указанием пути к папке шаблонов. В FastAPI нет ограничений на имя папки — ее можно назвать как угодно, главное указать путь к ней. Для реализации нужно импортировать класс Jinja2Templates из модуля шаблонов FastAPI и создать объект для этого класса с путем к папке.

  2. Затем нужно указать «response_class» в методе маршрута FastAPI (например, GET, POST) в качестве класса HTMLResponse. Этот класс можно импортировать из модуля fastapi.responses. Также необходимо создать параметр запроса типа класса Request («Запрос») в функции декоратора. Этот класс также можно импортировать из модуля fastapi.responses.

  3. Чтобы отобразить и предоставить данные этим шаблонам при достижении соответствующих эндпоинтов, необходимо вернуть функцию «TemplateResponse» объекта класса Jinja2Templates. Эта функция принимает имя шаблона HTML и контекстный словарь. Словарь должен иметь ключ Request («Запрос») со значением в качестве параметра запроса, созданного в функции декоратора. Данные, которые будут отправлены в шаблоны Jinja, могут быть добавлены в этот контекстный словарь как пара ключ-значение.

Так выглядит реализация использования шаблонов Jinja для FastAPI в виде кода:

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
import uvicorn

app = FastAPI()
templates = Jinja2Templates(directory="demo")


@app.get('/', response_class=HTMLResponse)
def index(request: Request):
    return templates.TemplateResponse("demo.html", {"request": request, "title": "Kaustubh Demo", "body_content": "This is the demo for using FastAPI with Jinja templates"})

if __name__ == "__main__":
    uvicorn.run("demo2:app", host='127.0.0.1', port=8000, reload=True)

Теперь в шаблонах HTML появится возможность заполнить переменные в соответствии с синтаксисом движка Jinja — с помощью имен, используемых в качестве ключей в контекстном словаре.





    {{title}}



{{body_content}}













Запустите приложение FastAPI, и вы получите следующий вывод:

Вывод для шаблона Jinja с кодом FastAPI.Вывод для шаблона Jinja с кодом FastAPI.

Чтобы включить все изображения, файлы JS и CSS в HTML, во Flask нужно использовать функцию url_for и указать в имени папки атрибут «static», за которым следует параметр имени файла «filename». 

В FastAPI алгоритм гораздо проще:

  1. Создайте объект для класса StaticFiles из модуля статических файлов FastAPI и укажите путь к папке для статического файла. Опять же, нет никаких ограничений на имя папки.

  2. Смонтируйте объект статических файлов по одному из путей (чтобы избежать путаницы, предпочтительно выбрать «static») и назначьте ему имя, которое будет использоваться внутри FastAPI.

Я добавил логику для статических файлов в тот же код, который используется для обслуживания файлов Jinja:

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
import uvicorn

app = FastAPI()
staticfiles = StaticFiles(directory="demo/toServe")
app.mount("/static", staticfiles, name="static")
templates = Jinja2Templates(directory="demo")


@app.get('/', response_class=HTMLResponse)
def index(request: Request):
    return templates.TemplateResponse("demo.html", {"request": request, "title": "Kaustubh Demo", "body_content": "This is the demo for using FastAPI with Jinja templates"})

if __name__ == "__main__":
    uvicorn.run("demo3:app", host='127.0.0.1', port=8000, reload=True)

На примере выше я показал, что, используя возможность обрабатывать статические файлы и включать механизм шаблонов Jinja, вы сможете создать полноценный веб-сайт с поддержкой FastAPI.

Используйте модель обработки конфигурации из Flask

Те, кто давно используют Flask, знают о такой функции веб-фреймворка, как конфигурация приложений. С ее помощью можно определить некоторые параметры приложения, которые будут доступны из любой его части. Обычно они включают переменные среды. 

В Flask все конфигурации хранятся в атрибуте config — подклассе структуры данных словаря, который находится в объекте класса. Это особенно полезно, когда нужно жестко запрограммировать некоторые части приложения и иметь возможность вызывать их в любом месте.

Во Flask параметры конфигурации приложения определяются достаточно просто:

from flask import Flask

app = Flask(__name__)
app.config['appName'] = 'Kaustubh Demo'

@app.route('/')
def index():
    return "Hello: {}".format(app.config['appName'])


if __name__ == "__main__":
    app.run(debug=True)

В FastAPI подобная функциональность не поддерживается. Хотя распространено мнение, что в этом случае можно использовать состояние приложения, по предложению создателя FastAPI Себастьяна Рамиреса (aka tiangolo), APIRouter не будет иметь доступа к этому состоянию. Из-за этого, даже если вы сохраните некую конфигурацию в состоянии, она не будет использоваться всеми частями приложения и, в конечном счете, будет бесполезна.

Простой обходной путь, предложенный tiangolo, — использовать класс BaseSettings библиотеки Pydantic и создать модель для этих параметров. Этот класс предлагает функции проверки и аннотации типов, что выгодно отличает его от обычных переменных среды, где могут обрабатываться только значения строкового типа.

Чтобы получить подобный функционал в приложении FastAPI, просто импортируйте класс BaseSettings из Pydantic и создайте подкласс для ваших настроек/конфигураций. Так как мы имеем дело с классами Pydantic, можно добавлять значения по умолчанию, Field () и многое другое. Позвольте мне представить вам пример для реализации этого.

Ниже приведен пример импорта BaseSettings из Pydantic в файле config.py:

from pydantic import BaseSettings


class Settings(BaseSettings):

    appName: str = "Kaustubh Demo"

    openapi_url: str = ''

Здесь я определил «openapi_url» как пустую строку. Это отключит генерацию документов для приложения FastAPI. 

В основном файле приложения создайте объект для модели конфигурации и получите доступ к параметрам настроек, как если бы имели дело со структурой данных словарного типа:

from fastapi import FastAPI
from config import Settings
import uvicorn

setting = Settings()
app = FastAPI(openapi_url=setting.openapi_url)

@app.get('/')
def index():
    return "Hello: {}".format(setting.appName)

if __name__ == "__main__":
    uvicorn.run("fastapi_config:app", host='127.0.0.1', port=8000, reload=True)

Использование этой модели в качестве глобальной конфигурации устраняет зависимость от FastAPI при настройке отдельных частей приложения.

Заключение

В этом кратком обзоре я описал все расширенные варианты использования FastAPI, с которыми сталкивался при разработке своих проектов. Тех, кто желает узнать больше об этих концепциях, а также найти собственные продвинутые варианты использования фреймворка, следует обратиться к официальному источнику знаний о нем. Потенциал этого инструмента способен дать многим разработчикам шанс повысить эффективность работы и творчески разнообразить свой привычный инструментарий.

НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

© Habrahabr.ru