yes, the example in 0.8 should be changed to this, but I haven't done it yet.   
dogpile's usage is similar to Beaker as far as the general calling pattern.   A 
tutorial format of the example using dogpile is attached.




On Sep 24, 2012, at 7:15 AM, David McKeone wrote:

> As per this comment: 
> http://techspot.zzzeek.org/2012/04/19/using-beaker-for-caching-why-you-ll-want-to-switch-to-dogpile.cache/#comment-503780670
> 
> Has any work been put into an example for using Dogpile.cache with 
> SQLAlchemy?  I'm about to embark on implementing caching and I don't want to 
> re-invent the wheel by creating my own adaptation if I don't need to.
> 
> 
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To view this discussion on the web visit 
> https://groups.google.com/d/msg/sqlalchemy/-/zf0HBD7s7SwJ.
> To post to this group, send email to sqlalchemy@googlegroups.com.
> To unsubscribe from this group, send email to 
> sqlalchemy+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/sqlalchemy?hl=en.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.




On Sep 24, 2012, at 7:15 AM, David McKeone wrote:

As per this comment: http://techspot.zzzeek.org/2012/04/19/using-beaker-for-caching-why-you-ll-want-to-switch-to-dogpile.cache/#comment-503780670

Has any work been put into an example for using Dogpile.cache with SQLAlchemy?  I'm about to embark on implementing caching and I don't want to re-invent the wheel by creating my own adaptation if I don't need to.



--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To view this discussion on the web visit https://groups.google.com/d/msg/sqlalchemy/-/zf0HBD7s7SwJ.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.

### slide::

#### Transparent Caching ####

# Illustrate using MapperOption objects to send caching directives
# to a custom Query subclass.
#

### slide::

# dogpile.cache is a new caching system which replaces Beaker.
#
# https://bitbucket.org/zzzeek/dogpile.cache
#
# Create a dogpile "cache region"


from dogpile.cache.region import make_region
regions = {
    "default":make_region().configure(
        'dogpile.cache.memory'
        )
  }

### slide:: -*- no_clear -*-

regions['default'].set("some key", "some value")

regions['default'].get("some key")

### slide:: -*- no_clear -*-
regions["default"].backend._cache

### slide::
# the dogpile (and Beaker) model allows you to pass
# a callable that generates a value

def generate_a_value():
    print "generating !"
    return "some value"

regions["default"].get_or_create("some other key", generate_a_value)

### slide:: -*- no_clear -*-

regions["default"].get_or_create("some other key", generate_a_value)

### slide:: -*- no_clear -*-

regions["default"].delete("some other key")
regions["default"].get_or_create("some other key", generate_a_value)

### slide::

# A Query which accesses a Dogpile cache.
# The parameters of the cache are derived partially from the
# structure of the query.

from sqlalchemy.orm.query import Query

class CachingQuery(Query):
    def __iter__(self):
        """override __iter__ to change where data comes from"""
        if hasattr(self, '_cache_region'):
            dogpile_region, cache_key = self._get_cache_plus_key()
            cached_value = dogpile_region.get_or_create(
                                        cache_key, 
                                        lambda: list(Query.__iter__(self))
                                    )
            return self.merge_result(cached_value, load=False)
        else:
            return super(CachingQuery, self).__iter__()

    def _get_cache_plus_key(self):
        """Return a cache region plus key."""
        return \
            regions[self._cache_region.region],\
            _key_from_query(self)

    def invalidate(self):
        """Invalidate the cache value represented by this Query."""
        dogpile_region, cache_key = self._get_cache_plus_key()
        dogpile_region.delete(cache_key)

### slide::

# the "key" for our cache will be based on the structure
# of the Query.   We define a helper that will give us
# all the "bind" values from a particular Query object.

from sqlalchemy.sql import visitors
import md5

def _key_from_query(query):
    """Given a Query, extract all bind parameter values from
    its structure."""

    v = []
    def visit_bindparam(bind):

        if bind.key in query._params:
            value = query._params[bind.key]
        elif bind.callable:
            value = bind.callable()
        else:
            value = bind.value

        v.append(unicode(value))

    stmt = query.statement
    visitors.traverse(stmt, {}, {'bindparam':visit_bindparam})
    return " ".join(
                [md5.md5(unicode(stmt)).hexdigest()] + v
            )

