[Zope3-checkins] CVS: Zope3/src/zope/app/event - globalservice.py:1.1 subs.py:1.1 __init__.py:1.3 configure.zcml:1.3 meta.zcml:1.3 globaleventservice.py:NONE logger.py:NONE metaconfigure.py:NONE

Steve Alexander steve@cat-box.net
Mon, 30 Dec 2002 09:03:36 -0500


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

Modified Files:
	__init__.py configure.zcml meta.zcml 
Added Files:
	globalservice.py subs.py 
Removed Files:
	globaleventservice.py logger.py metaconfigure.py 
Log Message:
Large refactoring of the event service.


=== Added File Zope3/src/zope/app/event/globalservice.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: globalservice.py,v 1.1 2002/12/30 14:03:02 stevea Exp $
"""

__metaclass__ = type

from zope.interface.type import TypeRegistry
from zope.exceptions import NotFoundError
from zope.proxy.introspection import removeAllProxies

from zope.app.interfaces.event import IEvent, ISubscriber, ISubscribingAware
from zope.app.interfaces.event import IGlobalSubscribable, IPublisher

from zope.configuration.action import Action

import logging
import pprint
from StringIO import StringIO

def checkEventType(event_type, allow_none=False):
    if not (
        (allow_none and event_type is None)
        or event_type.extends(IEvent, strict=False)
        ):
        raise TypeError('event_type must extend IEvent: %s' % repr(event_type))

directive_counter = 0
def subscribeDirective(_context, subscriber,
                       event_types=(IEvent,), filter=None):
    global directive_counter
    directive_counter += 1

    subscriber = _context.resolve(subscriber)

    event_type_names = event_types
    event_types=[]
    for event_type_name in event_type_names.split():
        event_types.append(_context.resolve(event_type_name))

    if filter is not None:
        filter = _context.resolve(filter)

    return [
        Action(
             # subscriptions can never conflict
             discriminator = ('subscribe', directive_counter),
             callable = globalSubscribeMany,
             args = (subscriber, event_types, filter)
             )
        ]

class Logger:
    """Class to log all events sent out by an event service.

    This is an event subscriber that you can add via ZCML to log all
    events sent out by Zope.
    """

    __implements__ = ISubscriber

    def __init__(self, severity=logging.INFO):
        self.severity = severity
        self.logger = logging.getLogger("Event.Logger")

    def notify(self, event):
        c = event.__class__
        detail = StringIO()
        if 0:
            # XXX Apparently this doesn't work; why not?
            data = event.__dict__.items()
            data.sort()
            pprint(data, detail)
        else:
            print >>detail, 'XXX detail temporarily disabled'
        self.logger.log(self.severity, "%s.%s: %s",
                        c.__module__, c.__name__, detail.getvalue())


class GlobalSubscribable:
    """A global mix-in"""
    
    __implements__ = IGlobalSubscribable

    def __init__(self):
        self._registry = TypeRegistry()
        self._subscribers = [] # use dict?
        
    _clear = __init__

    def globalSubscribe(self, subscriber, event_type=IEvent, filter=None):
        checkEventType(event_type)
        clean_subscriber = removeAllProxies(subscriber)
        
        if ISubscribingAware.isImplementedBy(subscriber):
            subscriber.subscribedTo(self, event_type, filter)
        
        if event_type is IEvent:
            event_type = None # optimization
        
        subscribers = self._registry.setdefault(event_type, [])
        subscribers.append((clean_subscriber, filter))

        for sub in self._subscribers:
            if sub[0] == clean_subscriber:
                try:
                    sub[1][event_type] += 1
                except KeyError:
                    sub[1][event_type] = 1
                break
        else:
            self._subscribers.append((clean_subscriber, {event_type: 1}))
            
        # Trigger persistence, if pertinent
        # self._registry = self._registry
        
    
    def unsubscribe(self, subscriber, event_type=None, filter=None):
        checkEventType(event_type, allow_none=True)
        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]  # the dict of type:subscriptionCount
                break
        else:
            if event_type is not None:
                raise NotFoundError(subscriber)
            else:
                # this was a generic unsubscribe all request; work may have
                # been done by a local service
                return
        
        do_alert = ISubscribingAware.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.get(ev_type)
            if not subscriptions:
                raise NotFoundError(subscriber, event_type, filter)
            try: 
                subscriptions.remove((clean_subscriber, filter))
            except ValueError:
                raise NotFoundError(subscriber, event_type, filter)
            if do_alert:
                subscriber.unsubscribedFrom(self, event_type, filter)
            ev_set[ev_type] -= 1
            if ev_set[ev_type] < 1:
                for sub in subscriptions:
                    if sub[0] == clean_subscriber:
                        break
                else:
                    if len(ev_set) > 1:
                        del ev_set[ev_type]
                    else:  # len(ev_set) == 1, and we just eliminated it
                        del self._subscribers[subscriber_index]
        else:
            for ev_type in ev_set:
                subscriptions = self._registry.get(ev_type)
                if ev_type is None:
                    ev_type = IEvent
                subs = subscriptions[:]
                subscriptions[:] = []
                for sub in subs:
                    if sub[0] == clean_subscriber: # deleted (not added back)
                        if do_alert:
                            subscriber.unsubscribedFrom(self, ev_type, sub[1])
                    else:  # kept (added back)
                        subscriptions.append(sub)
            del self._subscribers[subscriber_index]
        # Trigger persistence, if pertinent
        # self._registry = self._registry

    def listSubscriptions(self, subscriber, event_type=None):
        checkEventType(event_type, allow_none=True)
        subscriber = removeAllProxies(subscriber)
        
        result = []
        if event_type:
            ev_type = event_type
            if event_type is IEvent:
                ev_type = None # handle optimization
            subscriptions = self._registry.get(ev_type)
            if subscriptions:
                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.get(ev_type)
                if subscriptions:
                    if ev_type is None:
                        ev_type = IEvent
                    for sub in subscriptions:
                        if sub[0] == subscriber:
                            result.append((ev_type, sub[1]))
        return result


class GlobalEventChannel(GlobalSubscribable):
    
    __implements__ = IGlobalSubscribable, ISubscriber
        
    def notify(self, event):
        subscriptionsForEvent = self._registry.getAllForObject(event)
        for subscriptions in subscriptionsForEvent:
            for subscriber, filter in subscriptions:
                if filter is not None and not filter(event):
                    continue
                subscriber.notify(event)


class GlobalEventPublisher(GlobalSubscribable):
    
    __implements__ = IGlobalSubscribable, IPublisher

    def publish(self, event):
        assert IEvent.isImplementedBy(event)
        subscriptionsForEvent = self._registry.getAllForObject(event)
        for subscriptions in subscriptionsForEvent:
            for subscriber, filter in subscriptions:
                if filter is not None and not filter(event):
                    continue
                subscriber.notify(event)

# Repeated here, and in zope/app/event/__init__.py to avoid circular import.
def globalSubscribeMany(subscriber, event_types=(IEvent,), filter=None):
    subscribe_func = eventPublisher.globalSubscribe
    for event_type in event_types:
        subscribe_func(subscriber, event_type, filter)

eventPublisher = GlobalEventPublisher()

_clear = eventPublisher._clear

# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
from zope.testing.cleanup import addCleanUp
addCleanUp(_clear)
del addCleanUp
    


=== Added File Zope3/src/zope/app/event/subs.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: subs.py,v 1.1 2002/12/30 14:03:02 stevea Exp $
"""
from __future__ import generators
from zope.exceptions import NotFoundError
from persistence import Persistent
from types import StringTypes

