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 MetadataFilter for observing traits using metadata #1095

Merged
merged 8 commits into from
May 19, 2020

Conversation

kitchoi
Copy link
Contributor

@kitchoi kitchoi commented May 18, 2020

This PR targets this item in #977:

Implement the logic for observing traits whose metadata fulfils a given criterion (depends on 12). This will support the metadata method in Expression, and the use of "+" in the text parser.

  • Add MetadataFilter to be used as the filter callable in FilteredTraitObserver. Users typically create these observers via text "+metadata", and a new filter instance will be created. The reason why we need a separate class for this filter is for supporting equality check on the filter when the user requests an existing observer to be removed.
  • See TestWithFilteredTraitObserver for how this is used with an instance of HasTraits.

Checklist

  • Tests
  • Update API reference (docs/source/traits_api_reference)
  • Update User manual (docs/source/traits_user_manual)
  • Update type annotation hints in traits-stubs

if self.metadata_name in trait.__dict__:
value = getattr(trait, self.metadata_name)
return self.value(value)
return 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.

This value filter is more than necessary for supporting the "+metadata_name" feature in the mini-language.
On the one hand, it is easy to implement, and is consistent with HasTraits.traits support on filtering metadata using the values. On the other hand, this is a feature I am not sure if we have a use case for (but maybe we do, they don't exist because users have been working around its nonexistence.)

Furthermore, the filtering here is not the exactly same as that in HasTraits.traits: HasTraits.traits does not differentiate when a metadata is defined or not. If the metadata is undefined, it assumes it existed with None as the value and passes it to the filter and I don't think we wanted the same behaviour here.

Copy link
Contributor Author

@kitchoi kitchoi May 18, 2020

Choose a reason for hiding this comment

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

We could remove this value and deem this MetadataFilter for checking for metadata existence only, for now. If one really needs to filter the metadata value, filter_(lambda name, trait: trait.my_metadata_name is not None and trait.my_metadata_name > 10.0) can still be defined for that use case (note that is not None check), and if it becomes common enough, adding this value filter back is easy.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed; that sounds good to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

value is removed. This is now ready for review again.

@kitchoi kitchoi changed the title Add MetadataFilter for filtering traits with the requested metadata Add MetadataFilter for filtering traits using metadata May 18, 2020
@kitchoi kitchoi changed the title Add MetadataFilter for filtering traits using metadata Add MetadataFilter for observing traits using metadata May 18, 2020
@kitchoi kitchoi requested a review from mdickinson May 18, 2020 14:32
@codecov-commenter
Copy link

codecov-commenter commented May 19, 2020

Codecov Report

Merging #1095 into master will decrease coverage by 1.91%.
The diff coverage is 66.51%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1095      +/-   ##
==========================================
- Coverage   76.15%   74.24%   -1.92%     
==========================================
  Files          54       79      +25     
  Lines        6493     8479    +1986     
  Branches     1263     1610     +347     
==========================================
+ Hits         4945     6295    +1350     
- Misses       1205     1806     +601     
- Partials      343      378      +35     
Impacted Files Coverage Δ
traits/api.py 100.00% <ø> (+9.67%) ⬆️
traits/base_trait_handler.py 61.76% <ø> (ø)
traits/observers/_i_notifier.py 0.00% <0.00%> (ø)
traits/observers/events.py 0.00% <0.00%> (ø)
traits/traits.py 75.10% <ø> (-2.45%) ⬇️
traits/util/resource.py 15.25% <ø> (ø)
traits/observers/_generated_parser.py 51.96% <51.96%> (ø)
traits/has_traits.py 73.51% <64.70%> (+0.74%) ⬆️
traits/trait_types.py 72.29% <80.00%> (-0.16%) ⬇️
traits/observers/parsing.py 95.34% <95.34%> (ø)
... and 58 more

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 be82bc0...7a2ab5f. Read the comment docs.

self.metadata_name = metadata_name

def __call__(self, name, trait):
return self.metadata_name in trait.__dict__
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is the right test. What's in the __dict__ is an implementation detail; what matters is what happens on metadata retrieval.

For example, I'd expect the following two traits to behave the same under any metadata filtering:

>>> tr1 = Float(2.3, edible=None).as_ctrait()
>>> tr2 = Float(2.3).as_ctrait()
>>> tr2.edible == tr1.edible
True

So I think we want to retrieve getattr(trait, metadata_name) and then filter on the result. What's not clear to me is whether that filtering should be is not None or bool. What does the usual on_trait_change expression do with this?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, looks like this is the relevant code. So we want an is not None test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay. I thought the behaviour of returning None for an undefined metadata is conflating the concept of something being undefined VS something being defined with a value of None and here we only want to check for metadata existence. So it seems that the fact that these two concepts being combined is in fact the desired behaviour for metadata?

Copy link
Member

Choose a reason for hiding this comment

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

Not sure about "desired", but it is the current design. Changing that design would be an orthogonal discussion, I think.

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 see. Thanks. I will add a comment because this current design is still strange to me, not sure if I am alone in that.

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 apart from the ... in trait.__dict__ test, which I think we need to change (probably with an extra unit test to check that specifying metadata of None is equivalent to not specifying it at all).

@kitchoi kitchoi requested a review from mdickinson May 19, 2020 09:55
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

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.

None yet

3 participants