如何将枚举与 SQLAlchemy 和 Alembic 一起使用?

新手上路,请多包涵

这是我的 Post 模型:

 class Post(Base):
    __tablename__ = 'posts'

    title = db.Column(db.String(120), nullable=False)
    description = db.Column(db.String(2048), nullable=False)

我想向其中添加 Enum status 。所以,我创建了一个新的枚举:

 import enum

class PostStatus(enum.Enum):
    DRAFT='draft'
    APPROVE='approve'
    PUBLISHED='published'

并为模型添加了一个新字段:

 class Post(Base):
    ...
    status = db.Column(db.Enum(PostStatus), nullable=False, default=PostStatus.DRAFT.value, server_default=PostStatus.DRAFT.value)

在执行 FLASK_APP=server.py flask db migrate 之后,生成了这样的迁移:

 def upgrade():
    op.add_column('posts', sa.Column('status', sa.Enum('DRAFT', 'APPROVE', 'PUBLISHED', name='poststatus'), server_default='draft', nullable=False))

尝试升级数据库后,我得到:

 sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) type "poststatus" does not exist
LINE 1: ALTER TABLE posts ADD COLUMN status poststatus DEFAULT 'draf...
                                            ^
 [SQL: "ALTER TABLE posts ADD COLUMN status poststatus DEFAULT 'draft' NOT NULL"]

  1. 为什么类型 poststatus 没有在 DB 级自动创建?在类似的迁移中。
  2. 如何正确指定 server_default 选项?我需要 ORM 级默认值和 DB 级默认值,因为我正在更改现有行,因此不应用 ORM 默认值。
  3. 为什么 DB 中的实际值是 ‘DRAFT’、’APPROVE’、’PUBLISHED’,而不是 draft 等?我认为应该有 ENUM 值,而不是名称。

先感谢您。

原文由 f1nn 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

为什么 DB 中的真实值是“DRAFT”、“APPROVE”、“PUBLISHED”,而不是 draft 等?我认为应该有 ENUM 值,而不是名称。

正如 Peter Bašista 已经提到的,SQLAlchemy 在数据库中使用 枚举名称(DRAFT、APPROVE、PUBLISHED)。我假设这样做是因为枚举值(“草稿”、“批准”、…)在 Python 中可以是任意类型,并且不能保证它们是唯一的(除非使用 @unique )。

然而,自 SQLAlchemy 1.2.3 Enum 类接受一个参数 values_callable 可用于在数据库中存储 枚举值

     status = db.Column(
        db.Enum(PostStatus, values_callable=lambda obj: [e.value for e in obj]),
        nullable=False,
        default=PostStatus.DRAFT.value,
        server_default=PostStatus.DRAFT.value
    )

为什么类型 poststatus 没有在数据库级别自动创建?在类似的迁移中。

我认为基本上您遇到了 alembic 的限制:在某些情况下它无法正确处理 PostgreSQL 上的枚举。我怀疑你的主要问题是 自动生成没有正确处理 postgresql enums #278

我注意到如果我使用 alembic.op.create_table 类型创建正确,所以我的解决方法基本上是:

 enum_type = SQLEnum(PostStatus, values_callable=lambda enum: [e.value for e in enum])
op.create_table(
    '_dummy',
    sa.Column('id', Integer, primary_key=True),
    sa.Column('status', enum_type)
)
op.drop_table('_dummy')
c_status = Column('status', enum_type, nullable=False)
add_column('posts', c_status)

原文由 Felix Schwarz 发布,翻译遵循 CC BY-SA 4.0 许可协议

如果您使用的是 PostgreSQL,请使用以下函数示例:

 from sqlalchemy.dialects import postgresql
from ... import PostStatus
from alembic import op
import sqlalchemy as sa

def upgrade():
    post_status = postgresql.ENUM(PostStatus, name="status")
    post_status.create(op.get_bind(), checkfirst=True)
    op.add_column('posts', sa.Column('status',  post_status))

def downgrade():
    post_status = postgresql.ENUM(PostStatus, name="status")
    post_status.drop(op.get_bind())

原文由 Farshid Ashouri 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题