Skip to content

Commit

Permalink
Merge "Refer to existing of_type when resolving string attribute name"
Browse files Browse the repository at this point in the history
  • Loading branch information
zzzeek authored and Gerrit Code Review committed Dec 8, 2018
2 parents bfd3a68 + 099f3fd commit 0d4c0c1
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 6 deletions.
8 changes: 8 additions & 0 deletions doc/build/changelog/unreleased_12/4400.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. change::
:tags: bug, orm
:tickests: 4400

Fixed bug where chaining of mapper options using
:meth:`.RelationshipProperty.of_type` in conjunction with a chained option
that refers to an attribute name by string only would fail to locate the
attribute.
9 changes: 7 additions & 2 deletions lib/sqlalchemy/orm/strategy_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ def _process(self, query, raiseerr):
query._attributes.update(self.context)

def _generate_path(self, path, attr, wildcard_key, raiseerr=True):
existing_of_type = self._of_type
self._of_type = None

if raiseerr and not path.has_entity:
Expand All @@ -188,16 +189,20 @@ def _generate_path(self, path, attr, wildcard_key, raiseerr=True):
self.path = path
return path

if existing_of_type:
ent = inspect(existing_of_type)
else:
ent = path.entity
try:
# use getattr on the class to work around
# synonyms, hybrids, etc.
attr = getattr(path.entity.class_, attr)
attr = getattr(ent.class_, attr)
except AttributeError:
if raiseerr:
raise sa_exc.ArgumentError(
"Can't find property named '%s' on the "
"mapped entity %s in this Query. " % (
attr, path.entity)
attr, ent)
)
else:
return None
Expand Down
187 changes: 183 additions & 4 deletions test/orm/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@ def _assert_path_result(self, opt, q, paths):
q._attributes = q._attributes.copy()
attr = {}

for val in opt._to_bind:
val._bind_loader(
[ent.entity_zero for ent in q._mapper_entities],
q._current_path, attr, False)
if isinstance(opt, strategy_options._UnboundLoad):
for val in opt._to_bind:
val._bind_loader(
[ent.entity_zero for ent in q._mapper_entities],
q._current_path, attr, False)
else:
opt._process(q, True)
attr = q._attributes

assert_paths = [k[1] for k in attr]
eq_(
Expand Down Expand Up @@ -183,6 +187,127 @@ def test_set_strat_col(self):
)


class OfTypePathingTest(PathTest, QueryTest):
def _fixture(self):
User, Address = self.classes.User, self.classes.Address
Dingaling = self.classes.Dingaling
address_table = self.tables.addresses

class SubAddr(Address):
pass

mapper(SubAddr, inherits=Address, properties={
"sub_attr": column_property(address_table.c.email_address),
"dings": relationship(Dingaling)
})

return User, Address, SubAddr

def test_oftype_only_col_attr_unbound(self):
User, Address, SubAddr = self._fixture()

l1 = defaultload(
User.addresses.of_type(SubAddr)).defer(SubAddr.sub_attr)

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'sub_attr')]
)

def test_oftype_only_col_attr_bound(self):
User, Address, SubAddr = self._fixture()

l1 = Load(User).defaultload(
User.addresses.of_type(SubAddr)).defer(SubAddr.sub_attr)

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'sub_attr')]
)

def test_oftype_only_col_attr_string_unbound(self):
User, Address, SubAddr = self._fixture()

l1 = defaultload(
User.addresses.of_type(SubAddr)).defer("sub_attr")

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'sub_attr')]
)

def test_oftype_only_col_attr_string_bound(self):
User, Address, SubAddr = self._fixture()

l1 = Load(User).defaultload(
User.addresses.of_type(SubAddr)).defer("sub_attr")

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'sub_attr')]
)

def test_oftype_only_rel_attr_unbound(self):
User, Address, SubAddr = self._fixture()

l1 = defaultload(
User.addresses.of_type(SubAddr)).joinedload(SubAddr.dings)

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'dings')]
)

def test_oftype_only_rel_attr_bound(self):
User, Address, SubAddr = self._fixture()

l1 = Load(User).defaultload(
User.addresses.of_type(SubAddr)).joinedload(SubAddr.dings)

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'dings')]
)

def test_oftype_only_rel_attr_string_unbound(self):
User, Address, SubAddr = self._fixture()

l1 = defaultload(
User.addresses.of_type(SubAddr)).joinedload("dings")

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'dings')]
)

def test_oftype_only_rel_attr_string_bound(self):
User, Address, SubAddr = self._fixture()

l1 = Load(User).defaultload(
User.addresses.of_type(SubAddr)).defer("sub_attr")

sess = Session()
q = sess.query(User)
self._assert_path_result(
l1, q,
[(User, 'addresses'), (User, 'addresses', SubAddr, 'sub_attr')]
)


class OptionsTest(PathTest, QueryTest):

def _option_fixture(self, *arg):
Expand Down Expand Up @@ -439,6 +564,26 @@ class SubAddr(Address):
(u_mapper, u_mapper.attrs.addresses, a_mapper, a_mapper.attrs.user)
])

def test_of_type_string_attr(self):
User, Address = self.classes.User, self.classes.Address

sess = Session()

class SubAddr(Address):
pass
mapper(SubAddr, inherits=Address)

q = sess.query(User)
opt = self._option_fixture(
User.addresses.of_type(SubAddr), "user")

u_mapper = inspect(User)
a_mapper = inspect(Address)
self._assert_path_result(opt, q, [
(u_mapper, u_mapper.attrs.addresses),
(u_mapper, u_mapper.attrs.addresses, a_mapper, a_mapper.attrs.user)
])

def test_of_type_plus_level(self):
Dingaling, User, Address = (self.classes.Dingaling,
self.classes.User,
Expand Down Expand Up @@ -1295,6 +1440,23 @@ def test_unbound_cache_key_of_type_subclass_relationship(self):
)
)

def test_unbound_cache_key_of_type_subclass_relationship_stringattr(self):
User, Address, Order, Item, SubItem, Keyword = self.classes(
'User', 'Address', 'Order', 'Item', 'SubItem', "Keyword")

query_path = self._make_path_registry([Order, "items", Item])

opt = subqueryload(
Order.items.of_type(SubItem)).subqueryload("extra_keywords")

eq_(
opt._generate_cache_key(query_path),
(
(SubItem, ('lazy', 'subquery')),
('extra_keywords', Keyword, ('lazy', 'subquery'))
)
)

def test_bound_cache_key_of_type_subclass_relationship(self):
User, Address, Order, Item, SubItem, Keyword = self.classes(
'User', 'Address', 'Order', 'Item', 'SubItem', "Keyword")
Expand All @@ -1312,6 +1474,23 @@ def test_bound_cache_key_of_type_subclass_relationship(self):
)
)

def test_bound_cache_key_of_type_subclass_string_relationship(self):
User, Address, Order, Item, SubItem, Keyword = self.classes(
'User', 'Address', 'Order', 'Item', 'SubItem', "Keyword")

query_path = self._make_path_registry([Order, "items", Item])

opt = Load(Order).subqueryload(
Order.items.of_type(SubItem)).subqueryload("extra_keywords")

eq_(
opt._generate_cache_key(query_path),
(
(SubItem, ('lazy', 'subquery')),
('extra_keywords', Keyword, ('lazy', 'subquery'))
)
)

def test_unbound_cache_key_excluded_of_type_safe(self):
User, Address, Order, Item, SubItem = self.classes(
'User', 'Address', 'Order', 'Item', 'SubItem')
Expand Down

0 comments on commit 0d4c0c1

Please sign in to comment.