[Zope-Checkins] CVS: Zope3/lib/python/Zope/Event - ISubscriptionAware.py:1.1.2.1 Subscribable.py:1.1.2.1 EventChannel.py:1.1.2.7 GlobalEventService.py:1.1.2.2 ISubscribable.py:1.1.2.6 ISubscriber.py:1.1.2.6 __init__.py:1.1.2.7 event.zcml:1.1.2.3 metaConfigure.py:1.1.2.5

Gary Poster garyposter@earthlink.net
Fri, 17 May 2002 13:16:17 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/Event
In directory cvs.zope.org:/tmp/cvs-serv15153/Event

Modified Files:
      Tag: Zope-3x-branch
	EventChannel.py GlobalEventService.py ISubscribable.py 
	ISubscriber.py __init__.py event.zcml metaConfigure.py 
Added Files:
      Tag: Zope-3x-branch
	ISubscriptionAware.py Subscribable.py 
Log Message:
1) Fixed some of my own (wrapping) errors in CA and Traversal
2) added IBindingAware interface for services that need to know when they are bound and unbound to a local service manager
3) in subscribables, made event_types into event_type (YAGNI)
4) in subscribables, added ability to unsubscribe per subscription, not just whole-hog
5) in subscribables, added ability to query subscribable for subscriptions of a given subscriber
6) made ISubscribable extremely verbose, to hopefully describe exactly what's going on (see it for more smaller changes)
7) added ISubscriptionAware interface for objects that need to know when they have been subscribed and unsubscribed
8) built out local event service; once there are some views and once I have some more robust tests this *prototype* should by ready to fly.  Note that it is currently a standard service that can be added to any service manager, and thus the design does attempt to address many of the nested service manager issues.
9) as part of this, created the first indirect subscriber (all placeful
subscriptions will usually need to use indirect subscribers in order to retain their context when events are fired): PathSubscriber
10) removed an endless loop between local service managers and  ZopeSecurityPolicy in which both needed the other to function: ZopeSecurityPolicy now explicitly asks for adapters from the global service manager
11) unintentionally retained some of the "default=ComponentLookupError"-type argument signatures from my sandbox, but only within Container and Folder; I won't worry about undoing it though (unless I am otherwise requested) since it seems these interfaces are due for a dict-like overhaul anyway.

Also, if anyone needs a local event service setup for other tests (like the ObjectHub, for instance) see the LocalEventService/tests/EventSetup.py

more tests on the way for the local event service, and for the changes to the subscribable interface



=== Added File Zope3/lib/python/Zope/Event/ISubscriptionAware.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 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.
# 
##############################################################################
"""

Revision information:
$Id: ISubscriptionAware.py,v 1.1.2.1 2002/05/17 17:15:46 poster Exp $
"""
from Interface import Interface

# these are calls and not events because they are traditional messages
# between two objects, not events of general interest.

class ISubscriptionAware(Interface):
    
    def subscribedTo(subscribable, event_type, filter):
        """alerts the object that it has subscribed, via a call from
        itself or from another object, to the subscribable.  The
        event_type and filter match the arguments provided to the
        ISubscribable.subscribe.
        
        The subscribable must be appropriately placefully wrapped (note
        that the global event service will have no wrapping)."""
    
    def unsubscribedFrom(subscribable, event_type, filter):
        """alerts the object that it has unsubscribed, via a call from
        itself or from another object, to the subscribable.  The
        event_type and filter match the exact event_type and filter of
        the deleted subscription, rather than, necessarily, the
        arguments provided to the ISubscribable.unsubscribe.
        
        The subscribable must be appropriately placefully wrapped (note
        that the global event service will have no wrapping)."""

=== Added File Zope3/lib/python/Zope/Event/Subscribable.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 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.
# 
##############################################################################
"""

Revision information:
$Id: Subscribable.py,v 1.1.2.1 2002/05/17 17:15:46 poster Exp $
"""

from Zope.ComponentArchitecture.IToIRegistry import TypeRegistry
from Zope.Exceptions import NotFoundError
from ISubscribable import ISubscribable
from ISubscriptionAware import ISubscriptionAware
from IEvent import IEvent
from Zope.Proxy.ProxyIntrospection import removeAllProxies

