标签归档:alembic

如何在Alembic升级脚本中执行插入和更新?

问题:如何在Alembic升级脚本中执行插入和更新?

我需要在Alembic升级期间更改数据。

我目前在第一个修订版中有一个“玩家”表:

def upgrade():
    op.create_table('player',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.Unicode(length=200), nullable=False),
        sa.Column('position', sa.Unicode(length=200), nullable=True),
        sa.Column('team', sa.Unicode(length=100), nullable=True)
        sa.PrimaryKeyConstraint('id')
    )

我想介绍一个“团队”表。我创建了第二个修订版:

def upgrade():
    op.create_table('teams',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.String(length=80), nullable=False)
    )
    op.add_column('players', sa.Column('team_id', sa.Integer(), nullable=False))

我希望第二次迁移也添加以下数据:

  1. 填充团队表:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
  2. 根据players.team名称更新players.team_id:

    UPDATE players AS p JOIN teams AS t SET p.team_id = t.id WHERE p.team = t.name;

如何在升级脚本中执行插入和更新?

I need to alter data during an Alembic upgrade.

I currently have a ‘players’ table in a first revision:

def upgrade():
    op.create_table('player',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.Unicode(length=200), nullable=False),
        sa.Column('position', sa.Unicode(length=200), nullable=True),
        sa.Column('team', sa.Unicode(length=100), nullable=True)
        sa.PrimaryKeyConstraint('id')
    )

I want to introduce a ‘teams’ table. I’ve created a second revision:

def upgrade():
    op.create_table('teams',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.String(length=80), nullable=False)
    )
    op.add_column('players', sa.Column('team_id', sa.Integer(), nullable=False))

I would like the second migration to also add the following data:

  1. Populate teams table:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
    
  2. Update players.team_id based on players.team name:

    UPDATE players AS p JOIN teams AS t SET p.team_id = t.id WHERE p.team = t.name;
    

How do I execute inserts and updates inside the upgrade script?


回答 0

您需要的是数据迁移,而不是Alembic文档中最普遍的模式迁移

该答案假设您使用声明式(而不是class-Mapper-Table或core)定义模型。使它适应其他形式应该相对简单。

请注意,Alembic提供了一些基本数据功能:op.bulk_insert()op.execute()。如果操作很少,请使用这些操作。如果迁移需要关系或其他复杂的交互作用,我更喜欢使用模型和会话的全部功能,如下所述。

以下是示例迁移脚本,该脚本设置了一些声明性模型,这些声明性模型将用于处理会话中的数据。关键点是:

  1. 使用所需的列定义所需的基本模型。您不需要每一列,只需要主键和将要使用的那些。

  2. 在升级功能中,用于op.get_bind()获取当前连接并与其建立会话。

    • 或者使用bind.execute()SQLAlchemy的较低级别直接编写SQL查询。这对于简单的迁移很有用。
  3. 像通常在应用程序中一样使用模型和会话。

