[Checkins] SVN: zope.component/branches/wosc-test-stacking/ Create stackable classes in advance so they are picklable

Wolfgang Schnerring wosc at wosc.de
Thu Jun 9 07:02:52 EDT 2011


Log message for revision 121898:
  Create stackable classes in advance so they are picklable
  

Changed:
  U   zope.component/branches/wosc-test-stacking/NOTES.txt
  U   zope.component/branches/wosc-test-stacking/src/zope/component/stackable.py
  U   zope.component/branches/wosc-test-stacking/src/zope/component/tests.py

-=-
Modified: zope.component/branches/wosc-test-stacking/NOTES.txt
===================================================================
--- zope.component/branches/wosc-test-stacking/NOTES.txt	2011-06-08 18:09:31 UTC (rev 121897)
+++ zope.component/branches/wosc-test-stacking/NOTES.txt	2011-06-09 11:02:51 UTC (rev 121898)
@@ -3,6 +3,9 @@
 
 - persisting/pickling stackable registries
 
+- Name StackableBase methods with a prefix so they don't conflict with
+  delegated methods (e.g. pop).
+
 - Tests for zope.component-Stacking: can we run the existing tests on a push
   and then pop "level"? (Maybe use another existing application as a
   cross-check, too).
@@ -17,9 +20,6 @@
 
 - stackable.reset() needs to take into account "dying" stackables
 
-- don't create a new class object for each stackable() call
-- prettier class name, repr, etc.
-
 - have a name or "stack context", so you can say push('zope.component')
 - do we leak memory regarding unregistering of stackables?
 

Modified: zope.component/branches/wosc-test-stacking/src/zope/component/stackable.py
===================================================================
--- zope.component/branches/wosc-test-stacking/src/zope/component/stackable.py	2011-06-08 18:09:31 UTC (rev 121897)
+++ zope.component/branches/wosc-test-stacking/src/zope/component/stackable.py	2011-06-09 11:02:51 UTC (rev 121898)
@@ -2,42 +2,71 @@
 import inspect
 
 
-def delegate(name):
-    def inner(self, *args, **kw):
-        return getattr(self.stack[-1], name)(*args, **kw)
-    return inner
+class StackableBase(object):
 
+    def __init__(self, original):
+        self.stack = [original]
 
-def stackable(original):
-    type_ = type(original)
+    def push(self):
+        self.stack.append(copy.copy(self.stack[-1]))
 
-    class inner(object):
+    def pop(self):
+        self.stack.pop()
 
+    def can_pop(self):
+        return len(self.stack) > 1
+
+    def reset(self):
+        del self.stack[1:]
+
+    def __repr__(self):
+        return 'stackable:%r' % self.stack[-1]
+
+    def __reduce_ex__(self, protocol):
+        return (self.__class__, (), dict(stack=self.stack))
+
+    @classmethod
+    def delegate(cls, name):
+        def inner(self, *args, **kw):
+            return getattr(self.stack[-1], name)(*args, **kw)
+        return inner
+
+    @classmethod
+    def create_for(cls, type_):
+        exclude_methods = ['__getattribute__', '__setattr__']
+        overridden_methods = dict(
+            inspect.getmembers(cls, predicate=inspect.ismethod)).keys()
+        exclude_methods.extend(overridden_methods)
+
+        copied_methods = {}
+        # XXX ismethoddescriptor is correct for type_ in [dict, list], but
+        # probably not in general
         for name in dict(
             inspect.getmembers(type_, predicate=inspect.ismethoddescriptor)):
-            if name in ['__getattribute__', '__setattr__']:
+            if name in exclude_methods:
                 continue
-            locals()[name] = delegate(name)
+            copied_methods[name] = cls.delegate(name)
 
-        def __init__(self, original):
-            self.stack = [original]
+        return type(
+            'Stackable%s' % type_.__name__.title(), (cls,), copied_methods)
 
-        def push(self):
-            self.stack.append(copy.copy(self.stack[-1]))
 
-        def pop(self):
-            self.stack.pop()
+SUPPORTED_TYPES = dict(
+    (type_, StackableBase.create_for(type_)) for type_ in [dict, list])
 
-        def can_pop(self):
-            return len(self.stack) > 1
+for type_ in SUPPORTED_TYPES.values():
+    locals()[type_.__name__] = type_
 
-        def reset(self):
-            del self.stack[1:]
 
-        def __repr__(self):
-            return 'stackable:%r' % self.stack[-1]
+def stackable(original):
+    type_ = type(original)
 
-    stack = inner(original)
+    try:
+        factory = SUPPORTED_TYPES[type_]
+    except KeyError:
+        raise TypeError('%r is not stackable' % type_)
+
+    stack = factory(original)
     REGISTRY.register(stack)
     return stack
 

Modified: zope.component/branches/wosc-test-stacking/src/zope/component/tests.py
===================================================================
--- zope.component/branches/wosc-test-stacking/src/zope/component/tests.py	2011-06-08 18:09:31 UTC (rev 121897)
+++ zope.component/branches/wosc-test-stacking/src/zope/component/tests.py	2011-06-09 11:02:51 UTC (rev 121898)
@@ -1722,7 +1722,23 @@
         zope.component.stackable.reset()
         self.assertEqual([1], stack)
 
+    def test_is_persistable(self):
+        import tempfile
+        import ZODB
+        tmpfile = tempfile.NamedTemporaryFile(delete=True)
+        root = ZODB.DB(tmpfile.name).open().root()
+        orig = [1]
+        stack = zope.component.stackable.stackable(orig)
+        root['stack'] = stack
+        zope.component.stackable.push()
+        stack.append(2)
+        transaction.commit()
+        stack = root['stack']
+        self.assertEqual([1, 2], stack)
+        zope.component.stackable.pop()
+        self.assertEqual([1], stack)
 
+
 from zope.interface import Interface
 
 



More information about the checkins mailing list