-
Notifications
You must be signed in to change notification settings - Fork 85
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 ObserverGraph with NamedTraitObserver #976
Conversation
traits/observers/_observer_path.py
Outdated
# Thanks for using Enthought open source! | ||
|
||
|
||
class ObserverPath: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why I only realized this now: But this object actually represents many "paths", not just one.
An alternative name welcome...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would ObserverTree
be appropriate? ObserverGraph
? ObserverTree
certainly helps me when looking at the code: then I'm not surprised to see contents for this node along with a list of child nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ObserverGraph
? Or even ObserverDGraph
? Trees can't have cycles and I am not sure we can confidently rule that possibility out at the moment...
# is also available online at http://www.enthought.com/licenses/BSD.txt | ||
# | ||
# Thanks for using Enthought open source! | ||
from traits.observers._interfaces import IObserver |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On naming, should the module be named _iobserver
and we have one module per interface object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One module per IObserver
subclass sounds fine to me, especially since we have a traits.observers
subpackage (so we won't be cluttering the main traits
package).
So _named_trait_observer
seems like a fine name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up naming this module to _i_observer
(not the _iobserver
I initially proposed), to be inlined with naming in pyface (maybe elsewhere too).
traits/observers/_observer_path.py
Outdated
# Remove loops | ||
self_nexts = set(path for path in self.nexts if path is not self) | ||
other_nexts = set(path for path in other.nexts if path is not other) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also handle loops later when we implement the methods for supporting recursion.
Handling just the loop back to self is not enough actually, considering the example of root.[left,right]*.value
, there are actually two paths in the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#969 attempted an alternative for handling cycles. I will remove this loop handling here.
Codecov Report
@@ Coverage Diff @@
## master #976 +/- ##
=======================================
Coverage 74.92% 74.92%
=======================================
Files 54 54
Lines 6477 6477
Branches 1280 1280
=======================================
Hits 4853 4853
Misses 1253 1253
Partials 371 371 Continue to review full report at Codecov.
|
def __hash__(self): | ||
""" Return the hash of this ObserverGraph.""" | ||
return hash( | ||
(type(self), self.node, frozenset(self.children)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously I had hash(frozenset(self.children))
here, the hash is a bit redundant, and is removed.
Thank you for the review! |
One question about the use of sets here: will the unpredictability of the set ordering leak into user-visible behaviour (e.g., notifiers firing in different orders from run to run)? While it's true that well-written code shouldn't care about the order in which listeners are fired, I think we probably do still want to have predictability here. |
One other thought on the set ordering: right now that ordering should be genuinely unpredictable from run to run, thanks to the inclusion of the types themselves in the |
Good question. In order to address that, I need to understand if traits make any promises about (1) the order of notifications and (2) whether the order of notifications is deterministic. For (1), what order is promised? Before |
No promises, except that it's known through folklore that static listeners (those arising from Nevertheless, we've definitely seen code that happens to work only because listeners happen to be fired in the right order. And with developers often working on macOS or Linux but deploying on Windows, I'd like to be sure that we don't end up in a situation where the behaviour is consistent from run to run on local machines, but differs from platform to platform. |
Related doc question: Should it be documented that users should not make assumptions on the order for when the change handlers are called? e.g. the following may assume
|
Yes, should definitely be documented, and that would be a nice example to include in that documentation. |
And if the order concern is about interactions among change handlers, then I think the ordering of I pushed some new commits. See what you think. |
To clarify with an example, the "children" are
So the ordering will affect whether |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM; one nitpick and one suggestion for a minor change
# is also available online at http://www.enthought.com/licenses/BSD.txt | ||
# | ||
# Thanks for using Enthought open source! | ||
import abc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a style nitpick: could we keep a blank line separating the copyright header from the module body?
) | ||
|
||
|
||
IObserver.register(NamedTraitObserver) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: use this as a class decorator: @IObserver.register
, to better match the familiarity of the way we use @provides
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that might be more readable too!
LGTM! |
Thanks! Merging. |
This PR implements a few low-level objects required for implementing the new observer framework described in EEP-3.
Contribute towards #977
This PR mainly introduces the following:
ObserverPath
ObserverGraph
:A data structure that represents the attribute paths for traits being observed on an HasTraits instance
The following is included for context, I am happy to move them out to a separate PR
IObserver
:Interface for an observer. Currently it requires
__eq__
and__hash__
as are required forObserverPath
ObserverGraph
.NamedTraitObserver
:An observer that can be used as a node in
ObserverPath
ObserverGraph
.Checklist
docs/source/traits_api_reference
)All the modules introduced here are private. I don't think the API reference includes private modules, but I could be wrong.
docs/source/traits_user_manual
)All the modules introduced here are private.
traits-stubs
Nothing introduced in this PR are trait-ed.
Note:
The proof-of-concept PR is still being reviewed in #942. So there is still a chance this PR will need to be closed altogether or reworked.