Почему стоит начать использовать FastAPI прямо сейчас

Автор оригинала: Tivadar Danka
  • Перевод
Привет, Хабровчане! В преддверии старта занятий в группах базового и продвинутого курсов «Разработчик Python», мы подготовили для вас еще один полезный перевод.






Python всегда был популярен для разработки легковесных веб-приложений благодаря потрясающим фреймворкам, таким как Flask, Django, Falcon и многим другим. Из-за лидирующей позиции Python как языка для машинного обучения, он особенно удобен для упаковки моделей и предоставления их в качестве сервиса.

В течение многих лет Flask был основным инструментом для таких задач, но, если вы еще не слышали, на его место появился новый претендент. FastAPI – это относительно новый фреймворк на Python, создание которого было вдохновлено его предшественниками. Он совершенствует их функционал и исправляет множество недостатков. FastAPI был построен на базе Starlette, и несет в себе кучу потрясающих функций.

В последнее время он приобрел сильную популярность, и после того, как последние 8 месяцев я работал с ним каждый день, с уверенностью могу сказать, что вся шумиха вокруг него вполне оправдана. Если вы еще его не пробовали, то я собрал для вас пять причин, почему вам все-таки стоит с ним познакомиться.

Прекрасный простой интерфейс


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

Для примера, давайте посмотрим, как в FastAPI определяется конечная точка.

from fastapi import FastAPI
from pydantic import BaseModel


class User(BaseModel):
    email: str
    password: str


app = FastAPI()


@app.post("/login")
def login(user: User):
    # ...
    # do some magic
    # ...
    return {"msg": "login successful"}

Для определения схемы используется Pydantic, который является не менее потрясающей библиотекой Python для валидации данных. Выглядит достаточно просто, но под капотом происходит много всего интересного. Ответственность за проверку входных данных делегируется FastAPI. Если отправляется неверный запрос, например, в поле электронной почты стоит значение типа int, вернется соответствующий код ошибки, но приложение не ляжет, выдав Internal Server Error (500). И все это почти бесплатно.

Простой пример приложения с uvicorn:

uvicorn main:app

Теперь приложение может принимать запросы. В этом случае запрос будет выглядеть следующим образом:

curl -X POST "http://localhost:8000/login" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\"email\":\"string\",\"password\":\"string\"}"

Вишенкой на торте будет автоматическая генерация документации в соответствии с OpenAPI с помощью интерактивного интерфейса Swagger.


Интерфейс Swagger для приложения на FastAPI

Async


Одним из самых больших недостатков веб-фреймворков Python WSGI по сравнению с аналогичными в Node.js или Go, была невозможность асинхронной обработки запросов. С момента появления ASGI – это больше не проблема, и FastAPI в полной мере реализует эту возможность. Все, что вам нужно сделать – это просто объявить конечные точки с помощью ключевого слова async следующим образом:

@app.post("/")
async def endpoint():
    # ...
    # call async functions here with `await`
    # ...
    return {"msg": "FastAPI is awesome!"}

Внедрение зависимостей


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

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

from fastapi import FastAPI, Depends
from pydantic import BaseModel


class Comment(BaseModel):
    username: str
    content: str


app = FastAPI()


database = {
    "articles": {
        1: {
            "title": "Top 3 Reasons to Start Using FastAPI Now",
            "comments": []
        }
    }
}


def get_database():
    return database


@app.post("/articles/{article_id}/comments")
def post_comment(article_id: int, comment: Comment, database = Depends(get_database)):
    database["articles"][article_id]["comments"].append(comment)
    return {"msg": "comment posted!"}

FastAPI автоматически вычислит функцию get_database в рантайме при вызове конечной точки, поэтому вы сможете использовать возвращаемое значение на свое усмотрение. Для этого есть (по крайней мере) две веские причины.

  1. Вы можете глобально переопределить зависимости, изменив словарь app.dependency_overrides. Так вы сможете облегчить тестирование и мокать объекты.
  2. Зависимость (в нашем случае get_database) может выполнять более сложные проверки и позволяет отделить их от бизнес-логики. Дело значительно упрощается. Например, так можно легко реализовать аутентификацию пользователя.

