- AttributeExtension moved to interfaces, and .delete is now
.remove The event method signature has also been swapped around.
- - major interface pare-down for Query: all selectXXX methods
+ - major overhaul for Query: all selectXXX methods
are deprecated. generative methods are now the standard
way to do things, i.e. filter(), filter_by(), all(), one(),
etc. Deprecated methods are docstring'ed with their
new replacements.
+
+ - Class-level properties are now usable as query elements ...no
+ more '.c.' ! "Class.c.propname" is now superceded by "Class.propname".
+ All clause operators are supported, as well as higher level operators
+ such as Class.prop==<some instance> for scalar attributes and
+ Class.prop.contains(<some instance>) for collection-based attributes
+ (both are also negatable). Table-based column expressions as well as
+ columns mounted on mapped classes via 'c' are of course still fully available
+ and can be freely mixed with the new attributes.
+ [ticket:643]
+
- removed ancient query.select_by_attributename() capability.
+
- added "aliased joins" positional argument to the front of
filter_by(). this allows auto-creation of joins that are aliased
locally to the individual filter_by() call. This allows the
querying divergent criteria. ClauseElements at the front of
filter_by() are removed (use filter()).
+ - added query.populate_existing().. - marks the query to reload
+ all attributes and collections of all instances touched in the query,
+ including eagerly-loaded entities [ticket:660]
+
+ - added eagerload_all(), allows eagerload_all('x.y.z') to specify eager
+ loading of all properties in the given path
+
- Eager loading has been enhanced to allow even more joins in more places.
It now functions at any arbitrary depth along self-referential
and cyclical structures. When loading cyclical structures, specify "join_depth"
scheme now and are a lot easier on the eyes, as well as of course
completely deterministic. [ticket:659]
- - Class-level properties are now usable as query elements ...no
- more '.c.' ! "Class.c.propname" is now superceded by "Class.propname".
- All clause operators are supported, as well as higher level operators
- such as Class.prop==<some instance> for scalar attributes and
- Class.prop.contains(<some instance>) for collection-based attributes
- (both are also negatable). Table-based column expressions as well as
- columns mounted on mapped classes via 'c' are of course still fully available
- and can be freely mixed with the new attributes.
- [ticket:643]
- added composite column properties. This allows you to create a
type which is represented by more than one column, when using the
from sqlalchemy.orm.session import object_session, attribute_manager
__all__ = ['relation', 'column_property', 'composite', 'backref', 'eagerload',
- 'lazyload', 'noload', 'deferred', 'defer', 'undefer',
+ 'eagerload_all', 'lazyload', 'noload', 'deferred', 'defer', 'undefer',
'undefer_group', 'extension', 'mapper', 'clear_mappers',
'compile_mappers', 'class_mapper', 'object_mapper',
'MapperExtension', 'Query', 'polymorphic_union', 'create_session',
return strategies.EagerLazyOption(name, lazy=False)
+def eagerload_all(name):
+ """Return a ``MapperOption`` that will convert all
+ properties along the given dot-separated path into an
+ eager load.
+
+ e.g::
+ query.options(eagerload_all('orders.items.keywords'))...
+
+ will set all of 'orders', 'orders.items', and 'orders.items.keywords'
+ to load in one eager load.
+
+ Used with ``query.options()``.
+ """
+
+ return strategies.EagerLazyOption(name, lazy=False, chained=True)
+
def lazyload(name):
"""Return a ``MapperOption`` that will convert the property of the
given name into a lazy load.
def __init__(self, key):
self.key = key
- def process_query_property(self, context, property):
+ def process_query_property(self, context, properties):
pass
- def process_selection_property(self, context, property):
+ def process_selection_property(self, context, properties):
pass
def process_query_context(self, context):
- self.process_query_property(context, self._get_property(context))
+ self.process_query_property(context, self._get_properties(context))
def process_selection_context(self, context):
- self.process_selection_property(context, self._get_property(context))
+ self.process_selection_property(context, self._get_properties(context))
- def _get_property(self, context):
+ def _get_properties(self, context):
try:
- prop = self.__prop
+ l = self.__prop
except AttributeError:
+ l = []
mapper = context.mapper
for token in self.key.split('.'):
prop = mapper.get_property(token, resolve_synonyms=True)
+ l.append(prop)
mapper = getattr(prop, 'mapper', None)
- self.__prop = prop
- return prop
+ self.__prop = l
+ return l
PropertyOption.logger = logging.class_logger(PropertyOption)
for an operation by a StrategizedProperty.
"""
- def process_query_property(self, context, property):
+ def is_chained(self):
+ return False
+
+ def process_query_property(self, context, properties):
self.logger.debug("applying option to QueryContext, property key '%s'" % self.key)
- context.attributes[("loaderstrategy", property)] = self.get_strategy_class()
+ if self.is_chained():
+ for prop in properties:
+ context.attributes[("loaderstrategy", prop)] = self.get_strategy_class()
+ else:
+ context.attributes[("loaderstrategy", properties[-1])] = self.get_strategy_class()
- def process_selection_property(self, context, property):
+ def process_selection_property(self, context, properties):
self.logger.debug("applying option to SelectionContext, property key '%s'" % self.key)
- context.attributes[("loaderstrategy", property)] = self.get_strategy_class()
+ if self.is_chained():
+ for prop in properties:
+ context.attributes[("loaderstrategy", prop)] = self.get_strategy_class()
+ else:
+ context.attributes[("loaderstrategy", properties[-1])] = self.get_strategy_class()
def get_strategy_class(self):
raise NotImplementedError()
criterion = prop.compare(operator.eq, instance, value_is_parent=True)
return Query(target, **kwargs).filter(criterion)
query_from_parent = classmethod(query_from_parent)
+
+ def populate_existing(self):
+ """return a Query that will refresh all instances loaded.
+
+ this includes all entities accessed from the database, including
+ secondary entities, eagerly-loaded collection items.
+
+ All changes present on entities which are already present in the session will
+ be reset and the entities will all be marked "clean".
+
+ This is essentially the en-masse version of load().
+ """
+
+ q = self._clone()
+ q._populate_existing = True
+ return q
def with_parent(self, instance, property=None):
"""add a join criterion corresponding to a relationship to the given parent instance.
EagerLoader.logger = logging.class_logger(EagerLoader)
class EagerLazyOption(StrategizedOption):
- def __init__(self, key, lazy=True):
+ def __init__(self, key, lazy=True, chained=False):
super(EagerLazyOption, self).__init__(key)
self.lazy = lazy
-
- def process_query_property(self, context, prop):
+ self.chained = chained
+
+ def is_chained(self):
+ return not self.lazy and self.chained
+
+ def process_query_property(self, context, properties):
if self.lazy:
- if prop in context.eager_loaders:
- context.eager_loaders.remove(prop)
+ if properties[-1] in context.eager_loaders:
+ context.eager_loaders.remove(properties[-1])
else:
- context.eager_loaders.add(prop)
- super(EagerLazyOption, self).process_query_property(context, prop)
+ for prop in properties:
+ context.eager_loaders.add(prop)
+ super(EagerLazyOption, self).process_query_property(context, properties)
def get_strategy_class(self):
if self.lazy:
raise exceptions.ArgumentError("Fetchmode must be one of 'join' or 'select'")
self.type = type
- def process_selection_property(self, context, property):
- context.attributes[('fetchmode', property)] = self.type
+ def process_selection_property(self, context, properties):
+ context.attributes[('fetchmode', properties[-1])] = self.type
class RowDecorateOption(PropertyOption):
def __init__(self, key, decorator=None, alias=None):
self.decorator = decorator
self.alias = alias
- def process_selection_property(self, context, property):
+ def process_selection_property(self, context, properties):
if self.alias is not None and self.decorator is None:
if isinstance(self.alias, basestring):
- self.alias = property.target.alias(self.alias)
+ self.alias = properties[-1].target.alias(self.alias)
def decorate(row):
d = {}
- for c in property.target.columns:
+ for c in properties[-1].target.columns:
d[c] = row[self.alias.corresponding_column(c)]
return d
self.decorator = decorate
- context.attributes[("eager_row_processor", property)] = self.decorator
+ context.attributes[("eager_row_processor", properties[-1])] = self.decorator
RowDecorateOption.logger = logging.class_logger(RowDecorateOption)
obj = self._check_literal(obj)
type_ = self._compare_type(obj)
+
+ # TODO: generalize operator overloading like this out into the types module
if op == operator.add and isinstance(type_, (sqltypes.Concatenable)):
op = ColumnOperators.concat_op
continue
else:
if value is not None:
- if value != getattr(other, attr):
+ if value != getattr(other, attr, None):
return False
else:
return True
assert not hasattr(u, 'user_name')
def testrefresh(self):
- mapper(User, users, properties={'addresses':relation(mapper(Address, addresses))})
+ mapper(User, users, properties={'addresses':relation(mapper(Address, addresses), backref='user')})
s = create_session()
u = s.get(User, 7)
u.user_name = 'foo'
a = Address()
- import sqlalchemy.orm.session
- assert sqlalchemy.orm.session.object_session(a) is None
+ assert object_session(a) is None
u.addresses.append(a)
self.assert_(a in u.addresses)
# get the attribute, it refreshes
self.assert_(u.user_name == 'jack')
self.assert_(a not in u.addresses)
+
def testexpirecascade(self):
mapper(User, users, properties={'addresses':relation(mapper(Address, addresses), cascade="all, refresh-expire")})
print "-------MARK----------"
- # eagerload orders, orders.items, orders.items.keywords
- q2 = sess.query(User).options(eagerload('orders'), eagerload('orders.items'), eagerload('orders.items.keywords'))
+ # eagerload orders.items.keywords; eagerload_all() implies eager load of orders, orders.items
+ q2 = sess.query(User).options(eagerload_all('orders.items.keywords'))
u = q2.select()
def go():
print u[0].orders[1].items[0].keywords[1]
})
mapper(Keyword, keywords)
-
class GetTest(QueryTest):
def test_get(self):
s = create_session()
u2 = s.query(User).get(7)
assert u is not u2
+ def test_load(self):
+ s = create_session()
+
+ try:
+ assert s.query(User).load(19) is None
+ assert False
+ except exceptions.InvalidRequestError:
+ assert True
+
+ u = s.query(User).load(7)
+ u2 = s.query(User).load(7)
+ assert u is u2
+ s.clear()
+ u2 = s.query(User).load(7)
+ assert u is not u2
+
+ u2.name = 'some name'
+ a = Address(name='some other name')
+ u2.addresses.append(a)
+ assert u2 in s.dirty
+ assert a in u2.addresses
+
+ s.query(User).load(7)
+ assert u2 not in s.dirty
+ assert u2.name =='jack'
+ assert a not in u2.addresses
+
def test_unicode(self):
"""test that Query.get properly sets up the type for the bind parameter. using unicode would normally fail
on postgres, mysql and oracle unless it is converted to an encoded string"""
mapper(LocalFoo, table)
assert create_session().query(LocalFoo).get(ustring) == LocalFoo(id=ustring, data=ustring)
+ def test_populate_existing(self):
+ s = create_session()
+
+ userlist = s.query(User).all()
+
+ u = userlist[0]
+ u.name = 'foo'
+ a = Address(name='ed')
+ u.addresses.append(a)
+
+ self.assert_(a in u.addresses)
+
+ s.query(User).populate_existing().all()
+
+ self.assert_(u not in s.dirty)
+
+ self.assert_(u.name == 'jack')
+
+ self.assert_(a not in u.addresses)
+
+ u.addresses[0].email_address = 'lala'
+ u.orders[1].items[2].description = 'item 12'
+ # test that lazy load doesnt change child items
+ s.query(User).populate_existing().all()
+ assert u.addresses[0].email_address == 'lala'
+ assert u.orders[1].items[2].description == 'item 12'
+
+ # eager load does
+ s.query(User).options(eagerload('addresses'), eagerload_all('orders.items')).populate_existing().all()
+ assert u.addresses[0].email_address == 'jack@bean.com'
+ assert u.orders[1].items[2].description == 'item 5'
+
class OperatorTest(QueryTest):
"""test sql.Comparator implementation for MapperProperties"""