summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_12/4159.rst9
-rw-r--r--lib/sqlalchemy/orm/strategy_options.py1
-rw-r--r--test/orm/test_options.py80
3 files changed, 90 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_12/4159.rst b/doc/build/changelog/unreleased_12/4159.rst
new file mode 100644
index 000000000..5ed2773bc
--- /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 13c510047..1963e5843 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 731aee1e5..9d3b8b702 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):