如何在SQLAlchemy和Alembic中使用Enum?

问题描述 投票:5回答:3

这是我的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值,而不是名称。

先感谢您。

python sqlalchemy flask-sqlalchemy alembic
3个回答
2
投票

为什么DB中的实际值是'草稿','APPROVE','PUBLISHED',而不是草稿等?我认为应该有ENUM值,而不是名称。

正如PeterBaš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不是自动在DB级别创建的?在类似的迁移中它是。

我认为基本上你已经达到了alembic的限制:在某些情况下,它无法正确处理PostgreSQL上的枚举。我怀疑你的主要问题是Autogenerate doesn't correctly handle 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)

3
投票

我只能回答你问题的第三部分。

SQLAlchemy中documentation类型的Enum表示:

上面是每个元素的字符串名称,例如数据库中存有“一个”,“两个”,“三个”; Python枚举的值,这里用整数表示,不使用;因此,每个枚举的值可以是任何类型的Python对象,无论它是否可持久化。

因此,通过SQLAlchemy设计,Enum名称,而不是值被持久化到数据库中。


-2
投票

来自官方文档:https://docs.python.org/3/library/enum.html#creating-an-enum

import enum

class PostStatus(enum.Enum):
    DRAFT = 0
    APPROVE = 1
    PUBLISHED = 2

根据这个:

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

1)PostStatus不是数据库模型,它只是一个包含状态ID的类;

2)没关系

3)您不必在DB中存储状态字符串,您最好使用ID

© www.soinside.com 2019 - 2024. All rights reserved.