class Subscribable(object): # do we need this to be a type?
    """a global mix-in"""
    
    __implements__=ISubscribable

    def __init__(self):
        self._registry = TypeRegistry()
        self._subscribers = [] # using an array rather than a dict so
        # that subscribers may define custom __eq__ methods
        
    _clear = __init__

    def subscribe(self, subscriber, event_type=IEvent, filter=None):
        
        clean_subscriber=removeAllProxies(subscriber)
        
        if ISubscriptionAware.isImplementedBy(subscriber):
            subscriber.subscribedTo(self, event_type, filter)
        
        ev_type=event_type
        if ev_type is IEvent: ev_type=None # optimization
        
        subscribers = self._registry.getJustForType(ev_type)
        if subscribers is None:
            subscribers = []
            self._registry.register(ev_type, subscribers)
        subscribers.append((clean_subscriber, filter))

        subs = self._subscribers
        for sub in subs:
            if sub[0]==clean_subscriber:
                sub[1][ev_type]=1
                break
        else:
            subs.append((clean_subscriber,{ev_type:1}))
        
        self._registry=self._registry #trigger persistence, if pertinent
        
    
    def unsubscribe(self, subscriber, event_type=None, filter=None):
        
        clean_subscriber=removeAllProxies(subscriber)
        
        for subscriber_index in range(len(self._subscribers)):
            sub=self._subscribers[subscriber_index]
            if sub[0]==clean_subscriber:
                ev_set=sub[1]
                break
        else:
            if event_type: raise NotFoundError(subscriber)
            else: return # this was a generic unsubscribe all request;
            # work may have been done by a local service
        
        
        do_alert=ISubscriptionAware.isImplementedBy(clean_subscriber)
        
        if event_type:
            ev_type=event_type
            if event_type is IEvent:
                ev_type=None # handle optimization
            if ev_type not in ev_set:
                raise NotFoundError(subscriber, event_type, filter)
            subscriptions = self._registry.getJustForType(ev_type)
            try:
                subscriptions.remove((clean_subscriber, filter))
            except ValueError:
                raise NotFoundError(subscriber, event_type, filter)
            if do_alert:
                subscriber.unsubscribedFrom(self, event_type, filter)
            if len(ev_set)==1:
                for sub in subscriptions:
                    if sub[0]==clean_subscriber:
                        break
                else:
                    del self._subscribers[subscriber_index]
        else:
            for ev_type in ev_set:
                subscriptions = self._registry.getJustForType(ev_type)
                subs=subscriptions[:]
                subscriptions[:] = []
                for sub in subs:
                    if sub[0] == clean_subscriber: # deleted (not added back)
                        if do_alert:
                            subscriber.unsubscribedFrom(
                                self, ev_type or IEvent, sub[1])
                            # IEvent switch is to make optimization transparent
                    else: # kept (added back)
                        subscriptions.append(sub)
            del self._subscribers[subscriber_index]
        self._registry=self._registry #trigger persistence, if pertinent
    
    def listSubscriptions(self, subscriber, event_type=None):
        
        subscriber=removeAllProxies(subscriber)
        
        result=[]
        if event_type:
            ev_type=event_type
            if event_type is IEvent:
                ev_type=None # handle optimization
            subscriptions = self._registry.getJustForType(ev_type)
            for sub in subscriptions:
                if sub[0]==subscriber:
                    result.append((event_type, sub[1]))
        else:
            for subscriber_index in range(len(self._subscribers)):
                sub=self._subscribers[subscriber_index]
                if sub[0]==subscriber:
                    ev_set=sub[1]
                    break
            else:
                return result
            for ev_type in ev_set:
                subscriptions = self._registry.getJustForType(ev_type)
                for sub in subscriptions:
                    if sub[0]==subscriber:
                        result.append((ev_type or IEvent, sub[1]))
        return result