Легкая интеграция с базами данных


Что бы вы ни выбрали, будь то SQL, MongoDB, Redis или что-нибудь другое, FastAPI не заставит вас строить приложение вокруг базы данных. Если вы когда-нибудь работали с MongoDB через Django, вы знаете, насколько это может быть болезненно. С FastAPI вам не нужно будет делать лишний крюк, поскольку добавление базы данных в стек пройдет максимально безболезненно. (Или, если быть точнее, объем работы будет определяться выбранной базой данных, а не сложностями, появившимися из-за использования какого-то конкретного фреймворка.)

Серьезно, посмотрите на эту красоту.

from fastapi import FastAPI, Depends

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker


engine = create_engine("sqlite:///./database.db")
Session = sessionmaker(bind=engine)


def get_db():
    return Session()


app = FastAPI()


@app.get("/")
def an_endpoint_using_sql(db = Depends(get_db)):
    # ...
    # do some SQLAlchemy
    # ...
    return {"msg": "an exceptionally successful operation!"}

Вуаля! Уже вижу, как вы печатаете

pip install fastapi

в терминале на своем компьютере.

Поддержка GraphQL


Когда вы работаете со сложной моделью данных, REST может стать серьезной преградой. Очень не круто, когда малейшее изменение на фронте требует обновления схемы конечной точки. В таких случаях спасает GraphQL. Несмотря на то, что поддержка GraphQL – это не что-то новое для веб-фреймворков Python, Graphene и FastAPI хорошо работают вместе. Нет необходимости дополнительно ставить какие-либо расширения, например graphene_django для Django, все просто будет работать с самого начала.

+1: Отличная документация


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

Если вы хотите увидеть FastAPI в действии, то у меня для вас припасено отличное руководство. Я написал подробную инструкцию, с помощью которой вы можете развернуть свою модель машинного обучения на Docker, Docker Compose и GitHub Actions!

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



Узнать подробнее о курсах


OTUS. Онлайн-образование
Цифровые навыки от ведущих экспертов

