to state actually within the current flush.
[ticket:2082]
+ - Improvements to the error messages emitted when
+ querying against column-only entities in conjunction
+ with (typically incorrectly) using loader options,
+ where the parent entity is not fully present.
+ [ticket:2069]
+
- sql
- Added a fully descriptive error message for the
case where Column is subclassed and _make_proxy()
state['key'] = tuple(ret)
self.__dict__ = state
- def _find_entity( self, query, mapper, raiseerr):
+ def _find_entity_prop_comparator(self, query, token, mapper, raiseerr):
if mapperutil._is_aliased_class(mapper):
searchfor = mapper
isa = False
return ent
else:
if raiseerr:
- raise sa_exc.ArgumentError("Can't find entity %s in "
- "Query. Current list: %r" % (searchfor,
- [str(m.path_entity) for m in query._entities]))
+ raise sa_exc.ArgumentError(
+ "Can't find property '%s' on any entity "
+ "specified in this Query." % (token,)
+ )
+ else:
+ return None
+
+ def _find_entity_basestring(self, query, token, raiseerr):
+ for ent in query._mapper_entities:
+ # return only the first _MapperEntity when searching
+ # based on string prop name. Ideally object
+ # attributes are used to specify more exactly.
+ return ent
+ else:
+ if raiseerr:
+ raise sa_exc.ArgumentError(
+ "Can't find property named '%s' on the first mapped "
+ "entity in this Query. "
+ "Consider using an attribute object instead of a "
+ "string name to target a specific entity." % (token, )
+ )
else:
return None
sub_tokens = token.split(".", 1)
token = sub_tokens[0]
tokens.extendleft(sub_tokens[1:])
-
if not entity:
if current_path:
if current_path[1] == token:
current_path = current_path[2:]
continue
- entity = query._entity_zero()
+ entity = self._find_entity_basestring(query,
+ token, raiseerr)
path_element = entity.path_entity
mapper = entity.mapper
mappers.append(mapper)
prop = mapper.get_property(token)
else:
prop = None
- key = token
elif isinstance(token, PropComparator):
prop = token.property
if not entity:
prop.key]:
current_path = current_path[2:]
continue
- entity = self._find_entity(query,
- token.parententity, raiseerr)
+ entity = self._find_entity_prop_comparator(query,
+ prop.key, token.parententity, raiseerr)
if not entity:
return [], []
path_element = entity.path_entity
mapper = entity.mapper
mappers.append(prop.parent)
- key = prop.key
else:
raise sa_exc.ArgumentError('mapper option expects '
'string key or list of attributes')
import sqlalchemy as sa
from test.lib import testing
from sqlalchemy.orm import joinedload, deferred, undefer, \
- joinedload_all, backref
+ joinedload_all, backref, eagerload
from sqlalchemy import Integer, String, Date, ForeignKey, and_, select, \
func
from test.lib.schema import Table, Column
mapper(Item, items, properties = dict(
keywords = relationship(Keyword, secondary=item_keywords, lazy='select',
order_by=keywords.c.id)))
-
q = create_session().query(Item)
def go():
self.assert_sql_count(testing.db, go, 1)
+
@testing.resolve_artifact_names
def test_cyclical(self):
"""A circular eager relationship breaks the cycle with a lazy loader"""
assert_raises_message(
sa.exc.ArgumentError,
- r"Can't find entity Mapper\|Order\|orders in Query. "
- r"Current list: \['Mapper\|User\|users'\]",
+ "Can't find property 'items' on any entity "
+ "specified in this Query.",
sess.query(User).options, sa.orm.joinedload(Order.items))
# joinedload "keywords" on items. it will lazy load "orders", then
opt = self._option_fixture(Address.user, User.addresses)
self._assert_path_result(opt, q, [], [])
+class OptionsNoPropTest(_base.MappedTest):
+ """test the error messages emitted when using property
+ options in conjunection with column-only entities.
+
+ """
+
+ @testing.resolve_artifact_names
+ def test_option_with_mapper_using_basestring(self):
+ self._assert_option([Item], 'keywords')
+
+ @testing.resolve_artifact_names
+ def test_option_with_mapper_using_PropCompatator(self):
+ self._assert_option([Item], Item.keywords)
+
+ @testing.resolve_artifact_names
+ def test_option_with_mapper_then_column_using_basestring(self):
+ self._assert_option([Item, Item.id], 'keywords')
+
+ @testing.resolve_artifact_names
+ def test_option_with_mapper_then_column_using_PropComparator(self):
+ self._assert_option([Item, Item.id], Item.keywords)
+
+ @testing.resolve_artifact_names
+ def test_option_with_column_then_mapper_using_basestring(self):
+ self._assert_option([Item.id, Item], 'keywords')
+
+ @testing.resolve_artifact_names
+ def test_option_with_column_then_mapper_using_PropComparator(self):
+ self._assert_option([Item.id, Item], Item.keywords)
+
+ @testing.resolve_artifact_names
+ def test_option_with_column_using_basestring(self):
+ message = \
+ "Can't find property named 'keywords' on the first mapped "\
+ "entity in this Query. Consider using an attribute object "\
+ "instead of a string name to target a specific entity."
+ self._assert_eager_with_just_column_exception(Item.id,
+ 'keywords', message)
+
+ @testing.resolve_artifact_names
+ def test_option_with_column_using_PropComparator(self):
+ message = \
+ "Can't find property 'keywords' on any entity specified "\
+ "in this Query\."
+ self._assert_eager_with_just_column_exception(Item.id,
+ Item.keywords, message)
+
+ @classmethod
+ def define_tables(cls, metadata):
+ pass
+
+ @classmethod
+ @testing.resolve_artifact_names
+ def setup_mappers(cls):
+ mapper(Keyword, keywords)
+ mapper(Item, items,
+ properties=dict(keywords=relationship(Keyword,
+ secondary=item_keywords)))
+
+ @testing.resolve_artifact_names
+ def _assert_option(self, entity_list, option):
+ q = create_session().query(*entity_list).\
+ options(eagerload(option))
+ key = ('loaderstrategy', (class_mapper(Item), 'keywords'))
+ assert key in q._attributes
+
+ def _assert_eager_with_just_column_exception(self, column,
+ eager_option, message):
+ assert_raises_message(sa.exc.ArgumentError, message,
+ create_session().query(column).options,
+ eagerload(eager_option))