如何检测sqlalchemy中自定义类型的变化

0 投票
2 回答
39 浏览
提问于 2025-04-12 12:13

我正在使用 TypeDecorator 在 sqlalchemy 列中创建自定义类型。我把数据存储在 Postgres 数据库的 JSONB 格式里,但在代码中我使用一个数据类来进行序列化和反序列化。但是,当我修改这个数据类的任何字段时,它并没有检测到那一列的变化。我该如何在 sqlalchemy 中实现这个功能呢?

数据类:

@dataclass
class ConfigFlags(DataClassJsonMixin):
    abc: bool = True

自定义类型定义:

class ConfigFlagType(types.TypeDecorator):

    """Type for config flags."""

    impl = JSONB

    def process_result_value(  # noqa: ANN201
        self,
        value: Optional[dict],
        dialect,  # noqa: ANN001
    ):
        if not isinstance(value, dict):
            msg = "Value must be a dictionary"
            raise ValueError(msg)  # noqa: TRY004

        return ConfigFlags.from_dict(value)

    def process_bind_param(  # noqa: ANN201
        self,
        value: Optional[ConfigFlags],
        dialect,  # noqa: ANN001
    ):
        if not isinstance(value, ConfigFlags):
            msg = "Value must be a ConfigFlags"
            raise ValueError(msg)  # noqa: TRY004
        return value.to_dict()

数据库模型:

class ConvHistories(CBase):

    """conv_histories model."""

    __tablename__ = "test"

    id = Column(Integer, primary_key=True, autoincrement=True)
    config_flags: ConfigFlags = Column(
        ConfigFlagType,
    )

   def find_one(self, conv_id: int) -> "ConvHistories":
        return self.query.filter(  
            ConvHistories.id == conv_id,
        ).first()
res = ConvHistories(session=session).find_one(conv_id=3)

if res.config_flags:
    res.config_flags.abc = False


session.add(res)
session.commit()

但是它没有检测到 config_flags 这一列的变化。我该怎么做呢?

2 个回答

1

来自SqlAlchemy JSON 文档(同样适用于 JSONB):

在使用 SQLAlchemy 的 ORM 时,JSON 类型不会自动检测到数据结构的就地修改。为了能够检测到这些修改,必须使用 sqlalchemy.ext.mutable 扩展,通常是使用 MutableDict 类。这个扩展允许对数据结构的“就地”更改产生事件,这些事件会被工作单元检测到。有关字典的简单示例,请参见 HSTORE。

所以,如果你有一个简单的列,你可以这样包裹它:

config_flags = Column(MutableDict.as_mutable(JSONB))

在你的情况下,因为你有一个特殊的包装类,所以稍微复杂一些,但你仍然需要使用相同的机制。

更多示例代码和非常好的解释可以在这篇博客文章中找到:在 SQLAlchemy 中小心 JSON 字段

作为使用 MutableDict 的变更跟踪的更简单,但可能效率较低的替代方案,你也可以在每次修改 config_flag 时,先进行深拷贝,然后再添加回去:

if res.config_flags:
   res.config_flags = copy.deepcopy(res.config_flags)
   res.config_flags.abc = False

   session.add(res)
   session.commit() 

撰写回答