[Checkins] SVN: zope.component/branches/wosc-test-stacking/ Introduce "stackable variables" that provide a way to say "this state is current now", so we get semantics like the DemoStorage-wrapping on a lower level, which saves a lot of headache of dealing with registries and site hooks and so on.

Wolfgang Schnerring wosc at wosc.de
Thu May 12 03:39:16 EDT 2011


Log message for revision 121657:
  Introduce "stackable variables" that provide a way to say "this state is current now", so we get semantics like the DemoStorage-wrapping on a lower level, which saves a lot of headache of dealing with registries and site hooks and so on.
  
  This is a checkpoint so we don't lose the work we did, so it is incomplete (and a little rough around the edges).
  

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

-=-
Added: zope.component/branches/wosc-test-stacking/NOTES.txt
===================================================================
--- zope.component/branches/wosc-test-stacking/NOTES.txt	                        (rev 0)
+++ zope.component/branches/wosc-test-stacking/NOTES.txt	2011-05-12 07:39:15 UTC (rev 121657)
@@ -0,0 +1,37 @@
+TODO:
+
+
+- Tests for zope.component-Stacking
+  Registry-Object: new registration, delete, view old, bases chain
+  getGlobalSiteManager()
+  getSiteManager / setSite
+
+    - Do we need to make cache contents (in AdapterRegistry/Lookup) stackable?
+      aka, do list-like lookup results work or are they broken by push/pop?
+      Example-test:
+      register subscriber
+      A = subscribers
+      push
+      register some more subscribers
+      B = subscribers
+      pop
+      C = subscribers
+      assert C == A
+      assert len(B) > len(C)
+
+    - Do we have to make _v_subregistries stackable?
+
+
+- zope.interface uses C-code for some parts. Is it a performance problem that
+  'stackable' is Python-code? (This should only apply to tests, since we're
+  going to want to create a way to bypass the stackable stuff)
+
+* Stackable:
+- Needs a better name
+- Create separate egg?
+
+- 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 since we never unregister stackables?

Modified: zope.component/branches/wosc-test-stacking/src/zope/component/registry.py
===================================================================
--- zope.component/branches/wosc-test-stacking/src/zope/component/registry.py	2011-05-12 07:22:17 UTC (rev 121656)
+++ zope.component/branches/wosc-test-stacking/src/zope/component/registry.py	2011-05-12 07:39:15 UTC (rev 121657)
@@ -24,6 +24,7 @@
 from zope.interface.adapter import AdapterRegistry
 from zope.interface.interfaces import ISpecification
 
+from zope.component.stackable import stackable
 from zope.component.interfaces import ComponentLookupError
 from zope.component.interfaces import IAdapterRegistration
 from zope.component.interfaces import IComponents
@@ -56,10 +57,10 @@
         self.utilities = AdapterRegistry()
 
     def _init_registrations(self):
-        self._utility_registrations = {}
-        self._adapter_registrations = {}
-        self._subscription_registrations = []
-        self._handler_registrations = []
+        self._utility_registrations = stackable({})
+        self._adapter_registrations = stackable({})
+        self._subscription_registrations = stackable([])
+        self._handler_registrations = stackable([])
 
     def _getBases(self):
         # Subclasses might override

Added: zope.component/branches/wosc-test-stacking/src/zope/component/stackable.py
===================================================================
--- zope.component/branches/wosc-test-stacking/src/zope/component/stackable.py	                        (rev 0)
+++ zope.component/branches/wosc-test-stacking/src/zope/component/stackable.py	2011-05-12 07:39:15 UTC (rev 121657)
@@ -0,0 +1,82 @@
+import copy
+import inspect
+
+
+def delegate(name):
+    def inner(self, *args, **kw):
+        return getattr(self.stack[-1], name)(*args, **kw)
+    return inner
+
+
+def stackable(original):
+    type_ = type(original)
+
+    class inner(object):
+
+        for name in dict(
+            inspect.getmembers(type_, predicate=inspect.ismethoddescriptor)):
+            if name in ['__getattribute__', '__setattr__']:
+                continue
+            locals()[name] = delegate(name)
+
+        def __init__(self, original):
+            self.stack = [original]
+
+        def push(self):
+            self.stack.append(copy.copy(self.stack[-1]))
+
+        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]
+
+    stack = inner(original)
+    REGISTRY.register(stack)
+    return stack
+
+
+class Registry(object):
+
+    def __init__(self):
+        self.listeners = []
+
+    def register(self, obj):
+        self.listeners.append(obj)
+
+    def push(self):
+        for obj in self.listeners:
+            obj.push()
+
+    def pop(self):
+        for i, obj in reversed(list(enumerate(self.listeners))):
+            if obj.can_pop():
+                obj.pop()
+            else:
+                # obj was created on this "push level", which means its
+                # lifetime is now over (since its parent container will pop)
+                del self.listeners[i]
+
+    def reset(self):
+        for obj in self.listeners:
+            obj.reset()
+
+REGISTRY = Registry()
+
+
+def push():
+    REGISTRY.push()
+
+
+def pop():
+    REGISTRY.pop()
+
+
+def reset():
+    REGISTRY.reset()

