summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES7
-rw-r--r--examples/beaker_caching/__init__.py4
-rw-r--r--examples/beaker_caching/advanced.py4
-rw-r--r--examples/beaker_caching/meta.py118
-rw-r--r--examples/beaker_caching/model.py15
5 files changed, 98 insertions, 50 deletions
diff --git a/CHANGES b/CHANGES
index 7857c5136..cc34dd8e6 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 ba06c6ee8..9c5fcc8fd 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 ebb17cfe5..8e3361b1e 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 dd802fcd8..e1698f1f7 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 4c043b42b..daae0d512 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)
+ )