Alembic can't run migrations in async mode if loop already exists. #1355
Replies: 4 comments 4 replies
-
it's not clear how you are invoking Alembic commands, which themselves are not awaitable, however any sync-style function can be run in an asynchronous context using SQLAlchemy's run_sync() services, see https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html#running-synchronous-methods-and-functions-under-asyncio |
Beta Was this translation helpful? Give feedback.
-
Hi,
standard asyncio rules apply, you can't have more than one loop running on a thread. Can't you use https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_until_complete instead of asyncio.run if you already have a loop? |
Beta Was this translation helpful? Give feedback.
-
I found a workaround but I don't really like it. def run_migrations_online_sync():
# I have to delete +asyncpg from DSN
conf = config.get_section(config.config_ini_section)
conf["sqlalchemy.url"] = conf["sqlalchemy.url"].replace("+asyncpg", "")
connectable = engine_from_config(
conf,
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
# This doesn't work.
# connectable = async_engine_from_config(
# config.get_section(config.config_ini_section),
# prefix="sqlalchemy.",
# poolclass=pool.NullPool,
# ).sync_engine
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online_sync() In both cases (if I create sync engine with +asycnpg in dsn and if I create async engine and take sync_engine from it) the code is fails with the following exception: sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/20/xd2s) |
Beta Was this translation helpful? Give feedback.
-
I was in similar situation as yours and got the same issues. Exploring Alembic documentation I found Programmatic API use (connection sharing) With Asyncio which gave me a solution. This should also correspond to @zzzeek answer. You could edit def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
connectable = config.attributes.get("connection", None)
if connectable is None:
asyncio.run(run_async_migrations())
else:
do_run_migrations(connectable) and use this import asyncio
from alembic import command
from alembic.config import Config
from sqlalchemy.ext.asyncio import create_async_engine
def run_upgrade(connection, cfg):
cfg.attributes["connection"] = connection
command.upgrade(cfg, "head")
async def main():
alembic_cfg = Config("./alembic.ini")
async_engine = create_async_engine(
"postgresql+asyncpg://postgres:postgres@localhost:5432/postgres", echo=True
)
async with async_engine.begin() as conn:
await conn.run_sync(run_upgrade, alembic_cfg)
await asyncio.sleep(20)
asyncio.run(main()) Hoping this may help you |
Beta Was this translation helpful? Give feedback.
-
Describe the bug
Alembic docs gives a snippet on how to run migrations in async mode. This snippet only works if event loop do not exist and
asyncio.run()
will create a new one.In my current setup I'm trying to run migrations to create a db for (async) tests. So the loop is already exists and I can't use
asyncio.run()
. So I've tried to useloop.create_task(run_migrations_online())
. This worked tillcontext.configure(...)
line of code.Expected behavior
It would be great if alembic migration could be ran in async mode if loop is already exist.
To Reproduce
asyncio.run(run_migrations_online())
toasyncio.get_event_loop().create_task(run_migrations_online())
main.py
env.py
Error
Versions.
Additional context
So there you can find a zip archive with code. There is an extra comments, related to this problem
alembic-issue.zip
In this exact code it's possible to run a migrations via
asyncio.run()
(could someone explain why this happens? I have same thing in my project, but it happens randomly - one out of 5 runs the migrations will execute, other runs will be ended with asyncio exception about the already running event loop).Have a nice day!
Beta Was this translation helpful? Give feedback.
All reactions