Прежде всего, как вы поняли, вы должны отслеживать изменения объектов внутри объектов, так как 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
И мы также должны отслеживать изменения в списке путей, поэтому мы должны сделать, что измененный объект тоже. Тем не менее, Mutable треки меняются в дочерних элементах, распространяя их родителям при вызове метода changed(), и текущая реализация SQLAlchemy, по-видимому, назначает родителя только кому-то, назначенному как атрибут, а не как элемент последовательности, как словарь или список. Здесь все усложняется.
Я думаю, что элементы списка должны иметь список как родительский, но это не работает по двум причинам: во-первых, sparadict не может взять список для ключа, а во-вторых, измененный() сигнал не распространяется до самого верха, поэтому мы просто будем отмечать список как измененный. Я не уверен на 100%, насколько это правильно, но путь, кажется, назначает родительский список каждому элементу, поэтому объект группы получает вызов 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», поэтому во время инициализации идентификатор _parents is empty, и дети ничего не получают. Я думаю, может быть, есть еще один более чистый способ, которым вы можете это сделать, слушая также событие загрузки, но я решил, что грязный способ сделать это - это переназначить родителей при извлечении элементов, так что добавьте это:
def __getitem__(self, key):
value = list.__getitem__(self, key)
for obj, key in self._parents.items():
value._parents[obj] = key
return value
Наконец, мы должны использовать эту MutableList на Group.paths:
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
Честно говоря, я не уверен, насколько безопасно к ge t это при производстве, и если вам не нужна гибкая схема, вам, вероятно, лучше использовать таблицу и отношения для Path и Bound.
Блестящий ответ, спасибо, что нашли время, чтобы работать со всем этим - я ценю, что это очень нестандартный случай. Это общая идея того, как я думал, что это сработает, но не может понять детали.Мне нужно изучить это немного ближе, чтобы действительно понять это - как вы говорите, может быть, не очень хорошая идея, даже пойти по этому пути. Ваша идея о том, как обойти проблему с списком, очень умна, и я не уверен, что я бы ударил. Еще раз спасибо. Дай тебе щедрость на этом, когда я не на мобильный :) –
Спасибо. Я рад помочь. –
Раздражающе, что минимальная щедрость, которую я могу дать по этому вопросу, теперь 200, потому что раньше у меня была щедрость. Я начал щедрость на совершенно несвязанный вопрос, что я смогу вознаградить вас через 24 часа. –