【问题标题】:Flask Migrate "ValueError: Constraint must have a name"烧瓶迁移“ValueError:约束必须有一个名称”
【发布时间】:2020-06-29 14:41:31
【问题描述】:

我创建了一个烧瓶迁移脚本,但是,当我运行升级功能时,我收到以下错误:

INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 6378428b838a, empty message
Traceback (most recent call last):
  File "migrate.py", line 22, in <module>
    manager.run()
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/flask_script/__init__.py", line 417, in run
    result = self.handle(argv[0], argv[1:])
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/flask_script/__init__.py", line 386, in handle
    res = handle(*args, **config)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/flask_script/commands.py", line 216, in __call__
    return self.run(*args, **kwargs)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/flask_migrate/__init__.py", line 95, in wrapped
    f(*args, **kwargs)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/flask_migrate/__init__.py", line 280, in upgrade
    command.upgrade(config, revision, sql=sql, tag=tag)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/command.py", line 298, in upgrade
    script.run_env()
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/script/base.py", line 489, in run_env
    util.load_python_file(self.dir, "env.py")
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/util/pyfiles.py", line 98, in load_python_file
    module = load_module_py(module_id, path)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/util/compat.py", line 173, in load_module_py
    spec.loader.exec_module(module)
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "migrations/env.py", line 96, in <module>
    run_migrations_online()
  File "migrations/env.py", line 90, in run_migrations_online
    context.run_migrations()
  File "<string>", line 8, in run_migrations
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/runtime/environment.py", line 846, in run_migrations
    self.get_context().run_migrations(**kw)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/runtime/migration.py", line 518, in run_migrations
    step.migration_fn(**kw)
  File "/Users/slatifi/git/StaffTrainingLog/migrations/versions/6378428b838a_.py", line 23, in upgrade
    batch_op.create_foreign_key(None, 'organisation', ['organisation'], ['id'])
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/contextlib.py", line 119, in __exit__
    next(self.gen)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/operations/base.py", line 325, in batch_alter_table
    impl.flush()
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/operations/batch.py", line 106, in flush
    fn(*arg, **kw)
  File "/Users/slatifi/git/StaffTrainingLog/venv/lib/python3.7/site-packages/alembic/operations/batch.py", line 390, in add_constraint
    raise ValueError("Constraint must have a name")
ValueError: Constraint must have a name

我见过其他人有同样的错误,他们只是将 render_as_batch 添加到 env.py 文件中。我这样做了,但我仍然得到同样的错误。有什么想法吗?

注意:这是我在 env.py 文件中所做的修改:

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,
            render_as_batch=True
        )

这是迁移创建的升级脚本

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '2838e3e96536'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('user', schema=None) as batch_op:
        batch_op.add_column(sa.Column('organisation', sa.String(length=5), nullable=False))
        batch_op.create_foreign_key(None, 'organisation', ['organisation'], ['id'])

    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('user', schema=None) as batch_op:
        batch_op.drop_constraint(None, type_='foreignkey')
        batch_op.drop_column('organisation')

    # ### en

d Alembic 命令###

