[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