Комментарии 58
Мне казалось, я довольно ясно описал, чтож, артикулирую:
- асинхронность
- валидация и документация API из коробки
- быстродействие
aiohttp тормозной даже по меркам питона. Это известно даже мне, можно сказать, неблизкому к web-разработке, но имевшему с ним дело. Помнится, в том проекте мы просто выкинули его вместе с питоном и переписали на Go с fasthttp, но это не суть.
Если мне на слово не верите, бенчмарки тоже есть: https://www.techempower.com/benchmarks/
Если смотреть в списке Fortunes, то starlette на 159 месте, fastapi на 176, а aiohttp на 264
имхо 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, если не нужны преобразования) и задать валидатор как тут (раньше можно было декоратором, а теперь автор снова вернул явный словарь).
То есть мне получилось в целом переложить всю валидацию и сериализацию на автоматический код по спеке и это даже работает в проде :) С ответами пока сложнее :(
flask + web_args + marshmallow + ORM по вкусу.
Причем декларативности там будет побольше.
Вместе с Gevent будет.
Асинхронность сразу дает +100500 к производительности?
Нет, но статья про асинхронный фреймворк, поэтому всё в этом контексте и обсуждается. Ну и сразу, любой WSGI сервер синхронный, для асинхронности нужен другой протокол ASGI
Почему? Довольно банальный ответ, потому что со стороны клиента, пока сервер не ответит будет висеть ожидание ответа. А от того что асинхронность добавлена ниже, это никак не изменится.
Но да именно так.
если на любом уровне Вы применили асинхронный запрос, то все уровни выше придётся "обмазывать" async/await операторами
А можно посмотреть на язык, в котором не придётся?
Perl (Coro)
Насколько я понял туториал, весь код придётся обмазывать вызовом cede;
или обёртками над ним, так что мимо
Lua (Tarantool)
Можно ссылку? По этим ключевикам мне почему-то не удалось нагуглить что-то асинхронное
Си (libcoro)
Можно тоже ссылку? Кроме собственно исходников либы мне не удалось нагуглить каких-либо примеров или туториалов
Lwp, к ней приделали имплементатор и она стала поддерживать работу в асинхронной парадигме
Как ни странно, это мне тоже не удалось нагуглить, что я делаю не так? :(
неправильно поняли.
А как правильно понимать? Насколько я вижу примеры, они все обмазаны местными аналогами await (schedule, rouse_wait и т.п.) Возможно, я просто слишком плохо знаю перл, но пока я не понимаю, чем это принципиально лучше питона.
а обычная машина событий спит и переключается на сокетах и таймерах
Питоновая машина событий работает точно так же
там абсолютно всё асинхронное, но выглядит абсолютно всё как синхронное :)
Это какой-то прохаченный Lua внутри Tarantool, что ли? В питоне такая штука тоже издавна есть, называется gevent. Или он не такой уж и прохаченный и я могу запустить эту асинхронщину на самом обычном Lua?
вот имплементатор к ней чтобы она стала асинхронной
Читаю описание:
It loads Coro::Select, overwriting the perl select builtin globally.
Это ровно то же самое, что делает питоновый gevent.
Короче, судя по всему, вам в питоне просто нужен gevent, чтобы вы были довольны :)
во первых там нет аналога await
А чем тогда являются schedule, rouse_wait и т.п., если не аналогами await? Я по-прежнему не понимаю.
нет, файберы в любом языке примерно одинаковы
В Lua нет файберов, а в примерах Tarantool по ссылке нет явных обращений к файберам для засыпания/пробуждения и прочего. Значит Lua необходимо прохачить, чтобы эти самые файберы появились сами по себе в синхронном коде. Я что-то опять неправильно понимаю?
Есть какая-нибудь статья о его внутренностях и устройстве работы? В то время как про устройство работы asyncio/gevent/js promise и пр. есть куча матчасти и я их прекрасно понимаю, про coro мне удаётся нагуглить только примеры использования, а исходники читать лениво, отчего я его продолжаю не понимать.
В частности, по моим текущим знаниям за фразой «интеграция с калбечной евентмашиной» скрывается какая-то тёмная магия, так как я не знаю, как возможно сделать такую интеграцию без async/await/yield (asyncio, twisted), без коллбеков (js) и без хаков (gevent).
Ну и про Lua вопрос по-прежнему открытый.
Понятнее не стало. Вы просто повторили то, что я и так прекрасно знаю, а про детали реализации Coro умолчали.
а в Coro это делает schedule — он тупо приостанавливает выполнение текущей до тех пор пока её не разбудят извне.
Что такое «тупо приостанавливает»? Каков конкретный механизм приостановки? Как предотвращается полное блокирование текущего потока при приостановке?
стек можно переключить
Вот это действительно уже многое объясняет, но тем не менее как это реализовано в Perl? Запрос в гугле вроде «perl switch stack» ничего внятного не выдаёт.
Питоновый gevent (точнее, лежащий в его основе greenlet) тоже выполняет переключение стека, но делает это грязными хаками с вмешательством в работу интерпретатора и копированием стека в кучу и обратно.
Ну в общем понятно, вы предпочитаете асинхронщину, которая требует внешнего рантайма (по отношению к коду работающей программы) или в которую внешний рантайм вкорячивается хаками :)
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;
, так что тоже мимо
Выше пишут про 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 было бы
fields.Str()
, что, опять таки, инородный синтаксис.Ну например
@use_args({
"account": fields.Id()
"user": fields.nested(UserSchema())
})
def index(args):
на вход будет
{
"account":1,
"user": {
"id": "test"
"name": "test"
}}
Как бы вопрос в этом. webargs позволяет не только объекты добавлять на вход.
def index(user_id: int, user_name: str, account_id: int)
Но конкретно ваш пример я бы писал вот так:
def index(account: str, user: UserSchema)
Можно писать и так:
def index(payload: Payload)
где
Payload
будет классом, в который войдут и account
, и user
, но это ведь не обязательно.def index(account: str, user: UserSchema)
Так пойдет и будет идентично тому что я написал.
Каждый раз, видя очередной пост про FastAPI, я начинаю дико пригорать с вот этого:
published_at: datetime = None
Где Optional
, вашужмать? Такой код не проходит проверку в mypy --strict
.
Причём это не баг, а фича, и разработчики Pydantic предлагают просто отказаться от --strict
. Но тогда какой вообще смысл в типизации?
Очень, очень, очень медленно работает.
Это не притензия к FastApi, но наверное к всему питону. Обычные рутинные операции по вычитке 200 строчек из бд и их обработка легко может занимать 10-20 секунд, что на .NET, Java, Node.js невозможно представить.
Не ведитесь на слово Fast в названии, подумайте дважды выбирая питон для бекенда или проводите тесты и учитывайте низкую производительность изначально.
Знакомство с FastAPI