Python SqlAlchemy中具有可变跟踪的PickleType
我有一个项目,我想在关系数据库(Postgres)中存储一个大型结构(嵌套对象)。它是更大结构的一部分,我并不真正关心序列化格式——我很高兴它是列中的一个blob——我只希望能够相当快地持久化和恢复它 就我而言,SQLAlchemy类型主要完成这项工作。我所面临的问题是,我希望脏检查能够正常工作(可变类型用于此目的)。我希望它们不仅在我更改路径中的信息时起作用,而且在边界(位于另一层)中也起作用 我曾经和可变类型的人打过交道,试图让它工作起来,但没有任何运气。在其他地方,我确实为json列使用了可变类型,这很好,但方式似乎更简单,因为使用这些类,您也需要跟踪对象中对象的更改Python SqlAlchemy中具有可变跟踪的PickleType,python,postgresql,sqlalchemy,persistence,mutable,Python,Postgresql,Sqlalchemy,Persistence,Mutable,我有一个项目,我想在关系数据库(Postgres)中存储一个大型结构(嵌套对象)。它是更大结构的一部分,我并不真正关心序列化格式——我很高兴它是列中的一个blob——我只希望能够相当快地持久化和恢复它 就我而言,SQLAlchemy类型主要完成这项工作。我所面临的问题是,我希望脏检查能够正常工作(可变类型用于此目的)。我希望它们不仅在我更改路径中的信息时起作用,而且在边界(位于另一层)中也起作用 我曾经和可变类型的人打过交道,试图让它工作起来,但没有任何运气。在其他地方,我确实为json列使用了
任何想法都值得欣赏。首先,正如您所意识到的,您必须跟踪对象中对象的变化,因为SQLAlchemy无法知道内部对象的变化。因此,我们将使用一个基本可变对象来解决这一问题,我们可以同时用于:
class MutableObject(Mutable, object):
@classmethod
def coerce(cls, key, value):
return value
def __getstate__(self):
d = self.__dict__.copy()
d.pop('_parents', None)
return d
def __setstate__(self, state):
self.__dict__ = state
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
self.changed()
class Path(MutableObject):
def __init__(self, style, bounds):
super(MutableObject, self).__init__()
self.style = style
self.bounds = bounds
class Bound(MutableObject):
def __init__(self, l, t, r, b):
super(MutableObject, self).__init__()
self.l = l
self.t = t
self.r = r
self.b = b
我们还需要跟踪路径列表上的更改,因此,我们也必须使其成为可变对象。但是,可变项通过在调用changed()方法时将子项传播到父项来跟踪子项中的更改,并且SQLAlchemy中的当前实现似乎只将父项分配给作为属性而不是作为序列项(如字典或列表)分配的人。这就是事情变得复杂的地方
我认为列表项应该将列表本身作为父项,但这不起作用,原因有两个:首先,_parentsweakdict不能将列表作为键,其次,changed()信号不会一直传播到顶部,因此,我们只会将列表本身标记为changed。我不确定这是否100%正确,但方法似乎是将列表的父对象分配给每个项目,这样当项目发生更改时,group对象将获得flag_modified调用。这应该可以做到
class MutableList(Mutable, list):
@classmethod
def coerce(cls, key, value):
if not isinstance(value, MutableList):
if isinstance(value, list):
return MutableList(value)
value = Mutable.coerce(key, value)
return value
def __setitem__(self, key, value):
old_value = list.__getitem__(self, key)
for obj, key in self._parents.items():
old_value._parents.pop(obj, None)
list.__setitem__(self, key, value)
for obj, key in self._parents.items():
value._parents[obj] = key
self.changed()
def __getstate__(self):
return list(self)
def __setstate__(self, state):
self[:] = state
然而,这里还有最后一个问题。父对象通过侦听“load”事件的调用进行分配,因此在初始化时,\ u parents dict为空,子对象未分配任何内容。我认为,通过监听load事件,也许有更干净的方法可以做到这一点,但我认为最糟糕的方法是在检索项目时重新分配父项,因此,添加以下内容:
def __getitem__(self, key):
value = list.__getitem__(self, key)
for obj, key in self._parents.items():
value._parents[obj] = key
return value
最后,我们必须在Group.path上使用该可变列表:
class Group(BaseModel):
__tablename__ = 'group'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
paths = db.Column(MutableList.as_mutable(types.PickleType))
有了这些,您的测试代码应该可以工作:
g = Group(name='g1', paths=[Path('blah', Bound(1,1,2,3)),
Path('other_style', Bound(1,1,2,3)),])
session.add(g)
db.session.commit()
g.name = 'g2'
assert g in db.session.dirty
db.session.commit()
g.paths[0].style = 'something else'
assert g in db.session.dirty
坦率地说,我不确定在生产环境中使用该模式有多安全,如果您不需要灵活的模式,那么最好使用表和关系作为路径和绑定。回答得很好,感谢您花时间解决所有这些问题-我很欣赏这是一个非常不标准的案例。这是我认为它将如何工作的一般想法,但无法完全掌握细节。我需要更深入地研究这一点,才能真正理解它——正如你所说,走这条路可能不是个好主意。你关于如何解决列表问题的想法非常聪明,我不确定我是否会想到。再次感谢。我不在手机上的时候会给你一笔赏金:)谢谢。我很乐意帮忙。令人烦恼的是,在这个问题上我能给的最低悬赏现在是200,因为我以前在这里有悬赏。我已经开始悬赏一个完全无关的问题,我会在24小时内奖励你。别担心。正如我所说,我很乐意帮忙。我信守诺言。而且,我已经开始悬赏了,所以把它给别人是愚蠢的:)
g = Group(name='g1', paths=[Path('blah', Bound(1,1,2,3)),
Path('other_style', Bound(1,1,2,3)),])
session.add(g)
db.session.commit()
g.name = 'g2'
assert g in db.session.dirty
db.session.commit()
g.paths[0].style = 'something else'
assert g in db.session.dirty