【问题讨论】:

    标签: flask flask-sqlalchemy flask-migrate


    【解决方案1】:

    这是正常的,因为 SQLite3 不支持 ALTER 表。

    您可以像这样在 Flask-Migrate 的实例化过程中传递render_as_batch=True:

    migrate = Migrate(app,db,render_as_batch=True)

    不需要修改Flask-Migrate的env文件。

    此外,为了完全避免未来迁移的问题,您可以为 SQLAlchemy 元数据的所有类型的约束创建一个约束命名模板,然后我认为您将获得一致的名称。

    在Flask-SQLAlchemy 文档中查看如何执行此操作。为了您的方便,我从下面的文档中复制了代码示例:

    from sqlalchemy import MetaData
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    convention = {
        "ix": 'ix_%(column_0_label)s',
        "uq": "uq_%(table_name)s_%(column_0_name)s",
        "ck": "ck_%(table_name)s_%(constraint_name)s",
        "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
        "pk": "pk_%(table_name)s"
    }
    
    metadata = MetaData(naming_convention=convention)
    db = SQLAlchemy(app, metadata=metadata)
    

    【讨论】:

    • 我也试过了,但它仍然不起作用 - 我仍然遇到同样的错误。有什么想法吗?
    • 您能告诉我们迁移文件中的升级方法吗?
    • 我已更新原始问题以包含上述内容。感谢您的支持
    • @Saif:如果您在遵循此答案后仍然遇到相同的错误,您可能需要在 my answer 中进行额外更改,以便处理现有的未命名约束。
    【解决方案2】:

    其他答案显示了如何将 Flask 配置为将来使用命名约束,但这并不能解决丢弃的问题现有的未命名约束在 Alembic 迁移中。要处理任何现有的未命名约束,您还需要在迁移脚本中定义命名约定,如 Alembic docs 中所述。

    例如,假设您尝试进行迁移,将ON DELETE CASCADE 添加到test 表中的现有外键。如果您遵循其他答案之一(例如https://stackoverflow.com/a/62651160/470844)并将naming_convention 添加到 Flask 初始化脚本,那么flask db upgrade 将生成如下内容:

    def upgrade():
        with op.batch_alter_table('test', schema=None) as batch_op:
            batch_op.drop_constraint(None, type_='foreignkey')
            batch_op.create_foreign_key(batch_op.f('fk_test_user_id_user'), 'user', ['user_id'], ['id'], ondelete='CASCADE')
    

    请注意,虽然create_foreign_key 调用使用了约束名称(即fk_test_user_id_user),但drop_constraint 调用仍使用None 作为约束名称,这将导致此问题的标题出现ValueError: Constraint must have a name 错误。

    要解决此问题,您需要编辑迁移以使用 naming_convention,并将 None 替换为生成的约束名称。例如,您可以将上面的 upgrade 更改为:

    naming_convention = {
        "ix": 'ix_%(column_0_label)s',
        "uq": "uq_%(table_name)s_%(column_0_name)s",
        "ck": "ck_%(table_name)s_%(column_0_name)s",
        "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
        "pk": "pk_%(table_name)s"
    }
    
    def upgrade():
        with op.batch_alter_table('test', schema=None, naming_convention=naming_convention) as batch_op:
            batch_op.drop_constraint('fk_test_user_id_user', type_='foreignkey')
            batch_op.create_foreign_key(batch_op.f('fk_test_user_id_user'), 'user', ['user_id'], ['id'], ondelete='CASCADE')
    

    (在此示例中,您还需要在运行迁移脚本时禁用 SQLite 外键支持,例如使用技术 here,以避免批量迁移本身触发级联删除。问题记录在 @987654324 @.)

    【讨论】:

      【解决方案3】:

      安装 Flask-Migrate 有助于避免代码引用 declarative_base 或修改 env.py 文件。 对于具有应用工厂的应用,在 extensions.py 和 __init__.py 文件之间拆分元数据分配和 db 启动。

      extensions.py:

      from sqlalchemy import MetaData
      from flask_sqlalchemy import SQLAlchemy
      
      metadata = MetaData(
          naming_convention={
          "ix": 'ix_%(column_0_label)s',
          "uq": "uq_%(table_name)s_%(column_0_name)s",
          "ck": "ck_%(table_name)s_%(constraint_name)s",
          "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
          "pk": "pk_%(table_name)s"
          }
      )
      
      db=SQLAlchemy(metadata=metadata)
      

      __init__.py:

      from flask import Flask, Blueprint
      
      from config import ...
      from flask_migrate import Migrate
      from extensions import db
      
      ...
      
      def create_app(): 
          app = Flask(__name__,
                      template_folder='../app/templates')
      
          app.config.from_object(config[...])
          config[...].init_app(app)
          
          db.init_app(app)
      
          with app.app_context():
              db.create_all()
      
          migrate = Migrate(app, db)
      
          ...
          
          from .templates.main import main
      
          app.register_blueprint(main)
      
          return app
      

      【讨论】:

        猜你喜欢
        • 2016-12-24
        • 2019-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-10
        • 1970-01-01
        • 2022-01-17
        相关资源
        最近更新 更多