Асинхронный django: продолжение

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Некоторое время назад я писал о том, как можно добавить асинхронность в django. Теперь прошлый пост имеет только исторический интерес, если не читали, то не читайте. Принципиально подход не изменился, но стало гораздо проще и понятнее - не могу не написать. И пример кода другой.

Я писал только о django orm, и камнем преткновения там является обращение к базе данных, но подход можно обобщить на любой код, содержащий операции ввода-вывода.

В общем, выясняется, что добавить асинхронность в django - по сути, не сложнее, чем портировать его в асинхронность - раз и навсегда. Вы примерно представляете, как бы это нужно было делать: берём асинхронный драйвер с API примерно похожим на драйверы django и заменяем в недрах кода вызовы вроде driver.execute_sql(sql, params) на вызовы вроде await async_driver.execute_sql(sql, params). Конечно, мы сразу получим SyntaxError потому что нельзя использовать await в обычной функции, и будем вынуждены добавить async и await во всём стеке вызовов.

Весь API django отлично применим к асинхронной версии, за небольшими адаптации. Например, ленивых атрибутов больше не будет, а обращение к такому атрибуту будет возможно, если вы заранее извлекли его из базы, например, с помощью prefetch_related или select_related (для обращения к атрибуту, который требует обращения к базе, нужен другой API).

В принципе, работа несложная и даже относительно небольшая. Так вот, моя точка зрения, что добавление асинхронности (не ломая старый, синхронный, API) - это так же просто - возможно, за небольшими накладками.

В общем, есть в питоне такая вещь, как yield from, которая одинаково применима как в синхронном, так и в асинхронном контексте. Под капотом, разумеется, ничего не меняющая. На мой взгляд, её и нужно использовать для таких проектов, которые хотят поддержать одновременно синхронную и асинхронную версию. Даже для тех, которые пишутся с нуля - ну как мне кажется. А уж для легаси - вообще без разговоров.

Итак, у нас есть пул-реквест - в ветку main django (между прочим, пул из ветки main ещё ни разу не приводил к конфликтам). В нём, конечно, поддерживается только малая часть API django - конечно, потому что другие цели не ставились.

Что мы в нём видим? Конечно, множество вызовов yield from перед функциями - как я обещал.

SyncDriver и AsyncDriver на самом деле не драйверы, а классы со вспомогательными функциями. Переименовывать лень, да и не очень понятно, как назвать. Как видите, они реализуют функцию execute(operations)Дело в том, что мы превратили наш код в генератор, и эти самые 2 класса, которые не драйверы, запрашивают у него "операции", по одной, которые нужно исполнить: функции, как они есть, с параметрами.

Где же сами драйверы? Это тоже достаточно интересно. Вот где:

class SQLCompiler:
    a = Asynchrony()

    @a.branch()
    def execute_sql(self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE):
        ...

    @a.branch()
    async def execute_sql(self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE):
        ...

Как Вы поняли, это 2 ветки одной и той же функции - синхронная и асинхронная, которые и пользуются драйвером. Вот так всё просто, и таких веток можно иметь сколько угодно. Кстати, asyncpg ни капли не совместим с dbapi - я уверен, без всяких разумных причин.

Всё остальное не так интересно. Вот так, например, я сделал кверисеты Awaitable

def __await__(self):
    return self.__iter__().__await__()

Или например, у нас многие функции содержат yield from - что же делать, если у нас функция сама является генератором (в django часто используется yield для создания итераторов). Я нашёл простое решение: поместим конструкции yield внутрь другой функции (в моём случае - def iterator, и они не будут конфликтовать).

Ещё вы можете увидеть декоратор @consume_generator. Он превращает генератор в обычную функцию (которая возможно вернёт корутину). Предназначено для вызова извне (читай, пользователем). В общем, любопытные всё увидят.

Это всё. Репозиторию pwtail/django можно ставить звёздочки

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Для меня это
50% Открытие 1
50% Америки 1
Проголосовали 2 пользователя. Воздержался 1 пользователь.
Источник: https://habr.com/ru/post/592859/


Интересные статьи

Интересные статьи

В предыдущих статьях, опубликованных мной на Хабре («Автоматический кошачий туалет» и «Туалет для Мэйн-Кунов») представлялась модель туалета, реализованного на ином, от существующих, принципе смы...
Ранее в одном из наших КП добавление задач обрабатывалось бизнес-процессами, сейчас задач стало столько, что бизнес-процессы стали неуместны, и понадобился инструмент для массовой заливки задач на КП.
Устраивать конкурсы в инстаграме сейчас модно. И удобно. Инстаграм предоставляет достаточно обширный API, который позволяет делать практически всё, что может сделать обычный пользователь ручками.
В 1С Битрикс есть специальные сущности под названием “Информационные блоки, сокращенно (инфоблоки)“, я думаю каждый с ними знаком, но не каждый понимает, что это такое и для чего они нужны
1С Битрикс: Управление сайтом (БУС) - CMS №1 в России по версии портала “Рейтинг Рунета” за 2018 год. На рынке c 2003 года. За это время БУС не стоял на месте, обрастал новой функциональностью...