Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add notifier for wrapping user handler for the observer framework #1000

Merged
merged 63 commits into from
May 4, 2020

Conversation

kitchoi
Copy link
Contributor

@kitchoi kitchoi commented Apr 9, 2020

Targeting #977

This PR

  • Implements TraitEventNotifier for wrapping user's handler in the observer system. It used to be called TraitObserverNotifier in the PoC branch in [POC] Listener rework for EEP-3 #942.
  • Adds the IObservable interface. It used to be called INotifiableObject in [POC] Listener rework for EEP-3 #942. I realized the previous naming was a bit misleading, so I changed it. The interface is not used here imported anywhere yet. But because TraitEventNotifier uses it as a dependency, it is added here. In a later PR where we implement the observers, it will need to be turned into an abstract base class so that we can register virtual subclasses to it.
  • Adds push_exception_handler and pop_exception_handler similar to the ones we have in traits.trait_notifiers. The ones in trait_notifiers are not compatible because the handlers expect different call signatures. Furthermore this observer framework should be independent of the on_trait_change framework.

Currently all the modules are private (module names have a preceding underscore). We can opt-in bits of these to the public arena later.

Edited: #1023 may provide more context. The TraitEventNotifier is typically returned by the get_notifier method in IObserver.

Checklist

  • Tests
  • Update API reference (docs/source/traits_api_reference) All things private here.
  • Update User manual (docs/source/traits_user_manual) All things private here.
  • Update type annotation hints in traits-stubs. All things private here and nothing is trait-ed.

@codecov-io
Copy link

codecov-io commented Apr 9, 2020

Codecov Report

Merging #1000 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1000   +/-   ##
=======================================
  Coverage   76.22%   76.22%           
=======================================
  Files          58       58           
  Lines        6579     6579           
  Branches     1278     1278           
=======================================
  Hits         5015     5015           
  Misses       1218     1218           
  Partials      346      346           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fe7e4ab...fe7e4ab. Read the comment docs.

dispatcher : callable(handler, event)
A callable for dispatching the handler, e.g. on a different
thread or on a GUI event loop. ``event`` is the object
created by the event factory.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit tempted to group handler, target, dispatcher into another object. They do always go together in other places. That object can provide the methods for equals and for whether or not the weakrefs are still live.

We could also refactor this later when all the other pieces are here.

self.target = weakref.ref(target)

if isinstance(handler, types.MethodType):
self.handler = weakref.WeakMethod(handler)
Copy link
Contributor Author

@kitchoi kitchoi Apr 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously (in the PoC) there was a callback in both weakref for removing this notifier from the observable when either the handler or the target is deleted. That requires keeping a reference to the list of notifiers in the observable. If we don't do the cleanup, we may have some silenced notifiers lying around if the code that puts up the notifiers don't do a good job of taking them down (e.g. traitsui). This may be a bit inefficient, but I am not sure how common this scenario is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

19 days after making this comment: I am now more in favour of not having a weakref callback for removing the notifier. Without the weakref callback, there is only one way to remove the notifier: remove_from. Currently remove_from raises an exception if the notifier to be removed is not found. This is very helpful for catching programming error. If the code is right, every call to add_to should be undone by a call to remove_from, like open bracket and close bracket. If we had a weakref callback to remove the notifier from the observable, this introduces a second way for notifiers to be removed, from a random thread, and remove_from can no longer raise an exception if the notifier cannot be found.

return False
return (
self.handler() is other.handler()
and self.target() is other.target()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another discussion point: I think currently if one calls on_trait_change with the same arguments except for dispatch, the first one wins:

foo.on_trait_change(handler, "name", dispatch="same")
foo.on_trait_change(handler, "name", dispatch="ui")

The second call is ignored because "dispatch" is not considered for differentiating notifiers. Here we could differentiate them, and I am tempted to think that we should. In other words, calling the following will result in two notifiers, one to be dispatched on the same thread, and the second one to be dispatched on the GUI event loop:

obj.observe(handler, "name", dispatch="same")
obj.observe(handler, "name", dispatch="ui")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in favour of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I will add this one line for comparing the dispatch callable (and its test).

dispatcher : callable(handler, event)
A callable for dispatching the handler, e.g. on a different
thread or on a GUI event loop. ``event`` is the object
created by the event factory.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following comment is still valid but GitHub marked it outdated because I moved the docstring after making the comment: #1000 (comment) :

I am a bit tempted to group handler, target, dispatcher into another object. They do always go together in other places. That object can provide the methods for equals and for whether or not the weakrefs are still live.
We could also refactor this later when all the other pieces are here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this sentiment, but also that this is sufficiently deep internals that we can change it later without major issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right this is not going to affect the API users will interact with, so I'd be happy to do the refactoring in a separate PR, I will open an issue for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #1063

Copy link
Contributor

@corranwebster corranwebster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks basically good to me. If there are things that you think can be improved about the internals, but don't want to fold into this PR, open issues for them.

Copy link
Contributor Author

@kitchoi kitchoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @corranwebster!

dispatcher : callable(handler, event)
A callable for dispatching the handler, e.g. on a different
thread or on a GUI event loop. ``event`` is the object
created by the event factory.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right this is not going to affect the API users will interact with, so I'd be happy to do the refactoring in a separate PR, I will open an issue for that.

return False
return (
self.handler() is other.handler()
and self.target() is other.target()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I will add this one line for comparing the dispatch callable (and its test).

Copy link
Contributor

@ievacerny ievacerny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main code LGTM, just a couple of comments/questions about tests.

Copy link
Contributor Author

@kitchoi kitchoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks both.

@kitchoi
Copy link
Contributor Author

kitchoi commented May 4, 2020

Thank you both! Merging and then I will go on to fix merge conflict with #1007...

@kitchoi kitchoi merged commit 4145b41 into master May 4, 2020
@kitchoi kitchoi deleted the traits-observe-notifier branch May 4, 2020 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants