From: Mike Bayer Date: Mon, 5 Mar 2018 19:48:00 +0000 (-0500) Subject: Don't include AliasedClass when pickling options X-Git-Tag: rel_1_3_0b1~236 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=08a8e8cf3b06ed9eb4003f5727a3551d2b479509;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Don't include AliasedClass when pickling options 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 --- diff --git a/doc/build/changelog/unreleased_12/4209.rst b/doc/build/changelog/unreleased_12/4209.rst new file mode 100644 index 0000000000..f61e90eda6 --- /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 de9a11c56b..43f571146f 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 2369acb960..d9c30f6bf0 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):