SQLAlchemy:排除列被腌制

SQLAlchemy: exclude a column from being pickled

是否可以在 SQLAlchemy 中将列标记为“请勿腌制”,以便在取消腌制后按需从数据库加载该列?

这可以通过 deferred 列部分实现,但如果随后加载该列,它也会被 pickled。

(我问的是,由于内置 buffer 的不可腌制性质,Python2.7 中酸洗 Geoalchemy2 几何列似乎仍然存在问题:https://github.com/geoalchemy/geoalchemy/issues/24 )

这是一个天真的 __getstate__ 实现,它不起作用,但有望提示正确答案:

Base = declarative_base(metadata=route_metadata)

cols_to_omit = ['my_col_1']

class MyClass(Base):

    my_col_0 = Column(Integer)
    my_col_1 = Column(Integer)

    def __getstate__(self):
        return dict((c, getattr(self, c)) for c in \
              self.__table__.columns.keys() \
              if c not in cols_to_omit)

    def __setstate__(self, *args):
        for c, v in args[0].items():
            setattr(self, c, v)


from pickle import *
pp = dumps(MyClass.query.first())
obj = loads(pp)

这给出 AttributeError: 'MyClass' object has no attribute '_sa_instance_state'

这有点复杂,因为你不能仅仅阻止列的 pickle,因为控制延迟加载的实际上是在 _sa_instance_state 属性中,所以即使你不 pickle 某些属性在您的实例字典中,SQLAlchemy 不知道这意味着它已过期。

一个(有点hacky的)解决方案是在你unpickle时让属性过期:

class Foo(Base):
    __tablename__ = "foo"

    id = Column(Integer, primary_key=True)
    foo = Column(String)
    bar = Column(String)

    def __getstate__(self):
        return {k: v for k, v in self.__dict__.items() if k != "bar"}

    def __setstate__(self, d):
        for k, v in d.items():
            self.__dict__[k] = v
        self._sa_instance_state._expire_attributes(self.__dict__, ("bar",))

注意unpickling的结果是一个detached instance,所以你需要注意如何使用它:

foo = session.query(Foo).first()
foo = pickle.loads(pickle.dumps(foo))
print(foo.foo)  # fine
# print(foo.bar)  # sqlalchemy.orm.exc.DetachedInstanceError
foo = session.merge(foo, load=False)
print(foo.foo)  # no query
print(foo.bar)  # causes a query