summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_12/4209.rst10
-rw-r--r--lib/sqlalchemy/orm/strategy_options.py24
-rw-r--r--test/orm/test_pickled.py33
3 files changed, 54 insertions, 13 deletions
diff --git a/doc/build/changelog/unreleased_12/4209.rst b/doc/build/changelog/unreleased_12/4209.rst
new file mode 100644
index 000000000..f61e90eda
--- /dev/null
+++ b/doc/build/changelog/unreleased_12/4209.rst
@@ -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.
diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py
index de9a11c56..43f571146 100644
--- a/lib/sqlalchemy/orm/strategy_options.py
+++ b/lib/sqlalchemy/orm/strategy_options.py
@@ -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:
diff --git a/test/orm/test_pickled.py b/test/orm/test_pickled.py
index 2369acb96..d9c30f6bf 100644
--- a/test/orm/test_pickled.py
+++ b/test/orm/test_pickled.py
@@ -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):