Reputation: 61
I am attempting to run Alembic migrations every time my FastAPI application starts up. I am deploying my FastAPI application using a Docker container.
Here is the code I have implemented to achieve this:
from alembic.config import Config
from alembic import command
def run_migrations():
alembic_cfg = Config("alembic.ini")
command.upgrade(alembic_cfg, "head")
@app.on_event("startup")
async def startup_event():
run_migrations()
However, I am facing an issue where the Docker container stops once the startup is complete. Additionally, I noticed that the logger changes to Alembic instead of FastAPI.
I would like to understand how I can ensure that the migrations run successfully without stopping the container and while maintaining FastAPI logging. Any insights or suggestions would be appreciated.
Upvotes: 6
Views: 8423
Reputation: 1
axel's answer works perfectly for regular sqlalchemy engines. However, if you are using an async engine, you will need to use
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from alembic.config import Config
from alembic import command
log = logging.getLogger("uvicorn")
async def run_migrations():
alembic_cfg = Config("alembic.ini")
await asyncio.to_thread(command.upgrade, alembic_cfg, "head")
@asynccontextmanager
async def lifespan(app_: FastAPI):
log.info("Starting up...")
log.info("run alembic upgrade head...")
await run_migrations()
yield
log.info("Shutting down...")
app = FastAPI(lifespan=lifespan)
i.e., it should be asyncio.to_thread(command.upgrade, alembic_cfg, "head")
.
This is because alembic init -t async alembic
generates something like
def do_run_migrations(connection: Connection) -> None:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations() -> None:
connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
await connectable.dispose()
def run_migrations_online() -> None:
asyncio.run(run_async_migrations())
which requires a new thread to be run. Trying to run it in the current event loop does not work, because alembic does not use an async context manager (see this github issue).
Upvotes: 0
Reputation: 51
"startup" events are deprecated according to the documentation: https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated.
The lifespan parameter of the FastAPI app is the way to handle startup and shutdown: https://fastapi.tiangolo.com/advanced/events/#lifespan.
You could do this, I tested it with a Docker command that only runs uvicorn:
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from alembic.config import Config
from alembic import command
log = logging.getLogger("uvicorn")
def run_migrations():
alembic_cfg = Config("alembic.ini")
command.upgrade(alembic_cfg, "head")
@asynccontextmanager
async def lifespan(app_: FastAPI):
log.info("Starting up...")
log.info("run alembic upgrade head...")
run_migrations()
yield
log.info("Shutting down...")
app = FastAPI(lifespan=lifespan)
Upvotes: 4
Reputation: 23
I was also stuck in the same part. In my case, there was a error while running alembic upgrade. It was not shown in logs. It silently terminates the program because of which the fastapi server stops.
Try to run the alembic upgrade head command manually or increase the verbosity while calling upgrade, you will come to know about the error.
Upvotes: 0