zope.interface
zope.interface copied to clipboard
How to change the order of subscribers?
BUG/PROBLEM REPORT (OR OTHER COMMON ISSUE)
What I did:
In a Plone addon, I registered a subscriber:
<subscriber for="my.products.contents.mycontent.IMyContent
OFS.interfaces.IObjectWillBeRemovedEvent"
handler=".handler.myhandler" />
What I expect to happen:
I would like this event of mine to be executed before the subscribers registered by Zope. When I delete an object of mine, the notification occurs:
notify(ObjectWillBeRemovedEvent(ob, self, id))
In that notification, the event:
<subscriber
for=".interfaces.IContentish
zope.interface.interfaces.IObjectEvent"
handler=".CMFCatalogAware.handleContentishEvent"
/>
runs before mine. How do I get mine to run sooner?
What actually happened:
Event registered by Zope runs before mine.
What version of Python and Zope/Addons I am using:
Python: 3.8.12 Zope 4.6.3 zope.interface 5.4.0
Wesley Barroso Lopes wrote at 2022-3-28 11:57 -0700:
... In a Plone addon, I registered a subscriber:
<subscriber for="my.products.contents.mycontent.IMyContent OFS.interfaces.IObjectWillBeRemovedEvent" handler=".handler.myhandler" />
What I expect to happen:
I would like this event of mine to be executed before the subscribers registered by Zope.
Subscribers are a special kind of subscriptions.
The documentation specifies the order in which subscriptions are returned.
It is influenced by the registration order.
Thus, controling the registration order allows you to influence
the subscription order. The site.zcml
determines the top level
registrations, each of its directives can recursively cause
further registrations. Thus, it is possible (though quite difficult)
to ensure that your registrations are the first or last ones
(I do not know what is necessary to achieve your goal -- read the
documentation or try it out).
The other possibility is to directly change
zope.event
's subscribers
data structure.
The event mechanism is implented by (--> zope.event:__init__
):
subscribers = []
def notify(event):
""" Notify all subscribers of ``event``.
"""
for subscriber in subscribers:
subscriber(event)
Zope
puts a function dispatch
into subscribers[0]
which
delegates the event processing to the subscription registries
of zope.component/zope.interface
.
You can put your own "subscriber" in zope.event.subscribers
before
the default event processor (i.e. the dispatch
above).
This will guarantee that its effects come before the standard ones.
Unfortunately, subscriptions (unlike adapters
and 'utilities) cannot be named. It is therefore not easy to use the
zope.component/zope.interface` registries for your own
subscriber function. You might need to create your own registries
for this purpose: they can work like the standard registries
but have their own content.
You could also replace dispatch
.
Your replacement might for example determine the sequence
of all applicable subscriptions and then decide about the order
in which they should get applied.
Neither approach is easy:
zope.event/zope.interface
simply lacks support to easily
control the order in which event handlers are executed.
Neither approach is easy:
zope.event/zope.interface
simply lacks support to easily control the order in which event handlers are executed.
I just couldn't in any way put my subscriber ahead of Zope's. This would be a great feature for Zope.
What I'm trying to do is prevent the deletion of content, under certain conditions. So I thought I'd make a subscriber that checks this and raise an exception in case the object can't be deleted. It's just that there are subscribers that run before mine, that do things to the object, like uncatalog from catalog.
Wesley Barroso Lopes wrote at 2022-3-29 12:26 -0700:
What I'm trying to do is prevent the deletion of content, under certain conditions.
For this use case, it is not necessary that your subscriber runs before the normal subscribers: your subscriber will be informed about the deletion -- and if it objects, it can raise an exception. Then the transaction will be aborted and the deletion will not take effect.
It will not work for a Zope Manager
: someone decided that
exceptions during a deletion by a Manager
should not prevent
the deletion. But, non Manager
deletions can be prevented in this
way.
You can use a specific exception and register a corresponding error view: to inform the user about the prevented deletion. Without such an error view, the user will see a standard error message.
You can use a specific exception and register a corresponding error view: to inform the user about the prevented deletion.
If I catch the exception to display a friendly message, there is no rollback of things done by subscribers, like uncatalog.
Wesley Barroso Lopes wrote at 2022-3-29 15:17 -0700:
You can use a specific exception and register a corresponding error view: to inform the user about the prevented deletion.
If I catch the exception to display a friendly message, there is no rollback of things done by subscribers, like uncatalog.
You do not catch the exception: an error/exception view does not change transaction handling -- even if it "fires" the transaction is aborted.
@d-maurer remembering that I'm in the Plone context. In Plone, I can delete multiple objects at the same time. So it is necessary to catch the error, because some objects can be deleted and others not, in the same request. See:
https://github.com/plone/plone.app.content/blob/a4a7b277bd9b50b9755b23ea72d973a657e9b3f2/plone/app/content/browser/contents/init.py#L88-L104
self.action(obj)
it could be a delete:
https://github.com/plone/plone.app.content/blob/a4a7b277bd9b50b9755b23ea72d973a657e9b3f2/plone/app/content/browser/contents/delete.py#L66
Wesley Barroso Lopes wrote at 2022-3-30 05:11 -0700: @.*** remembering that I'm in the Plone context. In Plone, I can delete multiple objects at the same time. So it is necessary to catch the error, because some objects can be deleted and others not, in the same request. See:
Then, I am in doubt that your design it right. Apparently, you want to prevent (under the hood) the deletion of some objects while in the same transaction you want to delete other objects.
Deletion has a semantics: if it succeeds, the deleted object should no longer be there. If it fails, the transaction should get aborted -- to keep a consistent persistent state. Do not call Zope/Plone's deletion methods when you do not want this semantics.
You can use transaction.savepoint
to emulate nested
transactions. This would allow you to catch exceptions
from parts of your request processing and manually roll back
the persistent changes from those parts.
Still, you could not perform deletion of some objects
and prevent deletion for other objects in those parts
but other parts could do whatever they want (including
deletion of objects not prevented by your special logic).
but other parts could do whatever they want (including deletion of objects not prevented by your special logic).
Yes, that's why it would be good for validation to be on the subscriber. But for this to work, my subscriber would have to be run before Zope's subscribers. That's why it would be interesting to be able to choose the order of execution of the subscribers. I think I'll open an issue on zope.configuration requesting this feature