=== Zope3/lib/python/Zope/Event/EventChannel.py 1.1.2.6 => 1.1.2.7 ===
 """
 
+from Subscribable import Subscribable
 from IEventChannel import IEventChannel
-from Zope.ComponentArchitecture.IToIRegistry import TypeRegistry
-from Zope.Exceptions import NotFoundError
 
-class EventChannel(object):
+class EventChannel(Subscribable):
     
     __implements__ = IEventChannel
-
-    def __init__(self):
-        self._registry = TypeRegistry()
-        self._subscribers = {}
-        
-    _clear = __init__
-    
-    def subscribe(self, subscriber, event_types=(None,), filter=None):
-    
-        subs = self._subscribers
-        
-        for event_type in event_types:
-            
-            subscribers = self._registry.getJustForType(event_type)
-            if subscribers is None:
-                subscribers = []
-                self._registry.register(event_type, subscribers)
-            subscribers.append((subscriber, filter))
-
-            sub_types = subs.get(subscriber)
-            if sub_types is None:
-                sub_types={}
-                subs[subscriber]=sub_types
-            sub_types[event_type]=1
-        self._registry=self._registry #trigger persistence, if pertinent
-    
-    def unsubscribe(self, subscriber):
-        
-        try:
-            subs_set = self._subscribers[subscriber]
-        except KeyError:
-            raise NotFoundError, subscriber
-        
-        for event_type in subs_set:
-            subscriptions = self._registry.getJustForType(event_type)
-            subscriptions[:] = [sub
-                                for sub in subscriptions
-                                if sub[0] is not subscriber]
-        self._registry=self._registry #trigger persistence, if pertinent
         
     def notify(self, event):
         


=== Zope3/lib/python/Zope/Event/GlobalEventService.py 1.1.2.1 => 1.1.2.2 ===
 
 from IEventService import IEventService
-from EventChannel import EventChannel
+from Subscribable import Subscribable
 
-class GlobalEventService(EventChannel):
+class GlobalEventService(Subscribable):
     
     __implements__ = IEventService
         
     def publishEvent(self, event):
+        
+        subscriptionses = self._registry.getAllForObject(event)
 
-        self.notify(event)
+        for subscriptions in subscriptionses:
+            
+            for subscriber,filter in subscriptions:
+                if filter is not None and not filter(event):
+                    continue
+                subscriber.notify(event)
     
 
 eventService = GlobalEventService()


=== Zope3/lib/python/Zope/Event/ISubscribable.py 1.1.2.5 => 1.1.2.6 ===
 
 from Interface import Interface
+from IEvent import IEvent
 
 class ISubscribable(Interface):
     """Objects that broadcast events to subscribers."""
 
-    def subscribe(subscriber, event_types=None, filter=None):
+    def subscribe(subscriber, event_type=IEvent, filter=None):
         """Add subscriber to the list of subscribers for the channel.
         
         subscriber must implement ISubscriber.
+        Probable subscriber types include the following:
         
-        event_types, if supplied, is a list of the event interfaces
-        about which subscriber should be notified.
+            o Hard Reference (for placeless, global objects and event service)
+
+                Simply register the subscriber directly, in which case, the
+                subscription, and possibly the subscriber, is as persistent as the
+                subscribable.  The subscriber will not be wrapped for context
+                or security when called.
+
+            o Soft reference (for placeful, local objects and event service)
+
+                Register an object with a notify method and a path that
+                dereferences the path and delegates notifications.
+
+            o Location-independent reference (for placeful)
+
+                Register an object with a notify method and an ObjectHub ruid that
+                dereferences the ruid via the hub and delegates notifications.
+
+            o Abstract reference (for both)
+
+                Register an object with a notify method and an IReference that
+                dereferences the IReference and delegates notifications.
+        
+        event_type, if supplied, is the event interface
+        about which subscriber should be notified, and must implement
+        IEvent.  The subscriber will be notified of all events of this
+        event_type and of subclasses of this event_type.
+        The default value, IEvent, as the parent type,
+        is effectively a single catch-all event type that will pass all
+        event types to the subscriber.
 
         filter, if supplied, must implement IEventFilter; subscriber
         will be notified of events only if they pass.
 
         A subscriber may subscribe more than once, even if it has
-        already been subscribed with the same event types and
+        already been subscribed with the same event type and
         filter.  In this case the subscriber will receive multiple
         calls to its notify method.
-        """
         
-    def unsubscribe(subscriber):
-        """Remove subscriber from the list of subscribers.
-
-        Raises Zope.Exceptions.NotFound if subscriber wasn't already subscribed.
+        If the subscriber implements ISubscriptionAware, this function
+        will call the subscriber's subscribedTo method.
         """
         
+    def unsubscribe(subscriber, event_type=None, filter=None):
+        """Unsubscribe subscriber from receiving event types from this
+        subscribable.
+        
+        The subscriber is matched via equality (not identity).
+        
+        If event_type is None, the default value, the subscriber is
+        unsubscribed completely for all event types from this
+        subscribable (and its parents, if the subscribable is a placeful
+        service).  If no subscriptions for this subscriber are
+        present, no error is raised.
+        
+        if event_type is supplied, this method will unsubscribe the
+        subscriber from one subscription exactly matching the
+        event_type/filter pair given (the default filter being None).
+        If other subscriptions are present they will remain.  If the
+        subscription cannot be found and the subscribable is a placeful
+        service, the unsubscription request is passed to parent
+        services.  Raises Zope.Exceptions.NotFound if subscriber wasn't 
+        subscribed as expected.
+        
+        If the subscriber implements ISubscriptionAware, this function
+        will call the subscriber's unsubscribedFrom method for each
+        individual unsubscribe.
+        """
         
+    def listSubscriptions(subscriber, event_type=None):
+        """returns an iterator of the subscriptions to this channel for
+        the subscriber. if event_type is supplied, the list is limited
+        to that exact event_type.  If the subscribable is a placeful
+        service, the list will include subscriptions to parent services.
+        The subscriber is matched via equality (not identity).  No
+        subscriptions returns an empty iterator.  Each subscription is
+        represented as a tuple (event_type, filter)."""


=== Zope3/lib/python/Zope/Event/ISubscriber.py 1.1.2.5 => 1.1.2.6 ===
         """
 
