.. changelog::
:version: 1.2.0b1
+ .. change:: baked_opts
+ :tags: feature, ext
+
+ Added new flag :paramref:`.Session.enable_baked_queries` to the
+ :class:`.Session` to allow baked queries to be disabled
+ session-wide, reducing memory use. Also added new :class:`.Bakery`
+ wrapper so that the bakery returned by :paramref:`.BakedQuery.bakery`
+ can be inspected.
+
.. change:: 3988
:tags: bug, orm
:tickets: 3988
management, removal of all redundant Python execution, and queries built up
with conditionals needed to be addressed, leading to the final approach.
+Disabling Baked Queries Session-wide
+------------------------------------
+
+The flag :paramref:`.Session.enable_baked_queries` may be set to False,
+causing all baked queries to not use the cache when used against that
+:class:`.Session`::
+
+ session = Session(engine, enable_baked_queries=False)
+
+Like all session flags, it is also accepted by factory objects like
+:class:`.sessionmaker` and methods like :meth:`.sessionmaker.configure`.
+
+The immediate rationale for this flag is to reduce memory use in the case
+that the query baking used by relationship loaders and other loaders
+is not desirable. It also can be used in the case that an application
+which is seeing issues potentially due to cache key conflicts from user-defined
+baked queries or other baked query issues can turn the behavior off, in
+order to identify or eliminate baked queries as the cause of an issue.
+
+.. versionadded:: 1.2
+
Lazy Loading Integration
------------------------
The :func:`.relationship` construct includes a flag
:paramref:`.relationship.bake_queries` which when set to False will cause
-that relationship to opt out of caching queries.
+that relationship to opt out of caching queries. Additionally, the
+:paramref:`.Session.enable_baked_queries` setting can be used to disable
+all "baked query" use. These flags can be useful to conserve memory,
+when memory conservation is more important than performance for a particular
+relationship or for the application overall.
API Documentation
-----------------
.. autoclass:: BakedQuery
:members:
+.. autoclass:: Bakery
+ :members:
+
.. autoclass:: Result
:members:
log = logging.getLogger(__name__)
+class Bakery(object):
+ """Callable which returns a :class:`.BakedQuery`.
+
+ This object is returned by the class method
+ :meth:`.BakedQuery.bakery`. It exists as an object
+ so that the "cache" can be easily inspected.
+
+ .. versionadded:: 1.2
+
+
+ """
+ __slots__ = 'cls', 'cache'
+
+ def __init__(self, cls_, cache):
+ self.cls = cls_
+ self.cache = cache
+
+ def __call__(self, initial_fn, *args):
+ return self.cls(self.cache, initial_fn, args)
+
+
class BakedQuery(object):
"""A builder object for :class:`.query.Query` objects."""
@classmethod
def bakery(cls, size=200, _size_alert=None):
- """Construct a new bakery."""
+ """Construct a new bakery.
- _bakery = util.LRUCache(size, size_alert=_size_alert)
+ :return: an instance of :class:`.Bakery`
- def call(initial_fn, *args):
- return cls(_bakery, initial_fn, args)
+ """
- return call
+ return Bakery(cls, util.LRUCache(size, size_alert=_size_alert))
def _clone(self):
b1 = BakedQuery.__new__(BakedQuery)
def __iter__(self):
bq = self.bq
- if bq._spoiled:
+ if not self.session.enable_baked_queries or bq._spoiled:
return iter(self._as_query())
baked_context = bq._bakery.get(bq._cache_key, None)
_enable_transaction_accounting=True,
autocommit=False, twophase=False,
weak_identity_map=True, binds=None, extension=None,
+ enable_baked_queries=True,
info=None,
query_cls=query.Query):
r"""Construct a new Session.
:class:`.sessionmaker` function, and is not sent directly to the
constructor for ``Session``.
+ :param enable_baked_queries: defaults to ``True``. A flag consumed
+ by the :mod:`sqlalchemy.ext.baked` extension to determine if
+ "baked queries" should be cached, as is the normal operation
+ of this extension. When set to ``False``, all caching is disabled,
+ including baked queries defined by the calling application as
+ well as those used internally. Setting this flag to ``False``
+ can significantly reduce memory use, however will also degrade
+ performance for those areas that make use of baked queries
+ (such as relationship loaders). Additionally, baked query
+ logic in the calling application or potentially within the ORM
+ that may be malfunctioning due to cache key collisions or similar
+ can be flagged by observing if this flag resolves the issue.
+
+ .. versionadded:: 1.2
+
:param _enable_transaction_accounting: Defaults to ``True``. A
legacy-only flag which when ``False`` disables *all* 0.5-style
object accounting on transaction boundaries, including auto-expiry
self.autoflush = autoflush
self.autocommit = autocommit
self.expire_on_commit = expire_on_commit
+ self.enable_baked_queries = enable_baked_queries
self._enable_transaction_accounting = _enable_transaction_accounting
self.twophase = twophase
self._query_cls = query_cls
exp
)
+ def test_disable_on_session(self):
+ User = self.classes.User
+
+ canary = mock.Mock()
+
+ def fn1(s):
+ canary.fn1()
+ return s.query(User.id, User.name).order_by(User.id)
+
+ def fn2(q):
+ canary.fn2()
+ return q.filter(User.id == bindparam('id'))
+
+ def fn3(q):
+ canary.fn3()
+ return q
+
+ for x in range(3):
+ bq = self.bakery(fn1)
+
+ bq += fn2
+
+ sess = Session(autocommit=True, enable_baked_queries=False)
+ eq_(
+ bq.add_criteria(fn3)(sess).params(id=7).all(),
+ [(7, 'jack')]
+ )
+
+ eq_(
+ canary.mock_calls,
+ [mock.call.fn1(), mock.call.fn2(), mock.call.fn3(),
+ mock.call.fn1(), mock.call.fn2(), mock.call.fn3(),
+ mock.call.fn1(), mock.call.fn2(), mock.call.fn3()]
+ )
+
def test_spoiled_full_w_params(self):
User = self.classes.User