[Checkins] SVN: lovely.memcached/trunk/src/lovely/memcached/ Added an event handler for invalidation events.

Jürgen Kartnaller juergen at kartnaller.at
Tue May 8 07:03:50 EDT 2007


Log message for revision 75621:
  Added an event handler for invalidation events.
  Added a memcache client implementation for testing, this client doesn't need
  a running memcached instance.
  

Changed:
  U   lovely.memcached/trunk/src/lovely/memcached/README.txt
  U   lovely.memcached/trunk/src/lovely/memcached/browser/README.txt
  U   lovely.memcached/trunk/src/lovely/memcached/configure.zcml
  A   lovely.memcached/trunk/src/lovely/memcached/event.py
  U   lovely.memcached/trunk/src/lovely/memcached/interfaces.py
  A   lovely.memcached/trunk/src/lovely/memcached/testing/
  A   lovely.memcached/trunk/src/lovely/memcached/testing/README.txt
  A   lovely.memcached/trunk/src/lovely/memcached/testing/__init__.py
  A   lovely.memcached/trunk/src/lovely/memcached/testing/memcache.py
  U   lovely.memcached/trunk/src/lovely/memcached/tests.py
  U   lovely.memcached/trunk/src/lovely/memcached/utility.py

-=-
Modified: lovely.memcached/trunk/src/lovely/memcached/README.txt
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/README.txt	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/README.txt	2007-05-08 11:03:48 UTC (rev 75621)
@@ -253,3 +253,52 @@
   >>> util.set('notStored', 'ignored') is None
   True
 
+
+Invalidationevents
+==================
+
+Events can be used to create invalidations. The event handler invalidates in
+registered memcached utilities.
+
+  >>> from zope import component
+  >>> from lovely.memcached.interfaces import IMemcachedClient
+  >>> cacheUtil1 = MemcachedClient()
+  >>> component.provideUtility(cacheUtil1, IMemcachedClient, name='cacheUtil1')
+  >>> cacheUtil1.set('Value1', 'key1', dependencies=['dep1'])
+  '19192ccdbb8267c35b9bdaf2f1f5594b'
+
+  >>> cacheUtil1.query('key1')
+  'Value1'
+
+  >>> from lovely.memcached.event import InvalidateCacheEvent
+  >>> ev = InvalidateCacheEvent(dependencies=['dep1'])
+
+  >>> from lovely.memcached.event import invalidateCache
+  >>> invalidateCache(ev)
+  >>> cacheUtil1.query('key1') is None
+  True
+
+With more than one memcache utility we can invalidate in all utilities.
+
+  >>> from lovely.memcached.testing import TestMemcachedClient
+  >>> cacheUtil2 = TestMemcachedClient()
+  >>> component.provideUtility(cacheUtil2, IMemcachedClient, name='cacheUtil2')
+  >>> key = cacheUtil1.set('Value1', 'key1', dependencies=['dep1'])
+  >>> key = cacheUtil2.set('Value2', 'key2', dependencies=['dep1'])
+  >>> invalidateCache(InvalidateCacheEvent(dependencies=['dep1']))
+  >>> cacheUtil1.query('key1') is None
+  True
+  >>> cacheUtil2.query('key2') is None
+  True
+
+Or we specify in which memcache we want to invalidate.
+
+  >>> key = cacheUtil1.set('Value1', 'key1', dependencies=['dep1'])
+  >>> key = cacheUtil2.set('Value2', 'key2', dependencies=['dep1'])
+  >>> invalidateCache(InvalidateCacheEvent(cacheName='cacheUtil1',
+  ...                                      dependencies=['dep1']))
+  >>> cacheUtil1.query('key1') is None
+  True
+  >>> cacheUtil2.query('key2') is None
+  False
+

Modified: lovely.memcached/trunk/src/lovely/memcached/browser/README.txt
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/browser/README.txt	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/browser/README.txt	2007-05-08 11:03:48 UTC (rev 75621)
@@ -1,6 +1,6 @@
-==============================
-Memcached utilit browser views
-==============================
+===============================
+Memcached utility browser views
+===============================
 
 Let us add a memcached utility.
 

Modified: lovely.memcached/trunk/src/lovely/memcached/configure.zcml
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/configure.zcml	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/configure.zcml	2007-05-08 11:03:48 UTC (rev 75621)
@@ -23,6 +23,8 @@
   <adapter factory=".configurator.SetUpMemcachedClient"
            zcml:condition="installed z3c.configurator"
            name="lovely.memcachedclient"/>
+
+  <subscriber handler=".event.invalidateCache"/>
   
   <include package=".browser"/>
   

