From: Mike Bayer Date: Wed, 10 Jan 2018 20:27:33 +0000 (-0500) Subject: Set up of_type variable for legacy loader option deserialize X-Git-Tag: rel_1_2_1~7^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4332b970c9ec0b32170b1206b965fa7eb5d62a3f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Set up of_type variable for legacy loader option deserialize Fixed regression where pickle format of a Load / _UnboundLoad object (e.g. loader options) changed and ``__setstate__()`` was raising an UnboundLocalError for an object received from the legacy format, even though an attempt was made to do so. tests are now added to ensure this works. Change-Id: Idccf643e010776817dd32512facdefa263188814 Fixes: #4159 --- diff --git a/doc/build/changelog/unreleased_12/4159.rst b/doc/build/changelog/unreleased_12/4159.rst new file mode 100644 index 0000000000..5ed2773bc9 --- /dev/null +++ b/doc/build/changelog/unreleased_12/4159.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm + :tickets: 4159 + + Fixed regression where pickle format of a Load / _UnboundLoad object (e.g. + loader options) changed and ``__setstate__()`` was raising an + UnboundLocalError for an object received from the legacy format, even + though an attempt was made to do so. tests are now added to ensure this + works. diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 13c5100475..1963e5843d 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -449,6 +449,7 @@ class _UnboundLoad(Load): if len(key) == 2: # support legacy cls, propkey = key + of_type = None else: cls, propkey, of_type = key prop = getattr(cls, propkey) diff --git a/test/orm/test_options.py b/test/orm/test_options.py index 731aee1e55..9d3b8b7026 100644 --- a/test/orm/test_options.py +++ b/test/orm/test_options.py @@ -890,6 +890,86 @@ class OptionsNoPropTest(_fixtures.FixtureTest): joinedload(eager_option)) +class PickleTest(PathTest, QueryTest): + + def _option_fixture(self, *arg): + return strategy_options._UnboundLoad._from_keys( + strategy_options._UnboundLoad.joinedload, arg, True, {}) + + def test_modern_opt_getstate(self): + User = self.classes.User + + sess = Session() + q = sess.query(User) + + opt = self._option_fixture(User.addresses) + eq_( + opt.__getstate__(), + { + '_is_chain_link': False, + 'local_opts': {}, + 'is_class_strategy': False, + 'path': [(User, 'addresses', None)], + 'propagate_to_loaders': True, + '_to_bind': [opt], + 'strategy': (('lazy', 'joined'),)} + ) + + def test_modern_opt_setstate(self): + User = self.classes.User + + opt = strategy_options._UnboundLoad.__new__( + strategy_options._UnboundLoad) + state = { + '_is_chain_link': False, + 'local_opts': {}, + 'is_class_strategy': False, + 'path': [(User, 'addresses', None)], + 'propagate_to_loaders': True, + '_to_bind': [opt], + 'strategy': (('lazy', 'joined'),)} + + opt.__setstate__(state) + + query = create_session().query(User) + attr = {} + load = opt._bind_loader( + [ent.entity_zero for ent in query._mapper_entities], + query._current_path, attr, False) + + eq_( + load.path, + inspect(User)._path_registry + [User.addresses.property][inspect(self.classes.Address)]) + + def test_legacy_opt_setstate(self): + User = self.classes.User + + opt = strategy_options._UnboundLoad.__new__( + strategy_options._UnboundLoad) + state = { + '_is_chain_link': False, + 'local_opts': {}, + 'is_class_strategy': False, + 'path': [(User, 'addresses')], + 'propagate_to_loaders': True, + '_to_bind': [opt], + 'strategy': (('lazy', 'joined'),)} + + opt.__setstate__(state) + + query = create_session().query(User) + attr = {} + load = opt._bind_loader( + [ent.entity_zero for ent in query._mapper_entities], + query._current_path, attr, False) + + eq_( + load.path, + inspect(User)._path_registry + [User.addresses.property][inspect(self.classes.Address)]) + + class LocalOptsTest(PathTest, QueryTest): @classmethod def setup_class(cls):