Pull to refresh

Comments 58

UFO landed and left these words here

Мне казалось, я довольно ясно описал, чтож, артикулирую:


  • асинхронность
  • валидация и документация API из коробки
  • быстродействие
UFO landed and left these words here

aiohttp тормозной даже по меркам питона. Это известно даже мне, можно сказать, неблизкому к web-разработке, но имевшему с ним дело. Помнится, в том проекте мы просто выкинули его вместе с питоном и переписали на Go с fasthttp, но это не суть.


Если мне на слово не верите, бенчмарки тоже есть: https://www.techempower.com/benchmarks/
Если смотреть в списке Fortunes, то starlette на 159 месте, fastapi на 176, а aiohttp на 264

UFO landed and left these words here
имхо True way- валидацию строить вокруг спецификации (например сваггер), но проблема тут в том что нормальных либ на эту тему в Питоне нет. openapi-core — кастрированный вариант того что должно быть: "в Вашем запросе есть ошибка, а где конкретно — не скажу! Расширять format тоже нет возможности, а в остальном, прекрасная маркиза..."

На самом деле, если взять не 0.12.0, а немного старее версию, то там все норм. А в 0.12.0 можно монкипачнуть:


@wrapt.patch_function_wrapper('openapi_core.schema.schemas.models', 'Schema.validate')
def validate(_wrapped, self, args, kwargs):
    value = args[0]
    resolver = kwargs.get('resolver')
    validator = self.get_validator(resolver=resolver)
    errors: typing.List[jsonschema_exceptions.ValidationError] = list(validator.iter_errors(value))
    if errors:
        raise InvalidSchemaValues.extract(errors)

Кастомные форматы от тоже тянет, но не очень очевидно.


Сначала, нужно через custom_formatters параметр передать unmarshal метод, который будет преобразовывать изначальный тип в нужный объект (или просто lambda x: x, если не нужны преобразования) и задать валидатор как тут (раньше можно было декоратором, а теперь автор снова вернул явный словарь).


То есть мне получилось в целом переложить всю валидацию и сериализацию на автоматический код по спеке и это даже работает в проде :) С ответами пока сложнее :(

UFO landed and left these words here

Чёрт, не знал о такой особенности. И что теперь делать? Не писать же отдельный подзаголовок "это статья про питон!"

Собирается за 5 минут на базе
flask + web_args + marshmallow + ORM по вкусу.

Причем декларативности там будет побольше.
monkey.patch_all, усложняем и усложняем
UFO landed and left these words here
Зависит от того какой WSGI сервер поставите. Ну и да мантра такая теперь? Асинхронность сразу дает +100500 к производительности?
Асинхронность сразу дает +100500 к производительности?

Нет, но статья про асинхронный фреймворк, поэтому всё в этом контексте и обсуждается. Ну и сразу, любой WSGI сервер синхронный, для асинхронности нужен другой протокол ASGI
Асинхронность это инструмент. Какие плюсы она дает внутри фреймворка? Все работает на 50% быстрее? Или мне надо постоянно помнить что в нем все асинхронно и надо не забывать про это чтобы не прострелить себе ногу?
Да это инструмент, который дает большие плюсы в задачах где много сетевого IO.
Особенно это нам помогает когда есть взаимодействие с СУБД внутри кода.
помогает, есть очень хорошие асинхронные библиотеки, например asyncpg
Не помогает. Для тех кто не понял, если у вас одно подключение к СУБД, то код будет выполняться синхронно. Для выполнения кода асинхронно надо делать пул соединений и запускать все через него. И то тут могут быть проблемы потому на стороне СУБД куча синхронного кода из-за ACID.
Для тех кто не понял, я не припомню чтобы в реальных серверах использовалось только одно подключение к СУБД, а пул это практически стандарт. Ну и ACID тут уж совсем сбоку приплели. Я понимаю, в этом бессмысленном споре, вам хочется оставить последнее слово за собой, так что давайте напиши еще что-то, я больше не буду продолжать.
Я намекаю, что асинхронность не является «серебряной пулей». И во многих случаях ее добавление только усложняет код, не давая никакого увеличения производительности. К примеру в случае web-сервера, вполне достаточно асинхронности на уровне выполнения запроса.
Почему? Довольно банальный ответ, потому что со стороны клиента, пока сервер не ответит будет висеть ожидание ответа. А от того что асинхронность добавлена ниже, это никак не изменится.
UFO landed and left these words here
Я в асинхронщину питона заглядывал еще через twisted. А это отдельный ад :)

