Python中Sqlalchemy自定义字段类型的Mutation Tracking问题分享
import json
https://segmentfault.com/a/1190000004288061
from sqlalchemy import Column, Integer, String
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import Mutable
from sqlalchemy.orm import sessionmaker
from sqlalchemy.types import TypeDecorator, VARCHAR
***************************
engine = create_engine(‘sqlite:///./cnblogblog.db’, echo=False)
Base = declarative_base()
DBSession = sessionmaker(bind=engine)
class JSONEncodedDict(TypeDecorator):
“Represents an immutable structure as a json-encoded string.”
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
class MutableDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
“Convert plain dictionaries to MutableDict.”
if not isinstance(value, MutableDict):
if isinstance(value, dict):
return MutableDict(value)
# this call will raise ValueError
return Mutable.coerce(key, value)
else:
return value
def __setitem__(self, key, value):
"Detect dictionary set events and emit change events."
dict.__setitem__(self, key, value)
self.changed()
def __delitem__(self, key):
"Detect dictionary del events and emit change events."
dict.__delitem__(self, key)
self.changed()
class MyDataClass1(Base):
tablename = ‘my_data1’
id = Column(Integer, primary_key=True)
data = Column(MutableDict.as_mutable(JSONEncodedDict))
name = Column(String(50))
if name == ‘main’:
Base.metadata.create_all(engine)
session = DBSession()
m1 = MyDataClass1(data={'value1': 'foo1'}, name='xiaohong')
session.add(m1)
session.commit()
#######session 提交后,data 可以关联到 query
m1.name = 'xiaolang'
m1.data['value1'] = 'bar'#数据库的值,将再改变
# assert m1 in session.dirty
session.commit()
# my_data= session.query(MyDataClass).filter_by(id=1).one()
# a= my_data.data
# print (type(a))
# print (a)
# print (a["value1"])
#
# my_data.data["value1"] = "foo2"
#
# session.commit()
import json
from sqlalchemy import Column, Integer, String
https://segmentfault.com/a/1190000004288061
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.types import TypeDecorator, VARCHAR
***************************
engine = create_engine(‘sqlite:///./cnblogblog.db’, echo=False)
Base = declarative_base()
DBSession = sessionmaker(bind=engine)
class JSONEncodedDict(TypeDecorator):
“Represents an immutable structure as a json-encoded string.”
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
class MutableDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
“Convert plain dictionaries to MutableDict.”
if not isinstance(value, MutableDict):
if isinstance(value, dict):
return MutableDict(value)
# this call will raise ValueError
return Mutable.coerce(key, value)
else:
return value
def setitem(self, key, value):
“Detect dictionary set events and emit change events.”
dict.setitem(self, key, value)
self.changed()
def delitem(self, key):
“Detect dictionary del events and emit change events.”
dict.delitem(self, key)
self.changed()
class MyDataClass2(Base):
tablename = ‘my_data2’
id = Column(Integer, primary_key=True)
data = Column(JSONEncodedDict)
name = Column(String(50))
if name == ‘main’:
Base.metadata.create_all(engine)
session = DBSession()
m1 = MyDataClass2(data={'value1': 'foo1'}, name='xiaohong')
session.add(m1)
session.commit()
m1.name = 'xiaolang'
#######session 提交后,name 关联到了 query
# assert m1 in session.dirty
#######session 提交后,data 将不再关联到 query(解决方法,按着 JsonEncodeDict-demo-1.py 来)
m1.data['value1'] = 'bar'#数据库的值,不再改变
session.commit()
# assert m1 in session.dirty
# my_data= session.query(MyDataClass).filter_by(id=1).one()
# a= my_data.data
# print (type(a))
# print (a)
# print (a["value1"])
#
# my_data.data["value1"] = "foo2"
#
# session.commit()
```
Python中Sqlalchemy自定义字段类型的Mutation Tracking问题分享
我最近在项目里也遇到了类似的问题,折腾了半天才搞明白。
核心问题是:当你继承TypeDecorator创建自定义字段类型时,默认情况下SQLAlchemy不会自动追踪这个字段内部值的"变化"。比如你定义了一个JSONEncodedDict类型,直接修改字典内容(像obj.data['key'] = 'value')是不会触发更新语句的。
解决方案:需要实现TypeDecorator.coerce_compared_value()方法,并返回self.impl.coerce_compared_value(op, value)。更关键的是要设置impl = MutableDict.as_mutable(JSONEncodedDict),让SQLAlchemy知道这是个可变类型。
from sqlalchemy import TypeDecorator, JSON
from sqlalchemy.ext.mutable import MutableDict
import json
class JSONEncodedDict(TypeDecorator):
impl = JSON
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
def coerce_compared_value(self, op, value):
return self.impl.coerce_compared_value(op, value)
# 关键:用MutableDict包装
JSONEncodedDict = MutableDict.as_mutable(JSONEncodedDict)
# 使用
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.orm import declarative_base, Session
Base = declarative_base()
class MyModel(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
data = Column(JSONEncodedDict)
# 现在修改字典内容会自动触发更新
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
obj = MyModel(data={'key': 'old_value'})
session.add(obj)
session.commit()
# 直接修改字典 - 现在能正常追踪了
obj.data['key'] = 'new_value'
session.commit() # 会生成UPDATE语句
简单说就是记得用MutableDict.as_mutable()包装你的自定义类型。

