在Postgres中使用SQLAlchemy创建枚举数组

7

过去一年我一直在使用Postgres和SQLAlchemy的枚举数组,如下所示:

class MyModel(BaseModel):
    enum_field = Column(postgresql.ARRAY(EnumField(MyEnum, native_enum=False)))
EnumField来自于sqlalchemy_enum34库,它是一个小的包装器,可将Python枚举用作Python表示形式,而不是字符串。
尽管文档中说,不支持枚举数组,但我猜它能工作,因为我选择了'native_enum=False'。 最近我注意到它不再工作了,我认为这是由于从SQLA 1.0升级到1.1所致,但我不确定。
问题是它生成了无效的DQL:
CREATE TABLE my_model (
    enum_field VARCHAR(5)[3] NOT NULL CHECK (contexts IN ('ONE', 'TWO', 'THREE'))
)

我收到的错误信息是:
ERROR:  malformed array literal: "ONE"
DETAIL:  Array value must start with "{" or dimension information.

你有什么想法来找回我的枚举数组吗?顺便说一句:当它起作用时,实际上没有创建 CHECK 约束,只创建了一个可变长度的数组。只要我可以在 Python 代码中使用枚举(例如 query.filter(enum_field==MyEnum.ONE)),我就没问题。请注意不要修改 HTML 标签。
5个回答

5
我在SqlAlchemy源代码中找到了一个不错的解决方法:
import re

from sqlalchemy import TypeDecorator, cast
from sqlalchemy.dialects.postgresql import ARRAY


class ArrayOfEnum(TypeDecorator):

    impl = ARRAY

    def bind_expression(self, bindvalue):
        return cast(bindvalue, self)

    def result_processor(self, dialect, coltype):
        super_rp = super(ArrayOfEnum, self).result_processor(dialect, coltype)

        def handle_raw_string(value):
            inner = re.match(r"^{(.*)}$", value).group(1)

            return inner.split(",") if inner else []

        def process(value):
            if value is None:
                return None

            return super_rp(handle_raw_string(value))

        return process

现在:

achievements = Column(ArrayOfEnum(Enum(AchievementsType)))

然后:

career.achievements = [AchievementsType.world, AchievementsType.local]

4

在现代的SqlAlchemy中,您不需要为此定义自定义类型:

import sqlalchemy.dialects.postgresql as pg

class MyModel(Base):
    ...
    flags = Column(pg.ARRAY(sa.Enum(MyEnum, 
                   create_constraint=False, native_enum=False)))

在这种情况下,MyEnum是什么?它可以像以下代码一样是一个Python类,例如Genres吗:class Genres(enum.Enum): linebreak alternative ='Alternative' linebreak blues ='Blues' - Ndrslmpk
2
@Ndrslmpk 是的,MyEnum 是你代码中定义的任何 enum.Enum 子类 :) - kolypto

1
当我需要一个枚举数组时,我使用了Mike Bayer在此处提供的方法:https://bitbucket.org/zzzeek/sqlalchemy/issues/3467/array-of-enums-does-not-allow-assigning#comment-19370832 编辑:问题已转移到https://github.com/sqlalchemy/sqlalchemy/issues/3467 也就是说,创建一个自定义类型如下所示:
import sqlalchemy as sa

class ArrayOfEnum(ARRAY):

    def bind_expression(self, bindvalue):
        return sa.cast(bindvalue, self)

    def result_processor(self, dialect, coltype):
        super_rp = super(ArrayOfEnum, self).result_processor(dialect, coltype)

        def handle_raw_string(value):
            inner = re.match(r"^{(.*)}$", value).group(1)
            return inner.split(",")

        def process(value):
            return super_rp(handle_raw_string(value))
        return process

我已经有一段时间没有使用它了,所以我不确定它是否继续工作。

它的代码与您的enum34库不同,因此可能不会出现相同的问题?


我以前尝试过这个,但它并没有改变在Postgres中创建数组的方式,生成的DDL仍然是相同的。正如您所看到的,它只是在处理与默认数组不同的结果时有所不同。 - Tim-Erwin
第四行的“sa”是什么? - Andru
嘿@Andru,我在我的代码库中找不到这段代码了,所以我不能确定(除非深入研究git历史记录)。我怀疑它只是SQLAlchemy的别名导入。我已经更新了答案来填补这个空缺。 - WilliamMayor

0

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接