如何将枚举与 SQLAlchemy 和 Alembic 一起使用?
How to use Enum with SQLAlchemy and 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"]
- 为什么类型
poststatus
不是在数据库级自动创建的?在类似的迁移中。
- 如何正确指定
server_default
选项?我需要 ORM 级默认值和 DB 级默认值,因为我正在更改现有行,因此不应用 ORM 默认值。
- 为什么 DB 中的实际值是 'DRAFT'、'APPROVE'、'PUBLISHED' 而不是
draft
等?我认为应该有 ENUM 值,而不是名称。
提前致谢。
来自官方文档: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 的 class;
2) 没问题
3) 你不必在数据库中存储状态字符串,你最好使用 ids 代替
我只能回答你问题的第三部分
SQLAlchemy 中 Enum
类型的 documentation 指出:
Above, the string names of each element, e.g. “one”, “two”, “three”, are persisted to the database; the values of the Python Enum, here indicated as integers, are not used; the value of each enum can therefore be any kind of Python object whether or not it is persistable.
因此,SQLAlchemy 设计 Enum
名称,而不是将值持久保存到数据库中。
Why real values in DB are 'DRAFT', 'APPROVE', 'PUBLISHED', but not draft, etc? I supposed there should be ENUM values, not names.
正如 Peter Bašista 已经提到的,SQLAlchemy 在数据库中使用 枚举名称(草稿、批准、发布)。我认为这样做是因为枚举值 ("draft"、"approve"、...) 可以是 Python 中的任意类型,并且不保证它们是唯一的(除非 @unique
被使用)。
但是自从 SQLAlchemy 1.2.3 Enum
class 接受一个参数 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
)
Why type poststatus was not created on DB-level automatically? In the similar migration it was.
我认为基本上您遇到了 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)
如果您使用的是 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())
这是我的 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"]
- 为什么类型
poststatus
不是在数据库级自动创建的?在类似的迁移中。 - 如何正确指定
server_default
选项?我需要 ORM 级默认值和 DB 级默认值,因为我正在更改现有行,因此不应用 ORM 默认值。 - 为什么 DB 中的实际值是 'DRAFT'、'APPROVE'、'PUBLISHED' 而不是
draft
等?我认为应该有 ENUM 值,而不是名称。
提前致谢。
来自官方文档: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 的 class;
2) 没问题
3) 你不必在数据库中存储状态字符串,你最好使用 ids 代替
我只能回答你问题的第三部分
SQLAlchemy 中 Enum
类型的 documentation 指出:
Above, the string names of each element, e.g. “one”, “two”, “three”, are persisted to the database; the values of the Python Enum, here indicated as integers, are not used; the value of each enum can therefore be any kind of Python object whether or not it is persistable.
因此,SQLAlchemy 设计 Enum
名称,而不是将值持久保存到数据库中。
Why real values in DB are 'DRAFT', 'APPROVE', 'PUBLISHED', but not draft, etc? I supposed there should be ENUM values, not names.
正如 Peter Bašista 已经提到的,SQLAlchemy 在数据库中使用 枚举名称(草稿、批准、发布)。我认为这样做是因为枚举值 ("draft"、"approve"、...) 可以是 Python 中的任意类型,并且不保证它们是唯一的(除非 @unique
被使用)。
但是自从 SQLAlchemy 1.2.3 Enum
class 接受一个参数 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
)
Why type poststatus was not created on DB-level automatically? In the similar migration it was.
我认为基本上您遇到了 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)
如果您使用的是 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())