Search code examples
pythonsqlalchemyalembic

alembic create_table using declarative_base derived objects


I have an Alchemy ORM object:

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()


class MyORM(Base):
    id = Column(Integer, primary_key=True)
    name = Column(String(128), unique=True, nullable=False)

When using alembic to create the table I do the following:

def upgrade():
    op.create_table(
        'myorm',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('name', sa.String(128), nullable=False),
    )

Question: Is there a way to use the MyORM class to create the table? Something like this:

def upgrade():
    op.create_table(
        'myorm',
        sa.BaseObject(MyORM)
    )

Solution

  • This is exactly what Alembic migrations are trying to avoid. If you tie your migration to the current state of your model, it will not be a consistent upgrade path.

    You can use declarative in your migrations to create tables and migrate data, but not to alter. You will have to re-create the definitions separate from the application definitions. This can be useful if you want to do a data migration and are more familiar with ORM queries instead of core queries.

    Here is an example migration that creates Foo and Bar models with a many-to-many relationship using declarative, creates the tables, and inserts some data.

    """declarative
    
    Revision ID: 169ad57156f0
    Revises: 29b4c2bfce6d
    Create Date: 2014-06-25 09:00:06.784170
    """
    
    revision = '169ad57156f0'
    down_revision = '29b4c2bfce6d'
    
    from alembic import op
    import sqlalchemy as sa
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker, relationship
    
    Session = sessionmaker()
    Base = declarative_base()
    
    
    class Foo(Base):
        __tablename__ = 'foo'
    
        id = sa.Column(sa.Integer, primary_key=True)
        name = sa.Column(sa.String, nullable=False, unique=True)
    
    
    class Bar(Base):
        __tablename__ = 'bar'
    
        id = sa.Column(sa.Integer, primary_key=True)
        name = sa.Column(sa.String, nullable=False, unique=True)
        foos = relationship(Foo, lambda: foo_bar, backref='bars')
    
    
    foo_bar = sa.Table(
        'foo_bar', Base.metadata,
        sa.Column('foo_id', sa.Integer, sa.ForeignKey('foo.id'), primary_key=True),
        sa.Column('bar_id', sa.Integer, sa.ForeignKey('bar.id'), primary_key=True)
    )
    
    def upgrade():
        bind = op.get_bind()
    
        Base.metadata.create_all(bind=bind)
    
        session = Session(bind=bind)
        session._model_changes = False  # if you are using Flask-SQLAlchemy, this works around a bug
    
        f1 = Foo(name='f1')
        f2 = Foo(name='f2')
        b1 = Bar(name='b1')
        b2 = Bar(name='b2')
    
        f1.bars = [b1, b2]
        b2.foos.append(f2)
    
        session.add_all([f1, f2, b1, b2])
        session.commit()
    
    
    def downgrade():
        bind = op.get_bind()
    
        # in this case all we need to do is drop the tables
        # Base.metadata.drop_all(bind=bind)
    
        # but we could also delete data
        session = Session(bind=bind)
        session._model_changes = False  # if you are using Flask-SQLAlchemy, this works around a bug
    
        b1 = session.query(Bar).filter_by(name='b1').one()
    
        session.delete(b1)
        session.commit()