From: Mike Bayer Date: Fri, 12 Feb 2010 21:43:19 +0000 (+0000) Subject: - Changed the beaker cache example a bit to have a separate X-Git-Tag: rel_0_6beta2~202 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a2a3b701c71b8c424c4e63ee455aed12db2c54e2;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Changed the beaker cache example a bit to have a separate RelationCache option for lazyload caching. This object does a lookup among any number of potential attributes more efficiently by grouping several into a common structure. Both FromCache and RelationCache are simpler individually. --- diff --git a/CHANGES b/CHANGES index 7857c51363..cc34dd8e69 100644 --- a/CHANGES +++ b/CHANGES @@ -101,6 +101,13 @@ CHANGES on the connection. Note that this is not entirely compatible with the "func.current_date()", which will be returned as a string. [ticket:1685] + +- examples + - Changed the beaker cache example a bit to have a separate + RelationCache option for lazyload caching. This object + does a lookup among any number of potential attributes + more efficiently by grouping several into a common structure. + Both FromCache and RelationCache are simpler individually. 0.6beta1 ======== diff --git a/examples/beaker_caching/__init__.py b/examples/beaker_caching/__init__.py index ba06c6ee8a..9c5fcc8fde 100644 --- a/examples/beaker_caching/__init__.py +++ b/examples/beaker_caching/__init__.py @@ -22,7 +22,7 @@ E.g.:: # specify that each Person's "addresses" collection comes from # cache too - q = q.options(FromCache("default", "by_person", Person.addresses)) + q = q.options(RelationCache("default", "by_person", Person.addresses)) # query print q.all() @@ -35,7 +35,7 @@ exactly one SQL statement against two tables will be emitted - the displayed result however will utilize dozens of lazyloads that all pull from cache. -Three endpoint scripts, in order of complexity, are run as follows:: +The demo scripts themselves, in order of complexity, are run as follows:: python examples/beaker_caching/helloworld.py diff --git a/examples/beaker_caching/advanced.py b/examples/beaker_caching/advanced.py index ebb17cfe53..8e3361b1e4 100644 --- a/examples/beaker_caching/advanced.py +++ b/examples/beaker_caching/advanced.py @@ -8,7 +8,7 @@ and collection caching. import environment from model import Person, Address, cache_address_bits -from meta import Session, FromCache +from meta import Session, FromCache, RelationCache from sqlalchemy.orm import eagerload def load_name_range(start, end, invalidate=False): @@ -35,7 +35,7 @@ def load_name_range(start, end, invalidate=False): # have the "addresses" collection cached separately # each lazyload of Person.addresses loads from cache. - q = q.options(FromCache("default", "by_person", Person.addresses)) + q = q.options(RelationCache("default", "by_person", Person.addresses)) # alternatively, eagerly load the "addresses" collection, so that they'd # be cached together. This issues a bigger SQL statement and caches diff --git a/examples/beaker_caching/meta.py b/examples/beaker_caching/meta.py index dd802fcd83..e1698f1f79 100644 --- a/examples/beaker_caching/meta.py +++ b/examples/beaker_caching/meta.py @@ -9,6 +9,8 @@ The three new concepts introduced here are: retrieves results in/from Beaker. * FromCache - a query option that establishes caching parameters on a Query + * RelationCache - a variant of FromCache which is specific + to a query invoked during a lazy load. * _params_from_query - extracts value parameters from a Query. @@ -50,9 +52,10 @@ class CachingQuery(Query): session using Session.merge(load=False), which is a fast performing method to ensure state is present. - The FromCache mapper option below represents the "public" method of - configuring the "cache_region" and "cache_namespace" attributes, - and includes the ability to be invoked upon lazy loaders embedded + The FromCache and RelationCache mapper options below represent + the "public" method of + configuring the "cache_region" and "cache_namespace" attributes. + RelationCache has the ability to be invoked upon lazy loaders embedded in an object graph. """ @@ -113,15 +116,30 @@ class CachingQuery(Query): cache, cache_key = self._get_cache_plus_key() cache.put(cache_key, value) -class FromCache(MapperOption): +class _CacheOption(MapperOption): """A MapperOption which configures a Query to use a particular cache namespace and region. + """ - Can optionally be configured to be invoked for a specific - lazy loader. + def _set_query_cache(self, query): + """Configure this _CacheOption's region and namespace on a query.""" + + if hasattr(query, 'cache_region'): + raise ValueError("This query is already configured " + "for region %r namespace %r" % + (query.cache_region, query.cache_namespace) + ) + query.cache_region = self.region + query.cache_namespace = self.namespace + if self.cache_key: + query.cache_key = self.cache_key - """ - def __init__(self, region, namespace, key=None, cache_key=None): +class FromCache(_CacheOption): + """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. :param region: the cache region. Should be a @@ -131,10 +149,6 @@ class FromCache(MapperOption): be a name uniquely describing the target Query's lexical structure. - :param key: optional. A Class.attrname which - indicates a particular class relation() whose - lazy loader should be pulled from the cache. - :param cache_key: optional. A string cache key that will serve as the key to the query. Use this if your query has a huge amount of parameters (such @@ -145,44 +159,70 @@ class FromCache(MapperOption): self.region = region self.namespace = namespace self.cache_key = cache_key - if key: - self.cls_ = key.property.parent.class_ - self.propname = key.property.key - self.propagate_to_loaders = True - else: - self.cls_ = self.propname = None - self.propagate_to_loaders = False - def _set_query_cache(self, query): - """Configure this FromCache's region and namespace on a query.""" + def process_query(self, query): + """Process a Query during normal loading operation.""" - if hasattr(query, 'cache_region'): - raise ValueError("This query is already configured " - "for region %r namespace %r" % - (query.cache_region, query.cache_namespace) - ) - query.cache_region = self.region - query.cache_namespace = self.namespace - if self.cache_key: - query.cache_key = self.cache_key + self._set_query_cache(query) + +class RelationCache(_CacheOption): + """Specifies that a Query as called within a "lazy load" + should load results from a cache.""" + + propagate_to_loaders = True + cache_key = None + + def __init__(self, region, namespace, key): + """Construct a new RelationCache. + + :param region: the cache region. Should be a + region configured in the Beaker CacheManager. + + :param namespace: the cache namespace. Should + be a name uniquely describing the target Query's + lexical structure. + + :param key: A Class.attrname which + indicates a particular class relation() whose + lazy loader should be pulled from the cache. + + + """ + self.region = region + self.namespace = namespace + cls_ = key.property.parent.class_ + propname = key.property.key + self._grouped = { + (cls_, propname) : self + } 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 self.cls_ is not None and query._current_path: + if query._current_path: mapper, key = query._current_path[-2:] - if issubclass(mapper.class_, self.cls_) and key == self.propname: - self._set_query_cache(query) + + # search for a matching element in our _grouped + # dictionary. + for cls in mapper.class_.__mro__: + if (cls, key) in self._grouped: + self._grouped[(cls, key)]._set_query_cache(query) - def process_query(self, query): - """Process a Query during normal loading operation.""" + def and_(self, option): + """Chain another RelationCache option to this one. + + While many RelationCache objects can be specified on a single + Query separately, chaining them together allows for a more efficient + lookup during load. + + """ + self._grouped.update(option._grouped) + return self - if self.cls_ is None: - self._set_query_cache(query) def _params_from_query(query): """Pull the bind parameter values from a query. diff --git a/examples/beaker_caching/model.py b/examples/beaker_caching/model.py index 4c043b42ba..daae0d512d 100644 --- a/examples/beaker_caching/model.py +++ b/examples/beaker_caching/model.py @@ -10,7 +10,7 @@ City --(has a)--> Country """ from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relation -from meta import Base, FromCache, Session +from meta import Base, FromCache, Session, RelationCache class Country(Base): __tablename__ = 'country' @@ -92,12 +92,13 @@ class Person(Base): def format_full(self): return "\t".join([str(x) for x in [self] + list(self.addresses)]) -# Caching options. A set of three FromCache options +# Caching options. A set of three RelationCache options # which can be applied to Query(), causing the "lazy load" # of these attributes to be loaded from cache. -cache_address_bits = ( - FromCache("default", "byid", PostalCode.city), - FromCache("default", "byid", City.country), - FromCache("default", "byid", Address.postal_code), - ) +cache_address_bits = RelationCache("default", "byid", PostalCode.city).\ + and_( + RelationCache("default", "byid", City.country) + ).and_( + RelationCache("default", "byid", Address.postal_code) + )