2013-10-14 18 views
9

Tôi có một dự án mà tôi muốn lưu trữ một cấu trúc lớn (đối tượng lồng nhau) trong một db quan hệ (Postgres). Đó là một phần của cấu trúc lớn hơn và tôi không thực sự quan tâm đến định dạng tuần tự hóa - tôi rất vui vì nó là một đốm màu trong một cột - tôi chỉ muốn có thể tồn tại và khôi phục lại nó một cách nhanh chóng.PickleType với theo dõi Mutable trong SqlAlchemy

Vì mục đích của tôi, SQLAlchemy PickleType chủ yếu thực hiện công việc. Vấn đề tôi có là tôi muốn các kiểm tra bẩn để làm việc (một cái gì đó mà các loại Mutable được sử dụng cho). Tôi muốn họ làm việc không chỉ nếu tôi thay đổi thông tin trong các đường dẫn mà còn trong giới hạn (mà ngồi một cấp độ xuống).

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 

Tôi đã chơi xung quanh với các loại Mutable cố gắng làm cho nó hoạt động nhưng không có may mắn. Ở những nơi khác, tôi sử dụng các loại có thể thay đổi cho cột json, điều này có vẻ đơn giản hơn vì với các lớp này, bạn cần phải theo dõi các thay đổi đối với các đối tượng trong các đối tượng.

Bất kỳ suy nghĩ nào được đánh giá cao.

Trả lời

5

Trước hết, khi bạn nhận ra, bạn phải theo dõi các thay đổi đối tượng trong các đối tượng, vì không có cách nào để SQLAlchemy biết một đối tượng bên trong đã thay đổi. Vì vậy, chúng tôi sẽ nhận được rằng ra khỏi con đường với một đối tượng có thể thay đổi cơ sở chúng tôi có thể sử dụng cho cả hai:

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 

Và chúng tôi cũng cần phải theo dõi những thay đổi trong danh sách những con đường, vì vậy, chúng ta phải làm điều đó một cũng có thể thay đổi đối tượng. Tuy nhiên, Mutable theo dõi các thay đổi ở trẻ em bằng cách truyền chúng cho cha mẹ khi phương thức changed() được gọi và thực thi hiện tại trong SQLAlchemy dường như chỉ gán một phụ huynh cho một người được gán làm thuộc tính chứ không phải là một mục của chuỗi như một từ điển hoặc một danh sách. Đây là nơi mọi thứ trở nên phức tạp.

Tôi nghĩ rằng các mục trong danh sách phải có chính danh sách dưới dạng cha, nhưng không hoạt động vì hai lý do: đầu tiên, yếu tố _parents không thể lấy danh sách cho khóa và thứ hai, thay đổi() tín hiệu không lan truyền tất cả các cách để đầu trang, vì vậy, chúng tôi sẽ chỉ được đánh dấu danh sách chính nó như là thay đổi. Tôi không chắc chắn 100% như thế nào chính xác này, nhưng cách để đi dường như được chỉ định cha mẹ của danh sách cho mỗi mục, do đó, đối tượng nhóm nhận được cuộc gọi flag_modified khi một mục được thay đổi. Điều này nên làm điều đó.

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 

Tuy nhiên, có một vấn đề mới nhất ở đây. Các bậc cha mẹ được chỉ định bởi một cuộc gọi lắng nghe trên sự kiện 'tải', do đó, tại thời điểm khởi tạo, dict _parents là trống rỗng, và các em không có gì được giao. Tôi nghĩ rằng có thể có một số cách rõ ràng hơn bạn có thể làm điều đó bằng cách lắng nghe sự kiện tải trọng, nhưng tôi đã tìm ra cách bẩn thỉu để làm điều đó sẽ được chỉ định lại cha mẹ khi các mục được truy xuất, do đó, hãy thêm:

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

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

     return value 

Cuối cùng, chúng ta phải sử dụng MutableList trên 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)) 

Và với tất cả các mã này thử nghiệm của bạn nên làm việc:

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 

Thẳng thắn mà nói, tôi không chắc chắn như thế nào an toàn là đến ge Điều này về sản xuất, và nếu bạn không cần một lược đồ linh hoạt, bạn có thể tốt hơn bằng cách sử dụng một bảng và các mối quan hệ cho Đường dẫn và Ràng buộc.

+0

Câu trả lời rõ ràng, cảm ơn vì đã dành thời gian để làm việc tất cả những điều đó - tôi đánh giá cao đây là trường hợp rất chuẩn. Đó là ý tưởng chung về cách tôi đoán nó sẽ hoạt động nhưng không thể nắm bắt được chi tiết.Tôi cần phải nghiên cứu kỹ hơn một chút để thực sự hiểu nó - như bạn nói, có lẽ không phải là một ý tưởng tốt để thậm chí đi tuyến đường này. Ý tưởng của bạn về cách làm việc xung quanh vấn đề danh sách là rất thông minh, và tôi không chắc chắn tôi có thể đạt được. Cảm ơn một lần nữa. Sẽ cung cấp cho bạn một tiền thưởng về điều này khi tôi không có trên điện thoại di động :) –

+0

Cảm ơn bạn. Tôi rất vui được giúp đỡ. –

+0

Đáng ngại tiền thưởng tối thiểu tôi có thể đưa ra cho câu hỏi này bây giờ là 200 vì trước đây tôi đã có tiền thưởng ở đây. Tôi đã bắt đầu một tiền thưởng trên một câu hỏi hoàn toàn không liên quan đến việc tôi sẽ có thể thưởng cho bạn trong thời gian 24 giờ. –

Các vấn đề liên quan