使用 Flask-SQLAlchemy 在 Alembic 自动生成迁移中未检测到任何变化

Posted

技术标签:

【中文标题】使用 Flask-SQLAlchemy 在 Alembic 自动生成迁移中未检测到任何变化【英文标题】:No changes detected in Alembic autogeneration of migrations with Flask-SQLAlchemy 【发布时间】:2012-09-06 17:53:20 【问题描述】:

我无法让 Alembic 使用 db.Model (Flask-SQLAlchemy) 而不是 Base 自动生成从更改到类的候选迁移。

我已修改 env.py 以创建我的 Flask 应用,导入所有相关模型,初始化数据库,然后运行迁移:

...
uri = 'mysql://user:password@host/dbname?charset=utf8'
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = uri
app.config['SQLALCHEMY_ECHO'] = True
db.init_app(app)
with app.test_request_context():
    target_metadata = db.Model.metadata
    config.set_main_option('sqlalchemy.url', uri)
    if context.is_offline_mode():
        run_migrations_offline()
    else:
        run_migrations_online()
...

这种方法适用于drop_all()create_all()(例如,在为单元测试重新创建测试数据库时),但在这种情况下似乎会失败。自动生成的版本脚本总是有空的升级和降级方法,例如

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


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

我的更改包括重命名列、更改列定义等。不仅仅是对索引和外键的更改。

有人在使用 Alembic 和 Flask-SQLAlchemy 吗?知道我哪里出错了吗?

非常感谢!

【问题讨论】:

【参考方案1】:

Alembic 无法自动检测表或列重命名。默认情况下,它也不会查找列类型更改,但可以为此启用 compare_type 选项。

Alembic 文档节选:

自动生成将默认检测:

表添加、删除。 添加、删除列。 更改列上的可为空状态。

Autogenerate 可以选择性地检测:

更改列类型。如果您在EnvironmentContext.configure() 上设置compare_type=True,就会发生这种情况。该功能在大多数情况下运行良好,但默认情况下处于关闭状态,以便可以首先在目标架构上进行测试。也可以通过在此处传递可调用对象来自定义;有关详细信息,请参阅函数的文档。 更改服务器默认值。如果您在EnvironmentContext.configure() 上设置compare_server_default=True,就会发生这种情况。此功能适用于简单的情况,但不能始终产生准确的结果。 Postgresql 后端实际上会针对数据库调用“检测到的”和“元数据”值来确定等价性。该功能默认关闭,因此可以首先在目标架构上进行测试。和类型比较一样,也可以通过传递一个callable来自定义;有关详细信息,请参阅函数的文档。

自动生成无法检测:

表名更改。这些将作为两个不同表的添加/删除出现,并且应该手动编辑为名称更改。 列名的更改。与表名更改一样,这些被检测为列添加/删除对,这与名称更改完全不同。 特殊的 SQLAlchemy 类型,例如 Enum,当在不直接支持 ENUM 的后端生成时 - 这是因为这种类型在不支持的数据库中的表示,即 CHAR+CHECK 约束,可能是任何类型的CHAR+CHECK。 SQLAlchemy 确定这实际上是一个 ENUM 只是一个猜测,这通常是一个坏主意。要在此处实现您自己的“猜测”功能,请使用sqlalchemy.events.DDLEvents.column_reflect() 事件来更改为某些列传递的 SQLAlchemy 类型,并可能使用sqlalchemy.events.DDLEvents.after_parent_attach() 来拦截不需要的CHECK 约束。

目前无法自动生成,但最终会检测到:

独立约束添加、删除,如CHECKUNIQUEFOREIGN KEY - 这些尚未实现。现在,您将获得新表中的约束、用于“降级”到先前存在的表的 PK 和 FK 约束,以及使用 SQLAlchemy “模式”类型 BooleanEnum 生成的 CHECK 约束。李> 索引添加、删除 - 尚未实施。 序列添加、删除 - 尚未实现。

更新:最后一个列表中的某些项目在 Alembic 0.7.x 版本中受支持。

【讨论】:

