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

Warn when concrete_descendents clobbers classes #1035

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

maximlt
Copy link
Member

@maximlt maximlt commented Feb 20, 2025

No description provided.

@maximlt maximlt marked this pull request as draft February 20, 2025 22:52
Copy link

codecov bot commented Feb 20, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 87.28%. Comparing base (30edc3f) to head (bb72745).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1035      +/-   ##
==========================================
+ Coverage   87.26%   87.28%   +0.01%     
==========================================
  Files           9        9              
  Lines        4939     4945       +6     
==========================================
+ Hits         4310     4316       +6     
  Misses        629      629              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@maximlt
Copy link
Member Author

maximlt commented Feb 21, 2025

@jlstevens the PyPy tests have failed which raised an interesting question. concrete_descendents is used internally in ClassSelector.get_range (see the code below). One of the tests that fail defines a parameter with ClassSelector(class_=(int, str)) and then calls get_range(). On PyPy, there's somehow an enum that appears twice in the concrete descendants of int, leading to that warning being emitted.

I feel like if we want to fix this for good, we need to update get_range to return a list of descendents and no longer a dict, i.e. use descendents(parent, concrete=True) instead of concrete_descendents(parent). To implement that:

  • we could add a new as_list keyword to get_range that is False by default
  • warn whenever get_range is called with as_list is False, stating that in the future as_list will become True by default, users can opt-in by already calling get_range(as_list=True).

What do you think?

class ClassSelector(Parameter):
    ...
    def get_range(self):
        """
        Return the possible types for this parameter's value.

        (I.e. return `{name: <class>}` for all classes that are
        concrete_descendents() of `self.class_`.)

        Only classes from modules that have been imported are added
        (see concrete_descendents()).
        """
        classes = self.class_ if isinstance(self.class_, tuple) else (self.class_,)
        all_classes = {}
        for cls in classes:
            all_classes.update(concrete_descendents(cls))
        d = OrderedDict((name, class_) for name,class_ in all_classes.items())
        if self.allow_None:
            d['None'] = None
        return d

EDIT: This is following a discussion started here #1027 (comment)

@jbednar
Copy link
Member

jbednar commented Feb 21, 2025

I don't know the cleanest way to get out of this mess, but presumably it's also worth (briefly) considering a multidict, which would preserve the interface but allow duplicate keys? That might well cause other problems later, of course, if someone tries to use the multidict as a regular dict.

@maximlt
Copy link
Member Author

maximlt commented Feb 24, 2025

True, I didn't consider multidict until now. I'll chat with Jean-Luc this week more about all this!

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