Но да именно так.
если на любом уровне Вы применили асинхронный запрос, то все уровни выше придётся "обмазывать" async/await операторами

А можно посмотреть на язык, в котором не придётся?

UFO landed and left these words here
Perl (Coro)

Насколько я понял туториал, весь код придётся обмазывать вызовом cede; или обёртками над ним, так что мимо


Lua (Tarantool)

Можно ссылку? По этим ключевикам мне почему-то не удалось нагуглить что-то асинхронное


Си (libcoro)

Можно тоже ссылку? Кроме собственно исходников либы мне не удалось нагуглить каких-либо примеров или туториалов


Lwp, к ней приделали имплементатор и она стала поддерживать работу в асинхронной парадигме

Как ни странно, это мне тоже не удалось нагуглить, что я делаю не так? :(

UFO landed and left these words here
неправильно поняли.

А как правильно понимать? Насколько я вижу примеры, они все обмазаны местными аналогами await (schedule, rouse_wait и т.п.) Возможно, я просто слишком плохо знаю перл, но пока я не понимаю, чем это принципиально лучше питона.


а обычная машина событий спит и переключается на сокетах и таймерах

Питоновая машина событий работает точно так же


там абсолютно всё асинхронное, но выглядит абсолютно всё как синхронное :)

Это какой-то прохаченный Lua внутри Tarantool, что ли? В питоне такая штука тоже издавна есть, называется gevent. Или он не такой уж и прохаченный и я могу запустить эту асинхронщину на самом обычном Lua?


вот имплементатор к ней чтобы она стала асинхронной

Читаю описание:


It loads Coro::Select, overwriting the perl select builtin globally.

Это ровно то же самое, что делает питоновый gevent.


Короче, судя по всему, вам в питоне просто нужен gevent, чтобы вы были довольны :)

UFO landed and left these words here
во первых там нет аналога await

А чем тогда являются schedule, rouse_wait и т.п., если не аналогами await? Я по-прежнему не понимаю.


нет, файберы в любом языке примерно одинаковы

В Lua нет файберов, а в примерах Tarantool по ссылке нет явных обращений к файберам для засыпания/пробуждения и прочего. Значит Lua необходимо прохачить, чтобы эти самые файберы появились сами по себе в синхронном коде. Я что-то опять неправильно понимаю?

UFO landed and left these words here

Есть какая-нибудь статья о его внутренностях и устройстве работы? В то время как про устройство работы asyncio/gevent/js promise и пр. есть куча матчасти и я их прекрасно понимаю, про coro мне удаётся нагуглить только примеры использования, а исходники читать лениво, отчего я его продолжаю не понимать.


В частности, по моим текущим знаниям за фразой «интеграция с калбечной евентмашиной» скрывается какая-то тёмная магия, так как я не знаю, как возможно сделать такую интеграцию без async/await/yield (asyncio, twisted), без коллбеков (js) и без хаков (gevent).


Ну и про Lua вопрос по-прежнему открытый.

UFO landed and left these words here

Понятнее не стало. Вы просто повторили то, что я и так прекрасно знаю, а про детали реализации Coro умолчали.


а в Coro это делает schedule — он тупо приостанавливает выполнение текущей до тех пор пока её не разбудят извне.

Что такое «тупо приостанавливает»? Каков конкретный механизм приостановки? Как предотвращается полное блокирование текущего потока при приостановке?

UFO landed and left these words here
стек можно переключить

Вот это действительно уже многое объясняет, но тем не менее как это реализовано в Perl? Запрос в гугле вроде «perl switch stack» ничего внятного не выдаёт.


Питоновый gevent (точнее, лежащий в его основе greenlet) тоже выполняет переключение стека, но делает это грязными хаками с вмешательством в работу интерпретатора и копированием стека в кучу и обратно.

UFO landed and left these words here

Ну в общем понятно, вы предпочитаете асинхронщину, которая требует внешнего рантайма (по отношению к коду работающей программы) или в которую внешний рантайм вкорячивается хаками :)


async/await не совсем хаки (и уж точно не грязные), они что-то вроде синтаксического сахара для yield, а yield был в питоне задолго до всякой асинхронщины (и какой-нибудь асинхронный twisted был реализован через yield). А вот считать ли yield «стандартизированным хаком» или нет — чёт не знаю.


