Почему Вы должны попробовать FastAPI?

image Лого взято из Github репозитория FastAPI


FastAPI — относительно новый веб-фреймворк, написанный на языке программирования Python для создания REST (а если сильно постараться то и GraphQL) API, основанный на новых возможностях Python 3.6+, таких как: подсказки типов (type-hints), нативная асинхронность (asyncio). Помимо всего прочего, FastAPI плотно интегрируется с OpenAPI-schema и автоматически генерирует документацию для вашего API посредством Swagger и ReDoc


FastAPI построен на базе Starlette и Pydantic.
StarletteASGI микро-фреймворк для написания веб-приложений.
Pydantic — библиотека для парсинга и валидации данных основанная на Python type-hints.


Что говорят о FastAPI?


"[...] Я использую FastAPI очень часто в последние дни. [...] Я однозначно планирую использовать его для всех ML сервисов моей команды в Microsoft. Некоторые из них интегрируются в Windows и некоторые продукты Office."

Kabir Khan — Microsoft (ref)


"Если вы хотите выучить ещё один фреймворк для написания REST API, взгляните на FastAPI [...] Он быстрый, прост в использовании и изучении. [...]"

"Теперь мы используем FastAPI для наших API [...] Я думаю он вам понравится! [...]"

Ines Montani — Matthew Honnibal — Explosion AI founders — spaCy creators (ref) — (ref)


Минимальное API созданное с помощью FastAPI


Я постараюсь показать Вам как создать простое, но в то же время полезное API с документацией для разработчиков. Мы будем писать генератор случайных фраз!


Установка необходимых компонентов


pip install wheel -U
pip install uvicorn fastapi pydantic 

Новый модуль!
Uvicorn — это ASGI-совместимый веб-сервер, который мы будем использовать для запуска нашего приложения.


Для начала создадим основу нашего приложения.


from fastapi import FastAPI

app = FastAPI(title="Random phrase")

Это приложение уже работает и его можно запустить.
Пропишите данную команду в вашем терминале и откройте страничку в браузере по адресу http://127.0.0.1:8000/docs.


uvicorn <имя_вашего_файла>:app

Но пока в нашем приложении не обозначено ни одного эндпоинта — давайте это исправим!


База данных


Так как мы пишем генератор случайных фраз, мы очевидно должны их где-то хранить. Для этого я выбрал простой python-dict.


Создадим файл db.py и начнём писать код.


Импортируем необходимые модули:


import typing
import random
from pydantic import BaseModel
from pydantic import Field

После — обозначим две модели: входная фраза (та, которую нам будет отправлять пользователь) и "выходная" (та, которую мы будем отправлять пользователю).


class PhraseInput(BaseModel):
    """Phrase model"""

    author: str = "Anonymous"  # имя автора. Если не передано - используется стандартное значение.
    text: str = Field(..., title="Text", description="Text of phrase", max_length=200)  # Текст фразы. Максимальное значение - 200 символов.

class PhraseOutput(PhraseInput):
    id: typing.Optional[int] = None  # ID фразы в нашей базе данных.

После этого, создадим простой класс для работы с БД:



class Database:
    """
    Our **fake** database.
    """

    def __init__(self):
        self._items: typing.Dict[int, PhraseOutput] = {}  # id: model

    def get_random(self) -> int:
        # Получение случайной фразы
        return random.choice(self._items.keys())

    def get(self, id: int) -> typing.Optional[PhraseOutput]:
        # Получение фразы по ID
        return self._items.get(id)

    def add(self, phrase: PhraseInput) -> PhraseOutput:
        # Добавление фразы

        id = len(self._items) + 1
        phrase_out = PhraseOutput(id=id, **phrase.dict())
        self._items[phrase_out.id] = phrase_out
        return phrase_out

    def delete(self, id: int) -> typing.Union[typing.NoReturn, None]:
        # Удаление фразы

        if id in self._items:
            del self._items[id]
        else:
            raise ValueError("Phrase doesn't exist")

