text: """meta.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
   * 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.
  
  The rest of what's here are standard SQLAlchemy and
  Beaker constructs.
     
  """
  from sqlalchemy.orm import scoped_session, sessionmaker
  from sqlalchemy.orm.interfaces import MapperOption
  from sqlalchemy.orm.query import Query
  from sqlalchemy.sql import visitors
  from sqlalchemy.ext.declarative import declarative_base
  from beaker import cache
  
  class CachingQuery(Query):
      """A Query subclass which optionally loads full results from a Beaker 
      cache region.
      
      The CachingQuery is instructed to load from cache based on two optional
      attributes configured on the instance, called 'cache_region' and 'cache_namespace'.
      
      When these attributes are present, any iteration of the Query will configure
      a Beaker cache against this region and a generated namespace, which takes
      into account the 'cache_namespace' name as well as the entities this query
      is created against (i.e. the columns and classes sent to the constructor).
      The 'cache_namespace' is a string name that represents a particular structure
      of query.  E.g. a query that filters on a name might use the name "by_name",
      a query that filters on a date range to a joined table might use the name
      "related_date_range".
      
      The Query then attempts to retrieved a cached value using a key, which
      is generated from all the parameterized values present in the Query.  In
      this way, the combination of "cache_namespace" and embedded parameter values
      correspond exactly to the lexical structure of a SQL statement combined
      with its bind parameters.   If no such key exists then the ultimate SQL
      is emitted and the objects loaded.
      
      The returned objects, if loaded from cache, are merged into the Query's
      session using Session.merge(load=False), which is a fast performing
      method to ensure state is present.
  
      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.
      
      """
      
      def __iter__(self):
          """override __iter__ to pull results from Beaker
             if particular attributes have been configured.
          """
          if hasattr(self, '_cache_parameters'):
              cache, cache_key = _get_cache_parameters(self)
              ret = cache.get_value(cache_key, createfunc=lambda: list(Query.__iter__(self)))
              
              # merge the result in.  
              return self.merge_result(ret, load=False)
          else:
              return Query.__iter__(self)
  
      def invalidate(self):
          """Invalidate the cache represented in this Query."""
  
          cache, cache_key = _get_cache_parameters(self)
          cache.remove(cache_key)
  
      def set_value(self, value):
          """Set the value in the cache for this query."""
  
          cache, cache_key = _get_cache_parameters(self)
          cache.put(cache_key, value)        
  
  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'):
          raise ValueError("This Query does not have caching parameters configured.")
  
      region, namespace, cache_key = query._cache_parameters
  
      # cache namespace - the token handed in by the 
      # option + class we're querying against
      namespace = " ".join([namespace] + [str(x) for x in query._entities])
  
      # memcached wants this
      namespace = namespace.replace(' ', '_')
  
      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
      cache = cache_manager.get_cache_region(namespace, region)
  
      # 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
  
  def _set_cache_parameters(query, region, namespace, cache_key):
      
      if hasattr(query, '_cache_parameters'):
          region, namespace, cache_key = query._cache_parameters
          raise ValueError("This query is already configured "
                          "for region %r namespace %r" % 
                          (region, namespace)
                      )
      query._cache_parameters = region, namespace, cache_key
      
  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.
          
          :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 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
          as when using in_()) which correspond more simply to 
          some other identifier.
  
          """
          self.region = region
          self.namespace = namespace
          self.cache_key = cache_key
      
      def process_query(self, query):
          """Process a Query during normal loading operation."""
          
          _set_cache_parameters(query, self.region, self.namespace, self.cache_key)
  
  class RelationCache(MapperOption):
      """Specifies that a Query as called within a "lazy load" 
         should load results from a cache."""
  
      propagate_to_loaders = True
  
      def __init__(self, region, namespace, attribute):
          """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 attribute: A Class.attribute which
          indicates a particular class relation() whose
          lazy loader should be pulled from the cache.
          
          """
          self.region = region
          self.namespace = namespace
          self._relation_options = {
              ( attribute.property.parent.class_, attribute.property.key ) : 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 query._current_path:
              mapper, key = query._current_path[-2:]
  
              for cls in mapper.class_.__mro__:
                  if (cls, key) in self._relation_options:
                      relation_option = self._relation_options[(cls, key)]
                      _set_cache_parameters(
                              query, 
                              relation_option.region, 
                              relation_option.namespace, 
                              None)
  
      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._relation_options.update(option._relation_options)
          return self
  
  
  def _params_from_query(query):
      """Pull the bind parameter values from a query.
      
      This takes into account any scalar attribute bindparam set up.
      
      E.g. params_from_query(query.filter(Cls.foo==5).filter(Cls.bar==7)))
      would return [5, 7].
      
      """
      v = []
      def visit_bindparam(bind):
          value = query._params.get(bind.key, bind.value)
          
          # 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()
              
          v.append(value)
      if query._criterion is not None:
          visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
      return v
  
  # Beaker CacheManager.  A home base for cache configurations.
  # Configured at startup in __init__.py
  cache_manager = cache.CacheManager()
  
  # global application session.
  # configured at startup in __init__.py
  Session = scoped_session(sessionmaker(query_cls=CachingQuery))
  
  # global declarative base class.
  Base = declarative_base()
  
  