Added: lovely.memcached/trunk/src/lovely/memcached/event.py
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/event.py	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/event.py	2007-05-08 11:03:48 UTC (rev 75621)
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# 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 interfaces import IInvalidateCacheEvent, IMemcachedClient
+
+
+class InvalidateCacheEvent(object):
+    interface.implements(IInvalidateCacheEvent)
+
+    def __init__(self,
+            cacheName=None, key=None, ns=None, raw=False, dependencies=[]):
+        self.cacheName = cacheName
+        self.key = key
+        self.ns = ns
+        self.raw = raw
+        self.dependencies = dependencies
+
+
+ at component.adapter(IInvalidateCacheEvent)
+def invalidateCache(event):
+    if event.cacheName is not None:
+        cache = component.queryUtility(IMemcachedClient, event.cacheName)
+        caches = []
+        if cache is not None:
+            caches.append(cache)
+    else:
+        caches = component.getAllUtilitiesRegisteredFor(IMemcachedClient)
+    for cache in caches:
+        cache.invalidate(event.key, event.ns, event.raw, event.dependencies)
+


Property changes on: lovely.memcached/trunk/src/lovely/memcached/event.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: lovely.memcached/trunk/src/lovely/memcached/interfaces.py
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/interfaces.py	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/interfaces.py	2007-05-08 11:03:48 UTC (rev 75621)
@@ -20,7 +20,7 @@
 from zope import schema
 
 class IMemcachedClient(interface.Interface):
-    
+
     """A memcache client utility"""
 
     defaultNS = schema.TextLine(
@@ -28,7 +28,7 @@
         description=u"The default namespace used by this client",
         required=False,
         default=None)
-        
+
     servers = schema.List(
         title = u'Servers',
         description = u"Servers defined as <hostname>:<port>",
@@ -36,7 +36,7 @@
         required = True,
         default=['127.0.0.1:11211']
         )