Modified: zope.component/branches/wosc-test-stacking/src/zope/component/tests.py
===================================================================
--- zope.component/branches/wosc-test-stacking/src/zope/component/tests.py	2011-05-12 07:22:17 UTC (rev 121656)
+++ zope.component/branches/wosc-test-stacking/src/zope/component/tests.py	2011-05-12 07:39:15 UTC (rev 121657)
@@ -34,6 +34,7 @@
 from zope.component.testing import setUp, tearDown, PlacelessSetup
 import zope.component.persistentregistry
 import zope.component.globalregistry
+import zope.component.stackable
 
 from zope.configuration.xmlconfig import XMLConfig, xmlconfig
 from zope.configuration.exceptions import ConfigurationError
@@ -1681,6 +1682,88 @@
         reload(zope.component.zcml)
 
 
+class StackableTests(unittest.TestCase):
+
+    def test_should_be_transparent_dict(self):
+        orig = {1: 2}
+        stack = zope.component.stackable.stackable(orig)
+        stack[3] = 4
+        self.assertEqual(2, stack[1])
+        self.assertEqual(4, stack[3])
+        del stack[1]
+        self.assertRaises(KeyError, lambda: stack[1])
+
+    def test_should_be_transparent_list(self):
+        orig = [1]
+        stack = zope.component.stackable.stackable(orig)
+        stack.append(2)
+        self.assertEqual(1, stack[0])
+        self.assertEqual(2, stack[1])
+        self.assertRaises(IndexError, lambda: stack[2])
+        del stack[1]
+        self.assertRaises(IndexError, lambda: stack[1])
+
+    def test_push_creates_new_copy(self):
+        orig = [1]
+        stack = zope.component.stackable.stackable(orig)
+        zope.component.stackable.push()
+        stack.append(2)
+        self.assertEqual([1, 2], stack)
+        self.assertEqual([1], orig)
+        zope.component.stackable.pop()
+        self.assertEqual([1], stack)
+        self.assertEqual([1], orig)
+
+    def test_reset_removes_all_copies_and_leaves_the_original(self):
+        orig = [1]
+        stack = zope.component.stackable.stackable(orig)
+        zope.component.stackable.push()
+        stack.append(2)
+        zope.component.stackable.reset()
+        self.assertEqual([1], stack)
+
+
+from zope.interface import Interface
+
+
+class ZCAStackingTests(unittest.TestCase):
+
+    def tearDown(self):
+        zope.component.stackable.reset()
+
+    def test_after_push_old_registrations_are_still_visible(self):
+        registry = zope.component.registry.Components()
+        registry.registerUtility('foo', Interface)
+        self.assertEqual('foo', registry.getUtility(Interface))
+
+        zope.component.stackable.push()
+        self.assertEqual('foo', registry.getUtility(Interface))
+
+    def test_after_push_new_registration_can_be_added(self):
+        registry = zope.component.registry.Components()
+        zope.component.stackable.push()
+        registry.registerUtility('bar', Interface)
+        self.assertEqual('bar', registry.getUtility(Interface))
+
+    def test_pop_restores_previous_registrations(self):
+        registry = zope.component.registry.Components()
+        registry.registerUtility('foo', Interface)
+        zope.component.stackable.push()
+        registry.registerUtility('bar', Interface)
+        self.assertEqual('bar', registry.getUtility(Interface))
+        zope.component.stackable.pop()
+        self.assertEqual('foo', registry.getUtility(Interface))
+
+    def test_pop_restores_deleted_registrations(self):
+        registry = zope.component.registry.Components()
+        registry.registerUtility('foo', Interface)
+        zope.component.stackable.push()
+        registry.unregisterUtility('foo', Interface)
+        self.assertEqual(None, registry.queryUtility(Interface))
+        zope.component.stackable.pop()
+        self.assertEqual('foo', registry.getUtility(Interface))
+
+
 def setUpRegistryTests(tests):
     setUp()
 
@@ -1739,6 +1822,8 @@
         hooks_conditional,
         unittest.makeSuite(StandaloneTests),
         unittest.makeSuite(ResourceViewTests),
+        unittest.makeSuite(StackableTests),
+        unittest.makeSuite(ZCAStackingTests),
         ))
 
 if __name__ == "__main__":



More information about the checkins mailing list