使用SQLAlchemy建表时,期望某个属性值运行时为list,写入时为text
🤖 Assistant
使用SQLAlchemy建表时,期望某个属性值运行时为list,写入时为text。
针对需求,可以使用 SQLAlchemy 的 hybrid_property 和 TypeDecorator 来实现
如下举例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 字符串。如果value是None,则直接返回None。 -
process_result_value(self, value, dialect): 在从数据库读取数据之后调用。将 JSON 字符串value转换为 Python 列表。如果value是None, 则直接返回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() 时自动进行转换,无需手动操作。修改更灵活。
浙公网安备 33010602011771号