使用SQLAlchemy建表时,期望某个属性值运行时为list,写入时为text

🤖 Assistant

使用SQLAlchemy建表时,期望某个属性值运行时为list,写入时为text。
针对需求,可以使用 SQLAlchemy 的 hybrid_propertyTypeDecorator 来实现

如下举例Article 实例的 ids 列自动在 list 和 JSON 字符串之间转换。

以下是具体的实现方式:

1. 创建一个 TypeDecorator:

TypeDecorator 用于定义自定义的 SQLAlchemy 类型。 在这里,我们将创建一个 ListAsText 类型,它将 Python 列表转换为 JSON 字符串,反之亦然。

import json
from sqlalchemy import TypeDecorator, TEXT

class ListAsText(TypeDecorator):
    """Enables lists to be stored as TEXT."""

    impl = TEXT

    def process_bind_param(self, value, dialect):
        if value is not None:
            return json.dumps(value)
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            return json.loads(value)
        return value

2. 使用 hybrid_property:

hybrid_property 允许你定义一个属性,它的行为既像一个实例属性,又像一个类级别的表达式。 我们将使用 hybrid_property 创建一个 myids 属性,它将负责在 list 和 JSON 字符串之间进行转换。

from sqlalchemy import Column, TEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property

Base = declarative_base()

class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer, primary_key=True)  # 添加主键列
    ids = Column(ListAsText, nullable=True)   # 使用自定义类型
    #other_column = Column(...) # 其他列

    def __init__(self,  **kwargs):
      super().__init__(**kwargs)
      self._myids = [] #初始化一个List

    @hybrid_property
    def myids(self):
       if self.ids:
            return self.ids
       else:
           return self._myids

    @myids.setter
    def myids(self, myids):
        self._myids = myids

    @myids.expression
    def myids(cls):
        return cls.ids

代码解释:

  • ListAsText 类:

  • 继承自 TypeDecorator.

  • impl = TEXT: 指定底层数据库类型为 TEXT.

  • process_bind_param(self, value, dialect): 在将数据写入数据库之前调用。将 Python 列表 value 转换为 JSON 字符串。如果 valueNone,则直接返回 None

  • process_result_value(self, value, dialect): 在从数据库读取数据之后调用。将 JSON 字符串 value 转换为 Python 列表。如果 valueNone, 则直接返回 None

  • Article 类:
    *添加主键

  • ids = Column(ListAsText, nullable=True):使用我们自定义的类型

  • @hybrid_property def myids(self): 定义 myids 属性的 getter 方法.

  • return 的是 self.ids(即数据库存的),如果self.ids为空,返回self._myids(运行时使用的临时变量)。首次加载文章时,self.ids 为 None,因此 myids 将返回一个空列表 (实现了延迟加载和默认列表)

  • @myids.setter def myids(self, myids): 定义 myids 属性的 setter 方法.

  • 无论如何,都把外部给的List赋值给self._myids,从而保证运行时list的数据一直是最新的。

  • @myids.expression def myids(cls) 这个可以先忽略, 除非你需要编写更复杂的查询用到 myids 属性。

如何使用:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 1. 创建引擎和会话
engine = create_engine('sqlite:///:memory:')  # 使用内存数据库方便测试
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

# 2. 创建 Article 实例
article = Article() # 这里不需要传入ids参数

# 3. 操作 myids 属性 (就像操作一个列表)  *** 操作的是 self._myids, 不会立刻写入DB ****
article.myids.append("item1")
article.myids.append("item2")
print(f"in memory myids: {article.myids}") # 直接读写list

# 4. 添加到会话并提交
session.add(article) # 这里article.ids还是None
session.commit() # ** commit 的时候  ListAsText 会自动把 list 转成 json 字符串写入 DB **

# 5. 从数据库查询
retrieved_article = session.query(Article).first()
print(f"Retrieve from DB myids{retrieved_article.myids}")  # 从数据库读取后转成list

session.close()

总结:

这种方法有以下优点: 实现了目标: 在业务逻辑中使用 myids 属性 (list), 而数据库中存储的是 JSON 字符串. 在 session.commit() 时自动进行转换,无需手动操作。修改更灵活。

posted on 2025-08-22 00:51  痴心妄想  阅读(14)  评论(0)    收藏  举报

导航