Pull to refresh

Comments 17

People constantly asked about ability to run aiohttp servers together with other asyncio code, but aiohttp.web.run_app() is blocking synchronous call.

run_app() provides a simple blocking API for running an Application.

For starting the application asynchronously or serving on multiple HOST/PORT AppRunner exists.

Спасибо за ценное замечание! Действительно, мой косяк - надо внимательнее читать документацию. Поправил и плюс в карму.

sql = f'INSERT INTO requests VALUES ("{datetime.now()}", "{city}", "{weather}")'

await db.execute(sql)

Я понимаю, что примеры упрощённые. Но ведь написать запрос без потенциального SQL injection заняло бы столько же строчек. Вы же учить новичков пытаетесь, так учите хорошему.

url = f'http://api.openweathermap.org/data/2.5/weather' \

f'?q={city}&APPID=2a4ff86f9aaa70041ec8e82db64abf56'

Так подставлять параметр в URL тоже не безопасно.

Спасибо, согласен. Поправил.

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

Неважно какой фреймворк вы будете использовать: FastAPITornadoFalcon или какой-нибудь еще. Принцип останется тот же самый как в старом добром aiohttp: создаем ручку и в ней нанизываем "шашлык" из вызовов асинхронных функций.

Очень интересная статья, а можно, пожалуйста, небольшой пример кода, как запустить FastAPI в async режиме?

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

Если кто-то решит использовать это руководство для написания продуктового кода, то обращаю внимание, что создавать сессию на каждый http запрос - это моветон. Это очень распространенная ошибка при использовании Aiohttp. Сессии надо создать и закрывать только при старте и соотвественно при закрытии приложения. Прочитать можно тут, тут

И на главной есть сноска.

Но для учебных примеров такое использование сессий норм!

Да, совершенно верно. Канонически верное применение единой сессии для всего приложения показано здесь.

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

А теперь идем в документацию aiologger и читаем:

That’s why logging to files is NOT truly asyncaiologger implementation of file logging uses aiofiles, which uses a Thread Pool to write the data. Keep this in mind when using aiologger for file logging.

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

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

Выбор именно sqlite в качестве СУБД для проекта на асинхронном фреймворке тоже, честно говоря, странный. Дело в том, что sqlite - это файловая СУБД, а значит сталкивается с теми же ограничениями системных вызовов, что не позволяют сделать действительно асинхронной работу с файлами в aiologger. О чем нам авторы также честно пишут на странице проекта:

aiosqlite allows interaction with SQLite databases on the main AsyncIO event loop without blocking execution of other coroutines while waiting for queries or data fetches. It does this by using a single, shared thread per connection. This thread executes all actions within a shared request queue to prevent overlapping actions.

Спасибо за критику.

Относительно aiologger читаем:

Tldr; aiologger is only fully async when logging to stdout/stderr. If you log into files on disk you are not being fully async and will be using ThThreads.Ка

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

Что касается sqlite, то здесь все просто. Статья учебная и перегружать текст подробностями установки и подключения к "настоящей" БД я посчитал излишним. Тут и к использованию SQL тоже можно прицепиться, ведь на практике в большинстве случаев применяется ORM. Но грузить тут новичка еще и SqlAlchemy - это уже запредельный перебор)

Я бы с удовольствием почитал про SqlAlchemy 2.0 для Postgre async. Насколько могу судить там синтаксис чуть изменился, ну либо я что то не понял.

А там фишка вроде такая, что уже в версии 1.4 старый ORM синтаксис поддерживается в синхронном варианте как deprecated, а в асинхронном - вообще никак. Как я понял, в 2.0 они полностью заблочат старый стиль и для синхронного драйвера тоже.

Так что уже сейчас нет вообще смысла использовать олдскульный session.query.

У меня в текущем проекте вынужденно поддерживаются оба драйвера, но стиль запросов уже используется единый - новый. Как-то вот так это может выглядеть в синхронном варианте:

with mdl.session as session:
    with session.begin():
        query = select(
            mdl.Passport.name.label('passport_name'), mdl.Passport.value
        ).filter(mdl.Passport.value >= 37)

        results = session.execute(query)

for result in results:
  print(result.passport_name, result.value)

А в асинхронном так:


async with mdl.async_session as session:
  async with session.begin():
    query = select(
      mdl.Passport.name.label('passport_name'), mdl.Passport.value
    ).filter(mdl.Passport.value >= 37)

    results = await session.execute(query)

for result in results:
  print(result.passport_name, result.value)

И в 2.0, как я понимаю, все это будет работать аналогично.

А так ли осмысленно в последнем примере асинхронно создавать таблицу в базе данных? Ведь эта операция вызвывается только один раз и ни с чем не параллелится.

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

alembic вполне научился с асинхронным драйвером работать
env.py
import asyncio
from logging.config import fileConfig

from sqlalchemy import create_engine
from sqlalchemy import pool
from sqlalchemy.ext.asyncio import AsyncEngine

from alembic import context


config = context.config

fileConfig(config.config_file_name)

target_metadata = <db.Model.metadata>  # тут мета моделей

db_url = f"postgresql+asyncpg://postgres:postgres@localhost"


def run_migrations_offline():
    context.configure(
        url=db_url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


def do_run_migrations(connection):
    context.configure(connection=connection, target_metadata=target_metadata)

    with context.begin_transaction():
        context.run_migrations()


async def run_migrations_online():
    connectable = AsyncEngine(create_engine(db_url, poolclass=pool.NullPool, future=True))
    async with connectable.connect() as connection:
        await connection.run_sync(do_run_migrations)

    await connectable.dispose()


if context.is_offline_mode():
    run_migrations_offline()
else:
    asyncio.run(run_migrations_online())

О, круто, спасибо! Попробую.

Only those users with full accounts are able to leave comments. Log in, please.

Articles