Skip to content

Conversation

@maximlt
Copy link
Member

@maximlt maximlt commented Feb 20, 2025

concrete_descendents now warns when class name clobbering is detected. We recommend using descendents(cls, concrete=True) instead which returns a list of concrete classes.

ClassSelector.get_range() will not warn on clobbering, this is the current, unchanged behavior. Options to fix this would include updating somehow the returned type to a list of tuples, or creating another method like range that doesn't suffer from this problem. I'm not going to take care of that now but will record it in an issue.

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

codecov bot commented Feb 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.18%. Comparing base (a35358b) to head (4a9f3f5).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1035      +/-   ##
==========================================
+ Coverage   89.16%   89.18%   +0.01%     
==========================================
  Files           9        9              
  Lines        4679     4686       +7     
==========================================
+ Hits         4172     4179       +7     
  Misses        507      507              

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@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!

@maximlt
Copy link
Member Author

maximlt commented May 7, 2025

Reading this again #1035 (comment), I no longer think that's the right approach as I am not confident that there's a sane way to change the type of what Selector.get_range() returns.

Instead, I'd suggest either:

  1. Status quo for Selector.get_range() (e.g. by just swallowing the warnings emitted by the updated concrete_descendents or by using an implementation that doesn't emit a warning)
  2. Update concrete_descendents to no longer clobber the keys but instead append the duplicate ones with an increment (MyClass, MyClass1, MyClass2)

I might just go for 1) now to unblock this.

@maximlt maximlt marked this pull request as ready for review October 27, 2025 15:29
@maximlt
Copy link
Member Author

maximlt commented Oct 27, 2025

Unrelated test failures, merging.

@maximlt maximlt merged commit 56cabe7 into main Oct 27, 2025
12 of 15 checks passed
@maximlt maximlt deleted the concrete_descendents_warn branch October 27, 2025 16:26
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