Комментарии 13

    0

    Видимо, я буду копипастить этот комментарий под каждый пост про FastAPI)


    У Pydantic очень долгое время висел такой пример в документации самым первым:


    class User(BaseModel):
        # ...
        signup_ts: datetime = None

    Естественно такой код не проходит проверку в mypy --strict, и аннотации типов теряют всякий смысл. Причём это не баг, а фича, и разработчики Pydantic предлагают просто отказаться от --strict. Но тогда какой вообще смысл в типизации?


    Сейчас этот пример уже поправили, но не потому что разработчики изменили своё отношение к mypy --strict, а потому что кто-то пулл-реквест прислал. Но для Pydantic это по-прежнему валидный код, имеющий особую логику.


    И это для меня основная причина НЕ использовать FastAPI. Одумайтесь, в питоне и так с системой типов всё очень плохо, не надо её окончательно доламывать. Я вместо FastAPI лучше напишу свой велосипед, лишь бы не связываться с Pydantic. (Или попробовать форкнуть FastAPI с заменой Pydantic на что-то другое, что ли...)

      0
      А разве должен проходить?
      Можно же(и нужно) обернуть
      class User(BaseModel):
          # ...
          signup_ts: typing.Optional[datetime] = None

      или я что-то путаю/не понимаю?
        0

        В том-то и проблема, что с точки зрения разработчиков Pydantic — не нужно, и подобные примеры ещё встречаются повсюду в документации.

          0

          Не всё так просто, т.к. в настоящее время тайпинг Питона плохо отражает то, что происходит в рантайме. Особенно это касается интерпретации "значений по умолчанию", объявленных в теле класса. К примеру, "dataclasses" и "attr" имеют особые объявления полей с использованием семантики "значения по умолчанию" и функции field. Есть ещё descriptor protocol, хитрые инициализации аттрибутов экземпляра итд.


          Есть по данной теме обсуждение на гитхабе, где Гвидо посоветовал использовать плагины для mypy для разрешения подобных ситуаций.

            0

            Что конкретно «плохо отражает» аннотация вида signup_ts: Optional[datetime] = None и откуда появилась необходимость использования именно signup_ts: datetime = None, кроме как от тараканов в головах разработчиков Pydantic, которые зачем-то решили завязать на такую аннотацию специальное поведение?

              0

              Определение вида signup_ts: datetime = None вполне используемо, если при отсутствии явного значения при создании модели требуется вытащить его из соседних полей.


              Но есть маленькое "но" с которым я полностью согласен: в документации ничего такого не происходит, что не есть правильно. Особенно не правильно поведение из коробки, при котором значение по умолчанию будет взято без валидации. К счастью, это можно изменить через конфигурацию модели.


              Своим предыдущем комментарием я хотел напомнить, что такие разногласия — результат столкновения идеологии типов и банальной утилитарности, а также о том, что в мире Питона есть ещё много нерешённых вопросов на данную тему. Так что говорить о "тараканах в головах разработчиков Pydantic" — лишнее. В конце концов никто не мешает законтрибутить в этот хороший проект.

                0
                если при отсутствии явного значения при создании модели требуется вытащить его из соседних полей.

                Это конечно хорошо и правильно, только вот Pydantic плевать хотел на это всё, и какое-нибудь print(User(signup_ts=None)) всё равно великолепно работает и печатает signup_ts=None. Так что заявление про тараканов остаётся в силе.


                это можно изменить через конфигурацию модели.

                А можно забыть её изменить, или же не уследить за каким-нибудь джуниором, и в итоге получаем баг в проекте, который мог бы быть отловлен через mypy, но не будет отловлен, потому что у разработчиков Pydantic тараканы.


                В конце концов никто не мешает законтрибутить в этот хороший проект.

                Очень мешают заявления разработчиков, что это не баг, а фича. Висел бы issue с лейблом «help wanted» — тогда да, но вместо этого issue на эту тему просто закрывают. Значит и пулл-реквест тоже завернут.

                  0
                  А можно забыть её изменить, или же не уследить за каким-нибудь джуниором, и в итоге получаем баг в проекте, который мог бы быть отловлен через mypy, но не будет отловлен, потому что у разработчиков Pydantic тараканы.

                  +1


                  Очень мешают заявления разработчиков, что это не баг, а фича. Висел бы issue с лейблом ...

                  Дайте, пожалуста, ссылку, может получится поучаствовать.

                    0

                    Вот тут я ныл на ломаном английском, и мне там ответили в общем-то примерно то же самое что и вы рассказываете:


                    Yes, there are some patterns that work with pydantic that are not valid with mypy. There is also an enormous amount of non-pydantic python code that is valid python and not valid with mypy.

                    Но меня такие попытки усидеть на двух стульях не устраивают.


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


                    Там хоть и написано, что «we should absolutely strive toward better supporting --strict mode», но дальше слов, похоже, дело не идёт.

      +6

      Извините не сдержался :)

        +1

        Вы вот смеетесь, а человек за полтора года наклепал уже 96 релизов.
        Это же хорошо, да?
        Или по крайней мере не смешно.

        0

        отдавать 500 ошибку на неверные входные данные — не есть хорошо. скорее всего, фреймворк так не делает, а если и делает, то в случае неправильного использования

          0

          Он и не отдает. Он в дефолте кидает 422 и в ответном json — инфу, что именно пошло не так. Насчет 500 автор что-то путает. Вообще удобная штука, чтобы быстро накидать апишечку на json'ах, и, может даже restful, если чуть заморочиться. Из приятного — делать валидацию за счет тайп-хинтов без лишнего кода прям греет душу. :)

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое