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

Implement the logic for observing a named trait #1069

Merged
merged 15 commits into from
May 12, 2020

Conversation

kitchoi
Copy link
Contributor

@kitchoi kitchoi commented May 7, 2020

This PR implements item 7 in #977 for observing a named trait.

  • Add TraitChangeEvent for wrapping (object, name, old, new). This is the object to be received by the user handler. Edited: See Add TraitChangeEvent object for wrapping CTrait change #1070
  • Add optional argument in order to support "items" special keyword in the mini-language. The "items" keyword is ambiguous about whether it means a trait named "items" or items in a container. We will use this named trait observer but set optional to true for that usage.
  • Extend NamedTraitObserver to implement IObserver interface:
    - return the instance Ctrait for adding/removing notifiers (including "maintainer")
    - return the value for the next observer, if it is defined. Care is taken to avoid evaluating default values and passing on values such as Undefined.
    - return a notifier wrapping the user's change handler so that the handler receives TraitChangeEvent
    - return a notifier (the "maintainer") for maintaining downstream observers when the trait change.

When an observer is registered, it should not trigger default initializers to be evaluated. That is consistent with the current behaviour of on_trait_change. However the implementation in on_trait_change for avoiding evaluating defaults caused other issues (e.g. #275) which aren't easy to fix. The implementation here will not cause the same issue as seen in #275 and will avoid the side effect of evaluating default at the same time.

Not in this PR:

Note that in #977 there will also be a "FilteredTraitObserver" which can be used for observing traits with an arbitrary filter function. While NamedTraitObserver(..., optional=True) could have been implemented using FilteredTraitObserver, NamedTraitObserver(..., optional=False) cannot. Raising an error when a requested trait is not found will allow us to avoid issues like #447, it will also be useful for catching human errors or for migrating from on_trait_change to observer, as the mini-language syntax has changed.

Checklist

target=target,
dispatcher=dispatcher,
remove=False,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Functions in these modules are refactored as they will be reused by FilteredTraitObserver.

target=target,
dispatcher=dispatcher,
event_factory=trait_event_factory,
prevent_event=lambda event: event.old is Uninitialized,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is consistent with the current behaviour of on_trait_change.

@kitchoi
Copy link
Contributor Author

kitchoi commented May 8, 2020

I moved the TraitChangeEvent to a separate PR in #1070 because that part is easier to review. Having that event object in the code base will allow me to get a few other PRs going in parallel.

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.

LGTM, just a couple of tiny test mistakes (I think). Docstrings look really good and the tests show the behaviour of the observer very well!

Not approving yet because I think someone else should also take a look.

ObserverGraph(
node=NamedTraitObserver(name="bar", notify=True),
),
create_observer(name="bar"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be ObserverGraph(node=create_observer(name="bar"))? (same below)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, yes you are right, thank you!

@@ -43,12 +60,153 @@ def notify(self):
"""
return self._notify

@property
def optional(self):
Copy link
Contributor

@corranwebster corranwebster May 11, 2020

Choose a reason for hiding this comment

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

Thought: should this class be a subclass of named tuple? This would get you a lot of the infrastructure for free (you wouldn't need properties and shadow attributes, __hash__ will just work, and __eq__ may also just work or need minimal wrapping.

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 generally looks, clean, although there is a possibility to reduce some code by using named tuples, I think, but it's not critical.

@kitchoi
Copy link
Contributor Author

kitchoi commented May 11, 2020

Thank you both for your review!

On namedtuple: I would want to use it if it does not also throw in __iter__, __contains__, __len__ and friends. Those are features not needed by these observer classes. By not depending on more than needed, this allows more freedom for future changes. I don't mind the boilerplate so much, it allows ample space for documentation.

@kitchoi kitchoi merged commit 240d469 into master May 12, 2020
@kitchoi kitchoi deleted the 977-named-trait-observer branch May 12, 2020 08:40
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.

3 participants