-    
+
     defaultLifetime = schema.Int(
         title = u'Default Lifetime',
         description = u'The default lifetime of entries',
@@ -74,7 +74,7 @@
     def query(key, default=None, ns=None, raw=False):
         """query the cache for key in namespace, returns default if
         not found. ns defaults to default namespace."""
-        
+
     def invalidate(key=None, ns=None, raw=False, dependencies=[]):
         """invalidates key in namespace which defaults to default
         namespace, currently we can not invalidate just a namespace.
@@ -90,4 +90,38 @@
     def keys(ns=None):
         """if trackKeys is True, returns the keys defined in the
         namespace"""
-        
+
+
+class IInvalidateCacheEvent(interface.Interface):
+    """An event which invalidates cache entries."""
+
+    cacheName = schema.TextLine(
+            title = u'cacheName',
+            description = u"""
+                Invalidate in the cache with this name.
+                If no name is given all caches are invalidated.
+                """,
+            required = False,
+            )
+
+    key = schema.TextLine(
+            title = u'key',
+            required = False,
+            )
+
+    ns = schema.TextLine(
+            title = u'namespace',
+            required = False,
+            )
+
+    raw = schema.Bool(
+            title = u'raw',
+            required = False,
+            default = False,
+            )
+
+    dependencies = schema.List(
+            title = u'Dependencies',
+            required = False,
+            )
+

Added: lovely.memcached/trunk/src/lovely/memcached/testing/README.txt
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/testing/README.txt	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/testing/README.txt	2007-05-08 11:03:48 UTC (rev 75621)
@@ -0,0 +1,48 @@
+=====================
+Memcached Test Client
+=====================
+
+For testing we provide a memcache implementation which doesn't need a running
+memcache daemon.
+
+  >>> from lovely.memcached.testing import TestMemcachedClient
+  >>> cache = TestMemcachedClient()
+  >>> cache.set('value', 'key')
+  '613fb124907164bf8f0b04beb02cf59e'
+  >>> cache.query('key')
+  'value'
+  >>> cache.invalidate(key='key')
+  >>> cache.query('key') is None
+  True
+
+Also the lifetime is handled.
+
+  >>> cache.set('value', 'key', 1)
+  '613fb124907164bf8f0b04beb02cf59e'
+  >>> cache.query('key')
+  'value'
+  >>> from time import sleep
+  >>> sleep(1)
+  >>> cache.query('key') is None
+  True
+
+The TestMemcachedClient does it's best to simulate the behaviour of the
+original memcached implementation.
+
+  >>> from lovely.memcached.testing.memcache import SimulatedMemcached
+  >>> client = SimulatedMemcached()
+  >>> client.set(u'\xfckey', 'value', 1)
+  Traceback (most recent call last):
+  ...
+  UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 0: ordinal not in range(128)
+
+  >>> client.set('key', u'\xfcvalue', 1)
+  Traceback (most recent call last):
+  ...
+  UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 0: ordinal not in range(128)
+
+  >>> client.get(u'\xfckey')
+  Traceback (most recent call last):
+  ...
+  UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 0: ordinal not in range(128)
+


Property changes on: lovely.memcached/trunk/src/lovely/memcached/testing/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.memcached/trunk/src/lovely/memcached/testing/__init__.py
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/testing/__init__.py	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/testing/__init__.py	2007-05-08 11:03:48 UTC (rev 75621)
@@ -0,0 +1,20 @@
+##############################################################################
+#
+# Copyright (c) 2007 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 memcache import TestMemcachedClient
+


Property changes on: lovely.memcached/trunk/src/lovely/memcached/testing/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.memcached/trunk/src/lovely/memcached/testing/memcache.py
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/testing/memcache.py	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/testing/memcache.py	2007-05-08 11:03:48 UTC (rev 75621)
@@ -0,0 +1,66 @@
+##############################################################################
+#
+# Copyright (c) 2007 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 datetime import datetime, timedelta
+
+from lovely.memcached.utility import MemcachedClient
+
+
+class TestMemcachedClient(MemcachedClient):
+    """A memcache client which doesn't need a running memcache daemon"""
+
+    def _instantiateClient(self, debug):
+        return SimulatedMemcached()
+
+
+class SimulatedMemcached(object):
+
+    def __init__(self):
+        self.cache = {}
+
+    def getStats(self):
+        return []
+
+    def set(self, key, data, lifetime=0):
+        # raise an error if not a string
+        str(key)
+        str(data)
+        if lifetime:
+            lifetime = datetime.now()+timedelta(seconds=lifetime)
+        else:
+            lifetime = None
+        self.cache[key] = (data, lifetime)
+        return True
+
+    def get(self, key):
+        str(key)
+        data = self.cache.get(key, None)
+        if data is None:
+            return None
+        if data[1] is None or datetime.now()<data[1]:
+            return data[0]
+        del self.cache[key]
+        return None
+
+    def delete(self, key):
+        if key in self.cache:
+            del self.cache[key]
+
+    def flush_all(self):
+        self.cache = {}
+


Property changes on: lovely.memcached/trunk/src/lovely/memcached/testing/memcache.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: lovely.memcached/trunk/src/lovely/memcached/tests.py
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/tests.py	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/tests.py	2007-05-08 11:03:48 UTC (rev 75621)
@@ -22,15 +22,19 @@
 
 def test_suite():
     level1Suites = (
+        DocFileSuite(
+            'testing/README.txt',
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+        ),
         DocTestSuite(
-        'lovely.memcached.utility',
-        optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            'lovely.memcached.utility',
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
         ),
         )
     level2Suites = (
         DocFileSuite(
-        'README.txt',
-        optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            'README.txt',
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
         ),
         )
     for suite in level2Suites:

Modified: lovely.memcached/trunk/src/lovely/memcached/utility.py
===================================================================
--- lovely.memcached/trunk/src/lovely/memcached/utility.py	2007-05-08 08:15:40 UTC (rev 75620)
+++ lovely.memcached/trunk/src/lovely/memcached/utility.py	2007-05-08 11:03:48 UTC (rev 75621)
@@ -124,8 +124,8 @@
         for dep in dependencies:
             depKey = self._buildDepKey(dep, ns)
             keys = self.client.get(depKey)
-            self.invalidate(depKey)
             if keys is not None:
+                self.invalidate(depKey)
                 for key in keys:
                     self.client.delete(key)
 
@@ -203,7 +203,7 @@
             self.storage.servers = self.servers
         client = getattr(self.storage, 'client', None)
         if client is None:
-            client = memcache.Client(self.servers, debug=0)
+            client = self._instantiateClient(debug=0)
             self.storage.client = client
         return client
 
@@ -217,6 +217,8 @@
             self._keysInit(self._v_storage)
         return self._v_storage
 
+    def _instantiateClient(self, debug):
+        return memcache.Client(self.servers, debug=debug)
 
     def _keysInit(self, storage):
         storage.keys = {}
@@ -228,7 +230,6 @@
             clients.add(storage.uid)
             self.set(clients, 'clients', lifetime=0, ns=NS)
 
-
     def _keysSet(self, key, ns, lifetime):
         """track a key"""
         if not self.trackKeys or ns in (NS, STAMP_NS): return



More information about the Checkins mailing list