in the cache are not the same ones in the current Session.
"""
+ super_ = super(CachingQuery, self)
+
if hasattr(self, '_cache_region'):
- return self.get_value(createfunc=lambda: list(Query.__iter__(self)))
+ return self.get_value(createfunc=lambda: list(super_.__iter__()))
+ else:
+ return super_.__iter__()
+
+ def _execute_and_instances(self, context):
+ """override _execute_and_instances to pull results from dogpile
+ if the query is invoked directly from an external context.
+
+ This method is necessary in order to maintain compatibility
+ with the "baked query" system now used by default in some
+ relationship loader scenarios. Note also the
+ RelationshipCache._generate_cache_key method which enables
+ the baked query to be used within lazy loads.
+
+ .. versionadded:: 1.2.7
+ """
+ super_ = super(CachingQuery, self)
+
+ if context.query is not self and hasattr(self, '_cache_region'):
+ # special logic called when the Query._execute_and_instances()
+ # method is called directly from the baked query
+ return self.get_value(
+ createfunc=lambda: list(
+ super_._execute_and_instances(context)
+ )
+ )
else:
- return Query.__iter__(self)
+ return super_._execute_and_instances(context)
def _get_cache_plus_key(self):
"""Return a cache region plus key."""
self._relationship_options.update(option._relationship_options)
return self
+ def _generate_cache_key(self, path):
+ """Indicate to the lazy-loader strategy that a "baked" query
+ may be used by returning ``None``.
+
+ If this method is omitted, the default implementation of
+ :class:`.MapperOption._generate_cache_key` takes place, which
+ returns ``False`` to disable the "baked" query from being used.
+
+ .. versionadded:: 1.2.7
+
+ """
+ return None
# 2. o2m lazyload where m2o backrefs have an eager load, test
# that eager load is canceled out
# 3. uselist = False, uselist=False assertion
+
+# assert that the integration style illustrated in the dogpile.cache
+# example works w/ baked
+class CustomIntegrationTest(testing.AssertsCompiledSQL, BakedTest):
+ run_setup_mappers = 'each'
+
+ def _o2m_fixture(self, lazy="select", **kw):
+ User = self.classes.User
+ Address = self.classes.Address
+
+ mapper(User, self.tables.users, properties={
+ 'addresses': relationship(
+ Address, order_by=self.tables.addresses.c.id,
+ lazy=lazy, **kw)
+ })
+ mapper(Address, self.tables.addresses)
+ return User, Address
+
+ def _query_fixture(self):
+ from sqlalchemy.orm.query import Query, _generative
+
+ class CachingQuery(Query):
+ cache = {}
+
+ @_generative()
+ def set_cache_key(self, key):
+ self._cache_key = key
+
+ def __iter__(self):
+ super_ = super(CachingQuery, self)
+
+ if hasattr(self, '_cache_key'):
+ return self.get_value(
+ createfunc=lambda: list(super_.__iter__()))
+ else:
+ return super_.__iter__()
+
+ def _execute_and_instances(self, context):
+ super_ = super(CachingQuery, self)
+
+ if context.query is not self and hasattr(self, '_cache_key'):
+ return self.get_value(
+ createfunc=lambda: list(
+ super_._execute_and_instances(context)
+ )
+ )
+ else:
+ return super_._execute_and_instances(context)
+
+ def get_value(self, createfunc):
+ if self._cache_key in self.cache:
+ return iter(self.cache[self._cache_key])
+ else:
+ self.cache[self._cache_key] = retval = createfunc()
+ return iter(retval)
+
+ return Session(query_cls=CachingQuery)
+
+ def _option_fixture(self):
+ from sqlalchemy.orm.interfaces import MapperOption
+
+ class RelationshipCache(MapperOption):
+
+ propagate_to_loaders = True
+
+ def process_query_conditionally(self, query):
+ if query._current_path:
+ query._cache_key = "user7_addresses"
+
+ def _generate_cache_key(self, path):
+ return None
+
+ return RelationshipCache()
+
+ def test_non_baked(self):
+ User, Address = self._o2m_fixture()
+
+ sess = self._query_fixture()
+ q = sess._query_cls
+ eq_(q.cache, {})
+
+ q = sess.query(User).filter(User.id == 7).set_cache_key("user7")
+
+ eq_(
+ q.all(),
+ [User(id=7, addresses=[Address(id=1)])]
+ )
+
+ eq_(q.cache, {"user7": [User(id=7, addresses=[Address(id=1)])]})
+
+ eq_(
+ q.all(),
+ [User(id=7, addresses=[Address(id=1)])]
+ )
+
+ def test_use_w_baked(self):
+ User, Address = self._o2m_fixture()
+
+ sess = self._query_fixture()
+ q = sess._query_cls
+ eq_(q.cache, {})
+
+ base_bq = self.bakery(
+ lambda s: s.query(User))
+ base_bq += lambda q: q.filter(User.id == 7)
+ base_bq += lambda q: q.set_cache_key("user7")
+
+ eq_(
+ base_bq(sess).all(),
+ [User(id=7, addresses=[Address(id=1)])]
+ )
+
+ eq_(q.cache, {"user7": [User(id=7, addresses=[Address(id=1)])]})
+
+ eq_(
+ base_bq(sess).all(),
+ [User(id=7, addresses=[Address(id=1)])]
+ )
+
+ def test_plain_w_baked_lazyload(self):
+ User, Address = self._o2m_fixture()
+ opt = self._option_fixture()
+
+ sess = self._query_fixture()
+ q = sess._query_cls
+ eq_(q.cache, {})
+
+ q = sess.query(User).filter(User.id == 7).options(opt)
+
+ u = q.first()
+ eq_(u.addresses, [Address(id=1)])
+
+ eq_(q.cache, {"user7_addresses": [Address(id=1)]})
+
+ sess.close()
+
+ # ensure caching logic works after query has been baked
+ q.cache.clear()
+
+ u = q.first()
+ eq_(u.addresses, [Address(id=1)])
+ eq_(q.cache, {"user7_addresses": [Address(id=1)]})