from zope.proxy.context import ContextMethod
from zope.proxy.introspection import removeAllProxies

from zope.app.traversing import getPhysicalPathString
from zope.app.traversing import locationAsUnicode, getPhysicalPath, traverse
from zope.app.interfaces.event import IEvent, ISubscriber, ISubscribable
from zope.app.interfaces.event import ISubscribingAware

from zope.component import getService, getAdapter
from zope.interface.type import TypeRegistry

__metaclass__ = type

try:
    enumerate # python 2.3
except NameError:
    def enumerate(collection):
        'Generates an indexed series:  (0,coll[0]), (1,coll[1]) ...'     
        i = 0
        it = iter(collection)
        while 1:
            yield (i, it.next())
            i += 1

class Subscribable(Persistent):
    """A local mix-in"""
    
    __implements__ = ISubscribable
    
    def __init__(self):
        self._registry = TypeRegistry()
        # using a list rather than a dict so that subscribers may define
        # custom __eq__ methods
        self._subscribers = []
        
    def subscribe(wrapped_self, subscriber, event_type=IEvent, filter=None):
        '''See ISubscribable'''
        clean_subObj = removeAllProxies(subscriber)
        if isinstance(clean_subObj, int):
            hub = getService(wrapped_self, "HubIds")
            subObj = hub.getObject(subscriber)
            clean_subObj = removeAllProxies(subObj)
        elif isinstance(clean_subObj, StringTypes):
            subObj = traverse(wrapped_self, subscriber)
            clean_subObj = removeAllProxies(subObj)
            subscriber = locationAsUnicode(subscriber)
        else:
            subObj = subscriber
            hub = getService(wrapped_self, "HubIds")
            try:
                subscriber = hub.getHubId(subObj)
            except NotFoundError:
                # XXX this should be superfluous. getPhysicalPathString should
                #     always return a canonical path.
                subscriber = locationAsUnicode(
                    getPhysicalPath(subscriber))
        
        if ISubscribingAware.isImplementedBy(clean_subObj):
            subObj.subscribedTo(wrapped_self, event_type, filter)
        
        ev_type = event_type
        if ev_type is IEvent:
            ev_type = None  # optimization
        clean_self = removeAllProxies(wrapped_self)
        
        clean_self._p_changed = 1
        
        subscribers = clean_self._registry.get(ev_type)
        if subscribers is None:
            subscribers = []
            clean_self._registry.register(ev_type, subscribers)
        subscribers.append((subscriber, filter))

        subs = clean_self._subscribers
        for sub in subs:
            if sub[0] == subscriber:
                try:
                    sub[1][ev_type] += 1
                except KeyError:
                    sub[1][ev_type] = 1
                break
        else:
            subs.append((subscriber,{ev_type:1}))
        
        return subscriber
    subscribe = ContextMethod(subscribe)
    
    def _getSubscribers(clean_self, wrapped_self, subscriber):
        subscribers = []
        # XXX This comment needs explanation:
        # shortcut; useful for notify
        if wrapped_self is clean_self: 
            return [subscriber], None, None
        clean_subObj = removeAllProxies(subscriber)
        if isinstance(clean_subObj, int):
            hub = getService(wrapped_self, "HubIds")
            try:
                subObj = hub.getObject(subscriber)
            except NotFoundError:
                subObj = None
            else:
                clean_subObj = removeAllProxies(subObj)
            subscribers.append(subscriber)
        elif isinstance(clean_subObj, StringTypes):
            try:
                subObj = traverse(wrapped_self, subscriber)
            except NotFoundError:
                subObj = None
            else:
                clean_subObj = removeAllProxies(subObj)
            subscribers.append(locationAsUnicode(subscriber))
        else:
            subObj = subscriber
            hub = getService(wrapped_self, "HubIds")
            try:
                subscribers.append(hub.getHubId(subObj))
            except NotFoundError:
                pass
            subscribers.append(locationAsUnicode(
                getPhysicalPath(subscriber)))
        return subscribers, clean_subObj, subObj
    
    def _getEventSets(self, subscribers):
        ev_sets = {}
        for self_ix, sub in enumerate(self._subscribers):
            for arg_ix, subscriber in enumerate(subscribers):
                if sub[0] == subscriber:
                    ev_sets[(subscriber, self_ix)] = sub[1]
                    del subscribers[arg_ix]
                    break
            if not subscribers:
                break
        else:
            if len(ev_sets.keys()) == 0:
                raise NotFoundError(subscribers)
        return ev_sets
    
    def _cleanAllForSubscriber(clean_self,
                               wrapped_self,
                               ev_sets,
                               do_alert,
                               subObj):
        for (subscriber, subscriber_index), ev_set in ev_sets.items():
            for ev_type in ev_set:
                subscriptions = clean_self._registry.get(ev_type)
                if ev_type is None:
                    ev_type = IEvent
                subs = subscriptions[:]
                subscriptions[:] = []
                for sub in subs:
                    if sub[0] == subscriber:  # deleted (not added back)
                        if do_alert:
                            subObj.unsubscribedFrom(
                                wrapped_self, ev_type, sub[1]
                                )
                    else: # kept (added back)
                        subscriptions.append(sub)
            del clean_self._subscribers[subscriber_index]
    
    def unsubscribe(wrapped_self, subscriber, event_type=None, filter=None):
        '''See ISubscribable'''
        clean_self = removeAllProxies(wrapped_self)
        subscribers, clean_subObj, subObj = clean_self._getSubscribers(
            wrapped_self, subscriber)
        
        ev_sets = clean_self._getEventSets(subscribers)
        
        do_alert = (subObj is not None and
                    ISubscribingAware.isImplementedBy(clean_subObj)
                   )
        
        clean_self._p_changed = 1
        
        if event_type:
            # we have to clean out one and only one subscription of this
            # subscriber for event_type, filter (there may be more,
            # even for this exact combination of subscriber,
            # event_type, filter; we only do *one*)
            ev_type = event_type
            if event_type is IEvent:
                ev_type = None
                # *** handle optimization: a subscription to IEvent is a
                # subscription to all events; this is converted to 'None'
                # so that the _registry can shortcut some of its tests
            for (subscriber, subscriber_index), ev_set in ev_sets.items():
                if ev_type in ev_set:
                    subscriptions = clean_self._registry.get(ev_type)
                    if subscriptions:
                        try:
                            subscriptions.remove((subscriber, filter))
                        except ValueError:
                            pass
                        else:
                            if do_alert:
                                subObj.unsubscribedFrom(
                                    wrapped_self, event_type, filter)
                            ev_set[ev_type] -= 1
                            if ev_set[ev_type] < 1:
                                for sub in subscriptions:
                                    if sub[0] == subscriber:
                                        break
                                else:
                                    if len(ev_set) > 1:
                                        del ev_set[ev_type]
                                    else:  # len(ev_set) == 1
                                        del clean_self._subscribers[
                                            subscriber_index]
                            break
            else:
                raise NotFoundError(subscriber, event_type, filter)
        else:
            # we have to clean all the event types out (ignoring filter)
            clean_self._cleanAllForSubscriber(wrapped_self,
                                              ev_sets,
                                              do_alert,
                                              subObj)
    unsubscribe = ContextMethod(unsubscribe)

    def listSubscriptions(wrapped_self, subscriber, event_type=None):
        '''See ISubscribable'''
        clean_self = removeAllProxies(wrapped_self)
        subscribers, clean_subObj, subObj = clean_self._getSubscribers(
            wrapped_self, subscriber)
        
        result=[]
        if event_type:
            ev_type=event_type
            if event_type is IEvent:
                ev_type=None  # handle optimization
            subscriptions = self._registry.get(ev_type)
            if subscriptions:
                for sub in subscriptions:
                    for subscriber in subscribers:
                        if sub[0]==subscriber:
                            result.append((event_type, sub[1]))
        else:
            try:
                ev_sets = clean_self._getEventSets(subscribers)
            except NotFoundError:
                return result
            for (subscriber, subscriber_index), ev_set in ev_sets.items():
                for ev_type in ev_set:
                    subscriptions = self._registry.get(ev_type)
                    if subscriptions:
                        if ev_type is None:
                            ev_type = IEvent
                        for sub in subscriptions:
                            if sub[0]==subscriber:
                                result.append((ev_type, sub[1]))
        return result
    listSubscriptions = ContextMethod(listSubscriptions)