### slide::

# a brief example illustrating _params_from_query

from sqlalchemy.orm import Session
from sqlalchemy.sql import table, column

t1 = table('t1', column('a'), column('b'))

q = Session().query(t1).filter(t1.c.a=='test').filter(t1.c.b=='bar')

print q.statement

_key_from_query(q)

### slide::

# We now define a MapperOption that will place attributes 
# onto the query.

from sqlalchemy.orm.interfaces import MapperOption

class FromCache(MapperOption):
    """Specifies that a Query should load results from a cache."""

    propagate_to_loaders = False

    def __init__(self, region="default"):
        self.region = region

    def process_query(self, query):
        query._cache_region = self


### slide::

# Set up a session and base with CachingQuery

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite://')
Session = scoped_session(sessionmaker(engine, query_cls=CachingQuery))

Base = declarative_base()

### slide:: -*-no_exec-*-

# A model and some test data.

from sqlalchemy import Integer, String, Column, ForeignKey
from sqlalchemy.orm import relation

class Widget(Base):
    __tablename__ = 'widget'
    id = Column(Integer, primary_key=True)
    data = Column(String)

class SubWidget(Base):
    __tablename__ = 'subwidget'
    id = Column(Integer, primary_key=True)
    data = Column(String)
    widget_id=Column(Integer, ForeignKey('widget.id'))
    widget = relation(Widget, backref="subwidgets")

Base.metadata.create_all(engine)

Session.add_all([
    Widget(data='w1', subwidgets=[SubWidget(data='s1'), 
                                SubWidget(data='s2')]),
    Widget(data='w2', subwidgets=[SubWidget(data='s3')])
])
Session.commit()

### slide:: -*-no_exec-*-

# Load SubWidgets, place the results in cache

Session.query(SubWidget).\
                options(FromCache()).\
                join(SubWidget.widget).\
                filter(Widget.data=='w1').\
                all()

### slide:: -*- no_clear -*-
regions["default"].backend._cache

### slide:: -*-no_exec-*-

# On a second run, the results come from the cache.

Session.query(SubWidget).\
                options(FromCache()).\
                join(SubWidget.widget).\
                filter(Widget.data=='w1').\
                all()


### slide:: -*-no_exec-*-

# a new Query with the same form will produce the same
# cache key.   Using a new Query object we can 
# call invalidate() to remove the previously cached
# value.

q = Session.query(SubWidget).\
            options(FromCache()).\
            join(SubWidget.widget).\
            filter(Widget.data=='w1')

q.invalidate()
q.all()

### slide::

# This is a variant on the FromCache option, which will affect
# specifically the query that is invoked within a lazy load.

class RelationshipCache(MapperOption):
    """Specifies that a Query as called within a "lazy load" 
       should load results from a cache."""

    propagate_to_loaders = True

    def __init__(self, attribute, region="default"):
        self.region = region
        self.cls_ = attribute.property.parent.class_
        self.key = attribute.property.key

    def process_query_conditionally(self, query):
        if query._current_path:
            mapper, key = query._current_path[-2:]
            if issubclass(mapper.class_, self.cls_) and \
                key == self.key:
                query._cache_region = self

### slide:: -*-no_exec-*-

# To illustrate, we'll load a SubWidget using our option,
# then load it's "widget".  The lazyload for "widget" is 
# cached.   The recipe requires that the object isn't already
# in the session, so start clean.  Or you will go crazy.

Session.remove()

s1 = Session.query(SubWidget).\
            filter(SubWidget.data=='s1').\
            options(RelationshipCache(SubWidget.widget)).\
            one()
s1.widget

### slide:: -*-no_exec-*-

# Now Widget(data="w1") is cached by id.  A brand new session will
# lazyload, but the result pulls from cache.

Session.remove()

s2 = Session.query(SubWidget).\
            filter(SubWidget.data=='s2').\
            options(RelationshipCache(SubWidget.widget)).\
            one()
s2.widget

### slide::

Reply via email to