"""create teams table

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 import orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Player(Base):
    __tablename__ = 'players'

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String, nullable=False)
    team_name = sa.Column('team', sa.String, nullable=False)
    team_id = sa.Column(sa.Integer, sa.ForeignKey('teams.id'), nullable=False)

    team = orm.relationship('Team', backref='players')


class Team(Base):
    __tablename__ = 'teams'

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String, nullable=False, unique=True)


def upgrade():
    bind = op.get_bind()
    session = orm.Session(bind=bind)

    # create the teams table and the players.team_id column
    Team.__table__.create(bind)
    op.add_column('players', sa.Column('team_id', sa.ForeignKey('teams.id'), nullable=False)

    # create teams for each team name
    teams = {name: Team(name=name) for name in session.query(Player.team).distinct()}
    session.add_all(teams.values())

    # set player team based on team name
    for player in session.query(Player):
        player.team = teams[player.team_name]

    session.commit()

    # don't need team name now that team relationship is set
    op.drop_column('players', 'team')


def downgrade():
    bind = op.get_bind()
    session = orm.Session(bind=bind)

    # re-add the players.team column
    op.add_column('players', sa.Column('team', sa.String, nullable=False)

    # set players.team based on team relationship
    for player in session.query(Player):
        player.team_name = player.team.name

    session.commit()

    op.drop_column('players', 'team_id')
    op.drop_table('teams')

迁移定义了单独的模型,因为代码中的模型表示数据库的当前状态,而迁移表示过程中的步骤。您的数据库可能沿着该路径处于任何状态,因此模型可能尚未与数据库同步。除非您非常小心,否则直接使用真实模型会导致缺少列,无效数据等问题。更清晰地明确说明要在迁移中使用的列和模型。

What you are asking for is a data migration, as opposed to the schema migration that is most prevalent in the Alembic docs.

This answer assumes you are using declarative (as opposed to class-Mapper-Table or core) to define your models. It should be relatively straightforward to adapt this to the other forms.

Note that Alembic provides some basic data functions: op.bulk_insert() and op.execute(). If the operations are fairly minimal, use those. If the migration requires relationships or other complex interactions, I prefer to use the full power of models and sessions as described below.

The following is an example migration script that sets up some declarative models that will be used to manipulate data in a session. The key points are:

  1. Define the basic models you need, with the columns you’ll need. You don’t need every column, just the primary key and the ones you’ll be using.

  2. Within the upgrade function, use op.get_bind() to get the current connection, and make a session with it.

    • Or use bind.execute() to use SQLAlchemy’s lower level to write SQL queries directly. This is useful for simple migrations.
  3. Use the models and session as you normally would in your application.

"""create teams table

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 import orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Player(Base):
    __tablename__ = 'players'

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String, nullable=False)
    team_name = sa.Column('team', sa.String, nullable=False)
    team_id = sa.Column(sa.Integer, sa.ForeignKey('teams.id'), nullable=False)

    team = orm.relationship('Team', backref='players')


class Team(Base):
    __tablename__ = 'teams'

    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String, nullable=False, unique=True)


