##// END OF EJS Templates
backported fix for issue #271
backported fix for issue #271

File last commit:

r1365:cd865113 beta
r1891:6b25e681 default
Show More
caching_query.py
289 lines | 9.8 KiB | text/x-python | PythonLexer
renamed project to rhodecode
r547 """caching_query.py
Represent persistence structures which allow the usage of
Beaker caching with SQLAlchemy.
The three new concepts introduced here are:
* CachingQuery - a Query subclass that caches and
retrieves results in/from Beaker.
* FromCache - a query option that establishes caching
parameters on a Query
* RelationshipCache - a variant of FromCache which is specific
to a query invoked during a lazy load.
source code cleanup: remove trailing white space, normalize file endings
r1203 * _params_from_query - extracts value parameters from
renamed project to rhodecode
r547 a Query.
The rest of what's here are standard SQLAlchemy and
Beaker constructs.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 """
Fixed permissions for users groups, group can have create repo permission now....
r1271 import beaker
moved out sqlalchemy cache from meta to the config files....
r609 from beaker.exceptions import BeakerException
Fixed permissions for users groups, group can have create repo permission now....
r1271
renamed project to rhodecode
r547 from sqlalchemy.orm.interfaces import MapperOption
from sqlalchemy.orm.query import Query
from sqlalchemy.sql import visitors
Fixed permissions for users groups, group can have create repo permission now....
r1271
renamed project to rhodecode
r547
class CachingQuery(Query):
source code cleanup: remove trailing white space, normalize file endings
r1203 """A Query subclass which optionally loads full results from a Beaker
renamed project to rhodecode
r547 cache region.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 The CachingQuery stores additional state that allows it to consult
a Beaker cache before accessing the database:
source code cleanup: remove trailing white space, normalize file endings
r1203
* A "region", which is a cache region argument passed to a
renamed project to rhodecode
r547 Beaker CacheManager, specifies a particular cache configuration
(including backend implementation, expiration times, etc.)
* A "namespace", which is a qualifying name that identifies a
source code cleanup: remove trailing white space, normalize file endings
r1203 group of keys within the cache. A query that filters on a name
might use the name "by_name", a query that filters on a date range
renamed project to rhodecode
r547 to a joined table might use the name "related_date_range".
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 When the above state is present, a Beaker cache is retrieved.
source code cleanup: remove trailing white space, normalize file endings
r1203
The "namespace" name is first concatenated with
a string composed of the individual entities and columns the Query
renamed project to rhodecode
r547 requests, i.e. such as ``Query(User.id, User.name)``.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 The Beaker cache is then loaded from the cache manager based
on the region and composed namespace. The key within the cache
itself is then constructed against the bind parameters specified
source code cleanup: remove trailing white space, normalize file endings
r1203 by this query, which are usually literals defined in the
renamed project to rhodecode
r547 WHERE clause.
The FromCache and RelationshipCache mapper options below represent
the "public" method of configuring this state upon the CachingQuery.
fixed caching query to propagate data_dir default from beaker
r1046
renamed project to rhodecode
r547 """
Implemented fancier top menu for logged and anonymous users...
r784
renamed project to rhodecode
r547 def __init__(self, manager, *args, **kw):
self.cache_manager = manager
Query.__init__(self, *args, **kw)
Implemented fancier top menu for logged and anonymous users...
r784
renamed project to rhodecode
r547 def __iter__(self):
"""override __iter__ to pull results from Beaker
if particular attributes have been configured.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 Note that this approach does *not* detach the loaded objects from
the current session. If the cache backend is an in-process cache
(like "memory") and lives beyond the scope of the current session's
transaction, those objects may be expired. The method here can be
modified to first expunge() each loaded item from the current
session before returning the list of items, so that the items
in the cache are not the same ones in the current Session.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 """
if hasattr(self, '_cache_parameters'):
Fixed permissions for users groups, group can have create repo permission now....
r1271 return self.get_value(createfunc=lambda:
list(Query.__iter__(self)))
renamed project to rhodecode
r547 else:
return Query.__iter__(self)
def invalidate(self):
"""Invalidate the value represented by this Query."""
cache, cache_key = _get_cache_parameters(self)
cache.remove(cache_key)
def get_value(self, merge=True, createfunc=None):
"""Return the value from the cache for this query.
Raise KeyError if no value present and no
createfunc specified.
"""
cache, cache_key = _get_cache_parameters(self)
ret = cache.get_value(cache_key, createfunc=createfunc)
if merge:
ret = self.merge_result(ret, load=False)
return ret
def set_value(self, value):
"""Set the value in the cache for this query."""
cache, cache_key = _get_cache_parameters(self)
Implemented fancier top menu for logged and anonymous users...
r784 cache.put(cache_key, value)
renamed project to rhodecode
r547
Fixed permissions for users groups, group can have create repo permission now....
r1271
update caching_query from latest sqlalchemy
r1339 def query_callable(manager, query_cls=CachingQuery):
renamed project to rhodecode
r547 def query(*arg, **kw):
update caching_query from latest sqlalchemy
r1339 return query_cls(manager, *arg, **kw)
renamed project to rhodecode
r547 return query
moved out sqlalchemy cache from meta to the config files....
r609
Fixed permissions for users groups, group can have create repo permission now....
r1271
moved out sqlalchemy cache from meta to the config files....
r609 def get_cache_region(name, region):
fixed error message about cache settings
r610 if region not in beaker.cache.cache_regions:
Implemented fancier top menu for logged and anonymous users...
r784 raise BeakerException('Cache region `%s` not configured '
fixed error message about cache settings
r610 'Check if proper cache settings are in the .ini files' % region)
kw = beaker.cache.cache_regions[region]
return beaker.cache.Cache._get_cache(name, kw)
Implemented fancier top menu for logged and anonymous users...
r784
Fixed permissions for users groups, group can have create repo permission now....
r1271
renamed project to rhodecode
r547 def _get_cache_parameters(query):
"""For a query with cache_region and cache_namespace configured,
return the correspoinding Cache instance and cache key, based
on this query's current criterion and parameter values.
"""
if not hasattr(query, '_cache_parameters'):
Fixed permissions for users groups, group can have create repo permission now....
r1271 raise ValueError("This Query does not have caching "
"parameters configured.")
renamed project to rhodecode
r547
region, namespace, cache_key = query._cache_parameters
Implemented fancier top menu for logged and anonymous users...
r784
renamed project to rhodecode
r547 namespace = _namespace_from_query(namespace, query)
if cache_key is None:
# cache key - the value arguments from this query's parameters.
args = _params_from_query(query)
cache_key = " ".join([str(x) for x in args])
# get cache
moved out sqlalchemy cache from meta to the config files....
r609 #cache = query.cache_manager.get_cache_region(namespace, region)
cache = get_cache_region(namespace, region)
renamed project to rhodecode
r547 # optional - hash the cache_key too for consistent length
# import uuid
# cache_key= str(uuid.uuid5(uuid.NAMESPACE_DNS, cache_key))
return cache, cache_key
Fixed permissions for users groups, group can have create repo permission now....
r1271
renamed project to rhodecode
r547 def _namespace_from_query(namespace, query):
source code cleanup: remove trailing white space, normalize file endings
r1203 # cache namespace - the token handed in by the
renamed project to rhodecode
r547 # option + class we're querying against
namespace = " ".join([namespace] + [str(x) for x in query._entities])
# memcached wants this
namespace = namespace.replace(' ', '_')
return namespace
Fixed permissions for users groups, group can have create repo permission now....
r1271
renamed project to rhodecode
r547 def _set_cache_parameters(query, region, namespace, cache_key):
Implemented fancier top menu for logged and anonymous users...
r784
renamed project to rhodecode
r547 if hasattr(query, '_cache_parameters'):
region, namespace, cache_key = query._cache_parameters
raise ValueError("This query is already configured "
Implemented fancier top menu for logged and anonymous users...
r784 "for region %r namespace %r" %
renamed project to rhodecode
r547 (region, namespace)
)
query._cache_parameters = region, namespace, cache_key
Implemented fancier top menu for logged and anonymous users...
r784
Fixed permissions for users groups, group can have create repo permission now....
r1271
renamed project to rhodecode
r547 class FromCache(MapperOption):
"""Specifies that a Query should load results from a cache."""
propagate_to_loaders = False
def __init__(self, region, namespace, cache_key=None):
"""Construct a new FromCache.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 :param region: the cache region. Should be a
region configured in the Beaker CacheManager.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 :param namespace: the cache namespace. Should
be a name uniquely describing the target Query's
lexical structure.
source code cleanup: remove trailing white space, normalize file endings
r1203
:param cache_key: optional. A string cache key
renamed project to rhodecode
r547 that will serve as the key to the query. Use this
if your query has a huge amount of parameters (such
source code cleanup: remove trailing white space, normalize file endings
r1203 as when using in_()) which correspond more simply to
renamed project to rhodecode
r547 some other identifier.
"""
self.region = region
self.namespace = namespace
self.cache_key = cache_key
Implemented fancier top menu for logged and anonymous users...
r784
renamed project to rhodecode
r547 def process_query(self, query):
"""Process a Query during normal loading operation."""
Implemented fancier top menu for logged and anonymous users...
r784
Fixed permissions for users groups, group can have create repo permission now....
r1271 _set_cache_parameters(query, self.region, self.namespace,
self.cache_key)
renamed project to rhodecode
r547
class RelationshipCache(MapperOption):
source code cleanup: remove trailing white space, normalize file endings
r1203 """Specifies that a Query as called within a "lazy load"
renamed project to rhodecode
r547 should load results from a cache."""
propagate_to_loaders = True
def __init__(self, region, namespace, attribute):
"""Construct a new RelationshipCache.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 :param region: the cache region. Should be a
region configured in the Beaker CacheManager.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 :param namespace: the cache namespace. Should
be a name uniquely describing the target Query's
lexical structure.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 :param attribute: A Class.attribute which
indicates a particular class relationship() whose
lazy loader should be pulled from the cache.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 """
self.region = region
self.namespace = namespace
self._relationship_options = {
Fixed permissions for users groups, group can have create repo permission now....
r1271 (attribute.property.parent.class_, attribute.property.key): self
renamed project to rhodecode
r547 }
def process_query_conditionally(self, query):
"""Process a Query that is used within a lazy loader.
(the process_query_conditionally() method is a SQLAlchemy
hook invoked only within lazyload.)
"""
if query._current_path:
mapper, key = query._current_path[-2:]
for cls in mapper.class_.__mro__:
if (cls, key) in self._relationship_options:
Fixed permissions for users groups, group can have create repo permission now....
r1271 relationship_option = \
self._relationship_options[(cls, key)]
renamed project to rhodecode
r547 _set_cache_parameters(
query,
relationship_option.region,
relationship_option.namespace,
None)
def and_(self, option):
"""Chain another RelationshipCache option to this one.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 While many RelationshipCache objects can be specified on a single
Query separately, chaining them together allows for a more efficient
lookup during load.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 """
self._relationship_options.update(option._relationship_options)
return self
def _params_from_query(query):
"""Pull the bind parameter values from a query.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 This takes into account any scalar attribute bindparam set up.
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 E.g. params_from_query(query.filter(Cls.foo==5).filter(Cls.bar==7)))
would return [5, 7].
source code cleanup: remove trailing white space, normalize file endings
r1203
renamed project to rhodecode
r547 """
v = []
def visit_bindparam(bind):
value = query._params.get(bind.key, bind.value)
applied caching query update from latest sqlalchemy
r1365
renamed project to rhodecode
r547 # lazyloader may dig a callable in here, intended
# to late-evaluate params after autoflush is called.
# convert to a scalar value.
if callable(value):
value = value()
Implemented fancier top menu for logged and anonymous users...
r784
renamed project to rhodecode
r547 v.append(value)
if query._criterion is not None:
update caching_query from latest sqlalchemy
r1339 visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
renamed project to rhodecode
r547 return v