感谢您提供信息,但如果您可以在此处查看我的问题 (***.com/questions/17201800/…),您会发现 Alembic 没有检测到整个表格。这不是上述例外之一。 如果您阅读此 OP 的问题,他会说 Alembic 未检测到的内容是(引用)“重命名列、更改列定义等”。至少从他的描述来看,像你这样的整张桌子都没有这个问题。 啊,看来你是对的!应该更仔细地阅读这个问题。无论如何,谢谢你的回答。 Alembic 现在可以检测 FK 更改(从 0.71 版开始)。见bitbucket.org/zzzeek/alembic/issue/178/… 这个答案在使用 alembic 时对我很有帮助,谢谢@Miguel :)【参考方案2】:

我的错误是尝试使用已处于最终状态的数据库创建初始迁移,认为它会注意到它没有现有版本并将其基于模型。我得到了空版本,直到我删除了数据库中的所有表,然后它工作正常。

【讨论】:

谢谢布拉德,这实际上很有意义,对我来说效果很好。 (花了很长时间试图理解为什么 Alembic 没有检测到元数据) 这是否意味着我们需要在预迁移的数据库上运行迁移才能检测到更改? @cal97g close:需要在预迁移的数据库上生成迁移【参考方案3】:

我也遇到过这个问题,用这种方法解决:

打开migrations/env.py 文件,在def run_migrations_online() 函数上查看context.configure,在Alembic 1.0.8 上应该如下所示:

with connectable.connect() as connection:
    context.configure(
        connection=connection,
        target_metadata=target_metadata,
        process_revision_directives=process_revision_directives,
        **current_app.extensions['migrate'].configure_args,
    )

只需删除或评论process_revision_directives=process_revision_directives,然后在上面添加compare_type=True

像这样:

with connectable.connect() as connection:
    context.configure(
        connection=connection,
        target_metadata=target_metadata,
        # process_revision_directives=process_revision_directives,
        **current_app.extensions['migrate'].configure_args,
        compare_type=True
    )

【讨论】:

但是你能解释一下这些改变的目的是什么【参考方案4】:

这可能是一个愚蠢的建议,但我遇到了类似的问题。我所有的 env 文件都指向了正确的位置等,但我仍然无法生成这个新表。我使用命令序列“alembic revision -m 'comment'”,然后是“alembic upgrade head”,我得到了“空”修订文件。

最后,在删除所有迁移文件、销毁 docker 映像、恢复迁移文件、重新尝试上述 cli 命令序列以及手动手动编码迁移之前,我尝试了“alembic revision --autogenerate”找到并创建了表的迁移。

这可能是由于我之前采取的所有其他步骤造成的。如果不是很有帮助,我深表歉意。

【讨论】:

修复了我的错误!我忘了在命令中包含“自动生成”。【参考方案5】:

试试烧瓶-alembic https://github.com/tobiasandtobias/flask-alembic

我昨天试过了。除了drop 操作外,它对我来说很好。它们不适用于 sqlite (https://bitbucket.org/zzzeek/alembic/issue/21/column-renames-not-supported-on-sqlite)。

我使用它的方式。 首先,我使用python manage.py migrate revision --autogenerate 在 sqlite db 中创建空表。 它会产生这样的迁移

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.create_table('users_user',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=50), nullable=True),
    sa.Column('email', sa.String(length=120), nullable=True),
    sa.Column('password', sa.String(length=20), nullable=True),
    sa.Column('role', sa.SmallInteger(), nullable=True),
    sa.Column('status', sa.SmallInteger(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('email'),
    sa.UniqueConstraint('name')
)
### end Alembic commands ###

def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('users_user')
    ### end Alembic commands ###

那么 - python manage.py migrate upgrade head

然后我在用户模型中添加了一个新列test = db.Column(db.String(20)) 并运行了这个命令python manage.py migrate revision --autogenerate -m 'test field at users'

这产生了这样的迁移:

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.add_column('users_user', sa.Column('test', sa.String(length=20), nullable=True))
    ### end Alembic commands ###


def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('users_user', 'test')
    ### end Alembic commands ###

【讨论】:

以上是关于使用 Flask-SQLAlchemy 在 Alembic 自动生成迁移中未检测到任何变化的主要内容,如果未能解决你的问题,请参考以下文章

FLASK-SQLALCHEMY如何使用or和and条件进行组合查询

Python flask-sqlalchemy初级解析

如何在一段时间后更改Flask-SQLAlchemy URI?

51单片机ALE引脚的控制(摘录)

使用 Flask-SQLAlchemy 在 Alembic 自动生成迁移中未检测到任何变化

Flask-SqlAlchemy、Bcrypt、Postgres 的编码问题