def upgrade():
    bind = op.get_bind()
    session = orm.Session(bind=bind)

    # create the teams table and the players.team_id column
    Team.__table__.create(bind)
    op.add_column('players', sa.Column('team_id', sa.ForeignKey('teams.id'), nullable=False)

    # create teams for each team name
    teams = {name: Team(name=name) for name in session.query(Player.team).distinct()}
    session.add_all(teams.values())

    # set player team based on team name
    for player in session.query(Player):
        player.team = teams[player.team_name]

    session.commit()

    # don't need team name now that team relationship is set
    op.drop_column('players', 'team')


def downgrade():
    bind = op.get_bind()
    session = orm.Session(bind=bind)

    # re-add the players.team column
    op.add_column('players', sa.Column('team', sa.String, nullable=False)

    # set players.team based on team relationship
    for player in session.query(Player):
        player.team_name = player.team.name

    session.commit()

    op.drop_column('players', 'team_id')
    op.drop_table('teams')

The migration defines separate models because the models in your code represent the current state of the database, while the migrations represent steps along the way. Your database might be in any state along that path, so the models might not sync up with the database yet. Unless you’re very careful, using the real models directly will cause problems with missing columns, invalid data, etc. It’s clearer to explicitly state exactly what columns and models you will use in the migration.


回答 1

您还可以使用直接SQL参见(Alembic操作参考),如以下示例所示:

from alembic import op

# revision identifiers, used by Alembic.
revision = '1ce7873ac4ced2'
down_revision = '1cea0ac4ced2'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands made by andrew ###
    op.execute('UPDATE STOCK SET IN_STOCK = -1 WHERE IN_STOCK IS NULL')
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###

You can also use direct SQL see (Alembic Operation Reference) as in the following example:

from alembic import op

# revision identifiers, used by Alembic.
revision = '1ce7873ac4ced2'
down_revision = '1cea0ac4ced2'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands made by andrew ###
    op.execute('UPDATE STOCK SET IN_STOCK = -1 WHERE IN_STOCK IS NULL')
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###

回答 2

我建议使用临时表来使用SQLAlchemy核心语句(如官方文档中所述),因为它允许使用不可知论的SQL和pythonic编写,并且也是独立的。对于迁移脚本,SQLAlchemy Core是两全其美的。

这是概念的示例:

from sqlalchemy.sql import table, column
from sqlalchemy import String
from alembic import op

account = table('account',
    column('name', String)
)
op.execute(
    account.update().\\
    where(account.c.name==op.inline_literal('account 1')).\\
        values({'name':op.inline_literal('account 2')})
        )

# If insert is required
from sqlalchemy.sql import insert
from sqlalchemy import orm

session = orm.Session(bind=bind)
bind = op.get_bind()

data = {
    "name": "John",
}
ret = session.execute(insert(account).values(data))
# for use in other insert calls
account_id = ret.lastrowid

I recommend using SQLAlchemy core statements using an ad-hoc table, as detailed in the official documentation, because it allows the use of agnostic SQL and pythonic writing and is also self-contained. SQLAlchemy Core is the best of both worlds for migration scripts.

Here is an example of the concept:

from sqlalchemy.sql import table, column
from sqlalchemy import String
from alembic import op

account = table('account',
    column('name', String)
)
op.execute(
    account.update().\\
    where(account.c.name==op.inline_literal('account 1')).\\
        values({'name':op.inline_literal('account 2')})
        )

# If insert is required
from sqlalchemy.sql import insert
from sqlalchemy import orm

session = orm.Session(bind=bind)
bind = op.get_bind()

data = {
    "name": "John",
}
ret = session.execute(insert(account).values(data))
# for use in other insert calls
account_id = ret.lastrowid

目标数据库不是最新的

问题:目标数据库不是最新的

我想迁移一个Flask应用程序。我正在使用Alembic。

但是,我收到以下错误。

Target database is not up to date.

在网上,我读到它与此有关。 http://alembic.zzzcomputing.com/zh-CN/latest/cookbook.html#building-an-up-to-date-database-from-scratch

不幸的是,我不太了解如何使数据库保持最新状态,以及在何处/如何编写链接中给出的代码。如果您有迁移的经验,能否请您为我解释一下

谢谢

I’d like to make a migration for a Flask app. I am using Alembic.

However, I receive the following error.

Target database is not up to date.

Online, I read that it has something to do with this. http://alembic.zzzcomputing.com/en/latest/cookbook.html#building-an-up-to-date-database-from-scratch

Unfortunately, I don’t quite understand how to get the database up to date and where/how I should write the code given in the link. If you have experience with migrations, can you please explain this for me

Thanks


回答 0

创建迁移后,无论是手动还是as --autogenerate,都必须使用进行应用alembic upgrade head。如果db.create_all()从外壳程序使用alembic stamp head,则可以用来表示数据库的当前状态代表所有迁移的应用程序。

After creating a migration, either manually or as --autogenerate, you must apply it with alembic upgrade head. If you used db.create_all() from a shell, you can use alembic stamp head to indicate that the current state of the database represents the application of all migrations.


回答 1

这对我有用

$ flask db stamp head
$ flask db migrate
$ flask db upgrade

This Worked For me

$ flask db stamp head
$ flask db migrate
$ flask db upgrade

回答 2

我的想法是这样的,当我执行“ ./manage.py db migration -m’添加关系’”时,出现的错误是这样的:“ alembic.util.exc.CommandError:目标数据库不是最新的。”

因此,我检查了迁移状态:

(venv) ]#./manage.py db heads
d996b44eca57 (head)
(venv) ]#./manage.py db current
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
715f79abbd75

并发现磁头和电流不同!

我通过执行以下步骤对其进行了修复:

(venv)]#./manage.py db stamp heads
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running stamp_revision 715f79abbd75 -> d996b44eca57

而现在的头脑是一样的

(venv) ]#./manage.py db current
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
d996b44eca57 (head)

现在,我可以再次进行迁移了。

My stuation is like this question, When I execute “./manage.py db migrate -m ‘Add relationship'”, the error occused like this ” alembic.util.exc.CommandError: Target database is not up to date.”

So I checked the status of my migrate:

