]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Changed the beaker cache example a bit to have a separate
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 12 Feb 2010 21:43:19 +0000 (21:43 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 12 Feb 2010 21:43:19 +0000 (21:43 +0000)
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.

CHANGES
examples/beaker_caching/__init__.py
examples/beaker_caching/advanced.py
examples/beaker_caching/meta.py
examples/beaker_caching/model.py

diff --git a/CHANGES b/CHANGES
index 7857c51363d8b81a5c07d4f9be9280e7784e5909..cc34dd8e697aa76289cd4237a621ca8e47ff4d02 100644 (file)
--- 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
 ========
index ba06c6ee8a219cfec2ccdab71949b6f2eb5c6215..9c5fcc8fde7d369e46cbed80bacf8f6f3ba95c8c 100644 (file)
@@ -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
 
index ebb17cfe53de0d76a04f26ade3e4a5beca428cb0..8e3361b1e4e8c7b52684c86aa9b44575ff8ac8c6 100644 (file)
@@ -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
index dd802fcd83b340a1e9a47553838e167ce90322a9..e1698f1f79397e7747fe5ef9306b8d67514e0620 100644 (file)
@@ -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.
index 4c043b42baebb01b94762368ac6b3c34abf27e2f..daae0d512d79ed1298ac56c202dd4d204afcc209 100644 (file)
@@ -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)
+                )