Теперь можно приступить к написанию самого API.


API


Создадим файл main.py и импортируем следующие модули:


from fastapi import FastAPI
from fastapi import HTTPException
from db import PhraseInput
from db import PhraseOutput
from db import Database

Инициализируем наше приложение и базу данных:


app = FastAPI(title="Random phrase")
db = Database()

И напишем простой метод получения случайной фразы!


@app.get(
    "/get",
    response_description="Random phrase",
    description="Get random phrase from database",
    response_model=PhraseOutput,
)
async def get():
    try:
        phrase = db.get(db.get_random())
    except IndexError:
        raise HTTPException(404, "Phrase list is empty")
    return phrase

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


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


Аналогично пишем другие методы:


@app.post(
    "/add",
    response_description="Added phrase with *id* parameter",
    response_model=PhraseOutput,
)
async def add(phrase: PhraseInput):
    phrase_out = db.add(phrase)
    return phrase_out

@app.delete("/delete", response_description="Result of deleting")
async def delete(id: int):
    try:
        db.delete(id)
    except ValueError as e:
        raise HTTPException(404, str(e))

И всё! Наше маленькое, но полезное API — готово!


Теперь мы можем запустить приложение с помощью uvicorn, открыть интерактивную документацию (http://127.0.0.1/docs) и попробовать наше API!



Полезные материалы


Конечно, я не смог рассказать Вам о всех возможностях FastAPI, например таких как: умная DI система, middlewares, куки, стандартные методы аутентификация в API (jwt, oauth2, api-key) и многое другое!


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


Код из статьи на Github
Официальная документация
Репозиторий на Github


P.S


cryo8822 сделал перевод статьи на английский язык, спасибо!.

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 49

    +2

    Много кода и мало текста, люблю такие мануалы =)

      0
      Недавно наткнулся. Прикольная штука, непоминает мой костыль bitbucket.org/robotnaoborot/aiowebapi/src/master — тоже использует pydantic
        0
        Спасибо попробую как время будет, насколько я понимаю это замена DRF? Просто DRF пока не изучал, так немного пощупал.
          0
          Нет, ну точнее не совсем. FastAPI берёт некоторые идеи из DRF (ref), но предлагает другой подход — он более легковесен, и не вынуждает использовать какую-либо определенную структуру для проекта.
            +1

            Мое ИМХО:


            DRF это all-inclusive где с коробки есть почти всё что нужно для большинства сервисов.
            Благодаря этому для него есть много плагинов, потому что автор плагина знает "ага раз DRF значить Django ORM, DRF Serializers/Views/etc."


            С другой стороны есть Flask компоненты для которого нужно собирать словно кубики лего что требует траты много времени на исследованные всех "кубиков". Они к тому же очень часто не совместимы между собой.


            А FastAPI это где то по середине. (ближе к Flask, но со некоторыми фишками)


            Я бы пока брал DRF для продакшена.

            0
            А, подскажите, вот вы в качестве бд используете память, будет ли это вообще работать в продакшене? В WSGI — точно нет, так как у каждого процесса приложения своя память и состояние будет теряться. Как дела обстоят с ASGI?
              0
              Изначально ASGI (да и вся идея асинхронной модели) задумывался как однопроцессный сервер, следственно таких проблем быть не должно :) Но в продакшн можно запускать с помощью gunicorn+uvicorn-воркеры и тогда да, такая проблема появится)
              +1

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

                0
                Сразу припомнилось про — "Зачем использовать python -m pip вместо pip/pip3"
                  –3

                  Слова add и delete нарушают правила наименования restfull api. Стоит их убрать, что бы не вводить в заблуждение тех, кто будет пользоваться туториалом. У post и delete методов можно сделать /phrases или просто слеш.


                  То, что метод delete и так видно, дополнительное слово не нужно и не по стандарту.


                  Почему так, можно прочитать, например, тут.
                  https://restfulapi.net/resource-naming/

                    +3

                    Это распространенный миф. Не существует никаких правил именования URI для REST. Все, что написано об именовании URI в этом мануале (и в других) является не больше, чем стилистической рекомендацией.


                    То, что метод delete и так видно, дополнительное слово не нужно и не по стандарту.

                    По какому стандарту?) Не бойтесь самостоятельно копнуть глубже, перед тем как открывать кому-то глаза.

                      –1
                      По какому стандарту?)

                      Ну не стандарту, а стандартному стайл гайду. Это ничего не меняет.


                      Зачем писать:


                      POST /add
                      GET /get
                      DELETE /delete

                      вместо стандартного:


                      POST /
                      GET /
                      DELETE /

                      это то же самое что писать коментарии типа: n += 2 # add 2 to n — никакой новой информации

                        +2

                        Но ведь никто не говорит, что такие комментарии нарушают, например, ООП.
                        Никакие глаголы в URI не нарушают REST. Вопрос стиля URI это ортогональный вопрос вкуса и внутренних соглашений.

                          0
                          Но ведь никто не говорит, что такие комментарии нарушают, например, ООП.

                          Вы правы, тип ошибки указана не верно. Но всё же она есть, согласны?


                          Вопрос стиля URI это ортогональный вопрос вкуса и внутренних соглашений.

                          Есть основной стиль, а есть "все другие".
                          Чем DELETE /delete лучше за DELETE /?

                            +1
                            Чем DELETE /delete лучше за DELETE /?

                            Этот вопрос выходит за рамки REST. Я не хочу показаться ханжой, но поймите одну вещь. Зацикливаясь на необязательных ограничениях по стилистике URI, мы нарушаем одно из обязательных для REST — Uniform Interface, центральной частью которого является т. н. HATEOAS (https://restfulapi.net/hateoas/). Именно грубое нарушение этого ограничения не позволяет большинству API называться RESTful, вне зависимости от того, какой стиль URI используется.


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

                              0

                              Повторю вопрос:
                              В стандартном, указаном вами, гайде в разделе в разделе Never use CRUD function names in URIs предлагают делать DELETE /, но вы защищаете вариант с DELETE /delete


                              (пожалуйста не расширяйте фокус вопроса. можете даже проигнорировать то что сверху и ответить только на следующий вопрос)


                              Чем DELETE /delete лучше за DELETE /?

                                +2

                                Коротко: Никоим образом не нарушая REST, он ничем не лучше.


                                Во-первых, я не защищаю никакой вариант URI. Во-вторых, вас точно не смущает слово "tips" по ссылке выше? В своем гайде по именованию автор отталкивается от собственных утверждений, не ссылаясь на первоисточники. Проблема всех таких гайдов в том, что они дают противоречивую информацию, не согласуясь даже друг с другом. Один запрещает использовать глаголы в URI; второй разрешает только те, которые нельзя выразить с помощью HTTP-методов, используя для этого свои понятия, которые отсутствуют в REST. Другие же честно разделяют обязательные и необязательные ограничения. Это порождает просто чудовищную путаницу в головах, превращая REST в хреновину, которую никто не может может толком описать.

                                  0
                                  вас точно не смущает слово "tips" по ссылке выше?

                                  Нет.


                                  Один запрещает использовать глаголы в URI; второй разрешает только те, которые нельзя выразить с помощью HTTP-методов, используя для этого свои понятия, которые отсутствуют в REST. Другие же честно разделяют обязательные и необязательные ограничения. Это порождает просто чудовищную путаницу в головах, превращая REST в хреновину, которую никто не может может толком описать.

                                  Не согласен, но сейчас не об этом.


                                  он ничем не лучше.

                                  Я скажу чем хуже: +одно слово +0 информации (не я один такой умный, поэтому, это уже описали в этих "tips")
                                  Вы согласны?

                      –1
                      Согласен с предыдущим коментарием, по смыслу REST ресурс это что-то что обознчается словом существительным, а не глаголом. Глаголы в REST используются стандартные от протокола HTTP.
                        +1

                        Это очень узкая трактовка термина. Архитектура очень раннего веба (начало 1990 годов, еще до стандартизации HTTP/1.1) действительно определяла URI как идентификаторы документов, но такое определение оказалось неудовлетворительным и было отброшено. Любая адресуемая концепция вписывается в это понятие.
                        Чтобы не повторятся, можете прочитать https://habr.com/post/476576/#comment_20949240

                        –2

                        Так и rest "не существует", если так рассуждать. Это просто правила, о которых люди договорились. К тому же не все, а некоторые. Вас же никто не заставляет их соблюдать. Но если вы хотите соблюдать и сделать именно rest api, тогда использовать delete или add неуместно.


                        И умерьте пафос, по возможности.

                          +2

                          Чтобы долго не тошнить в комментариях, я задам несколько вопросов, а заодно кину несколько цитат от автора REST для дальнейших размышлений.


                          • Что именно нарушают /add, /crop, /verify, /withdraw, глобальный /search в REST?
                          • Как именно должны быть переименованы такие идентификаторы?
                          • Кто именно создал такие требования?

                          A REST API must not define fixed resource names or hierarchies (an obvious coupling of client and server). Servers must have the freedom to control their own namespace. Instead, allow servers to instruct clients on how to construct appropriate URIs, such as is done in HTML forms and URI templates, by defining those instructions within media types and link relations.
                          http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven


                          There is no such thing as a REST endpoint. There are resources. A countably infinite set of resources bound only by restrictions on URL length. A client can POST to a REST service to create a resource that is a GraphQL query, and then GET that resource with all benefits of REST [...]
                          https://twitter.com/fielding/status/1052976631374000128


                          At no time whatsoever do the server or client software need to know or understand the meaning of a URI — they merely act as a conduit through which the creator of a resource (a human naming authority) can associate representations with the semantics identified by the URI.
                          https://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm#sec_6_2_4


                          REST accomplishes this by defining a resource to be the semantics of what the author intends to identify, rather than the value corresponding to those semantics at the time the reference is created.
                          https://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm#sec_6_2_1

                            –2

                            /crop, /verify, /withdraw не нарушают ничего. В коротком документе, на который я дал ссылку, это описано, если вам интересно, прочитайте.


                            Цитаты — вода (что и не удивительно, диссертация же). Общий смысл — делайте так, как нужно для вашего приложения.


                            Что ж, соглашусь, если для вашего приложения действительно нужно два раза повторить слово delete и вы даже сможете это аргументировать, то пожалуйста. Если у вас таких аргументов нет, то на этом можно закончить.

                              –1
                              Общий смысл — делайте так, как нужно для вашего приложения.

                              А когда-то было по другому?

                              Выбирая архитектуру/архитектурный стиль для своего приложения, вы полагаетесь на то получение какого-то профита от этого, а не просто для красивого словца берёте(надеюсь).

                              Если конкретный архитектурный стиль вам не подходит — вы его не берёте. Но зачем пытаться сделать по другому, явно указывая что ограничения REST вам не нужны, и в то же время продолжать называть это REST, ещё и производя подмену понятий?

                              Просто фраза «делайте так, как нужно для вашего приложения» не может представлять собой архитектурный стиль.
                              Цитаты — вода (что и не удивительно, диссертация же).

                              Из приведённых выше цитат только две из диссертации, и даже их я бы водой не назвал. Просто вы вряд ли являетесь целевой аудиторией сего документа(и я тоже).
                      0
                      А для PHP есть такое же чудо?
                        0
                        Чем не устраивает lumen/laravel?
                          0
                          Да мне-бы только API…
                            –1
                            api-platform.com

                            Это обрубленный Symfony только для API

                            Опять же многие используют Slim, Silex
                              –1
                              Как и lumen — laravel без лишнего.
                        0
                        Спасибо за статью, вопрос такой:
                        А что с перфом?
                        не helloworld перфом, а с нормальной нагрузкой — доставать данные по сети, совершать обсчет каких-нибудь матриц ( не очень больших, но всё равно матриц)
                          0
                          Ходить за данными можно прекрасно, ибо асинхронный фреймворк, а вот считать матрицы придется в процесс-пулл экзекьюторе, чтобы эвент луп не заблокировать, но в wsgi с этим имхо еще хуже
                            +1

                            Более подробные бенчмарки тык.
                            По поводу обсчёта матриц — если это нужно сделать в роуте который ничего асинхронно не делает, то можно маркнуть его как обычный def, а не async def, тогда FastAPI запустит его в тредпуле. А если всё таки хочется и сделать запрос с помощью aiohttp и посчитать что-то, то можно использовать такой код (WARNING: не тестировал, но уверен что работает):


                            from starlette.concurrency import run_in_threadpool
                            
                            @app.get(...)
                            async def some(..):
                                await do_async_request()
                                result = await run_in_threadpool(compute_some(123))
                              +1

                              Тут тоже всё не так однозначно: если нужно что-то посчитать, и числодробилка потенциально не высвобождает GIL, то всё равно выйдет блокирующая CPU-bound операция, и по-хорошему нужно либо вызвать loop.set_default_executor с инстансом ProcessPoolExecutor, либо спуститься на уровень ниже к loop.run_in_executor и делать ровно то же самое.
                              Конечно при условии, что это реально нужно, а не преждевременная оптимизация.

                            0
                            А как подключить к FastAPI базу с mysql?
                            Есть какой-нибудь рабочий образец?
                            С обычным sqlite работает норм:
                            main.py
                            from typing import List
                            
                            import databases
                            import sqlalchemy
                            from fastapi import FastAPI
                            from pydantic import BaseModel
                            
                            # SQLAlchemy specific code, as with any other app
                            DATABASE_URL = "sqlite:///./test.db"
                            
                            
                            database = databases.Database(DATABASE_URL)
                            
                            metadata = sqlalchemy.MetaData()
                            
                            notes = sqlalchemy.Table(
                                "notes",
                                metadata,
                                sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
                                sqlalchemy.Column("text", sqlalchemy.String),
                                sqlalchemy.Column("completed", sqlalchemy.Boolean),
                            )
                            
                            
                            engine = sqlalchemy.create_engine(
                                DATABASE_URL, connect_args={"check_same_thread": False}
                            )
                            metadata.create_all(engine)
                            
                            
                            class NoteIn(BaseModel):
                                text: str
                                completed: bool
                            
                            
                            class Note(BaseModel):
                                id: int
                                text: str
                                completed: bool
                            
                            
                            app = FastAPI()
                            
                            
                            @app.on_event("startup")
                            async def startup():
                                await database.connect()
                            
                            
                            @app.on_event("shutdown")
                            async def shutdown():
                                await database.disconnect()
                            
                            
                            @app.get("/notes/", response_model=List[Note])
                            async def read_notes():
                                query = notes.select()
                                return await database.fetch_all(query)
                            
                            
                            @app.post("/notes/", response_model=Note)
                            async def create_note(note: NoteIn):
                                query = notes.insert().values(text=note.text, completed=note.completed)
                                last_record_id = await database.execute(query)
                                return {**note.dict(), "id": last_record_id}
                            



                            Подсовываю в
                            DATABASE_URL = "mysql://mydb:welcome@localhost/mydb"

                            И пишет, мол ModuleNotFoundError: No module named 'MySQLdb'
                              0

                              Попробуйте это:


                              pip install databases[mysql]

                              А вообще, в документации есть часть и про работу с БД.

                                0
                                Спасибо за ответ. Это самое первое, что я сделал.
                                Так же сделал
                                pip install aiomysql


                                  +1
                                  Как-то худо бедно заставил работать вот таким макаром:
                                  pip install mysql-connector


                                  DATABASE_URL = "mysql+mysqlconnector://mydb:welcome@localhost/mydb" 
                              0
                              Очень раздражают рекламные статьи, где рассказывают, как вместо зачем.

                              Вот мы видим очередной фреймворк (а то ж мы страдали, что мало фреймворков), и чо? Сильно ли оно поможет условному Васе Пупкину, developer-у, ускорить и обезглючить dev?

                              Для примера, доки на asyncio начинаются (ну надо же!!!) со слов: Why use asyncio?

                              И это правильно, товарищи. Условный Вася Пупкин, developer, для начала должен понять: why? Почему следует считать бесцельно прожитими все те годы, когда он не юзал фреймворк?

                              И уж только после этого начинать тратить годы жизни (если это можно назвать жизнью) на изучение этой ненужной фигни крайне полезной штуки.
                                0
                                еще радуют эти графики перфа, где показывают: смотри, наш фреймворк впереди планеты всей, он практически как JS, смотри: Триллиардище запросов в секунду. И график, где %framework_name%, о котором статья, всегда стоит на первом месте, а C идет типа за ним или перед ним))
                                и никто че-то не берется сравнить те фреймворки, которые на первых местах были еще в начале 2019-го года, ну странновато
                                0
                                Для построения своих REST API я использую связку Flask-RESTplus + Flask-Marshmallow.
                                Чем FastAPI может привлечь мое внимание на фоне указанных модулей?
                                Это не вопрос с кандибобером, мне действительно интересно, стоит ли переходить на использование FastAPI с вышеперечисленного?
                                  0
                                  Я тоже юзаю фляшку, но вот начал изучать FastAPI из-за его асинхронности и быстроты. Имхо, думаю, что будущее за ним. Правда столкнулся с граблями, которые пока не могу понять.
                                  Вот даже задал вопрос на тостере:
                                  qna.habr.com/q/692597
                                    0
                                    Ну дык, я для быстроты использую .Net Core. Там всё что надо присутствует.
                                    То есть, концептуально, кроме «быстроты» ничего нового? Но я ведь правильно понимаю, что если надо что-то тяжелое достать или обработать, то можно использовать условный Celery и не париться?
                                      0
                                      Я делюсь с вами своими впечатлениями и мыслями:

                                      1) Flask задумывался как микро фреймворк и лишь позже там появились модули отвечающие за restfull, и они ещё между собой спорят, кто лучше.
                                      А fastapi уже на уровне архитектуры сразу rest.

                                      2) Я согласен с вот этим комментом цитата:
                                      Нафиг это фласк в 2019. Тогда уж лучше просто drf взять.
                                      Есть aiomongo и aiohttp. Вот пример api на нем dev.to/apcelent/how-to-create-rest-api-using-aiohttp-54p1


                                      3) Отличная документация у fastapi, читаешь как роман.

                                      4) Очень понравилась работа с параметрами, которая идёт из коробки.

                                      5) Никаких blueprint, которые дико бесят во фляшке. Разбивание на файлы, роуты и тд работает тоже из коробки.

                                      Ну вот как-то так, но я пока с fastapi только разбираюсь и кое-что пишу на нем чисто для себя.
                                        0
                                        Дело не только в быстроте, асинхронное приложение более устойчиво к пиковым нагрузкам и равномерно распределяет нагрузку. Flask же работает через воркеры, их фиксированное число — столько приложение может обработать одновременных подключений, остальным придётся подождать в очереди.

                                        Я же порекомендую связку aiohttp + jsonschema (для валидации схем в openapi) + iko (асинхронный аналог Marshmallow).

                                        Замечу что асинхронный код писать сложнее, и нет устоявшихся решений для restapi.

                                  Only users with full accounts can post comments. Log in, please.