Alembic
- Install
alembicwithpip install alembic(or some equivalent package install command) - Initialize
alembic- If using a
srcdirectory, i.e.src/app, initializealembicatsrc/app/alembicalembic init src/app/alembic
- If using a “flat” repository, simply run
alembic init alembic- You can use any name for the directory instead of “alembic.” For instance, another common convention is to initialize
alembicwithalembic init migrations - Whatever directory name you choose, use that throughout these instructions where you see references to the
alembicinit path
- You can use any name for the directory instead of “alembic.” For instance, another common convention is to initialize
- If using a
- Edit the
alembic.inifile- Change
script_locationto the path you set for alembic, i.e.src/app/alembic - Edit
prepend_sys_path, set tosrc/app- This adds the script’s path to
alembiccan do things like importing your app’s config, loading the SQLAlchemyBaseobject, etc
- This adds the script’s path to
- Change
## alembic.ini
[alembic]
script_location = src/app/alembic
...
prepend_system_path = src/app
...- Edit the
alembicenv.pyfile, which should be insrc/app/alembic(or whatever path you initializedalembicin)- If you initialized a SQLAlchemy database URI string (of type
sqlalchemy.URL), you can import it inenv.py, or you can create a new one:
- If you initialized a SQLAlchemy database URI string (of type
## src/app/alembic.py
...
import sqlalchemy as sa
## Using a SQLite example
DB_URI: sa.URL = sa.URL.create(
drivername="sqlite+pysqlite",
username=None,
password=None,
host=None,
port=None,
database="database.sqlite"
)
...
## Set config's sqlalchemy.url value, after "if config.config_filename is not None:"
config.set_main_option(
"sqlalchemy.url",
## Use .render_as_string(hide_password=False) with sqlalchemy.URL objects,
# otherwise database password will be "**"
DB_URI.render_as_string(hide_password=False)
)
...
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from app.module.models import SomeModel # Import project's SQLAlchemy table classes
from app.module.database import Base # Import project's SQLAlchemy Base object
target_metadata = Base().metadata # Tell Alembic to use the project's Base() objectPerforming Alembic migrations
- Perform first/initial migration:
alembic revision --autogenerate -m "initial migration"- If no changes have been made,
alembic revisionwill create an empty revision, with no changes.
- If no changes have been made,
- Upgrade:
- If you are doing multiple migrations at once, you can do:
alembic upgrade +1- (
+1is how many migration levels to apply at once. If you have multiple migrations that have not been committed, you can use+2,+3, etc)
- (
- To push the current revision:
alembic upgrade head- This will push all current migrations, up to the current migration, to the database
- If you are doing multiple migrations at once, you can do:
- To Downgrade/revert a migration:
alembic downgrade -1- (
-1is how many migration levels to revert, can also be-2,-3, etc)
- (
Manually specify migration changes when Alembic does not correctly detect them
Some changes, like renaming a column, are not possible for Alembic to accurately track. In these cases, you will need to create an Alembic migration, then edit the new file in alembic/versions/{revision-hash}.py.
In the def upgrade() section, comment the innacurate op.add_column()/op.drop_column(), then add something like this (example uses the User class, with a renamed column .username -> .user_name):
# alembic/versions/{revision-hash}.py
...
def upgrade() -> None:
...
## Comment the inaccurate changes
# op.add_column("users", sa.Column("user_name", sa.VARCHAR(length=255), nullable=True))
# op.drop_column("users", "username)
## Manually add a change of column type that wasn't detected by alembic
op.alter_column("products", "description", type_=sa.VARCHAR(length=3000))
## Manually describe column rename
op.alter_column("users", "username", new_column_name="user_name")Also edit the def downgrade() function to describe the changes that should be reverted when using alembic downgrade:
# alembic/versions/{revision-hash}.py
...
def downgrade() -> None:
## Comment the inaccurate changes
# op.add_column("users", sa.Column("user_name", sa.VARCHAR(length=255), nullable=True))
# op.drop_column("users", "username)
## Manually describe changes to reverse if downgrading
op.alter_column("users", "user_name", new_column_name="username")
op.drop_column("products", "price")
After describing manual changes in an Alembic version file, you need to run alembic upgrade head to push the changes from the revision to the database.
Last updated on