[Zope3-checkins] CVS: Zope3/src/zope/app/observable - __init__.py:1.2 interfaces.py:1.2 observable.py:1.2 observers.py:1.2 observers.txt:1.2 tests.py:1.2

Nathan Yergler nathan at yergler.net
Tue Mar 30 09:14:15 EST 2004


Update of /cvs-repository/Zope3/src/zope/app/observable
In directory cvs.zope.org:/tmp/cvs-serv2053/src/zope/app/observable

Added Files:
	__init__.py interfaces.py observable.py observers.py 
	observers.txt tests.py 
Log Message:


Merging observable-branch.


=== Zope3/src/zope/app/observable/__init__.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 30 09:14:14 2004
+++ Zope3/src/zope/app/observable/__init__.py	Tue Mar 30 09:13:57 2004
@@ -0,0 +1,20 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Zope.app Observable package
+
+$Id$
+"""
+
+from interfaces import IObservable
+


=== Zope3/src/zope/app/observable/interfaces.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 30 09:14:14 2004
+++ Zope3/src/zope/app/observable/interfaces.py	Tue Mar 30 09:13:57 2004
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Observable interfaces
+
+$Id$
+"""
+
+from zope.interface import implements
+from zope.interface.interfaces import Interface, IInterface
+
+class IObservable(Interface):
+    
+    def notify(event, provided):
+        """Notify all subscribers that the event provided has occured."""
+
+    def subscribe(required, provided, subscriber):
+        """Subscribe to an event for a particular instance."""
+        


=== Zope3/src/zope/app/observable/observable.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 30 09:14:14 2004
+++ Zope3/src/zope/app/observable/observable.py	Tue Mar 30 09:13:57 2004
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Observable adapter
+
+$Id$
+"""
+from zope.interface import implements, providedBy
+from zope.app.observable.interfaces import IObservable
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.observable.observers import Observers
+
+key = 'zope.app.observable'
+
+class ObservableAdapter:
+
+    implements(IObservable)
+    
+    def __init__(self, context):
+        self.context = context
+
+    def subscribe(self, required, provided, subscriber):
+        annotations = IAnnotations(self.context)
+        registry = annotations.get(key)
+        
+        if registry is None:
+            annotations[key] = registry = Observers()
+
+        registry.subscribe(required, provided, subscriber)
+
+    def unsubscribe(self, required, provided, subscriber):
+        annotations = IAnnotations(self.context)
+        registry = annotations.get(key)
+        
+        if registry is not None:
+            # if there is no registry, we can't unsubscribe
+            registry.unsubscribe(required, provided, subscriber)
+        
+    def notify(self, event, provided):
+        annotations = IAnnotations(self.context)
+        registry = annotations.get(key)
+        
+        if registry is not None:
+            for subscriber in registry.subscriptions([providedBy(event)],
+                                                     provided):
+                subscriber.notify(event)
+
+        
+


=== Zope3/src/zope/app/observable/observers.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 30 09:14:14 2004
+++ Zope3/src/zope/app/observable/observers.py	Tue Mar 30 09:13:57 2004
@@ -0,0 +1,200 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Observer Registry
+
+Observers observe other objects by getting notified of object events
+on those objects. Observers subscribe to particular types of events.
+
+  >>> registry = Observers()
+
+  >>> import zope.interface
+  >>> class IR1(zope.interface.Interface):
+  ...     pass
+  >>> class IP1(zope.interface.Interface):
+  ...     pass
+  >>> class IP2(IP1):
+  ...     pass
+  >>> class IQ(zope.interface.Interface):
+  ...     pass
+
+  >>> registry.subscribe([IR1], IP2, 'sub12 1')
+  >>> registry.subscriptions([IR1], IP2)
+  ['sub12 1']
+
+You can have multiple subscribers for the same specification::
+
+  >>> registry.subscribe([IR1], IP2, 'sub12 2')
+  >>> subs = registry.subscriptions([IR1], IP2)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2']
+
+You can register subscribers for all specifications using None::
+
+  >>> class IR2(IR1):
+  ...     pass
+
+  >>> registry.subscribe([None], IP1, 'sub_1')
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2', 'sub_1']
+
+Subscriptions may be combined over multiple compatible specifications::
+
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2', 'sub_1']
+  >>> registry.subscribe([IR1], IP1, 'sub11')
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub11', 'sub12 1', 'sub12 2', 'sub_1']
+  >>> registry.subscribe([IR2], IP2, 'sub22')
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub11', 'sub12 1', 'sub12 2', 'sub22', 'sub_1']
+  >>> subs = registry.subscriptions([IR2], IP2)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2', 'sub22']
+
+Subscriptions can be on multiple specifications::
+
+  >>> class IQ(zope.interface.Interface):
+  ...     pass
+
+  >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2')
+  >>> registry.subscriptions([IR1, IQ], IP2)
+  ['sub1q2']
+  
+As with single subscriptions, you can specify None for the first
+required interface, to specify a default::
+
+  >>> registry.subscribe([None, IQ], IP2, 'sub_q2')
+
+  >>> class IS(zope.interface.Interface):
+  ...     pass
+
+  >>> registry.subscriptions([IS, IQ], IP2)
+  ['sub_q2']
+  >>> subs = registry.subscriptions([IR1, IQ], IP2)
+  >>> subs.sort()
+  >>> subs
+  ['sub1q2', 'sub_q2']
+
+You can unsubscribe:
+
+  >>> registry.unsubscribe([IR1], IP2, 'sub12 2')
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub11', 'sub12 1', 'sub22', 'sub_1']
+    
+  
+"""
+
+
+from persistent import Persistent
+from zope.interface.adapter import Default
+from zope.interface.adapter import Surrogate, AdapterRegistry
+
+class LocalSurrogate(Surrogate):
+    """Local surrogates
+
+    Local surrogates are transient, rather than persistent.
+
+    Their adapter data are stored in their registry objects.
+    """
+
+    def __init__(self, spec, registry):
+        Surrogate.__init__(self, spec, registry)
+        self.registry = registry
+
+    def clean(self):
+        spec = self.spec()
+        ladapters = self.registry.adapters.get(spec)
+        if ladapters:
+            self.adapters = dict(
+                [((True, required, '', provided), subs)
+                 for ((required, provided), subs) in ladapters.iteritems()
+                 ]
+                )
+        else:
+            self.adapters = {}
+
+        Surrogate.clean(self)
+
+class Observers(AdapterRegistry, Persistent):
+    """Local/persistent surrogate registry
+    """
+
+    
+    _surrogateClass = LocalSurrogate
+
+    def __init__(self):
+        self.adapters = {}
+        AdapterRegistry.__init__(self)
+
+    def __getstate__(self):
+        state = Persistent.__getstate__(self).copy()
+        del state['_surrogates']
+        del state['_default']
+        del state['_null']
+        del state['_remove']
+        return state
+
+    def __setstate__(self, state):
+        Persistent.__setstate__(self, state)
+        AdapterRegistry.__init__(self)
+
+    def subscribe(self, required, provided, subscriber):
+        if len(required) == 0:
+            raise ValueError("required can not be zero length")
+
+        akey = required[0]
+        if akey is None:
+            akey = Default
+        adapters = self.adapters.get(akey)
+        if not adapters:
+            adapters = self.adapters[akey] = {}
+        key = tuple(required[1:]), provided
+        adapters[key] = adapters.get(key, ()) + (subscriber, )
+
+        # reinitialize, thus clearing surrogates *and* marking as changed :)
+        AdapterRegistry.__init__(self)
+
+    def unsubscribe(self, required, provided, subscriber):
+        akey = required[0]
+        if akey is None:
+            akey = Default
+        adapters = self.adapters.get(akey)
+        if not adapters:
+            return
+
+        key = tuple(required[1:]), provided
+        subscribers = adapters.get(key, ())
+        if subscriber in subscribers:
+            subscribers = list(subscribers)
+            subscribers.remove(subscriber)
+            if subscribers:
+                adapters[key] = tuple(subscribers)
+            else:
+                del adapters[key]
+
+            # reinitialize, thus clearing surrogates *and* marking as changed
+            AdapterRegistry.__init__(self)
+            self._p_changed = True


