[Checkins] SVN: lovely.viewcache/trunk/ Initial import of the view
cache package.
Jürgen Kartnaller
juergen at kartnaller.at
Fri Feb 2 04:32:22 EST 2007
Log message for revision 72319:
Initial import of the view cache package.
Changed:
A lovely.viewcache/trunk/
A lovely.viewcache/trunk/src/
A lovely.viewcache/trunk/src/lovely/
A lovely.viewcache/trunk/src/lovely/viewcache/
A lovely.viewcache/trunk/src/lovely/viewcache/HOWTOUSE.txt
A lovely.viewcache/trunk/src/lovely/viewcache/README.txt
A lovely.viewcache/trunk/src/lovely/viewcache/TODO.txt
A lovely.viewcache/trunk/src/lovely/viewcache/__init__.py
A lovely.viewcache/trunk/src/lovely/viewcache/browser/
A lovely.viewcache/trunk/src/lovely/viewcache/browser/__init__.py
A lovely.viewcache/trunk/src/lovely/viewcache/browser/configure.zcml
A lovely.viewcache/trunk/src/lovely/viewcache/configurator.py
A lovely.viewcache/trunk/src/lovely/viewcache/configure.zcml
A lovely.viewcache/trunk/src/lovely/viewcache/event.py
A lovely.viewcache/trunk/src/lovely/viewcache/ftesting.zcml
A lovely.viewcache/trunk/src/lovely/viewcache/ftests.py
A lovely.viewcache/trunk/src/lovely/viewcache/i18n.py
A lovely.viewcache/trunk/src/lovely/viewcache/interfaces.py
A lovely.viewcache/trunk/src/lovely/viewcache/lovely.viewcache-configure.zcml
A lovely.viewcache/trunk/src/lovely/viewcache/manager.py
A lovely.viewcache/trunk/src/lovely/viewcache/manager.txt
A lovely.viewcache/trunk/src/lovely/viewcache/mixin.py
A lovely.viewcache/trunk/src/lovely/viewcache/ram.py
A lovely.viewcache/trunk/src/lovely/viewcache/ram.txt
A lovely.viewcache/trunk/src/lovely/viewcache/stats/
A lovely.viewcache/trunk/src/lovely/viewcache/stats/README.txt
A lovely.viewcache/trunk/src/lovely/viewcache/stats/__init__.py
A lovely.viewcache/trunk/src/lovely/viewcache/stats/browser.py
A lovely.viewcache/trunk/src/lovely/viewcache/stats/configure.zcml
A lovely.viewcache/trunk/src/lovely/viewcache/stats/interfaces.py
A lovely.viewcache/trunk/src/lovely/viewcache/stats/stats.pt
A lovely.viewcache/trunk/src/lovely/viewcache/tests.py
A lovely.viewcache/trunk/src/lovely/viewcache/view.py
A lovely.viewcache/trunk/src/lovely/viewcache/zodb.py
A lovely.viewcache/trunk/src/lovely/viewcache/zodb.txt
-=-
Added: lovely.viewcache/trunk/src/lovely/viewcache/HOWTOUSE.txt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/HOWTOUSE.txt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/HOWTOUSE.txt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,23 @@
+==================
+How To Use Caching
+==================
+
+
+Cache Utility
+-------------
+
+Caching requires a storage to store the cache entries. The view cache uses an
+unnamed utility implementing IViewCache. If no utility can be found it will
+not cache the results but it will still work.
+
+
+RAM Based Cache Storage
+~~~~~~~~~~~~~~~~~~~~~~~
+
+ZODB Based Cache Storage
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+Turning An Existing View Class Into A Cached Class
+--------------------------------------------------
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/HOWTOUSE.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/README.txt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/README.txt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/README.txt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,263 @@
+==========
+View Cache
+==========
+
+
+Test Setup
+----------
+
+Because the view cache uses a utility to store the cached value we provide a
+cache utility here.
+
+ >>> from zope import component
+ >>> from lovely.viewcache.ram import ViewCache
+ >>> from lovely.viewcache.interfaces import IViewCache
+ >>> cache = ViewCache()
+ >>> component.provideUtility(cache, IViewCache)
+
+We need some content to use as context.
+
+ >>> from zope.app.container.contained import Contained
+ >>> from zope import interface
+ >>> class IContent(interface.Interface):
+ ... pass
+ >>> class Content(Contained):
+ ... interface.implements(IContent)
+ ... def __init__(self, name):
+ ... self.count = 0
+ ... self.name = name
+ >>> content = Content(u'content 1')
+ >>> root[u'content'] = content
+
+A browser is also needed.
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+
+Then we need a view which we try to cache. The view we define here is a normal
+implementation of a view. The view renders a result which depends on a counter
+in it's context. The counter is incremented every time the view is rendered.
+This allows us to check if the result is coming from the cache.
+
+ >>> from zope.publisher.interfaces.browser import IBrowserView
+ >>> from zope.publisher.browser import BrowserView
+ >>> class View(BrowserView):
+ ... def __call__(self, *args, **kwargs):
+ ... self.context.count += 1
+ ... return u'"%s" is rendered %s time(s)'% (
+ ... self.context.name, self.context.count)
+
+
+Cached View
+-----------
+
+For the caching of views we provide a special adapter factory for views.
+
+ >>> from lovely.viewcache.view import cachedView
+
+To make the view cached we create a cached view class from the original view
+class.
+
+ >>> CachedView = cachedView(View, dependencies = ('content',))
+
+Instead of registering the original view class we can now use the newly
+created view class.
+
+ >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+ >>> component.provideAdapter(CachedView,
+ ... (IContent, IDefaultBrowserLayer),
+ ... IBrowserView,
+ ... name='cachedView')
+
+If we now lookup our view we get an instance of the view which is proxied by
+the cache manager view.
+
+ >>> view = component.getMultiAdapter((content, request), name='cachedView')
+ >>> view.__name__ = 'cachedView'
+ >>> from lovely.viewcache.view import CachedViewMixin
+ >>> isinstance(view, CachedViewMixin)
+ True
+
+When we render the view by calling it we get the result.
+
+ >>> view()
+ u'"content 1" is rendered 1 time(s)'
+
+Rendering the view again will return the same result because the cached result
+is used.
+
+ >>> view()
+ u'"content 1" is rendered 1 time(s)'
+
+The cachingOn property allows the control of the caching of the view. If
+cachingOn returns False the view is not cached.
+
+ >>> view.cachingOn
+ True
+ >>> view.cachingOn = False
+ >>> view()
+ u'"content 1" is rendered 2 time(s)'
+
+If we switch back we get the old cached value back.
+
+ >>> view.cachingOn = True
+ >>> view()
+ u'"content 1" is rendered 1 time(s)'
+
+We invalidate the cache entry.
+
+ >>> cache = component.queryUtility(IViewCache)
+ >>> cache.invalidate(dependencies=['content'])
+
+And the view will be rendered and cached again.
+
+ >>> view()
+ u'"content 1" is rendered 3 time(s)'
+ >>> view()
+ u'"content 1" is rendered 3 time(s)'
+
+If we request a new view we get the already cached value.
+
+ >>> view = component.getMultiAdapter((content, request), name='cachedView')
+ >>> view.__name__ = 'cachedView'
+ >>> view()
+ u'"content 1" is rendered 3 time(s)'
+
+
+Views On Different Contexts
+---------------------------
+
+If the view is used in another context it creates a new cache entry.
+
+ >>> content2 = Content(u'content 2')
+ >>> root[u'content2'] = content2
+
+ >>> view2 = component.getMultiAdapter((content2, request), name='cachedView')
+ >>> view2()
+ u'"content 2" is rendered 1 time(s)'
+ >>> view2()
+ u'"content 2" is rendered 1 time(s)'
+
+
+Providing static dependencies
+-----------------------------
+
+A cached view provides the static dependencies via the 'staticCachingDeps'
+attribute. The static dependency can be set as a class member or can be
+provided when creating the cached view. It can not be cached during runtime.
+
+ >>> view.staticCachingDeps
+ ('content',)
+ >>> view.staticCachingDeps = ('not possible',)
+ Traceback (most recent call last):
+ ...
+ AttributeError: can't set attribute
+
+
+Providing dynamic dependencies
+------------------------------
+
+The view can provide dynamic dependencies via the 'dynamicCachingDeps'
+attribute.
+
+ >>> view.dynamicCachingDeps
+ ()
+ >>> view.dynamicCachingDeps = ('changeable',)
+ >>> view.dynamicCachingDeps
+ ('changeable',)
+
+
+Using 'minAge'
+--------------
+
+A view with a different name get's a different cache entry. It is also
+possible to provide a minAge for the cache entry.
+
+ >>> AnotherCachedView = cachedView(View,
+ ... dependencies = ('content',),
+ ... minAge=1)
+ >>> component.provideAdapter(AnotherCachedView,
+ ... (IContent, IDefaultBrowserLayer),
+ ... IBrowserView,
+ ... name='anotherCachedView')
+ >>> view = component.getMultiAdapter((content, request), name='anotherCachedView')
+ >>> view.__name__ = 'anotherCachedView'
+ >>> view()
+ u'"content 1" is rendered 4 time(s)'
+
+Because of the minimum age if we invalidate the the cache the new view is not
+removed from the cache.
+
+ >>> cache.invalidate(dependencies=['content'])
+ >>> view()
+ u'"content 1" is rendered 4 time(s)'
+
+
+Cached Viewlets
+---------------
+
+Caching for viewlets can be used the same as cached views are used.
+
+ >>> from lovely.viewcache.view import cachedViewlet
+
+ >>> from zope.viewlet.viewlet import ViewletBase
+ >>> class Viewlet(ViewletBase):
+ ... def update(self):
+ ... self.context.count += 1
+ ... def render(self):
+ ... return u'viewlet for context "%s" is rendered %s time(s)'% (
+ ... self.context.name, self.context.count)
+
+ >>> CachedViewlet = cachedViewlet(Viewlet, dependencies=('viewlet',))
+
+Now we can build a viewlet instance from the cached viewlet.
+
+ >>> content3 = Content(u'content 3')
+ >>> root[u'content3'] = content3
+ >>> viewlet = CachedViewlet(content3, request, None, None)
+ >>> from lovely.viewcache.view import CachedViewletMixin
+ >>> isinstance(viewlet, CachedViewletMixin)
+ True
+
+ >>> viewlet.update()
+ >>> viewlet.render()
+ u'viewlet for context "content 3" is rendered 1 time(s)'
+
+Because the viewlet is now cached update is not called again. Because the
+update method increments the count in the context we check for a change on the
+count.
+
+ >>> content3.count
+ 1
+ >>> viewlet.update()
+ >>> content3.count
+ 1
+
+Also rendering the viewlet again will return the cached value.
+
+ >>> viewlet.render()
+ u'viewlet for context "content 3" is rendered 1 time(s)'
+
+ >>> cache.invalidate(dependencies=['viewlet'])
+ >>> viewlet.update()
+ >>> viewlet.render()
+ u'viewlet for context "content 3" is rendered 2 time(s)'
+
+
+Subclassing Cached Views
+------------------------
+
+Subclassing a cached view is possible but if __call__ is used in a derived
+class caching is only done for the values from the base class.
+
+ >>> class DerivedView(CachedView):
+ ... def __call__(self):
+ ... return u'Derived was called'
+ >>> cachedDerivedView = cachedView(DerivedView)
+ >>> derived = cachedDerivedView(content, request)
+ >>> derived.__name__ = 'derived'
+ >>> derived()
+ u'Derived was called'
+ >>> derived()
+ u'Derived was called'
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/TODO.txt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/TODO.txt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/TODO.txt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,19 @@
+=====
+TODOs
+=====
+
+- Time based invalidation, min-lifetime, max-lifetime
+
+- cache for views low prio
+
+- ftests (demo package)
+
+- statistics view for zmi
+
+- alternative cache implementations (e.g. inter instance cache,
+ webservice)
+
+- invalidate on multiple instances
+
+- zeo storage
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/TODO.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/__init__.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/__init__.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/__init__.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1 @@
+# package
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/browser/__init__.py
===================================================================
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/browser/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/browser/configure.zcml
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/browser/configure.zcml 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/browser/configure.zcml 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,21 @@
+<configure
+ xmlns="http://namespaces.zope.org/browser"
+ i18n_domain="lovely.mount">
+
+ <addform
+ schema="lovely.viewcache.interfaces.IZODBViewCache"
+ content_factory="lovely.viewcache.zodb.ViewCache"
+ label="Add ZODB ViewCache"
+ name="addZODBViewCache.html"
+ permission="zope.ManageContent"
+ />
+
+ <editform
+ schema="lovely.viewcache.interfaces.IZODBViewCache"
+ label="Edit"
+ name="editZODBViewCache.html"
+ menu="zmi_views" title="Edit"
+ permission="zope.ManageContent"
+ />
+
+</configure>
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/browser/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/configurator.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/configurator.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/configurator.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,41 @@
+from zope import component
+from zope import event
+
+from zope.security.proxy import removeSecurityProxy
+from zope.app.component.interfaces import ISite
+from zope.lifecycleevent import ObjectCreatedEvent
+
+from z3c import configurator
+
+from lovely.viewcache.interfaces import IViewCache
+from lovely.viewcache.ram import ViewCache as RAMViewCache
+from lovely.viewcache.zodb import ViewCache as ZODBViewCache
+
+
+class RAMViewCacheConfigurator(configurator.ConfigurationPluginBase):
+ component.adapts(ISite)
+
+ def __call__(self, data):
+ sm = removeSecurityProxy(self.context.getSiteManager())
+ default = sm['default']
+
+ if 'view-cache-RAM' not in default:
+ util = RAMViewCache()
+ event.notify(ObjectCreatedEvent(util))
+ default['view-cache-RAM'] = util
+ sm.registerUtility(util, IViewCache)
+
+
+class ZODBViewCacheConfigurator(configurator.ConfigurationPluginBase):
+ component.adapts(ISite)
+
+ def __call__(self, data):
+ sm = removeSecurityProxy(self.context.getSiteManager())
+ default = sm['default']
+
+ if 'view-cache-ZODB' not in default:
+ util = ZODBViewCache()
+ event.notify(ObjectCreatedEvent(util))
+ default['view-cache-ZODB'] = util
+ sm.registerUtility(util, IViewCache)
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/configurator.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/configure.zcml
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/configure.zcml 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/configure.zcml 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,64 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="lovely.viewcache">
+
+ <class
+ class=".ram.ViewCache">
+
+ <require
+ permission="zope.ManageServices"
+ interface="zope.app.cache.interfaces.ram.IRAMCache"
+ />
+ <require
+ permission="zope.ManageServices"
+ attributes="getExtendedStatistics"
+ />
+ </class>
+
+ <class
+ class=".zodb.ViewCache">
+
+ <require
+ permission="zope.ManageServices"
+ interface="lovely.viewcache.interfaces.IZODBViewCache"
+ />
+
+ <require
+ permission="zope.ManageServices"
+ set_schema="lovely.viewcache.interfaces.IZODBViewCache"
+ />
+ </class>
+
+ <!-- event subscribers for the invalidation based on Object events -->
+ <subscriber
+ handler=".event.invalidateCache"
+ for="zope.interface.Interface
+ zope.lifecycleevent.interfaces.IObjectModifiedEvent"
+ />
+
+ <subscriber
+ handler=".event.invalidateCache"
+ for="zope.interface.Interface
+ zope.app.container.interfaces.IObjectAddedEvent"
+ />
+
+ <subscriber
+ handler=".event.invalidateCache"
+ for="zope.interface.Interface
+ zope.app.container.interfaces.IObjectRemovedEvent"
+ />
+
+ <!-- configurator -->
+ <adapter
+ factory=".configurator.RAMViewCacheConfigurator"
+ name="lovely.viewcache.ram"
+ />
+
+ <adapter
+ factory=".configurator.ZODBViewCacheConfigurator"
+ name="lovely.viewcache.zodb"
+ />
+
+ <include package=".stats" />
+ <include package=".browser" />
+</configure>
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/event.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/event.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/event.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import component
+
+from zope.proxy import removeAllProxies
+
+from zope.app.intid.interfaces import IIntIds
+
+from lovely.viewcache.interfaces import IViewCache
+
+
+def invalidateCache(obj, event):
+ cache = component.queryUtility(IViewCache)
+ if cache is None:
+ return
+ deps = [removeAllProxies(iface) for iface in interface.providedBy(obj)]
+ intids = component.queryUtility(IIntIds, context=obj)
+ if intids is not None:
+ uid = intids.queryId(obj)
+ if uid is not None:
+ deps.append(uid)
+ if deps:
+ cache.invalidate(dependencies=deps)
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/event.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/ftesting.zcml
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/ftesting.zcml 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/ftesting.zcml 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,92 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ xmlns:meta="http://namespaces.zope.org/meta"
+ i18n_domain="zope">
+
+ <include package="zope.app.zcmlfiles" />
+ <include package="zope.app.cache" />
+ <include package="lovely.viewcache" />
+
+
+dependencies for viewlets and managers
+ <include package="zope.contentprovider" />
+ <include package="zope.viewlet" />
+ <include package="zope.viewlet" file="meta.zcml" />
+
+
+to run the deom we need to include the demo package too
+ <include package="lovely.viewcache.demo" />
+
+
+ <include package="zope.app.securitypolicy" file="meta.zcml" />
+
+ <include package="zope.app.server" />
+
+ <include package="zope.app.authentication" />
+ <securityPolicy
+ component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+
+ <include package="zope.app.securitypolicy" />
+
+ <role id="zope.Anonymous" title="Everybody"
+ description="All users have this role implicitly" />
+
+ <role id="zope.Manager" title="Site Manager" />
+
+
+ <principal
+ id="zope.manager"
+ title="Administrator"
+ login="mgr"
+ password="mgrpw" />
+ <grant
+ role="zope.Manager"
+ principal="zope.manager"
+ />
+
+ <unauthenticatedPrincipal
+ id="zope.anybody"
+ title="Unauthenticated User" />
+
+ <unauthenticatedGroup
+ id="zope.Anybody"
+ title="Unauthenticated Users"
+ />
+
+ <authenticatedGroup
+ id="zope.Authenticated"
+ title="Authenticated Users"
+ />
+
+ <everybodyGroup
+ id="zope.Everybody"
+ title="All Users"
+ />
+
+ <include package="zope.app.form.browser" />
+ <include package="zope.formlib" />
+ <include package="z3c.configurator"/>
+
+ <adapter
+ name="z3c.configurator.testing.settitle"
+ factory="z3c.configurator.browser.testing.SetTitle"/>
+
+ <adapter
+ name="z3c.configurator.testing.setdescription"
+ factory="z3c.configurator.browser.testing.SetDescription"/>
+
+
+ <grantAll role="zope.Manager" />
+
+ <grant
+ role="zope.Anonymous"
+ permission="zope.View"
+ />
+
+ <grant
+ role="zope.Anonymous"
+ permission="zope.app.dublincore.view"
+ />
+
+
+</configure>
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/ftests.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/ftests.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/ftests.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,58 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from zope.app.testing import functional
+
+functional.defineLayer('TestLayer', 'ftesting.zcml')
+
+
+def setUp(test):
+ """Setup a reasonable environment for the category tests"""
+ pass
+
+def setUpRamCache(test):
+ """runs the configurator for a ram cache.
+ """
+ from zope.app.testing import functional
+ root = functional.getRootFolder()
+ from lovely.viewcache.configurator import RAMViewCacheConfigurator
+ RAMViewCacheConfigurator(root)(None)
+ test.globs['root']=root
+
+
+
+
+def tearDown(test):
+ pass
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suites = (
+ functional.FunctionalDocFileSuite('README.txt', package='lovely.viewcache.demo'),
+ functional.FunctionalDocFileSuite('README.txt', package='lovely.viewcache.stats',
+ setUp=setUpRamCache),
+ )
+ for s in suites:
+ s.layer=TestLayer
+ suite.addTest(s)
+ return suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/ftests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/i18n.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/i18n.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/i18n.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,22 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.i18nmessageid
+
+_ = zope.i18nmessageid.MessageFactory('lovely.viewcache')
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/i18n.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/interfaces.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/interfaces.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/interfaces.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,121 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import schema
+
+from zope.app.cache.interfaces.ram import IRAMCache
+from zope.location.interfaces import ILocation
+
+from lovely.viewcache.i18n import _
+
+
+class ICachedViewletManager(ILocation):
+ """A viewlet manager wich caches the results returned from his viewlets"""
+
+
+class ICacheableView(ILocation):
+ """A marker to make a viewlet cacheable"""
+
+ cachingKey = schema.TextLine(
+ title = u'Cache key',
+ description = u"""
+ The key for the specific viewlet in the cache.
+ None if the viewlet needs no discrimination.
+ Make sure the value is hashable.
+ """,
+ default = None,
+ )
+
+ cachingOn = schema.Bool(
+ title = u'Caching on',
+ description = u"""
+ The view is not cached if this property is False.
+ """,
+ default=True
+ )
+
+ staticCachingDeps = interface.Attribute(
+ "Objects the view depends on",
+ """Usually this is used to define dependencies on class level.
+ This attribute is read only in the CachedView implementation.
+ """)
+
+ dynamicCachingDeps = interface.Attribute(
+ "Objects the view depends on",
+ """"
+ This is used for the dynamic dependencies created by the view
+ at runtime.
+ """)
+
+
+class IViewCache(IRAMCache):
+ """A special cache used for the view cache."""
+
+ def set(data, ob, key=None, dependencies=None):
+ """It is possible to provide dependencies for the cache entry."""
+
+ def invalidate(ob=None, key=None, dependencies=None):
+ """Invalidation also allows to invalidate on the dependencies of a
+ view.
+ """
+
+ def getExtendedStatistics():
+ """return an extended statistics dictionary"""
+
+
+class IZODBViewCache(IViewCache):
+ """A cache which stores it's values in the ZODB using a mountpoint."""
+
+ dbName = schema.Choice(
+ title=_(u'Database Name'),
+ description=_(u"The database to be used for the cache"),
+ vocabulary='Database Names',
+ )
+
+
+class IViewModule(interface.Interface):
+
+ def cachedView(viewClass, dependencies=(), minAge=0, maxAge=None):
+ """Create a cached view class from an existing view class.
+
+ Returns a class which is using the cache. Caching is done when calling
+ the view.
+
+ viewClass : the class which should be turned into a cached class
+ dependencies : used for the 'staticCachingDeps'
+ minAge : the minimum lifetime of a cache entry for this view. If a
+ view is ivalidated before this time it is marked as
+ invalidated and removed after this time from the cache.
+ maxAge : allows to overwrite the maxAge of cache entries in the cache
+ the view is cached. This parameter can not extend the maxAge
+ defined in the cache!
+ """
+
+ def cachedViewlet(viewletClass, dependencies=(), minAge=0, maxAge=None):
+ """Create a cached view class from an existing view class.
+
+ This is exactly the same an 'cachedView' but allows caching for
+ viewlets.
+
+ If a cached value is present 'update' and 'render' is not called
+ instead the cached value is returned when calling render. The new
+ class is derived from the cachedView class so that it also provides
+ caching when the viewlet is called.
+ """
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/lovely.viewcache-configure.zcml
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/lovely.viewcache-configure.zcml 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/lovely.viewcache-configure.zcml 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1 @@
+<include package="lovely.viewcache" />
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/lovely.viewcache-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/manager.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/manager.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/manager.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,84 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import interface
+from zope import component
+
+from zope.traversing.api import canonicalPath
+from zope.location.interfaces import ILocation
+from zope.viewlet.viewlet import ViewletBase
+from zope.viewlet.manager import ViewletManagerBase
+from zope.viewlet.interfaces import IViewlet
+
+from lovely.viewcache.interfaces import (ICachedViewletManager,
+ ICacheableView,
+ IViewCache,
+ )
+
+
+class CachedViewletManager(ViewletManagerBase):
+ interface.implements(ICachedViewletManager)
+
+ __name__ = u''
+
+ def getCache(self):
+ return component.queryUtility(IViewCache)
+
+ def _getCachePath(self, viewlet):
+ return u'%s/%s/%s'% (canonicalPath(self.context),
+ unicode(self.__name__),
+ unicode(viewlet.__name__))
+
+ def _updateViewlets(self):
+ cache = self.getCache()
+ if cache is not None:
+ viewlets = []
+ for viewlet in self.viewlets:
+ # try to get the cached value from the cache
+ if ICacheableView.providedBy(viewlet) and viewlet.cachingOn:
+ result = cache.query(self._getCachePath(viewlet),
+ dict(key=viewlet.key))
+ if result is not None:
+ viewlet.__cachedValue__ = result
+ viewlets.append(viewlet)
+ self.viewlets = viewlets
+ for viewlet in self.viewlets:
+ if not hasattr(viewlet, '__cachedValue__'):
+ viewlet.update()
+
+ def render(self):
+ cache = self.getCache()
+ result = []
+ for viewlet in self.viewlets:
+ if not hasattr(viewlet, '__cachedValue__'):
+ viewletResult = viewlet.render()
+ if ( cache is not None
+ and ICacheableView.providedBy(viewlet)
+ and viewlet.cachingOn
+ ):
+ deps = set(viewlet.staticCachingDeps)
+ deps.update(viewlet.dynamicCachingDeps)
+ cache.set(viewletResult,
+ self._getCachePath(viewlet),
+ dict(key=viewlet.key),
+ dependencies=deps)
+ result.append(viewletResult)
+ else:
+ result.append(viewlet.__cachedValue__)
+ return u'\n'.join([r for r in result])
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/manager.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/manager.txt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/manager.txt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/manager.txt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,263 @@
+======================
+Cached Viewlet Manager
+======================
+
+We provide a viewlet manager which is able to handle the view cache of his
+viewlets.
+
+ >>> from lovely.viewcache.manager import CachedViewletManager
+
+ >>> from zope.app.container.contained import Contained
+ >>> from zope import interface
+ >>> class IContent(interface.Interface):
+ ... pass
+ >>> class Content(Contained):
+ ... interface.implements(IContent)
+ >>> content = Content()
+ >>> root[u'content'] = content
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+
+ >>> from zope.publisher.interfaces.browser import IBrowserView
+ >>> class View(object):
+ ... interface.implements(IBrowserView)
+ ... def __init__(self, context, request):
+ ... self.context = context
+ ... self.request = request
+
+ >>> view = View(content, request)
+
+We use the update/render pattern to render the manager. We get no result
+because no viewlet is registered for the manager.
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> manager.render()
+ u''
+
+Now we provide a viewlet and register it for out manager.
+
+ >>> count = 0
+ >>> from lovely.viewcache.interfaces import ICachedViewletManager
+ >>> from lovely.viewcache.mixin import CachableViewMixin
+ >>> from zope.viewlet.viewlet import ViewletBase
+ >>> class TestViewlet(ViewletBase, CachableViewMixin):
+ ... staticCachingDeps = (1,)
+ ... def render(self):
+ ... global count
+ ... count += 1
+ ... return 'From TestViewlet %s'% count
+ >>> from zope.security.checker import NamesChecker, defineChecker
+ >>> viewletChecker = NamesChecker(('update', 'render'))
+ >>> defineChecker(TestViewlet, viewletChecker)
+
+ >>> from zope import component
+ >>> from zope.viewlet.interfaces import IViewlet
+ >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+ >>> component.provideAdapter(TestViewlet,
+ ... adapts=(IContent, IDefaultBrowserLayer,
+ ... IBrowserView, ICachedViewletManager),
+ ... provides=IViewlet,
+ ... name='test1')
+
+Using the manager again renders the new viewlet.
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> manager.render()
+ u'From TestViewlet 1'
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> manager.render()
+ u'From TestViewlet 2'
+
+We still do not get the viewlet from the cache. This is because we didn't
+provide a cache for the manager. We provide a simple ram cache here.
+
+ >>> from lovely.viewcache.ram import ViewCache
+ >>> from lovely.viewcache.interfaces import IViewCache
+ >>> cache = ViewCache()
+ >>> component.provideUtility(cache, IViewCache)
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> manager.render()
+ u'From TestViewlet 3'
+
+Yipee, we got it.
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> manager.render()
+ u'From TestViewlet 3'
+
+ >>> component.provideAdapter(TestViewlet,
+ ... adapts=(IContent, IDefaultBrowserLayer,
+ ... IBrowserView, ICachedViewletManager),
+ ... provides=IViewlet,
+ ... name='test2')
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ From TestViewlet 3
+ From TestViewlet 4
+
+
+Viewlet Providing A Key For The Cache
+-------------------------------------
+
+The viewlet can provide a key to discriminate on internal values.
+
+ >>> class SelectiveViewlet(ViewletBase, CachableViewMixin):
+ ... @property
+ ... def key(self):
+ ... return str(self.request['selector'])
+ ... def update(self):
+ ... self.dynamicCachingDeps = (self.request['selector'],)
+ ... def render(self):
+ ... global count
+ ... count += 1
+ ... return u'%s (selector = %s)'% (count, self.request['selector'])
+ >>> viewletChecker = NamesChecker(('update', 'render'))
+ >>> defineChecker(SelectiveViewlet, viewletChecker)
+ >>> component.provideAdapter(SelectiveViewlet,
+ ... adapts=(IContent, IDefaultBrowserLayer,
+ ... IBrowserView, ICachedViewletManager),
+ ... provides=IViewlet,
+ ... name='selector')
+
+ >>> request.form['selector'] = 1
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 5 (selector = 1)
+ ...
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 5 (selector = 1)
+ ...
+
+Changing the selector must render the viewlet again.
+
+ >>> request.form['selector'] = 2
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 6 (selector = 2)
+ ...
+
+The request with selector 1 is still in the cache.
+
+ >>> request.form['selector'] = 1
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 5 (selector = 1)
+ ...
+
+
+Invalidating Cache Entries
+--------------------------
+
+ >>> cache.invalidate(dependencies=(1,))
+
+ >>> request.form['selector'] = 1
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 7 (selector = 1)
+ ...
+
+
+Dynamically disabling the cache
+-------------------------------
+
+A view can disable caching using the 'cachingOn' property.
+
+ >>> class CacheSwitchingViewlet(ViewletBase, CachableViewMixin):
+ ... @property
+ ... def cachingOn(self):
+ ... return 'cacheMe' in self.request
+ ... def render(self):
+ ... global count
+ ... count += 1
+ ... return u'%s (cacheMe = %s)'% (
+ ... count, self.request.get('cacheMe', False))
+ >>> viewletChecker = NamesChecker(('update', 'render'))
+ >>> defineChecker(CacheSwitchingViewlet, viewletChecker)
+ >>> component.provideAdapter(CacheSwitchingViewlet,
+ ... adapts=(IContent, IDefaultBrowserLayer,
+ ... IBrowserView, ICachedViewletManager),
+ ... provides=IViewlet,
+ ... name='cacheSwitchin')
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 10 (cacheMe = False)
+ ...
+
+As long as we do not provide 'cacheMe' in the reqeust the viewlet is not
+cached.
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 11 (cacheMe = False)
+ ...
+
+Now the first call with 'cacheMe' set in the reqeust will render it and stores
+the render view in the cache.
+
+ >>> request.form['cacheMe'] = 1
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 12 (cacheMe = 1)
+ ...
+
+Now we get the cached result.
+
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 12 (cacheMe = 1)
+ ...
+
+Removing the 'cacheMe' property will render the view.
+
+ >>> del request.form['cacheMe']
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 13 (cacheMe = False)
+ ...
+
+Setting 'cacheMe' again takes the existing cache entry.
+
+ >>> request.form['cacheMe'] = 1
+ >>> manager = CachedViewletManager(content, request, view)
+ >>> manager.update()
+ >>> print manager.render()
+ 12 (cacheMe = 1)
+ ...
+
+
+Configurator
+------------
+
+We also provide a configurator to register a view cache on a site.
+
+ >>> from lovely.viewcache.configurator import RAMViewCacheConfigurator
+ >>> RAMViewCacheConfigurator(root)(None)
+ >>> sm = root.getSiteManager()
+ >>> 'view-cache-RAM' in sm['default']
+ True
+ >>> component.getUtility(IViewCache) is not None
+ True
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/manager.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/mixin.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/mixin.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/mixin.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,33 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+
+from lovely.viewcache.interfaces import ICacheableView
+
+class CachableViewMixin(object):
+ interface.implements(ICacheableView)
+
+ __parent__ = None
+ __name__ = None
+
+ key = None
+ cachingOn = True
+ staticCachingDeps = ()
+ dynamicCachingDeps = ()
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/mixin.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/ram.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/ram.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/ram.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,207 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import logging
+from time import time
+
+from zope import interface
+from cPickle import dumps
+from zope.app.cache.ram import RAMCache, Storage, writelock, caches
+
+from lovely.viewcache.interfaces import IViewCache
+
+
+class ViewCache(RAMCache):
+ interface.implements(IViewCache)
+
+ def set(self, data, ob, key=None,
+ dependencies=None,
+ lifetime=(0, None)):
+ logging.info('Viewcache.set(%r, %r, %r)'% (ob, key, dependencies))
+ s = self._getStorage()
+ key = self._buildKey(key)
+ s.setEntry(ob, key, data, lifetime)
+ if dependencies is not None:
+ for dep in dependencies:
+ try:
+ obs = s.getEntry(dep, None)
+ obs += (ob, )
+ except KeyError:
+ obs = (ob, )
+ s.setEntry(dep, None, obs, lifetime)
+
+ def invalidate(self, ob=None, key=None, dependencies=None):
+ logging.info('Viewcache.invalidate(%r, %r, %r)'% (
+ ob, key, dependencies))
+ if dependencies is not None:
+ s = self._getStorage()
+ if key:
+ key = self._buildKey(key)
+ for dep in dependencies:
+ try:
+ obs = s.getEntry(dep, None)
+ s.invalidate(dep)
+ except KeyError:
+ obs = ()
+ for ob in obs:
+ s.invalidate(ob, key)
+ else:
+ #TODO: invalidate dependency-indices
+ super(ViewCache, self).invalidate(ob, key)
+
+
+ def _getStorage(self):
+ "Finds or creates a storage object."
+ cacheId = self._cacheId
+ writelock.acquire()
+ try:
+ if cacheId not in caches:
+ caches[cacheId] = LifetimeStorage(self.maxEntries, self.maxAge,
+ self.cleanupInterval)
+ return caches[cacheId]
+ finally:
+ writelock.release()
+
+
+ def getExtendedStatistics(self):
+ s = self._getStorage()
+ return s.getExtendedStatistics()
+
+
+class LifetimeStorage(Storage):
+
+ def setEntry(self, ob, key, value, lifetime=(0, None)):
+ """Stores a value for the object. Creates the necessary
+ dictionaries."""
+
+ if self.lastCleanup <= time() - self.cleanupInterval:
+ self.cleanup()
+
+ self.writelock.acquire()
+ try:
+ if ob not in self._data:
+ self._data[ob] = {}
+
+ timestamp = time()
+ # [data, ctime, access count, lifetime, Invalidated]
+ self._data[ob][key] = [value, timestamp, 0, lifetime, False]
+ finally:
+ self.writelock.release()
+ self._invalidate_queued()
+
+ def _do_invalidate(self, ob, key=None):
+ """This does the actual invalidation, but does not handle the locking.
+
+ This method is supposed to be called from `invalidate`
+ """
+ obEntry = self._data.get(ob, None)
+ if obEntry is not None:
+ keyEntry = obEntry.get(key, None)
+ if keyEntry is not None:
+ minTime = keyEntry[3][0]
+ lifetime = time() - keyEntry[1]
+ if lifetime < minTime:
+ # minimum lifetime not reached, just mark it for removal
+ keyEntry[4]=True
+ return
+ try:
+ if key is None:
+ del self._data[ob]
+ self._misses[ob] = 0
+ else:
+ del self._data[ob][key]
+ if not self._data[ob]:
+ del self._data[ob]
+ except KeyError:
+ pass
+
+ def removeStaleEntries(self):
+ "Remove the entries older than `maxAge`"
+ punchline = time() - self.maxAge
+ self.writelock.acquire()
+ try:
+ data = self._data
+ for object, dict in data.items():
+ for ob, key in dict.items():
+ minTime = key[3][0]
+ lifetime = time() - key[1]
+ if lifetime < minTime:
+ # minimum lifetime not reached, do not remove it
+ continue
+ lifetime = time() - key[1]
+ if ( key[4]
+ or ( self.maxAge > 0
+ and key[1] < punchline
+ )
+ or ( key[3][1] is not None
+ and key[3][1] < lifetime
+ )
+ ):
+ # invalidation flag set or maxAge reached
+ del dict[ob]
+ if not dict:
+ del data[object]
+ finally:
+ self.writelock.release()
+ self._invalidate_queued()
+
+ def getExtendedStatistics(self):
+ "Basically see IRAMCache"
+ result = []
+ for ob in self._getStatisticObjects():
+ # use min and maxage for first cache entry (one is always present)
+ minage = self._data[ob].values()[0][3][0] #damn complicating!
+ maxage = self._data[ob].values()[0][3][1] or self.maxAge #damn complicating!
+ #the size of all cached values of all subkeys as pickeled in zodb
+ totalsize = len(dumps(self._data[ob]))
+ deps = []
+ for dep in self._data.keys():
+ for cacheentry in self._data[dep].values():
+ if str(ob) in cacheentry[0]:
+ #dependency cache entries have a list of dependen objects in val[0]
+ deps.append(dep)
+ hits = sum(entry[2] for entry in self._data[ob].itervalues())
+ result.append({'path': ob,
+ 'key': None,
+ 'misses': self._misses.get(ob, 0),
+ 'size': totalsize,
+ 'entries': len(self._data[ob]),
+ 'hits': hits,
+ 'minage': minage,
+ 'maxage': maxage,
+ 'deps': deps,
+ 'keys': []})
+ pathObj = result[-1]
+
+ for key, value in self._data[ob].items():
+ if key is not None:
+ pathObj['keys'].append({'path': ob,
+ 'key': key,
+ 'misses': '',
+ 'size': len(dumps(value)),
+ 'entries': '',
+ 'hits': value[2],
+ 'minage': '',
+ 'maxage': '',
+ 'deps': None,
+ 'keys':[]})
+ return tuple(result)
+
+ def _getStatisticObjects(self):
+ return sorted(self._data.keys())
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/ram.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/ram.txt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/ram.txt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/ram.txt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,49 @@
+=========
+ViewCache
+=========
+
+
+The viewcache is an extended RAMCache, which can invalidate entries based
+on object-interfaces and intids.
+
+ >>> from lovely.viewcache import ram
+ >>> viewCache = ram.ViewCache()
+
+It is possible to define dependencies on entries, which can be used to
+invalidate specific entries. If we don't specify dependencies the behaviour is
+the same as the normal RAMCache.
+
+ >>> viewCache.set('value1', 'object1', key={'x':1}, dependencies=[1])
+ >>> viewCache.set('value2', 'object2', dependencies=[2])
+ >>> viewCache.set('value3', 'object3', key={'y':1}, dependencies=[1, 2, 3])
+ >>> viewCache.query('object1', key={'x':1})
+ 'value1'
+ >>> viewCache.query('object3', key={'y':1})
+ 'value3'
+ >>> viewCache.invalidate(dependencies=[1])
+ >>> viewCache.query('object1', key={'x':1}) is None
+ True
+ >>> viewCache.query('object2')
+ 'value2'
+ >>> viewCache.query('object3', key={'y':1}) is None
+ True
+
+ >>> viewCache.set('timed',
+ ... 'timed_object1',
+ ... {'timed': True},
+ ... dependencies=[10],
+ ... lifetime=(1, None))
+ >>> viewCache.query('timed_object1', {'timed': True})
+ 'timed'
+ >>> viewCache.invalidate(key={'timed': True}, dependencies=[10])
+ >>> viewCache.query('timed_object1', {'timed': True}) is None
+ False
+ >>> from time import sleep
+ >>> sleep(2)
+
+Now we need to explictly call the storage to remove stale entries.
+
+ >>> viewCache._getStorage().removeStaleEntries()
+ >>> viewCache.query('timed_object1', {'timed': True}) is None
+ True
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/ram.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/stats/README.txt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/stats/README.txt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/stats/README.txt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,103 @@
+====================
+viewcache statistics
+====================
+
+viewcache statistics are based on the statistics you know from ramcache
+but offer additional functionality to
+
+* `invalidate cache entries`_
+* show the different keys
+* display their min and max lifetime XXX
+* and show their dependencies XXX
+
+
+
+
+
+The CacheView has been registered in the tests setUp method.
+We can access the statistic view using the sitemanager
+
+ >>> from zope.testbrowser.testing import Browser
+ >>> manager = Browser()
+ >>> manager.addHeader('Authorization', 'Basic mgr:mgrpw')
+ >>> manager.handleErrors = False
+ >>> manager.open('http://localhost:8080/++etc++site/default/view-cache-RAM/statistics.html')
+
+
+Now we just add some fake rendered views to our cache. The cache entries are similar to the viewlets
+used in the demo-package. we create a cache-entry for the idlist-viewlet with the key 'asc' and one
+with the key 'desc'. We create another cache-entry for the metadata-viewlet where we don't specify a key.
+Both viewlet have a dependency on IFolder.
+
+ >>> viewCache = root.getSiteManager()['default']['view-cache-RAM']
+ >>> viewCache
+ <lovely.viewcache.ram.ViewCache object at ...>
+
+ >>> from zope.app.folder.interfaces import IFolder
+ >>> viewCache.set('<div>some html snippets</div>', 'idlist', key={'key': u'asc'}, dependencies=[IFolder,])
+ >>> viewCache.set('<div>some html snippets</div>', 'idlist', key={'key': u'desc'}, dependencies=[IFolder,])
+ >>> viewCache.set('<div>some html snippets</div>', 'idlist', key={'key': u'desc'}, dependencies=[IFolder,])
+ >>> viewCache.set('<div>some html</div>', 'metadatalist', key={'key': None}, dependencies=[IFolder,])
+
+
+invalidate cache entries
+========================
+
+We want to display the cache-statistics now, and then select the entry with the key 'asc' for invalidation.
+
+ >>> manager.open('http://localhost:8080/++etc++site/default/view-cache-RAM/statistics.html')
+ >>> form = manager.getForm(index=0)
+ >>> controls = form.getControl(name='ids:list')
+ >>> items = controls.mech_control.get_items()
+ >>> items[1].selected = True
+ >>> from pprint import pprint as pp
+ >>> pp([(input.id, input.selected) for input in items])
+ [('idlist-None', False),
+ ("idlist-(('key', u'asc'),)", True),
+ ("idlist-(('key', u'desc'),)", False),
+ ('metadatalist-None', False),
+ ("metadatalist-(('key', None),)", False),
+ ...
+
+We see, that the checkbox of the 2nd entry has been selected. We click 'Invalidate' to remove this entry
+from our cache
+
+ >>> form.getControl(name='form.Invalidate').click()
+
+As we open the statistics page again the respective cache-entry should be gone now:
+
+ >> manager.open('http://localhost:8080/++etc++site/default/view-cache-RAM/statistics.html')
+ >>> form = manager.getForm(index=0)
+ >>> controls = form.getControl(name='ids:list')
+ >>> items = controls.mech_control.get_items()
+ >>> pp([(input.id, input.selected) for input in items])
+ [('idlist-None', False),
+ ("idlist-(('key', u'desc'),)", False),
+ ('metadatalist-None', False),
+ ("metadatalist-(('key', None),)", False),
+ ...
+
+
+
+The first row of a new view does not stand for a cache entry but for all cache entries of the respective view.
+It summarizes the sizes, hits and misses, shows the number of cache-entries and the lifetime settings.
+
+
+You can invalidate all cache entries for a view by invalidating this first row.
+
+ >>> controls.controls[0].selected = True
+ >>> form.getControl(name='form.Invalidate').click()
+
+now all cache entries for the view `idlist` got removed from our cache.
+
+ >>> manager.open('http://localhost:8080/++etc++site/default/view-cache-RAM/statistics.html')
+ >>> form = manager.getForm(index=0)
+ >>> controls = form.getControl(name='ids:list')
+ >>> items = controls.mech_control.get_items()
+ >>> pp([(input.id, input.selected) for input in items])
+ [('metadatalist-None', False),
+ ("metadatalist-(('key', None),)", False),
+ ...
+
+
+XXX show/test invalidateAll
\ No newline at end of file
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/stats/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/stats/__init__.py
===================================================================
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/stats/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/stats/browser.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/stats/browser.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/stats/browser.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,32 @@
+from zope.security.proxy import removeSecurityProxy
+from zope.traversing.browser.absoluteurl import absoluteURL
+from lovely.viewcache.interfaces import IViewCache
+from zope.publisher.browser import BrowserView
+
+class StatisticsView(BrowserView):
+ """
+ """
+ __used_for__ = IViewCache
+
+ separator='---'
+
+ def invalidateItems(self):
+ if 'form.Invalidate' in self.request:
+ ids = self.request.get('ids', [])
+ context = removeSecurityProxy(self.context)
+ data = context._getStorage()._data
+ for obj in data.keys(): #XXX discuss to get an method like this into viewcache interface
+ for key in data[obj].keys():
+ if self.getHash(obj, None) in ids:
+ #for invalidation of all entries for an object
+ self.context.invalidate(obj)
+ elif self.getHash(obj, key) in ids:
+ self.context.invalidate(obj, {key[0][0]:key[0][1]})
+ self.request.response.redirect(absoluteURL(self.context, self.request) + '/statistics.html')
+
+ def invalidateAll(self):
+ self.context.invalidateAll()
+ self.request.response.redirect(absoluteURL(self.context, self.request) + '/statistics.html')
+
+ def getHash(self, obj, key):
+ return "%s:%s" % (hash(obj), hash(key))
\ No newline at end of file
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/stats/browser.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/stats/configure.zcml
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/stats/configure.zcml 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/stats/configure.zcml 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,32 @@
+<configure
+ xmlns="http://namespaces.zope.org/browser"
+ i18n_domain="lovely.viewcache">
+
+ <page
+ name="statistics.html"
+ menu="zmi_views"
+ title="ViewCache Statistics"
+ for="..interfaces.IViewCache"
+ class=".browser.StatisticsView"
+ permission="zope.Public"
+ template="stats.pt"
+ />
+
+ <page
+ name="invalidateitems"
+ for="..interfaces.IViewCache"
+ class=".browser.StatisticsView"
+ attribute="invalidateItems"
+ permission="zope.Public"
+ />
+
+ <page
+ name="invalidateall"
+ for="..interfaces.IViewCache"
+ class=".browser.StatisticsView"
+ attribute="invalidateAll"
+ permission="zope.Public"
+ />
+
+
+</configure>
\ No newline at end of file
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/stats/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/stats/interfaces.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/stats/interfaces.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/stats/interfaces.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,8 @@
+from zope import interface
+
+
+class IViewCacheStatistics(interface.Interface):
+ """methods needed for cache statistics.
+ """
+
+ pass #XXX stub
\ No newline at end of file
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/stats/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/stats/stats.pt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/stats/stats.pt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/stats/stats.pt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,96 @@
+<html metal:use-macro="context/@@standard_macros/view"
+ i18n:domain="zope">
+<body>
+<div metal:fill-slot="body">
+
+ <p><span tal:replace="context/zope:name"/>
+ <span i18n:translate="">ViewCache statistics</span></p>
+
+ <div tal:condition="python: options.has_key('errors') and
+ options['errors']">
+ <span style="font-weight: bold" i18n:translate="">Errors</span>:
+ <div tal:repeat="error options/errors | nothing">
+ <span tal:replace="python: error[0].title" />:
+ <span tal:replace="python: error[1].error_name" />
+ </div>
+ </div>
+ <br />
+
+ <form name="viewCacheContentsForm" method="post" action="."
+ tal:attributes="action string:invalidateitems">
+
+ <table id="sortable" class="listing" summary="Content listing"
+ cellpadding="2" cellspacing="0" >
+ <thead>
+ <tr>
+ <th i18n:translate="">Invalidate</th>
+ <th i18n:translate="" colspan="2">Path</th>
+ <th i18n:translate="">Hits</th>
+ <th i18n:translate="">Misses</th>
+ <th i18n:translate="">Size, bytes</th>
+ <th i18n:translate="">Entries</th>
+ <th i18n:translate="">Minage</th>
+ <th i18n:translate="">Maxage</th>
+ <th i18n:translate="">Dependencies</th>
+ </tr>
+ </thead>
+ <tbody tal:repeat="data context/getExtendedStatistics">
+ <tr bgcolor="#d3bc99">
+ <td>
+ <input type="checkbox" class="noborder" name="ids:list" id="#" value="#"
+ tal:define="id python:view.getHash(data['path'],data['key'])"
+ tal:attributes="value id;
+ id string:${data/path}-${data/key}"
+ />
+ </td>
+ <td colspan="2">
+ <span tal:condition="not: data/key" tal:replace="data/path">/url/manager/view</span>
+ </td>
+ <td tal:content="data/hits">1</td>
+ <td tal:content="data/misses">0</td>
+ <td tal:content="data/size">1.7</td>
+ <td tal:content="data/entries">2</td>
+ <td tal:content="data/minage">1000</td>
+ <td tal:content="data/maxage">3000</td>
+ <td>
+ <div tal:repeat="dep data/deps">
+ <div tal:content="dep"><InterfaceClass zope.app.folder.interfaces.IFolder></div>
+ </div>
+ </td>
+ </tr>
+ <tr tal:condition="data/keys"
+ tal:repeat="item data/keys">
+ <td>
+ <input type="checkbox" class="noborder" name="ids:list" id="#" value="#"
+ tal:define="id python:view.getHash(item['path'],item['key'])"
+ tal:attributes="value id;
+ id string:${item/path}-${item/key}"
+ />
+ </td>
+ <td width="20px"></td>
+ <td><span tal:replace="item/key">/url/manager/view</span></td>
+ <td tal:content="item/hits">1</td>
+ <td tal:content="item/misses">0</td>
+ <td tal:content="item/size">1.7</td>
+ <td tal:content="item/entries">2</td>
+ <td tal:content="item/minage">1000</td>
+ <td tal:content="item/maxage">3000</td>
+ <td>
+ <div tal:repeat="dep item/deps">
+ <div tal:replace="dep"><InterfaceClass zope.app.folder.interfaces.IFolder></div>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <input type="submit" name="form.Invalidate" value="Invalidate" />
+ </form>
+ <form name="viewCacheContentsForm" method="post" action="."
+ tal:attributes="action string:invalidateall">
+ <input type="submit" name="form.InvalidateAll" value="Invalidate all" />
+ </form>
+ <div tal:content="options/message|nothing" i18n:translate="" />
+</div>
+</body>
+
+</html>
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/stats/stats.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/tests.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/tests.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/tests.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,90 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from zope import component
+import zope.interface
+import zope.security
+from zope.testing import doctest
+from zope.app.folder import rootFolder
+from zope.app.publication.zopepublication import ZopePublication
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
+from zope.app.testing import setup
+import ZODB.tests.util, transaction
+from ZODB.interfaces import IDatabase
+from zope.schema.interfaces import IVocabularyFactory
+from lovely.mount.vocabulary import DatabaseVocabulary
+from zope.app.schema import vocabulary
+
+from zope.app.testing.setup import (placefulSetUp,
+ placefulTearDown)
+
+
+def setUp(test):
+ root = placefulSetUp(site=True)
+ test.globs['root'] = root
+
+def setUpZODB(test):
+ setUp(test)
+ databases = {}
+ db1 = ZODB.tests.util.DB(databases=databases, database_name='1')
+ db2 = ZODB.tests.util.DB(databases=databases, database_name='2')
+ test.db1 = db1
+ test.db2 = db2
+ cx = db1.open()
+ root = cx.root()
+ test.root_folder = rootFolder()
+ root[ZopePublication.root_name] = test.root_folder
+ test.globs['root'] = test.root_folder
+ transaction.commit()
+ vocabulary._clear()
+ component.provideUtility(DatabaseVocabulary, IVocabularyFactory,
+ name="Database Names")
+ test.globs['db'] = db1
+
+def tearDown(test):
+ placefulTearDown()
+
+def tearDownZODB(test):
+ test.db1.close()
+ test.db2.close()
+ tearDown(test)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ DocFileSuite('README.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('manager.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('ram.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ DocFileSuite('zodb.txt',
+ setUp=setUpZODB, tearDown=tearDownZODB,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/tests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/view.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/view.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/view.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,150 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import component
+from zope import interface
+
+from zope.traversing.api import canonicalPath
+from zope.publisher.browser import BrowserView
+from zope.traversing.browser.absoluteurl import absoluteURL
+
+from lovely.viewcache.interfaces import IViewCache, ICacheableView
+
+
+class CacheMixinBase(object):
+ interface.implements(ICacheableView)
+
+ _cachingOn = True
+ __cachedValue__ = None
+ _dynamicCachingDeps = ()
+
+ @apply
+ def cachingOn():
+ def get(self):
+ return getattr(super(CacheMixinBase, self),
+ 'cachingOn',
+ self._cachingOn)
+ def set(self, value):
+ self._cachingOn = value
+ return property(get, set)
+
+ @property
+ def cachingKey(self):
+ return getattr(super(CacheMixinBase, self),
+ 'cachingKey',
+ self.__name__)
+
+ @property
+ def staticCachingDeps(self):
+ return getattr(super(CacheMixinBase, self),
+ 'staticCachingDeps',
+ self._staticCachingDeps)
+
+ @apply
+ def dynamicCachingDeps():
+ def get(self):
+ return getattr(super(CacheMixinBase, self),
+ 'dynamicCachingDeps',
+ self._dynamicCachingDeps)
+ def set(self, value):
+ self._dynamicCachingDeps = value
+ return property(get, set)
+
+ def getCache(self):
+ return component.queryUtility(IViewCache)
+
+ def _getCachePath(self):
+ url = absoluteURL(self.context, self.request)
+ return '/'.join(url.split('/')[3:])
+
+ def _getCachedResult(self):
+ self.__cachedValue__ = None
+ if self.cachingOn:
+ cache = self.getCache()
+ if cache is not None:
+ result = cache.query(self._getCachePath(),
+ dict(key=self.cachingKey))
+ if result is not None:
+ self.__cachedValue__ = result
+ return self.__cachedValue__ is not None
+
+ def _setCachedResult(self, value):
+ self.__cachedValue__ = value
+ if self.cachingOn:
+ cache = self.getCache()
+ if cache is not None:
+ deps = set(self.staticCachingDeps)
+ deps.update(self.dynamicCachingDeps)
+ cache.set(value,
+ self._getCachePath(),
+ dict(key=self.cachingKey),
+ lifetime=self.lifetime,
+ dependencies=deps)
+
+
+class CachedViewMixin(CacheMixinBase):
+
+ def __call__(self, *args, **kwargs):
+ if not self._getCachedResult():
+ result = super(CacheMixinBase, self).__call__(*args, **kwargs)
+ self._setCachedResult(result)
+ return self.__cachedValue__
+
+
+def cachedView(ViewClass, dependencies=(), minAge=0, maxAge=None):
+ """A factory to provide a view which is possibly in the view cache."""
+ klass = ViewClass
+ if ICacheableView not in interface.implementedBy(klass):
+ attrs = dict(_staticCachingDeps=dependencies,
+ lifetime = (minAge, maxAge),
+ __name__=None,
+ )
+ klass = type('<ViewCache for %s>'% ViewClass.__name__,
+ (CachedViewMixin, ViewClass, ),
+ attrs)
+ return klass
+
+
+class CachedViewletMixin(CacheMixinBase):
+
+ def update(self):
+ if not self._getCachedResult():
+ super(CachedViewletMixin, self).update()
+
+ def render(self):
+ if self.__cachedValue__ is None:
+ if not self._getCachedResult():
+ result = super(CachedViewletMixin, self).render()
+ self._setCachedResult(result)
+ return self.__cachedValue__
+
+
+def cachedViewlet(ViewClass, dependencies=(), minAge=0, maxAge=None):
+ """A factory to provide a viewlet which is possibly in the view cache."""
+ klass = ViewClass
+ if ICacheableView not in interface.implementedBy(klass):
+ # our class is not cached, so make it a cached class
+ attrs = dict(_staticCachingDeps=dependencies,
+ lifetime = (minAge, maxAge),
+ __name__=None,
+ )
+ klass = type('<ViewletCache for %s>'% ViewClass.__name__,
+ (CachedViewletMixin, ViewClass, ),
+ attrs)
+ return klass
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/view.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/zodb.py
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/zodb.py 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/zodb.py 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,264 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from cPickle import dumps
+from time import time
+from threading import Lock
+
+import persistent
+from persistent.list import PersistentList
+from BTrees.OOBTree import OOBTree
+from ZODB.interfaces import IDatabase
+
+from zope import interface
+from zope import component
+
+from zope.app.cache.ram import Storage
+
+from ram import ViewCache as RAMViewCache
+
+from lovely.mount.root import DBRoot
+from lovely.viewcache.interfaces import IZODBViewCache
+
+
+class ViewCache(RAMViewCache):
+ interface.implements(IZODBViewCache)
+
+ mountpoint = None
+
+ def __init__(self, dbName=''):
+ self.dbName = dbName
+ super(ViewCache, self).__init__()
+
+ def _getStorage(self):
+ "Finds or creates a storage object."
+ if self.mountpoint is None:
+ self.mountpoint = DBRoot(str(self.dbName))
+ if self.dbName != self.mountpoint.dbName:
+ self.mountpoint.dbName = str(self.dbName)
+ if self.__name__ not in self.mountpoint:
+ storage = PersistentStorage(
+ self.maxEntries, self.maxAge, self.cleanupInterval)
+ self.mountpoint[self.__name__] = storage
+ return self.mountpoint[self.__name__]
+
+
+class PersistentStorage(persistent.Persistent):
+ """A storage for ViewCache using ZODB.
+
+ Storage keeps the count and does the aging and cleanup of cached
+ entries.
+
+ This object is shared between threads. It corresponds to a single
+ persistent `RAMCache` object. Storage does the locking necessary
+ for thread safety.
+ """
+
+ def __init__(self, maxEntries=1000, maxAge=3600, cleanupInterval=300):
+ self.invalidateAll()
+ self.maxEntries = maxEntries
+ self.maxAge = maxAge
+ self.cleanupInterval = cleanupInterval
+ self.lastCleanup = time()
+
+ def update(self, maxEntries=None, maxAge=None, cleanupInterval=None):
+ """Set the registration options.
+
+ ``None`` values are ignored.
+ """
+ if maxEntries is not None:
+ self.maxEntries = maxEntries
+ if maxAge is not None:
+ self.maxAge = maxAge
+ if cleanupInterval is not None:
+ self.cleanupInterval = cleanupInterval
+
+ def getEntry(self, ob, key):
+ if self.lastCleanup <= time() - self.cleanupInterval:
+ self.cleanup()
+ try:
+ data = self._data[ob][key]
+ except KeyError:
+ if ob not in self._misses:
+ self._misses[ob] = 0
+ self._misses[ob] += 1
+ raise
+ else:
+ data[2] += 1 # increment access count
+ return data[0]
+
+ def setEntry(self, ob, key, value, lifetime=(0, None)):
+ """Stores a value for the object. Creates the necessary
+ dictionaries."""
+ if self.lastCleanup <= time() - self.cleanupInterval:
+ self.cleanup()
+ if ob not in self._data:
+ self._data[ob] = OOBTree()
+ timestamp = time()
+ # [data, ctime, access count, lifetime, Invalidated]
+ self._data[ob][key] = PersistentList(
+ [value, timestamp, 0, lifetime, False])
+
+ def invalidate(self, ob, key=None):
+ """Drop the cached values.
+
+ Drop all the values for an object if no key is provided or
+ just one entry if the key is provided.
+ """
+ try:
+ if key is None:
+ del self._data[ob]
+ self._misses[ob] = 0
+ else:
+ del self._data[ob][key]
+ if not self._data[ob]:
+ del self._data[ob]
+ except KeyError:
+ pass
+
+ def invalidateAll(self):
+ """Drop all the cached values.
+ """
+ self._data = OOBTree()
+ self._misses = OOBTree()
+
+ def removeStaleEntries(self):
+ "Remove the entries older than `maxAge`"
+ punchline = time() - self.maxAge
+ data = self._data
+ for object, dict in data.items():
+ for ob, key in dict.items():
+ minTime = key[3][0]
+ lifetime = time() - key[1]
+ if lifetime < minTime:
+ # minimum lifetime not reached, do not remove it
+ continue
+ lifetime = time() - key[1]
+ if ( key[4]
+ or ( self.maxAge > 0
+ and key[1] < punchline
+ )
+ or ( key[3][1] is not None
+ and key[3][1] < lifetime
+ )
+ ):
+ # invalidation flag set or maxAge reached
+ del dict[ob]
+ if not dict:
+ del data[object]
+
+ def cleanup(self):
+ "Cleanup the data"
+ self.removeStaleEntries()
+ self.removeLeastAccessed()
+
+ def removeLeastAccessed(self):
+ ""
+ data = self._data
+ keys = [(ob, k) for ob, v in data.iteritems() for k in v]
+
+ if len(keys) > self.maxEntries:
+ def getKey(item):
+ ob, key = item
+ return data[ob][key]
+ sort([v for v in keys], key=getKey)
+
+ ob, key = keys[self.maxEntries]
+ maxDropCount = data[ob][key][2]
+
+ keys.reverse()
+
+ for ob, key in keys:
+ if data[ob][key][2] <= maxDropCount:
+ del data[ob][key]
+ if not data[ob]:
+ del data[ob]
+
+ self._clearAccessCounters()
+
+ def _clearAccessCounters(self):
+ for dict in self._data.itervalues():
+ for val in dict.itervalues():
+ val[2] = 0
+ for k in self._misses:
+ self._misses[k] = 0
+
+ def getKeys(self, object):
+ return self._data[object].keys()
+
+ def getStatistics(self):
+ "Basically see IRAMCache"
+ objects = list(self._data.keys())
+ objects.sort()
+ result = []
+
+ for ob in objects:
+ size = len(dumps(self._data[ob]))
+ hits = sum(entry[2] for entry in self._data[ob].itervalues())
+ result.append({'path': ob,
+ 'hits': hits,
+ 'misses': self._misses[ob],
+ 'size': size,
+ 'entries': len(self._data[ob])})
+ return tuple(result)
+
+ def getExtendedStatistics(self):
+ "Basically see IRAMCache"
+ result = []
+ for ob in self._getStatisticObjects():
+ # use min and maxage for first cache entry (one is always present)
+ minage = self._data[ob].values()[0][3][0] #damn complicating!
+ maxage = self._data[ob].values()[0][3][1] or self.maxAge #damn complicating!
+ #the size of all cached values of all subkeys as pickeled in zodb
+ totalsize = len(dumps(self._data[ob]))
+ deps = []
+ for dep in self._data.keys():
+ for cacheentry in self._data[dep].values():
+ if str(ob) in cacheentry[0]:
+ #dependency cache entries have a list of dependen objects in val[0]
+ deps.append(dep)
+ hits = sum(entry[2] for entry in self._data[ob].itervalues())
+ result.append({'path': ob,
+ 'key': None,
+ 'misses': self._misses.get(ob, 0),
+ 'size': totalsize,
+ 'entries': len(self._data[ob]),
+ 'hits': hits,
+ 'minage': minage,
+ 'maxage': maxage,
+ 'deps': deps,
+ 'keys': []})
+ pathObj = result[-1]
+
+ for key, value in self._data[ob].items():
+ if key is not None:
+ pathObj['keys'].append({'path': ob,
+ 'key': key,
+ 'misses': '',
+ 'size': len(dumps(value)),
+ 'entries': '',
+ 'hits': value[2],
+ 'minage': '',
+ 'maxage': '',
+ 'deps': None,
+ 'keys':[]})
+ return tuple(result)
+
+ def _getStatisticObjects(self):
+ return sorted(list(self._data.keys()))
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/zodb.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.viewcache/trunk/src/lovely/viewcache/zodb.txt
===================================================================
--- lovely.viewcache/trunk/src/lovely/viewcache/zodb.txt 2007-02-02 09:25:53 UTC (rev 72318)
+++ lovely.viewcache/trunk/src/lovely/viewcache/zodb.txt 2007-02-02 09:32:20 UTC (rev 72319)
@@ -0,0 +1,53 @@
+=========
+ViewCache
+=========
+
+The viewcache is an extended RAMCache, which can invalidate entries based
+on object-interfaces and intids.
+
+ >>> from lovely.viewcache.zodb import ViewCache
+ >>> viewCache = ViewCache('1')
+ >>> root['cache'] = viewCache
+
+
+It is possible to define dependencies on entries, which can be used to
+invalidate specific entries. If we don't specify dependencies the behaviour is
+the same as the normal RAMCache.
+
+ >>> viewCache.set('value1', 'object1', key={'x':1}, dependencies=[1])
+ >>> viewCache.set('value2', 'object2', dependencies=[2])
+ >>> viewCache.set('value3', 'object3', key={'y':1}, dependencies=[1, 2, 3])
+ >>> viewCache.query('object1', key={'x':1})
+ 'value1'
+ >>> viewCache.invalidate(dependencies=[1])
+ >>> viewCache.query('object3') is None
+ True
+ >>> viewCache.query('object1', key={'x':1}) is None
+ True
+ >>> viewCache.query('object2')
+ 'value2'
+ >>> viewCache.query('object3') is None
+ True
+
+Storage
+=======
+
+ >>> s = viewCache._getStorage()
+ >>> import random, time
+ >>> def w():
+ ... s.setEntry(random.randint(1,100), 'object1', None)
+ ... import transaction
+ ... time.sleep(random.randint(0,100)/100.0)
+ ... if random.randint(0,1):
+ ... s.invalidate('object1')
+ ... transaction.commit()
+ >>> import threading
+ >>> t = []
+ >>> for i in range(100):
+ ... thread = threading.Thread(target=w)
+ ... thread.start()
+ ... t.append(thread)
+ >>> for thread in t:
+ ... thread.join()
+
+
Property changes on: lovely.viewcache/trunk/src/lovely/viewcache/zodb.txt
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Checkins
mailing list