class SubscriptionTracker:
    "Mix-in for subscribers that want to know to whom they are subscribed"
    
    __implements__ = ISubscribingAware
    
    def __init__(self):
        self._subscriptions = ()
    
    def subscribedTo(self, subscribable, event_type, filter):
        # XXX insert super() call here
        # This raises an error for subscriptions to global event service.
        subscribable_path = getPhysicalPathString(subscribable)
        if (subscribable_path, event_type, filter) not in self._subscriptions:
            self._subscriptions += ((subscribable_path, event_type, filter),)
    
    def unsubscribedFrom(self, subscribable, event_type, filter):
        # XXX insert super() call here
        # This raises an error for subscriptions to global event service.
        subscribable_path = getPhysicalPathString(subscribable)
        sub = list(self._subscriptions)
        sub.remove((subscribable_path, event_type, filter))
        self._subscriptions = tuple(sub)



=== Zope3/src/zope/app/event/__init__.py 1.2 => 1.3 ===
--- Zope3/src/zope/app/event/__init__.py:1.2	Wed Dec 25 09:12:51 2002
+++ Zope3/src/zope/app/event/__init__.py	Mon Dec 30 09:03:02 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 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.
-#
+# 
 ##############################################################################
 """
 
@@ -17,18 +17,27 @@
 $Id$
 """
 
