[Zope3-dev] best way to deal with dependency errors

Jim Fulton jim at zope.com
Tue Feb 10 04:38:26 EST 2004


Anthony Baxter wrote:
> At the moment if you try to delete a registered service or utility,
> you get a system error. The traceback shows a dependency error on
> the registration object. This could be handled by one of two ways:
> 
> before deleting, auto-delete the registration(s)
> or
> make the delete code catch dependencyerror and present a nice
> message. 
> 
> Which is preferred? Or both?

Neither. :)

I want a more general solution. I'm willing to live with the current
blight until that happens, especially since I'd like to get rid
(or redo) the dependency framework soon.

Here's the issue: when we generate an event, there may be more than
one subscriber that has an issue with it. There needs to be a way of
resolving these issues and the code generating the event shouldn't
be responsible for resolving the issues specifically, although there
should be a general way for it to do so.

The thing that makes it hard is that we want to be able to collect
multiple issues.  Exceptions don't work very well for this, unless
there is something willing to collect them together.

One idea that Steve and I came up with was to generate tentative events
to collect issues. Something like:

   # publish a tentative event
   event = Tentative(ObjectRemovedEvent(ob))
   publish(event)
   if event,issues:
      # There were issues, do something
      ....

   # Now that we've dealt with the issues, publish the event
   publish(event.event)

If we want to raise issues, we create two subscribers. One subscriber
subscribes to tentative events and one subscribes to normal events.
The tentative event subscribers can add issues to the event to be resolved:

   def notify_tentative_remove(tentative_event):
      real_event = tentative_event.event
      if has_dependents(event.object):
          tentative_event.addIssue(DependencyIssue(event.object))


This seems rather complicated for the code that generates events,
especially since, I suppose, it could, potentially, apply to most events.

Another possibility is to provide a specialized event channel that
can help with the tentative events.  The application code would just publish
the normal event. The channel gets the event and sends a tentative event
to it's subscribers. If there are no issues it sends the regular event.

If there are issues, the event channel could just raise a special
exception (e.g. EventRejected).  The exception could contain the issues.
At that point, the application code can catch the exception
and try to deal with the issues.  How would the application code deal with
the issues?  Well, in a number of ways. First, let's assume that EventRejected
exceptions have event and issues attributes.  The issues attribute is an iterable
of issues.

We can suppose some properties of issues:

- We can display them by displaying views of them

- Some issues might have (or be adaptable to objects that have) a
   reolve method.  If we have a resolve method, we can call it to resolve the
   issue.

- Some issues might require confirmation. If this is the case, the user
   should be asked to confirm the action necessary to resolve the issue.
   if this is the case, the application should display the issue to the user
   and get them to confirm it before calling the fixup method.

- Some issues may be unresolvable.  I'm not sure we should allow this.
   Perhaps this could be useful in the case of issues that can't be simply
   resolved but that could be resolved through some more involved action
   by the user.  This is a little bit risky, as you can end up in a position
   in which you can't resolve an issue and can't make progress.

If all of the issues are resolvable without confirmation, then the event
channel can just resolve them.  I suppose that if one of the events are
not resolveable, the channel could raise a different exception
(e.g. EventError) that isn't caught by the app.  In that case, the
app only needs to worry about confirmation.

So, careful application code might look something like this:

    try:
       publish(ObjectRemovedEvent(ob))
    except EventRejected, exc:
       # we have some issues that can be resolved, but these
       # need to be confirmed.  We'll check to see if the user has
       # confirmed the issues.  We'll call a helper view that checks
       # this for us:
       if getViewProviding(exc, request, IVerifyConfirmation):
           # The user has said OK, do it. Publish a confirmed
           # event:
           publish(ConfirmedEvent(ObjectRemovedEvent(ob), exc.issues)
       else:
           # Raise a confirmation exception that, when viewed displays
           # a conformation message and returns to the request URL.
           raise ConfirmationRequired(exc)

The ConfirmationRequired exception has a view that:

   - Displays the issues and a confirmation button

   - Includes hidden fields that identify the issue.
     This is needed to make sure that we have confirmed all
     of the same issues when the application is reexecuted.

The IVerifyConfirmation view compares the information in the hidden
request data (setup by the ConfirmationRequired view) with the issues
in the exception.to make sure that all of the issues in the esception
have been confirmed.

When the event channnel gets a ConfirmedEvent, it:

   - Sends a tentative event to its subscribers, and collects all of the
     issues.

   - Compares the issues collected with the issues passed to it.

     o If the issues are the same, it knows that they have been confirmed
       and calls resolve on each of them

     o Otherwise, it raises EventRejected.

If the application doesn't catch the exception, then the exception will
be displayed to the user.  In doing so it will display the issues.
It will display the issues with a different view than the one used for
confirmations. The issue views could provide UIs or links to UIs for resolving
the issues.  In this way, even if the application doesn't handle the issues,
the issues could lead the user to take actions that will resolve the issues.
Having done so, the user can then redo the application action, which should
succeed.

Whew!  But I think this is workable. :)

Thoughts?

Jim

-- 
Jim Fulton           mailto:jim at zope.com       Python Powered!
CTO                  (540) 361-1714            http://www.python.org
Zope Corporation     http://www.zope.com       http://www.zope.org




More information about the Zope3-dev mailing list