]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Don't include AliasedClass when pickling options
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 5 Mar 2018 19:48:00 +0000 (14:48 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 5 Mar 2018 23:05:56 +0000 (18:05 -0500)
Fixed 1.2 regression where a mapper option that contains an
:class:`.AliasedClass` object, as is typical when using the
:meth:`.QueryableAttribute.of_type` method, could not be pickled.   1.1's
behavior was to omit the aliased class objects from the path, so this
behavior is restored.

Change-Id: I4b36a422b7c0e6a6da7ee3ba3ab282c13917a31f
Fixes: #4209
doc/build/changelog/unreleased_12/4209.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategy_options.py
test/orm/test_pickled.py

diff --git a/doc/build/changelog/unreleased_12/4209.rst b/doc/build/changelog/unreleased_12/4209.rst
new file mode 100644 (file)
index 0000000..f61e90e
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 4209
+    :versions: 1.3.0b1
+
+    Fixed 1.2 regression where a mapper option that contains an
+    :class:`.AliasedClass` object, as is typical when using the
+    :meth:`.QueryableAttribute.of_type` method, could not be pickled.   1.1's
+    behavior was to omit the aliased class objects from the path, so this
+    behavior is restored.
index de9a11c56ba339f3f7e5ab64c9661dca1ddcd457..43f571146f9ba4f9e5ba3751f329638307b1a030 100644 (file)
@@ -439,7 +439,7 @@ class _UnboundLoad(Load):
 
     def __getstate__(self):
         d = self.__dict__.copy()
-        d['path'] = self._serialize_path(self.path)
+        d['path'] = self._serialize_path(self.path, filter_aliased_class=True)
         return d
 
     def __setstate__(self, state):
@@ -454,7 +454,7 @@ class _UnboundLoad(Load):
                     cls, propkey, of_type = key
                 prop = getattr(cls, propkey)
                 if of_type:
-                    prop = prop.of_type(prop)
+                    prop = prop.of_type(of_type)
                 ret.append(prop)
             else:
                 ret.append(key)
@@ -520,19 +520,19 @@ class _UnboundLoad(Load):
 
         return to_chop[i:]
 
-    def _serialize_path(self, path, reject_aliased_class=False):
+    def _serialize_path(self, path, filter_aliased_class=False):
         ret = []
         for token in path:
             if isinstance(token, QueryableAttribute):
-                if reject_aliased_class and (
-                    (token._of_type and
-                        inspect(token._of_type).is_aliased_class)
-                    or
-                    inspect(token.parent).is_aliased_class
-                ):
-                    return False
-                ret.append(
-                    (token._parentmapper.class_, token.key, token._of_type))
+                if filter_aliased_class and token._of_type and \
+                        inspect(token._of_type).is_aliased_class:
+                    ret.append(
+                        (token._parentmapper.class_,
+                         token.key, None))
+                else:
+                    ret.append(
+                        (token._parentmapper.class_, token.key,
+                         token._of_type))
             elif isinstance(token, PropComparator):
                 ret.append((token._parentmapper.class_, token.key, None))
             else:
index 2369acb960b4bd7bb006de6cab816059fb87b883..d9c30f6bf0b902060486c203065532e12f0745c7 100644 (file)
@@ -11,7 +11,7 @@ from sqlalchemy.orm import mapper, relationship, create_session, \
     sessionmaker, attributes, interfaces,\
     clear_mappers, exc as orm_exc,\
     configure_mappers, Session, lazyload_all,\
-    lazyload, aliased
+    lazyload, aliased, subqueryload
 from sqlalchemy.orm import state as sa_state
 from sqlalchemy.orm import instrumentation
 from sqlalchemy.orm.collections import attribute_mapped_collection, \
@@ -21,6 +21,10 @@ from test.orm import _fixtures
 from sqlalchemy.testing.pickleable import User, Address, Dingaling, Order, \
     Child1, Child2, Parent, Screen, EmailUser
 
+from sqlalchemy.orm import with_polymorphic
+
+from .inheritance._poly_fixtures import Company, Person, Engineer, Manager, \
+    Boss, Machine, Paperwork, _Polymorphic
 
 class PickleTest(fixtures.MappedTest):
 
@@ -467,6 +471,33 @@ class PickleTest(fixtures.MappedTest):
                 Address(id=1, email_address="email1"))
 
 
+class OptionsTest(_Polymorphic):
+    @testing.requires.non_broken_pickle
+    def test_options_of_type(self):
+
+        with_poly = with_polymorphic(Person, [Engineer, Manager], flat=True)
+        for opt, serialized in [
+            (
+                sa.orm.joinedload(Company.employees.of_type(Engineer)),
+                [(Company, "employees", Engineer)]),
+            (
+                sa.orm.joinedload(Company.employees.of_type(with_poly)),
+                [(Company, "employees", None)]),
+        ]:
+            opt2 = pickle.loads(pickle.dumps(opt))
+            eq_(opt.__getstate__()['path'], serialized)
+            eq_(opt2.__getstate__()['path'], serialized)
+
+    def test_load(self):
+        s = Session()
+
+        with_poly = with_polymorphic(Person, [Engineer, Manager], flat=True)
+        emp = s.query(Company).options(
+            subqueryload(Company.employees.of_type(with_poly))).first()
+
+        e2 = pickle.loads(pickle.dumps(emp))
+
+
 class PolymorphicDeferredTest(fixtures.MappedTest):
     @classmethod
     def define_tables(cls, metadata):