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 observe decorator and instance method #1086

Merged
merged 29 commits into from
May 19, 2020
Merged

Conversation

kitchoi
Copy link
Contributor

@kitchoi kitchoi commented May 15, 2020

This PR implements item 14 of #977

Expose the observe decorator and HasTraits.observe instance method.

Details:

  • Add observe function in the observers subpackage (to be renamed to observe too)
    - This prevents HasTraits from knowing any more internal details than necessary.
    - Maybe for discussion: This also allows advanced users to supply their own dispatcher function, e.g. gevent.spawn without requiring another release of Traits, or requiring registration mechanism
  • Add HasTraits.observe method in the traits API. It is similar to HasTraits.on_trait_change.
    • Naming: name is renamed to expression.
    • The overloaded signature: A list is supported, so that people migrating from on_trait_change don't have to combine a list of string (preferred by me) into a comma-separated string. This will be more compatible with the future counterpart of Property depends_on.
    • The overloaded signature: All strings are parsed to Expression object.
    • Previously name is optional and can be None to mean anytrait. Now expression is a required positional argument and cannot be None. The use case of anytrait can be supported by the filter_ expression (to be introduced in a future PR, related to Add an observer for observing traits using an arbitrary filter #1078).
    • The positional argument order of (handler, expression) is retained to be consistent with on_trait_change. This will lower the barrier for migrating to observe.
  • Add @observe decorator in the traits API. It is very similar to @on_trait_change in that it wraps the HasTraits.observe method and it supports post_init flag.
  • Added traits.observers.api and expanded traits.api

Checklist

  • Tests
  • Update API reference (docs/source/traits_api_reference)
  • Update User manual (docs/source/traits_user_manual) : Will be in a separate PR
  • Update type annotation hints in traits-stubs

Copy link
Member

@mdickinson mdickinson left a comment

Choose a reason for hiding this comment

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

Done with first-pass review. Could you explain a bit more how the observers dict is working in update_traits_class_dict? It's not clear to me why it makes sense to put both trait names and method names in there. (I'm not saying it doesn't make sense; just that I'm not yet seeing how it works.)

traits/ctraits.c Outdated
@@ -735,6 +735,14 @@ has_traits_init(PyObject *obj, PyObject *args, PyObject *kwds)
Py_DECREF(value);
}

/* Make sure all of the object's observers have been set up: */
value = PyObject_CallMethod(obj, "_init_trait_observers", "()");
Copy link
Member

Choose a reason for hiding this comment

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

It would be better to use NULL instead of "()" for the third argument: that's the documented way to spell "no arguments", and it avoids doing runtime parsing of the format string.

We should change the pre-existing cases of a "()" format too, but not in this PR. If you prefer to defer all the changes to a separate PR, that's fine, too.

traits/ctraits.c Outdated
if (value == NULL) {
return -1;
}

Copy link
Member

Choose a reason for hiding this comment

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

Spacing nitpick: suggest removing this blank line. (I know this spacing matches what's elsewhere in this function, but to me the Py_DECREF is part of the same conceptual code block as the call and error check.)

But this is totally a nitpick: feel free to ignore.

@@ -385,6 +394,11 @@ def update_traits_class_dict(class_name, bases, class_dict):
prefix_list = []
view_elements = {}

# Mapping from method name or trait name to list(dict)
Copy link
Member

Choose a reason for hiding this comment

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

Can you elaborate on how method names are being used here? The description here is surprising at first glance (that the keys of the dictionary could be either a method name or a trait name). How does this work, and why don't we need two separate dictionaries?

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 sorry for the confusion - the keys here are only method names.
I went ahead with the Property support in the other branches where the name (defined in the class dict) could refer to the name of a trait that is a property. Here we are not dealing with property so that half of the comment is misleading. I will remove that.

value dispatch
=========== =======================================================
``same`` Run notifications on the same thread where the change
occurs.
Copy link
Member

Choose a reason for hiding this comment

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

For ease of comparison with the "ui" case, it may also be worth documenting here that the listeners/observers are executed immediately in this case.

HasTraits.observe
"""

def decorator(function):
Copy link
Member

Choose a reason for hiding this comment

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

decorator is a generic name here; can we use something more informative? These names could potentially show up in tracebacks or during debugging. (Same for function.)

Parameters
----------
function : callable
Unbound method of a subclass of HasTraits
Copy link
Member

Choose a reason for hiding this comment

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

"Unbound method" isn't quite right here: Python 3 doesn't have unbound methods any more. What's being passed here is simply a function. Not sure what to suggest instead, though: maybe just drop the word "Unbound" and say "Method of a HasTraits subclass"?

""" Create input arguments for HasTraits.observe and attach the input
to the callable.

Metaclass will then collect this information for calling
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Metaclass will then collect this information for calling
The metaclass will then collect this information for calling

@@ -2066,6 +2260,8 @@ def on_trait_change(
"""Causes the object to invoke a handler whenever a trait attribute
matching a specified pattern is modified, or removes the association.

See also ``HasTraits.observe`` for a newer API.
Copy link
Member

Choose a reason for hiding this comment

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

Could use the Numpy doc "See Also" header here to make this more prominent.

Copy link
Member

@mdickinson mdickinson left a comment

Choose a reason for hiding this comment

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

LGTM. One docstring suggestion.

@kitchoi
Copy link
Contributor Author

kitchoi commented May 19, 2020

Thank you.

One docstring suggestion.

I think I lost it... maybe GH buried it, could you point me to it please?

@mdickinson
Copy link
Member

I think I lost it

I've lost it, too. I frequently add a comment, forget to hit the "submit" button on that comment, and then navigate away; this might have been one of those cases. Sorry.

LGTM

@kitchoi
Copy link
Contributor Author

kitchoi commented May 19, 2020

No problem. Thanks.

@kitchoi kitchoi merged commit 1fe353a into master May 19, 2020
@kitchoi kitchoi deleted the 977-observe-top-level branch May 19, 2020 08:08
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.

2 participants