-from zope.interfaces.event import IEvent
-from zope.event import getEventService
+from zope.component import getService
+from zope.app.interfaces.event import IEvent
+from zope.app.event.globalservice import eventPublisher, checkEventType
 
-def globalSubscribe(subscriber, event_type=IEvent, filter=None, context=None):
-    if context is None:
-        context = subscriber
-    return getEventService(None).globalSubscribe(
-        subscriber, event_type, filter)
-
-def globalSubscribeMany(subscriber, event_types=(IEvent,),
-                        filter=None, context=None):
-    if context is None: context=subscriber
-    subscribe_func = getEventService(None).globalSubscribe
+def getEventService(context): # the "publish" service
+    return getService(context, 'Events')
+
+def publish(context, event):
+    return getEventService(context).publish(event)
+
+def globalSubscribe(subscriber, event_type=IEvent, filter=None):
+    return eventPublisher.globalSubscribe(subscriber, event_type, filter)
+
+def globalSubscribeMany(subscriber, event_types=(IEvent,), filter=None):
+    subscribe_func = eventPublisher.globalSubscribe
     for event_type in event_types:
         subscribe_func(subscriber, event_type, filter)
+
+def globalUnsubscribe(subscriber, event_type=None, filter=None):
+    return eventPublisher.unsubscribe(subscriber, event_type, filter)
+
+def globalListSubscriptions(subscriber, event_type=None):
+    return eventPublisher.listSubscriptions(subscriber, event_type)
+        


