+++ /dev/null
-"""
-Ready for some really powerful stuff ?
-
-We're going to use Beaker caching, and create functions that load and cache what we want, which
-can be used in any scenario. Then we're going to associate them with many-to-one relations
-for individual queries.
-
-Think of it as lazy loading from a long term cache. For rarely-mutated objects, this is a super
-performing way to go.
-
-"""
-
-from sqlalchemy.orm.query import Query, _generative
-from sqlalchemy.orm.interfaces import MapperOption
-from sqlalchemy.orm.session import Session
-from sqlalchemy.sql import visitors
-
-class CachingQuery(Query):
- """override __iter__ to pull results from a callable
- that might have been attached to the Query.
-
- """
- def __iter__(self):
- if hasattr(self, 'cache_callable'):
- try:
- ret = self.cache_callable(self)
- except KeyError:
- ret = list(Query.__iter__(self))
- for x in ret:
- self.session.expunge(x)
-
- return iter(self.session.merge(x, dont_load=True) for x in ret)
-
- else:
- return Query.__iter__(self)
-
-class FromCallable(MapperOption):
- """A MapperOption that associates a callable with particular 'path' load.
-
- When a lazyload occurs, the Query has a "path" which is a tuple of
- (mapper, key, mapper, key) indicating the path along relations from
- the original mapper to the endpoint mapper.
-
- """
-
- propagate_to_loaders = True
-
- def __init__(self, key):
- self.cls_ = key.property.parent.class_
- self.propname = key.property.key
-
- def __call__(self, q):
- raise NotImplementedError()
-
- def process_query(self, query):
- if query._current_path:
- mapper, key = query._current_path[-2:]
- if mapper.class_ is self.cls_ and key == self.propname:
- query.cache_callable = 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)
- v.append(value)
- visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
- return v
-
-if __name__ == '__main__':
- """Usage example. We'll use Beaker to set up a region and then a short def
- that loads a 'Widget' object by id.
-
- """
- from sqlalchemy.orm import sessionmaker, scoped_session
- from sqlalchemy import create_engine
-
- # beaker 1.4 or above
- from beaker.cache import CacheManager
-
- # sample CacheManager. In reality, use memcached. (seriously, don't bother
- # with any other backend.)
- cache_manager = CacheManager(
- cache_regions={
- 'default_region':{'type':'memory','expire':3600}
- }
- )
-
- # SQLA configuration
- engine=create_engine('sqlite://', echo=True)
- Session = scoped_session(sessionmaker(query_cls=CachingQuery, bind=engine))
-
- from sqlalchemy import Column, Integer, String, ForeignKey
- from sqlalchemy.orm import relation
- from sqlalchemy.ext.declarative import declarative_base
-
- # mappings
- Base = declarative_base()
-
- class User(Base):
- __tablename__ = 'user'
- id = Column(Integer, primary_key=True)
- name = Column(String(100))
- widget_id = Column(Integer, ForeignKey('widget.id'))
-
- widget = relation("Widget")
-
- class Widget(Base):
- __tablename__ = 'widget'
- id = Column(Integer, primary_key=True)
- name = Column(String(100))
-
- # Widget loading.
-
- @cache_manager.region('default_region', 'byid')
- def load_widget(widget_id):
- """Load a widget by id, caching the result in Beaker."""
-
- return Session.query(Widget).filter(Widget.id==widget_id).first()
-
- class CachedWidget(FromCallable):
- """A MapperOption that will pull user widget links from Beaker.
-
- We build a subclass of FromCallable with a __call__ method
- so that the option itself is pickleable.
-
- """
- def __call__(self, q):
- return [load_widget(*params_from_query(q))]
-
- Base.metadata.create_all(engine)
-
- sess = Session()
-
- # create data.
- w1 = Widget(name='w1')
- w2 = Widget(name='w2')
- sess.add_all(
- [User(name='u1', widget=w1), User(name='u2', widget=w1), User(name='u3', widget=w2)]
- )
- sess.commit()
-
- # call load_widget with 1 and 2. this will cache those widget objects in beaker.
- w1 = load_widget(1)
- w2 = load_widget(2)
-
- # clear session entirely.
- sess.expunge_all()
-
- # load users, sending over our option.
- u1, u2, u3 = sess.query(User).options(CachedWidget(User.widget)).order_by(User.id).all()
-
- print "------------------------"
-
- # access the "Widget". No SQL occurs below this line !
- assert u1.widget.name == 'w1'
-
- # access the same "Widget" on u2. Local w1 is reused, no extra cache roundtrip !
- assert u2.widget.name == 'w1'
- assert u2.widget is u1.widget
-
- assert u3.widget.name == 'w2'
-
- # user + the option (embedded in its state)
- # are pickleable themselves (important for further caching)
- import pickle
- assert pickle.dumps(u1)
\ No newline at end of file