В отличие от gevent и прочих libcoro, asyncio реализован на чистом питоне (есть сишная реализация, но она чисто для улучшения производительности и необязательна) и не требует сторонних хаков, и даже использования async/await на самом деле не требует (они появились после появления asyncio, а поначалу вместо них использовался yield from). Как следствие, для «переключения стека» приходится городить цепочку из await'ов (или yield'ов), которые вернут управление обратно в event loop (который тоже реализован на чистом питоне и является обычной частью программы, а не внешним рантаймом).


Впрочем, я всё равно не знаю, почему асинхронщина в питоне была реализована именно так, но предполагаю, что это ж неспроста (всё-таки gevent уже давно есть, и если не позаимствовали его идею, значит наверное на то были причины). Надо бы почитать PEP и дискуссии в mailing lists на эту тему какие-нибудь

Lwp, к ней приделали имплементатор и она стала поддерживать работу в асинхронной парадигме

Если вы имеете в виду HTTP::Async, то в нём поток полностью блокируется до тех пор, пока все запрошенные данные не будут скачаны — это нельзя считать настоящей асинхронностью. Если же хочется без блокирования, то придётся обмазывать код постоянным дёрганием $async->poke; и $async->next_response;, так что тоже мимо

UFO landed and left these words here
Этот обзор очень куцый. У FastAPI отличная документация, лучше просто пролистать ее, чтобы сложилось понимание.
Выше пишут про Flask, marshmallow и web_args. Но здесь сразу же отличие, причем для кого-то оно может показаться существенным. Валиация у FastAPI построена на питоновской типизации, которая доступна начиная с 3.6. Не нужно тащить новый синтаксис.

Вот это, написанное на webargs + marshmallow:
@use_args(UserSchema())
def index(args):

Будет выглядеть вот так:
def index(user: UserSchema):

Генерация документации автоматическая. С примерами. И она сразу из коробки, ничего настраивать не нужно.
Мне FastAPI очень понравился. Это после Flask, Django, Sanic, AioHttp.
Тут палка о двух концах. В случае webargs не обязательно на каждый чих тащить и писать объект, а пока по вашему написаному обязательно.
В моем примере модель представлена объектом. Это не «каждый чих». User в данном примере имеет не один атрибут.
Если бы это был не объект, а, скажем, просто имя юзера, то у webargs было бы fields.Str(), что, опять таки, инородный синтаксис.
Эм. Чем инородный то? Вполне нормально, декларативно объявлено что будет, более того ошибки обрабатываются на раз два. Из минусов которые я словил, что значения по умолчанию нельзя задавать в fields они инициализируются один раз, что например в случае времени так себе :)
Тем, что он импортируется из webargs, тем и инородный.
Не если у вас есть желание делать на каждый чих объект ну хорошо делайте.
Опять вы со своими объектами. Как вообще это связано?
Поясняю. У вас есть запрос который кроме самого объекта включает еще параметры и время к примеру.

Ну например
@use_args({
"account": fields.Id()
"user": fields.nested(UserSchema())
})
def index(args):


на вход будет
{
"account":1,
"user": {
   "id": "test"
   "name": "test"
}}

Как бы вопрос в этом. webargs позволяет не только объекты добавлять на вход.
Если мне на вход приходит user, внутри которого еще несколько полей, то для него будет использоваться одельный класс. Это удобно и логично, на мой взгляд. А если не хочется создавать классы, то просто передайте на вход отдельные поля:

def index(user_id: int, user_name: str, account_id: int)

Но конкретно ваш пример я бы писал вот так:

def index(account: str, user: UserSchema)

Можно писать и так:

def index(payload: Payload)

где Payload будет классом, в который войдут и account, и user, но это ведь не обязательно.
Речь идет про то что внешние объекты могут не совпадать с внутренними, причем это происходит весьма часто. Например у вас уже есть БД пересекается с внешним API весьма условно или не удобно.

def index(account: str, user: UserSchema)

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

Каждый раз, видя очередной пост про FastAPI, я начинаю дико пригорать с вот этого:


published_at: datetime = None

Где Optional, вашужмать? Такой код не проходит проверку в mypy --strict.


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

Смею предположить, что просто данный пример из среды, где не используют mypy, а типы там лишь для fastapi и pydantic'а.
UFO landed and left these words here
Действительно fast, было интересно, спасибо.

Очень, очень, очень медленно работает.

Это не притензия к FastApi, но наверное к всему питону. Обычные рутинные операции по вычитке 200 строчек из бд и их обработка легко может занимать 10-20 секунд, что на .NET, Java, Node.js невозможно представить.

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

Sign up to leave a comment.

Articles