=== Zope3/src/zope/app/event/configure.zcml 1.2 => 1.3 ===
--- Zope3/src/zope/app/event/configure.zcml:1.2	Wed Dec 25 09:12:51 2002
+++ Zope3/src/zope/app/event/configure.zcml	Mon Dec 30 09:03:02 2002
@@ -1,12 +1,13 @@
 <zopeConfigure
    xmlns='http://namespaces.zope.org/zope'
-   xmlns:browser='http://namespaces.zope.org/browser'
 >
 
-<serviceType id='Events' 
-             interface='zope.interfaces.event.IEventService' />
+<serviceType
+    id='Events' 
+    interface='zope.app.interfaces.event.IPublisher' />
 
-<service serviceType='Events'
-         component='zope.app.event.globaleventservice.eventService' />
+<service
+    serviceType='Events'
+    component='zope.app.event.globalservice.eventPublisher' />
 
 </zopeConfigure>


=== Zope3/src/zope/app/event/meta.zcml 1.2 => 1.3 ===
--- Zope3/src/zope/app/event/meta.zcml:1.2	Wed Dec 25 09:12:51 2002
+++ Zope3/src/zope/app/event/meta.zcml	Mon Dec 30 09:03:02 2002
@@ -3,7 +3,7 @@
   <directives namespace="http://namespaces.zope.org/event">
 
     <directive name="subscribe" attributes="subscriber event_types filter"
-       handler="zope.app.event.metaconfigure.subscribe" />
+       handler="zope.app.event.globalservice.subscribeDirective" />
 
   </directives>
 

=== Removed File Zope3/src/zope/app/event/globaleventservice.py ===

=== Removed File Zope3/src/zope/app/event/logger.py ===

=== Removed File Zope3/src/zope/app/event/metaconfigure.py ===