=== Zope3/src/zope/app/observable/observers.txt 1.1 => 1.2 ===
--- /dev/null	Tue Mar 30 09:14:14 2004
+++ Zope3/src/zope/app/observable/observers.txt	Tue Mar 30 09:13:57 2004
@@ -0,0 +1,123 @@
+=================
+Observer Registry
+=================
+
+Observer registries provide a way to register observers that depend on
+one or more interface specifications and provide (perhaps indirectly)
+some interface.  
+
+The term "interface specification" refers both to interfaces and to
+interface declarations, such as declarations of interfaces implemented
+by a class.
+
+
+
+  >>> from zope.interface.adapter import AdapterRegistry
+  >>> import zope.interface
+
+  >>> class IR1(zope.interface.Interface):
+  ...     pass
+  >>> class IP1(zope.interface.Interface):
+  ...     pass
+  >>> class IP2(IP1):
+  ...     pass
+
+  >>> registry = AdapterRegistry()
+
+
+  >>> class IR2(IR1):
+  ...     pass
+  >>> registry.lookup([IR2], IP2, '')
+  12
+
+  >>> class C2:
+  ...     zope.interface.implements(IR2)
+
+
+
+  >>> class IP3(IP2):
+  ...     pass
+
+Normally, we want to look up an object that most-closely matches a
+specification.  Sometimes, we want to get all of the objects that
+match some specification.  We use subscriptions for this.  We
+subscribe objects against specifications and then later find all of
+the subscribed objects::
+
+  >>> registry.subscribe([IR1], IP2, 'sub12 1')
+  >>> registry.subscriptions([IR1], IP2)
+  ['sub12 1']
+
+Note that, unlike regular adapters, subscriptions are unnamed.
+
+The order of returned subscriptions is not specified.
+
+You can have multiple subscribers for the same specification::
+
+  >>> registry.subscribe([IR1], IP2, 'sub12 2')
+  >>> subs = registry.subscriptions([IR1], IP2)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2']
+
+You can register subscribers for all specifications using None::
+
+  >>> registry.subscribe([None], IP1, 'sub_1')
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2', 'sub_1']
+
+Subscriptions may be combined over multiple compatible specifications::
+
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2', 'sub_1']
+  >>> registry.subscribe([IR1], IP1, 'sub11')
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub11', 'sub12 1', 'sub12 2', 'sub_1']
+  >>> registry.subscribe([IR2], IP2, 'sub22')
+  >>> subs = registry.subscriptions([IR2], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub11', 'sub12 1', 'sub12 2', 'sub22', 'sub_1']
+  >>> subs = registry.subscriptions([IR2], IP2)
+  >>> subs.sort()
+  >>> subs
+  ['sub12 1', 'sub12 2', 'sub22']
+
+Subscriptions can be on multiple specifications::
+
+  >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2')
+  >>> registry.subscriptions([IR1, IQ], IP2)
+  ['sub1q2']
+  
+As with single subscriptions and non-subscription adapters, you can
+specify None for the first required interface, to specify a default::
+
+  >>> registry.subscribe([None, IQ], IP2, 'sub_q2')
+  >>> registry.subscriptions([IS, IQ], IP2)
+  ['sub_q2']
+  >>> subs = registry.subscriptions([IR1, IQ], IP2)
+  >>> subs.sort()
+  >>> subs
+  ['sub1q2', 'sub_q2']
+
+You can have subscriptions that are indepenent of any specifications::
+  
+  >>> registry.subscriptions([], IP1)
+  []
+
+  >>> registry.subscribe([], IP2, 'sub2')
+  >>> registry.subscriptions([], IP1)
+  ['sub2']
+  >>> registry.subscribe([], IP1, 'sub1')
+  >>> subs = registry.subscriptions([], IP1)
+  >>> subs.sort()
+  >>> subs
+  ['sub1', 'sub2']
+  >>> registry.subscriptions([], IP2)
+  ['sub2']


=== Zope3/src/zope/app/observable/tests.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 30 09:14:14 2004
+++ Zope3/src/zope/app/observable/tests.py	Tue Mar 30 09:13:57 2004
@@ -0,0 +1,114 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Tests for the Observable Adapter.
+
+$Id$
+"""
+
+import doctest
+import unittest
+
+from zope.interface import implements
+from zope.app.observable.observable import ObservableAdapter, key
+from zope.app.observable.interfaces import IObservable
+from zope.app.event.interfaces import ISubscriber
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.container.interfaces import IObjectAddedEvent
+
+class DummyAnnotationsClass(dict):
+    implements(IAnnotations)
+
+class DummySubscriber:
+
+    implements(ISubscriber)
+
+    def __init__(self):
+        self.events = []
+        
+    def notify(self, event):
+
+        self.events.append(event)
+
+class DummyEvent:
+    implements(IObjectAddedEvent)
+    
+def test_subscribe():
+    """
+    First create an annotatable object and an adapter
+    >>> obj = DummyAnnotationsClass()
+    >>> adapter = ObservableAdapter(obj)
+
+    Make a subscriber and make a faux subscription
+    >>> subscriber = DummySubscriber()
+    >>> adapter.subscribe([IObjectAddedEvent], ISubscriber, subscriber)
+
+    Make sure an ObjectAdapterRegistry was created
+    >>> obj[key] is not None
+    True
+
+    Make sure the registry contains a subscription for the correct event
+    >>> IObjectAddedEvent in obj[key].adapters
+    True
+
+    """
+
+def test_unsubscribe():
+    """
+    First create an annotatable object and an adapter
+    >>> obj = DummyAnnotationsClass()
+    >>> adapter = ObservableAdapter(obj)
+
+    Make a subscriber and make a faux subscription
+    >>> subscriber = DummySubscriber()
+    >>> adapter.subscribe([IObjectAddedEvent], ISubscriber, subscriber)
+
+    Now unsubscribe from the registry
+    >>> adapter.unsubscribe([IObjectAddedEvent], ISubscriber, subscriber)
+
+    There should be no subscribers for IObjectAddedEvent after unsubscription.
+    >>> obj[key].adapters[IObjectAddedEvent]
+    {}
+    """
+
+def test_notify():
+    """
+    First create an annotatable object and an adapter
+    >>> obj = DummyAnnotationsClass()
+    >>> adapter = ObservableAdapter(obj)
+
+    Make a subscriber and make a faux subscription
+    >>> subscriber = DummySubscriber()
+    >>> adapter.subscribe([IObjectAddedEvent], ISubscriber, subscriber)
+
+    Make sure an ObjectAdapterRegistry was created
+    >>> obj[key] is not None
+    True
+
+    Call notify
+    >>> event = DummyEvent()
+    >>> adapter.notify(event, ISubscriber)
+    >>> subscriber.events == [event]
+    True
+    """
+
+def test_suite():
+    import sys
+    return unittest.TestSuite((
+        doctest.DocTestSuite(),
+        doctest.DocTestSuite('zope.app.observable.observers'),
+        ))
+
+if __name__ == '__main__':
+    test_suite()
+    




More information about the Zope3-Checkins mailing list