(venv) ]#./manage.py db heads
d996b44eca57 (head)
(venv) ]#./manage.py db current
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
715f79abbd75

and found that the heads and the current are different!

I fixed it by doing this steps:

(venv)]#./manage.py db stamp heads
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running stamp_revision 715f79abbd75 -> d996b44eca57

And now the current is same to the head

(venv) ]#./manage.py db current
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
d996b44eca57 (head)

And now I can do the migrate again.


回答 3

可以用多种方法解决bby:

1要解决此错误,请删除最新的迁移文件(python文件),然后尝试重新执行迁移。

如果问题仍然存在,请尝试以下命令:

$ flask db stamp head  # To set the revision in the database to the head, without performing any migrations. You can change head to the required change you want.
$ flask db migrate     # To detect automatically all the changes.
$ flask db upgrade     # To apply all the changes.

This can be solved bby many ways :

1 To fix this error, delete the latest migration file ( a python file) then try to perform a migration afresh.

If issue still persists try these commands :

$ flask db stamp head  # To set the revision in the database to the head, without performing any migrations. You can change head to the required change you want.
$ flask db migrate     # To detect automatically all the changes.
$ flask db upgrade     # To apply all the changes.

回答 4

由于某种原因,我不得不删除一些迁移文件。不知道为什么。但这解决了问题。

一个问题是数据库最终会正确更新,包括所有新表等,但是当我使用自动迁移时,迁移文件本身不会显示任何更改。

如果有人有更好的解决方案,请让我知道,因为目前我的解决方案有点怪异。

I had to delete some of my migration files for some reason. Not sure why. But that fixed the problem, kind of.

One issue is that the database ends up getting updated properly, with all the new tables, etc, but the migration files themselves don’t show any changes when I use automigrate.

If someone has a better solution, please let me know, as right now my solution is kind of hacky.


回答 5

$ flask db stamp head  # To set the revision in the database to the head, without performing any migrations. You can change head to the required change you want.
$ flask db migrate  # To detect automatically all the changes.
$ flask db upgrade  # To apply all the changes.

您可以在文档https://flask-migrate.readthedocs.io/en/latest/中找到更多信息。

$ flask db stamp head  # To set the revision in the database to the head, without performing any migrations. You can change head to the required change you want.
$ flask db migrate  # To detect automatically all the changes.
$ flask db upgrade  # To apply all the changes.

You can find more info at the documentation https://flask-migrate.readthedocs.io/en/latest/


回答 6

我也遇到了不同的想法,我想将其中一个字段从字符串更改为整数,因此首先运行:

$ flask db stamp head # to make the current the same
$ flask db migrate
$ flask db upgrade

现在解决了!

I did too run into different heads and I wanted to change one of the fields from string to integer, so first run:

$ flask db stamp head # to make the current the same
$ flask db migrate
$ flask db upgrade

It’s solved now!


回答 7

如果您和我一样刚刚开始一个新项目,并且正在使用内存中的SQLite数据库(sqlite:///:memory:),也会发生这种情况。如果您在这样的数据库上应用迁移,那么很明显,下次您要说自动生成修订时,数据库仍将保持其原始状态(空),因此,Alembic会抱怨目标数据库未达到要求日期。解决方案是切换到持久数据库。

This can also happen if you, like myself, have just started a new project and you are using in-memory SQLite database (sqlite:///:memory:). If you apply a migration on such a database, obviously the next time you want to say auto-generate a revision, the database will still be in its original state (empty), so alembic will be complaining that the target database is not up to date. The solution is to switch to a persisted database.


回答 8

要解决此错误,请删除最新的迁移文件(python文件),然后尝试重新执行迁移。

To fix this error, delete the latest migration file ( a python file) then try to perform a migration afresh.


回答 9

在执行db upgrade命令之前,请尝试删除所有表。

Try to drop all tables before execute the db upgrade command.


回答 10

为了解决这个问题,我删除(删除)了迁移中的表并运行以下命令

flask db migrate

flask db upgrade

To solve this, I drop(delete) the tables in migration and run these commands

flask db migrate

and

flask db upgrade