[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