为什么Flask-Migrate让我进行两步迁移?

前端之家收集整理的这篇文章主要介绍了为什么Flask-Migrate让我进行两步迁移?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我正在与Flask,sqlAlchemy,Alembic以及Flask(Flask-sqlAlchemy和Flask-Migrate)的包装器开展一个项目.我有四次迁移:
1c5f54d4aa34 -> 4250dfa822a4 (head),Feed: Countries
312c1d408043 -> 1c5f54d4aa34,Feed: Continents
41984a51dbb2 -> 312c1d408043,Basic Structure
<base> -> 41984a51dbb2,Init Alembic

当我启动一个新的干净的数据库并尝试运行迁移时,我收到一个错误

vagrant@precise32:/vagrant$python manage.py db upgrade
...
sqlalchemy.exc.ProgrammingError: (ProgrammingError) relation "continent" does not exist
...

如果我要求Flask-Migrate运行所有迁移,但是最后一次,它可以运行.如果之后我再次运行升级命令,它就可以工作 – 也就是说,它完全升级我的数据库而不需要对代码进行任何单一更改:

vagrant@precise32:/vagrant$python manage.py db upgrade 312c1d408043
INFO  [alembic.migration] Context impl PostgresqlImpl.
INFO  [alembic.migration] Will assume transactional DDL.
INFO  [alembic.migration] Running upgrade  -> 41984a51dbb2,Init Alembic
INFO  [alembic.migration] Running upgrade 41984a51dbb2 -> 312c1d408043,Basic Structure

vagrant@precise32:/vagrant$python manage.py db upgrade
INFO  [alembic.migration] Context impl PostgresqlImpl.
INFO  [alembic.migration] Will assume transactional DDL.
INFO  [alembic.migration] Running upgrade 312c1d408043 -> 1c5f54d4aa34,Feed: Continents
INFO  [alembic.migration] Running upgrade 1c5f54d4aa34 -> 4250dfa822a4,Feed: Countries

TL; DR

上次迁移(Feed:Countries)对前一个(Feed:Continents)提供的表运行查询.如果我有大陆表创建和馈送,脚本应该工作.但事实并非如此.
为什么我必须在此之间停止迁移过程以在另一个命令中重新启动它?我真的不明白这一点.是否有一些命令Alembic在一系列迁移后执行?有任何想法吗?

以防万一

我的模型定义如下:

class Country(db.Model):

    __tablename__ = 'country'
    id = db.Column(db.Integer,primary_key=True)
    alpha2 = db.Column(db.String(2),index=True,unique=True)
    title = db.Column(db.String(140))
    continent_id = db.Column(db.Integer,db.ForeignKey('continent.id'))
    continent = db.relationship('Continent',backref='countries')

    def __repr__(self):
        return '<Country #{}: {}>'.format(self.id,self.title)

class Continent(db.Model):

    __tablename__ = 'continent'
    id = db.Column(db.Integer,unique=True)
    title = db.Column(db.String(140))

    def __repr__(self):
        return '<Continent #{}: {}>'.format(self.id,self.title)

非常感谢,

更新1:最后两次迁移的升级方法

正如@Miguel在评论中所说,这里有最后两次迁移的升级方法

饲料:大陆

def upgrade():
    csv_path = app.config['BASEDIR'].child('migrations','csv','en')
    csv_file = csv_path.child('continents.csv')
    with open(csv_file) as file_handler:
        csv = list(reader(file_handler))
        csv.pop(0)
        data = [{'alpha2': c[0].lower(),'title': c[1]} for c in csv]
        op.bulk_insert(Continent.__table__,data)

Feed:国家/地区(取决于上一次迁移的表格)

def upgrade():

    # load countries iso3166.csv and build a dictionary
    csv_path = app.config['BASEDIR'].child('migrations','en')
    csv_file = csv_path.child('iso3166.csv')
    countries = dict()
    with open(csv_file) as file_handler:
        csv = list(reader(file_handler))
        for c in csv:
            countries[c[0]] = c[1]

    # load countries-continents from country_continent.csv
    csv_file = csv_path.child('country_continent.csv')
    with open(csv_file) as file_handler:
        csv = list(reader(file_handler))
        country_continent = [{'country': c[0],'continent': c[1]} for c in csv]

    # loop
    data = list()
    for item in country_continent:

        # get continent id
        continent_guess = item['continent'].lower()
        continent = Continent.query.filter_by(alpha2=continent_guess).first()

        # include country
        if continent is not None:
            country_name = countries.get(item['country'],False)
            if country_name:
                data.append({'alpha2': item['country'].lower(),'title': country_name,'continent_id': continent.id})

我正在使用的CSV基本上遵循以下模式:

continents.csv

...
AS,"Asia"
EU,"Europe"
NA,"North America"
...

iso3166.csv

...
CL,"Chile"
CM,"Cameroon"
CN,"China"
...

_country_continent.csv_

...
US,NA
UY,SA
UZ,AS
...

因此Feed:Continents提供大陆表,Feed:Countries提供国家/地区表.但它必须查询大陆表,以便在国家和大陆之间建立适当的联系.

更新2:来自Reddit的一些人已经提供了解释和解决方法

我问了the same question on Reddit,而themathemagician说:

I’ve run into this before,and the issue is that the migrations don’t
execute individually,but instead alembic batches all of them (or all
of them that need to be run) and then executes the sql. This means
that by the time the last migration is trying to run,the tables don’t
actually exist yet so you can’t actually make queries. Doing

06009

This isn’t the most elegant solution (and that was for Postgres,the
command may be different for other dbs),but it worked for me. Also,
this isn’t actually an issue with Flask-Migrate as much as an issue
with alembic,so if you want to Google for more info,search for
alembic. Flask-Migrate is just a wrapper around alembic that works
with Flask-Script easily.

解决方法

正如@dhemathemagician on reddit所示,Alembic默认在单个事务中运行所有迁移,因此根据数据库引擎和迁移脚本中的操作,某些依赖于先前迁移中添加内容的操作可能会失败.

我自己没试过,但是Alembic 0.6.5引入了一个transaction_per_migration选项,可以解决这个问题.这是env.py中configure()调用的一个选项.如果您使用默认配置文件,因为Flask-Migrate会创建它们,那么您可以在迁移/ env.py中修复此问题:

def run_migrations_online():
    """Run migrations in 'online' mode.

    # ...
    context.configure(
                connection=connection,target_Metadata=target_Metadata,transaction_per_migration=True        # <-- add this
                )
    # ...

另请注意,如果您还计划运行脱机迁移,则需要以相同方式修复run_migrations_offline()中的configure()调用.

尝试一下,让我知道它是否解决了这个问题.

猜你在找的Python相关文章