Как стать автором
Обновить

Комментарии 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, если не нужны преобразования) и задать валидатор как тут (раньше можно было декоратором, а теперь автор снова вернул явный словарь).


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

НЛО прилетело и опубликовало эту надпись здесь

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

В заголовок добавьте: (Python)

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

Причем декларативности там будет побольше.
и тут будет асинхронность?

Вместе с Gevent будет.

monkey.patch_all, усложняем и усложняем
НЛО прилетело и опубликовало эту надпись здесь
Зависит от того какой WSGI сервер поставите. Ну и да мантра такая теперь? Асинхронность сразу дает +100500 к производительности?
Асинхронность сразу дает +100500 к производительности?

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

Но да именно так.
если на любом уровне Вы применили асинхронный запрос, то все уровни выше придётся "обмазывать" 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;, так что тоже мимо

НЛО прилетело и опубликовало эту надпись здесь
Этот обзор очень куцый. У 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'а.
НЛО прилетело и опубликовало эту надпись здесь
Действительно fast, было интересно, спасибо.

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

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории