2013-10-14 35 views
9

我有一個項目,我想在關係數據庫(Postgres)中存儲一個大型結構(嵌套對象)。它是一個更大的結構的一部分,我並不關心序列化格式 - 我很高興它成爲一個專欄中的一個塊 - 我只想能夠堅持並相當快地恢復它。PickleType與SqlAlchemy中的可變跟蹤

爲了我的目的,SQLAlchemy PickleType主要完成這項工作。我遇到的問題是,我希望髒檢查工作(可變類型用於的)。我希望他們不僅可以在路徑中更改信息,而且可以在界限內(下一級別)進行工作。

class Group(Base): 
    __tablename__ = 'group' 

    id = Column(Integer, primary_key=True) 
    name = Column(String, nullable=False) 
    paths = Column(types.PickleType) 

class Path(object): 
    def __init__(self, style, bounds): 
     self.style = style 
     self.bounds = bounds 

class Bound(object): 
    def __init__(self, l, t, r, b): 
     self.l = l 
     self.t = t 
     self.r = r 
     self.b = b 

# this is all fine 
g = Group(name='g1', paths=[Path('blah', Bound(1,1,2,3)), 
          Path('other_style', Bound(1,1,2,3)),]) 
session.add(g) 
session.commit() 

# so is this 
g.name = 'g2' 
assert g in session.dirty 
session.commit() 

# but this won't work without some sort of tracking on the deeper objects 
g.paths[0].style = 'something else' 
assert g in session.dirty # nope 

我玩過可變類型,試圖讓它工作,但沒有任何運氣。在其他地方,我使用json列的可變類型,這很好 - 以一種看起來更簡單的方式,但因爲使用這些類,您還需要跟蹤對象內對象的更改。

任何想法讚賞。

回答

5

首先,如您所知,您必須跟蹤對象內對象的更改,因爲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中的當前實現似乎只將父對象指派給作爲屬性指定的對象,而不是序列的項目,像字典或列表。這是事情變得複雜的地方。我認爲列表項應該有列表本身作爲父項,但這不起作用的原因有兩個:首先,_parents弱項無法獲取關鍵字列表,其次,已更改()信號不會一直傳播到最頂端,所以我們只會將列表本身標記爲已更改。我不是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 

但是,這裏有最後一個問題。父母通過一個正在收聽「加載」事件的呼叫被分配,所以在初始化時,_父母字典是空的,孩子們什麼也沒有分配。我想也許有一些更清晰的方法可以通過監聽負載事件來做到這一點,但我認爲這樣做的骯髒方法是在檢索項目時重新指派父母,因此,請添加以下內容:

def __getitem__(self, key): 
     value = list.__getitem__(self, key) 

     for obj, key in self._parents.items(): 
      value._parents[obj] = key 

     return value 

最後,我們不得不使用上Group.paths是MutableList:

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如果你不需要靈活的模式,你可能會更好地使用路徑和邊界的表和關係。

+0

明智的答案,感謝您抽出時間完成所有工作 - 我明白這是一個非常不規範的案例。這是我如何看待它的工作的一般想法,但無法完全掌握細節。我需要仔細研究這一點,真正理解它 - 正如你所說,甚至可能不是一個好主意,即使走這條路。你如何解決清單問題的想法非常聰明,而且我不確定我會遇到什麼問題。再次感謝。當我不在手機上時,會給你一個獎勵:) –

+0

謝謝。我很樂意提供幫助。 –

+0

令人煩惱的是,我可以在這個問題上給出的最低獎金是200,因爲我以前在這裏有過賞金。我已經開始了一個完全無關的問題的賞金,我會在24小時內獎勵你。 –