+class IIndirectSubscriber(ISubscriber):
+    """Interface for objects that handle subscriptions for another object"""
+    
+    def __eq__(other):
+        """this standard python hook allows indirect subscribers to
+        participate in subscribable introspection and unsubscription
+        without being the actual original subscriber object"""
+
     
     


=== Zope3/lib/python/Zope/Event/__init__.py 1.1.2.6 => 1.1.2.7 ===
 
 from Zope.ComponentArchitecture import getService
+from IEvent import IEvent
 
 
 def getEventService(context):
-    return getService(context, 'EventService')
+    return getService(context, 'Events')
 
 def publishEvent(context, event):
     return getEventService(context).publishEvent(event)
 
-def subscribe(context, subscriber, event_types=(None,), filter=None):
-    return getEventService(context).subscribe(subscriber, event_types, filter)
+def subscribe(subscriber, event_type=IEvent, filter=None, context=None):
+    if context is None: context=subscriber
+    return getEventService(context).subscribe(
+        subscriber, event_type, filter)
 
-def unsubscribe(context, subscriber):
-    return getEventService(context).unsubscribe(subscriber)
+def subscribeMany(subscriber, event_types=(IEvent,), filter=None, context=None):
+    if context is None: context=subscriber
+    es= getEventService(context).subscribe
+    for event_type in event_types:
+        es(subscriber, event_type, filter)
+
+def unsubscribe(subscriber, event_type=None, filter=None, context=None):
+    if context is None: context=subscriber
+    return getEventService(context).unsubscribe(
+        subscriber, event_type, filter)
+
+def listSubscriptions(subscriber, event_type=None, context=None):
+    if context is None: context=subscriber
+    return getEventService(context).listSubscriptions(
+        subscriber, event_type)
 
 
 


=== Zope3/lib/python/Zope/Event/event.zcml 1.1.2.2 => 1.1.2.3 ===
 >
 
-<serviceType name='EventService' 
+<serviceType name='Events' 
              interface='Zope.Event.IEventService.' />
 
-<service name='EventService'
+<service name='Events'
          component='Zope.Event.GlobalEventService.eventService' />
 
 </zopeConfigure>


=== Zope3/lib/python/Zope/Event/metaConfigure.py 1.1.2.4 => 1.1.2.5 ===
 from Zope.Configuration.Action import Action
 
-from Zope.Event import subscribe as eventSubscribe
+from Zope.Event import subscribeMany
+from Zope.Event.IEvent import IEvent
 
 counter = 0
 
-def subscribe(_context, subscriber, event_types=None, filter=None):
+def subscribe(_context, subscriber, event_types=(IEvent,), filter=None):
     global counter
     counter += 1
 
     subscriber = _context.resolve(subscriber)
 
-    if event_types is None:
-        event_types=(None,)
-    else:
-        event_type_names = event_types
-        event_types=[]
-        for event_type_name in [element.strip()
-                                for element
-                                in event_type_names.split(',')]:
-            event_types.append(_context.resolve(event_type_name))
+    event_type_names = event_types
+    event_types=[]
+    for event_type_name in [element.strip()
+                            for element
+                            in event_type_names.split(',')]:
+        event_types.append(_context.resolve(event_type_name))
                         
     if filter is not None:
         filter = _context.resolve(filter)
@@ -46,7 +44,7 @@
         Action(
              # subscriptions can never conflict
              discriminator = ('subscribe', counter),
-             callable = eventSubscribe,
-             args = (None, subscriber, event_types, filter)
+             callable = subscribeMany,
+             args = (subscriber, event_types, filter)
              )
         ]