From 029ae72b2fffc5a69acf7fc610377cd0a148870e Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 28 Jan 2012 13:48:05 -0500 Subject: [PATCH] - [bug] Fixed bug where unpickled object didn't have enough of its state set up to work correctly within the unpickle() event established by the mutable object extension, if the object needed ORM attribute access within __eq__() or similar. [ticket:2362] --- CHANGES | 7 +++++++ lib/sqlalchemy/orm/state.py | 4 ++++ test/ext/test_mutable.py | 32 +++++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index afe9168cd8..e1c9969df7 100644 --- a/CHANGES +++ b/CHANGES @@ -66,6 +66,13 @@ CHANGES setattr/delattr used on a hybrid that doesn't define fset or fdel. [ticket:2353] + - [bug] Fixed bug where unpickled object didn't + have enough of its state set up to work + correctly within the unpickle() event established + by the mutable object extension, if the object + needed ORM attribute access within + __eq__() or similar. [ticket:2362] + - sql - [feature] Added "false()" and "true()" expression constructs to sqlalchemy.sql namespace, though diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 117341275a..4803ecdc3d 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -187,6 +187,10 @@ class InstanceState(object): if 'load_path' in state: self.load_path = interfaces.deserialize_path(state['load_path']) + # setup _sa_instance_state ahead of time so that + # unpickle events can access the object normally. + # see [ticket:2362] + manager.setup_instance(inst, self) manager.dispatch.unpickle(self, state) def initialize(self, key): diff --git a/test/ext/test_mutable.py b/test/ext/test_mutable.py index 129a460b0c..60576f0078 100644 --- a/test/ext/test_mutable.py +++ b/test/ext/test_mutable.py @@ -17,6 +17,13 @@ class Foo(fixtures.BasicEntity): class SubFoo(Foo): pass +class FooWithEq(object): + def __init__(self, **kw): + for k in kw: + setattr(self, k, kw[k]) + def __eq__(self, other): + return self.id == other.id + class _MutableDictTestBase(object): run_define_tables = 'each' @@ -317,12 +324,10 @@ class _CompositeTestBase(object): return self.x, self.y def __getstate__(self): - d = dict(self.__dict__) - d.pop('_parents', None) - return d + return self.x, self.y - #def __setstate__(self, state): - # self.x, self.y = state + def __setstate__(self, state): + self.x, self.y = state def __eq__(self, other): return isinstance(other, Point) and \ @@ -330,6 +335,23 @@ class _CompositeTestBase(object): other.y == self.y return Point +class MutableCompositesUnpickleTest(_CompositeTestBase, fixtures.MappedTest): + + @classmethod + def setup_mappers(cls): + foo = cls.tables.foo + + cls.Point = cls._type_fixture() + + mapper(FooWithEq, foo, properties={ + 'data':composite(cls.Point, foo.c.x, foo.c.y) + }) + + def test_unpickle_modified_eq(self): + u1 = FooWithEq(data=self.Point(3, 5)) + for loads, dumps in picklers(): + loads(dumps(u1)) + class MutableCompositesTest(_CompositeTestBase, fixtures.MappedTest): @classmethod -- 2.47.2