[Zope3-checkins] CVS: Zope3/src/zope/app/event -
localservice.py:1.1 __init__.py:1.12 configure.zcml:1.8
interfaces.py:1.3
Stephan Richter
srichter at cosmos.phy.tufts.edu
Thu Mar 11 03:14:34 EST 2004
Update of /cvs-repository/Zope3/src/zope/app/event
In directory cvs.zope.org:/tmp/cvs-serv12137/src/zope/app/event
Modified Files:
__init__.py configure.zcml interfaces.py
Added Files:
localservice.py
Log Message:
Moved local event service code into zope.app.event.
=== Added File Zope3/src/zope/app/event/localservice.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.
#
##############################################################################
"""Local Event Service and related classes.
$Id: localservice.py,v 1.1 2004/03/11 08:14:02 srichter Exp $
"""
import logging
from zope.exceptions import NotFoundError
from zope.app import zapi
from zope.component import queryService
from zope.app.event.interfaces import IEvent, ISubscriber, IEventChannel
from zope.app.event.interfaces import ISubscriptionService, IEventService
from zope.app.interfaces.services.service import IBindingAware
from zope.component import ComponentLookupError
from zope.app.services.servicenames import HubIds, EventPublication
from zope.app.services.servicenames import EventSubscription
from zope.app.component.nextservice import getNextService, queryNextService
from zope.proxy import removeAllProxies
from zope.interface import implements
from zope.app.event.subs import Subscribable, SubscriptionTracker
from zope.security.proxy import trustedRemoveSecurityProxy
from zope.app.container.contained import Contained
def getSubscriptionService(context):
return zapi.getService(context, EventSubscription)
def subscribe(subscriber, event_type=IEvent, filter=None, context=None):
if context is None and not isinstance(subscriber, (int, str, unicode)):
context = subscriber
return getSubscriptionService(context).subscribe(
subscriber, event_type, filter)
def subscribeMany(subscriber, event_types=(IEvent,),
filter=None, context=None):
if context is None and not isinstance(subscriber, (int, str, unicode)):
context = subscriber
subscribe = getSubscriptionService(context).subscribe
for event_type in event_types:
subscribe(subscriber, event_type, filter)
def unsubscribe(subscriber, event_type, filter=None, context=None):
if context is None and not isinstance(subscriber, (int, str, unicode)):
context = subscriber
return getSubscriptionService(context).unsubscribe(
subscriber, event_type, filter)
def unsubscribeAll(subscriber, event_type=IEvent, context=None,
local_only=False):
if context is None and not isinstance(subscriber, (int, str, unicode)):
context = subscriber
return getSubscriptionService(context).unsubscribeAll(
subscriber, event_type, local_only=local_only)
def iterSubscriptions(subscriber=None, event_type=None, local_only=False,
context=None):
if context is None and not isinstance(subscriber, (int, str, unicode)):
context = subscriber
return getSubscriptionService(context).iterSubscriptions(
subscriber, event_type, local_only)
class EventChannel(Subscribable):
implements(IEventChannel)
# needs __init__ from zope.app.event.subs.Subscribable
def _notify(clean_self, wrapped_self, event):
subscriptionsForEvent = clean_self._registry.getAllForObject(event)
hubIdsService = queryService(wrapped_self, HubIds)
if hubIdsService is None:
# This will only happen if there is no HubIds service.
# This is only true at start-up, so we don't bother testing
# whether hubGet is None in the loop below.
hubGet = None
else:
hubGet = hubIdsService.getObject
root = removeAllProxies(zapi.getRoot(wrapped_self))
badSubscribers = {} # using a dict as a set
for subscriptions in subscriptionsForEvent:
for subscriber,filter in subscriptions:
if filter is not None and not filter(event):
continue
if isinstance(subscriber, int):
try:
obj = hubGet(subscriber)
# XXX we need to figure out exactly how we want to
# handle this. For now, we'll assume that all
# subscriptions are trusted, so can always notify
obj = trustedRemoveSecurityProxy(obj)
except NotFoundError:
badSubscribers[subscriber] = None
continue
else:
try:
obj = zapi.traverse(root, subscriber)
except NotFoundError:
badSubscribers[subscriber] = None
continue
# Get an ISubscriber adapter in the context of the object
# This is probably the right context to use.
#
# Using getAdapter rather than queryAdapter because if there
# is no ISubscriber adapter available, that is an application
# error that should be fixed. So, failing is appropriate, and
# adding this subscriber to badSubscribers is inappropriate.
ISubscriber(obj).notify(event)
for subscriber in badSubscribers:
logging.getLogger('SiteError').warn(
"Notifying a subscriber that does not exist."
" Unsubscribing it: %s" % subscriber)
# Also, is it right that we should sometimes have
# "write caused by a read" semantics? I'm seeing notify() as
# basically a read, and (un)subscribe as a write.
wrapped_self.unsubscribeAll(subscriber)
def notify(wrapped_self, event):
clean_self = removeAllProxies(wrapped_self)
clean_self._notify(wrapped_self, event)
class ServiceSubscriberEventChannel(SubscriptionTracker, EventChannel):
"""An event channel that wants to subscribe to the nearest
event service when bound, and unsubscribe when unbound.
"""
implements(IBindingAware)
def __init__(self):
SubscriptionTracker.__init__(self)
EventChannel.__init__(self)
subscribeOnBind = True
# if true, event service will subscribe
# to the parent event service on binding, unless the parent
# service is the global event service; see 'bound' method
# below
_serviceName = None
# the name of the service that this object is providing, or
# None if unbound
_subscribeToServiceName = EventSubscription
_subscribeToServiceInterface = IEvent
_subscribeToServiceFilter = None
def subscribe(self, reference, event_type=IEvent, filter=None):
if getattr(self, "_v_ssecunbinding", None) is not None:
raise Exception(
'Cannot subscribe to a subscriber that is unbinding.')
return super(ServiceSubscriberEventChannel, self
).subscribe(reference, event_type, filter)
def bound(wrapped_self, name):
"See IBindingAware"
# Note: if a component is used for more than one service then
# this and the unbound code must be conditional for the
# pertinent service that should trigger event subscription
clean_self = removeAllProxies(wrapped_self)
clean_self._serviceName = name # for ServiceSubscribable
if clean_self.subscribeOnBind:
es = queryService(
wrapped_self, clean_self._subscribeToServiceName)
if es is not None:
if removeAllProxies(es) is clean_self:
es = queryNextService(
wrapped_self, clean_self._subscribeToServiceName)
if es is None:
subscribe_to = clean_self._subscribeToServiceName
logging.getLogger('SiteError').warn(
"Unable to subscribe %s service to the %s service "
"while binding the %s service. This is because the "
"%s service could not be found." %
(name, subscribe_to, name, subscribe_to))
else:
es.subscribe(
wrapped_self,
clean_self._subscribeToServiceInterface,
clean_self._subscribeToServiceFilter
)
def unbound(wrapped_self, name):
"See IBindingAware"
# Note: if a component is used for more than one service then
# this and the unbound code must be conditional for the
# pertinent service that should trigger event subscription
clean_self = removeAllProxies(wrapped_self)
# unsubscribe all subscriptions
hubIds = clean_self._hubIds
unsubscribeAll = wrapped_self.unsubscribeAll
# XXX Temporary hack to make unsubscriptions local in scope when
# this mix-in is used as part of a subscriptions service.
# The dependences of these mixins need to be documented and
# reevaluated.
if ISubscriptionService.providedBy(wrapped_self):
real_unsubscribeAll = unsubscribeAll
unsubscribeAll = lambda x: real_unsubscribeAll(x, local_only=True)
try:
clean_self._v_ssecunbinding = True
while hubIds:
hubId = iter(hubIds).next()
# XXX This code path needs a unit test!
# This code is also wrong.
# The call to unsubscribeAll assumes that whatever class
# mixes this class in provides an unsubscribeAll method
# that correctly uses the self._subscribeToServiceName
# to decide what it should be unsubscribing from.
# This could be any service that implements
# ISubscriptionService
unsubscribeAll(hubId)
paths = clean_self._paths
while paths:
path = iter(paths).next()
# XXX This code path needs a unit test!
# Also, see comment above.
unsubscribeAll(path)
finally:
del clean_self._v_ssecunbinding
assert len(paths) == len(hubIds) == len(clean_self._registry) == 0
clean_self._serviceName = None
class ServiceSubscribable(Subscribable):
"""A mix-in for local event services.
* unsubscribe() asks the next higher service to unsubscribe if this
service cannot.
* unsubscribeAll() does the same.
* listSubscriptions() includes this service's subscriptions, and
those of the next higher service.
"""
_serviceName = None # should be replaced; usually done in "bound"
# method of a subclass that is IBindingAware
# requires __init__ from zope.app.event.subs.Subscribable
def unsubscribe(self, reference, event_type, filter=None):
# The point here is that if we can't unsubscribe here, we should
# allow the next event service to unsubscribe.
try:
super(ServiceSubscribable, self
).unsubscribe(reference, event_type, filter)
except NotFoundError:
next_service = queryNextService(self,
self._serviceName)
if next_service is not None:
next_service.unsubscribe(reference, event_type, filter)
else:
raise
def unsubscribeAll(self, reference, event_type=IEvent,
local_only=False):
# unsubscribe all from here, and from the next service
# n is the number of subscriptions removed
n = super(ServiceSubscribable, self
).unsubscribeAll(reference, event_type)
if not local_only:
next_service = queryNextService(self, self._serviceName)
if next_service is not None:
n += next_service.unsubscribeAll(reference, event_type)
return n
def resubscribeByHubId(self, reference):
n = super(ServiceSubscribable, self
).resubscribeByHubId(reference)
next_service = queryNextService(self, self._serviceName)
if next_service is not None:
n += next_service.resubscribeByHubId(reference)
return n
def resubscribeByPath(self, reference):
n = super(ServiceSubscribable, self
).resubscribeByPath(reference)
next_service = queryNextService(self, self._serviceName)
if next_service is not None:
n += next_service.resubscribeByPath(reference)
return n
def iterSubscriptions(self, reference=None, event_type=IEvent,
local_only=False):
'See ISubscriptionService'
subs = super(ServiceSubscribable, self
).iterSubscriptions(reference, event_type)
for subscription in subs:
yield subscription
if not local_only:
next_service = queryNextService(self, self._serviceName)
if next_service is not None:
for subscription in next_service.iterSubscriptions(
reference, event_type):
yield subscription
from zope.app.interfaces.services.service import ISimpleService
class EventService(ServiceSubscriberEventChannel, ServiceSubscribable,
Contained):
implements(IEventService, ISubscriptionService, ISimpleService)
def __init__(self):
ServiceSubscriberEventChannel.__init__(self)
ServiceSubscribable.__init__(self)
def isPromotableEvent(self, event):
"""A hook. Returns True if, when publishing an event, the event
should also be promoted to the next (higher) level of event service,
and False otherwise."""
# XXX A probably temporary appendage. Depending on the usage,
# this should be (a) kept as is, (b) made into a registry, or
# (c) removed.
return True
def publish(wrapped_self, event):
"see IEventPublisher"
clean_self = removeAllProxies(wrapped_self)
publishedEvents = getattr(clean_self, "_v_publishedEvents", [])
clean_self._v_publishedEvents = publishedEvents
publishedEvents.append(event)
try:
clean_self._notify(wrapped_self, event)
if clean_self.isPromotableEvent(event):
getNextService(wrapped_self, EventPublication).publish(event)
finally:
publishedEvents.remove(event)
def notify(wrapped_self, event):
"see ISubscriber"
clean_self = removeAllProxies(wrapped_self)
publishedEvents = getattr(clean_self, "_v_publishedEvents", [])
if event not in publishedEvents:
clean_self._notify(wrapped_self, event)
def bound(wrapped_self, name):
"See IBindingAware"
# An event service is bound as EventSubscription and EventPublication.
# We only want to subscribe to the next event service when we're bound
# as EventSubscription
if name == EventSubscription:
clean_self = removeAllProxies(wrapped_self)
clean_self._serviceName = name # for ServiceSubscribable
if clean_self.subscribeOnBind:
try:
es = getNextService(wrapped_self, EventSubscription)
except ComponentLookupError:
pass
else:
es.subscribe(wrapped_self)
def unbound(self, name):
"See IBindingAware"
# An event service is bound as EventSubscription and EventPublication.
# We only want to unsubscribe from the next event service when
# we're unbound as EventSubscription
if name == EventSubscription:
clean_self = removeAllProxies(self)
# This flag is used by the unsubscribedFrom method (below) to
# determine that it doesn't need to further unsubscribe beyond
# what we're already doing.
clean_self._v_unbinding = True
try:
super(EventService, self).unbound(name)
finally:
# unset flag
del clean_self._v_unbinding
def unsubscribedFrom(self, subscribable, event_type, filter):
"See ISubscribingAware"
super(EventService, self
).unsubscribedFrom(subscribable, event_type, filter)
clean_self = removeAllProxies(self)
if getattr(clean_self, "_v_unbinding", None) is None:
# we presumably have been unsubscribed from a higher-level
# event service because that event service is unbinding
# itself: we need to remove the higher level event service
# from our subscriptions list and try to find another event
# service to which to attach
clean_subscribable = removeAllProxies(subscribable)
if ISubscriptionService.providedBy(
removeAllProxies(clean_subscribable)):
try:
context = zapi.getService(self, EventSubscription)
# we do this instead of getNextService because the order
# of unbinding and notification of unbinding is not
# guaranteed
while removeAllProxies(context) in (
clean_subscribable, clean_self):
context = getNextService(context, EventSubscription)
except ComponentLookupError:
pass
else:
context.subscribe(self)
=== Zope3/src/zope/app/event/__init__.py 1.11 => 1.12 ===
--- Zope3/src/zope/app/event/__init__.py:1.11 Tue Mar 2 13:50:59 2004
+++ Zope3/src/zope/app/event/__init__.py Thu Mar 11 03:14:02 2004
@@ -14,14 +14,13 @@
"""
$Id$
"""
-
-from zope.component import getService
+from zope.app import zapi
from zope.app.services.servicenames import EventPublication
from zope.app.event.interfaces import IEvent
from zope.app.event.globalservice import eventPublisher
def getEventService(context): # the "publish" service
- return getService(context, EventPublication)
+ return zapi.getService(context, EventPublication)
def publish(context, event):
return getEventService(context).publish(event)
=== Zope3/src/zope/app/event/configure.zcml 1.7 => 1.8 ===
--- Zope3/src/zope/app/event/configure.zcml:1.7 Tue Mar 2 13:50:59 2004
+++ Zope3/src/zope/app/event/configure.zcml Thu Mar 11 03:14:02 2004
@@ -1,18 +1,53 @@
<configure
- xmlns='http://namespaces.zope.org/zope'
- xmlns:event="http://namespaces.zope.org/event"
->
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:event="http://namespaces.zope.org/event"
+ >
+
+ <!-- For backward compatibility -->
+
+ <modulealias
+ module="zope.app.event.localservice"
+ alias="zope.app.services.event"
+ />
+
+ <modulealias
+ module=".interfaces"
+ alias="zope.app.interfaces.services.event"
+ />
+
<serviceType
- id='EventPublication'
- interface='zope.app.event.interfaces.IPublisher' />
+ id="EventPublication"
+ interface="zope.app.event.interfaces.IPublisher" />
<service
- serviceType='EventPublication'
- component='zope.app.event.globalservice.eventPublisher' />
+ serviceType="EventPublication"
+ component="zope.app.event.globalservice.eventPublisher" />
<event:subscribe
subscriber=".objectevent.objectEventNotifierInstance"
event_types="zope.app.event.interfaces.IObjectEvent" />
+
+
+<!-- Local Event Service -->
+<content class=".localservice.EventService">
+ <factory
+ id="Events"
+ />
+ <require
+ permission="zope.View"
+ attributes="publish notify"
+ />
+ <require
+ permission="zope.ManageServices"
+ attributes="bound unbound subscribe unsubscribe subscribeOnBind
+ unsubscribedFrom subscribedTo"
+ />
+</content>
+
+<serviceType
+ id="Subscription"
+ interface=".interfaces.ISubscriptionService"
+ />
</configure>
=== Zope3/src/zope/app/event/interfaces.py 1.2 => 1.3 ===
--- Zope3/src/zope/app/event/interfaces.py:1.2 Wed Mar 3 04:15:26 2004
+++ Zope3/src/zope/app/event/interfaces.py Thu Mar 11 03:14:02 2004
@@ -15,7 +15,6 @@
$Id$
"""
-
from zope.interface import Interface, Attribute
class IEvent(Interface):
@@ -264,6 +263,55 @@
subscribed. The second element is the event_type subscribed.
The third is the filter subscribed.
"""
+
+class ISubscriptionService(ISubscribable):
+ """A Subscribable that implements the Subscription service."""
+ def unsubscribe(reference, event_type, filter=None):
+ '''See ISubscribable.unsubscribe
+
+ In addition, if the reference cannot be unsubscribed in this service,
+ pass this on to the next service.
+ '''
+
+ def unsubscribeAll(reference, event_type=IEvent, local_only=False):
+ '''See ISubscribable.unsubscribeAll
+
+ If local_only is True, only subscriptions to this event service
+ instance are removed.
+ Otherwise, the unsubscribeAll request is passed on to the next
+ service.
+ '''
+
+ def resubscribeByHubId(reference):
+ '''See ISubscribable.resubscribeByHubId
+
+ In addition, the request is passed on to the next service.
+ '''
+
+ def resubscribeByPath(reference):
+ '''See ISubscribable.resubscribeByPath
+
+ In addition, the request is passed on to the next service.
+ '''
+
+ def iterSubscriptions(reference, event_type=IEvent, local_only=False):
+ '''See ISubscribable.iterSubscriptions
+
+ If local_only is True, only subscriptions to this event service
+ instance are returned.
+ Otherwise, after subscriptions to this event service, subscriptions
+ to the next event service are returned.
+ '''
+
+class IEventChannel(ISubscribable, ISubscriber):
+ """Interface for objects which distribute events to subscribers. """
+
+class IEventService(ISubscriptionService, IPublisher):
+ """Local event service implementation.
+
+ Offers the Events and Subscription services.
+ """
+
class IObjectEvent(IEvent):
"""Something has happened to an object.
More information about the Zope3-Checkins
mailing list