Rapptz / discord-ext-menus

MIT License
234 stars 87 forks source link

Check if parent classes should also inherit buttons #34

Closed ghost closed 2 years ago

ghost commented 3 years ago

Summary

When creating a class from a menu that does not inherit buttons (let's call it class A), and then create another class (let's call it class B) that inherits that class A, class B inherits all buttons from parent classes.

Example:

  1. Let's create a base menu class, let's call it MenuPagesBase that inherits MenuPages:
    # We explicitly don't inherit buttons!
    class MenuPagesBase(MenuPages, inherit_buttons=False):
    # We add some buttons here.
  2. Then we create another class that inherits MenuPagesBase, let's call it MyCustomMenuPages:
    class MyCustomMenuPages(MenuPagesBase):
    # We do fun stuff here.

    When using MyCustomMenuPages, the buttons that MenuPagesBase should not inherit are inherited, see:

image

The reason and the solution

This is due to the fact that the _MenuMeta class does not check whether parent classes should also inherit the buttons, therefore, it only checks whether the current class should inherit. See the relevant part of the source code by clicking here.

The solution to this problem is to store the value in the class that says whether or not it should inherit (I called it __inherit_buttons__, to maintain the default with __menu_buttons__), and then check in the iteration if it should continue to iterate looking for new buttons:

inherit_buttons = kwargs.pop('inherit_buttons', True)
# We need to store this value to find out if the class
# should inherit buttons when a subclass is created.
new_cls.__inherit_buttons__ = inherit_buttons

if inherit_buttons:
    # Since order matters, we can't use `reversed()` here, since we need to 
    # know whether the last inherited classes should inherit the "old" ones.
    for base in new_cls.__mro__:
        for elem, value in base.__dict__.items():
            try:
                value.__menu_button__
            except AttributeError:
                continue
            else:
                buttons.append(value)

        # We check if parent classes should also inherit buttons
        # If they don't, then we stop the loop.
        should_inherit = getattr(base, '__inherit_buttons__', True)
        if not should_inherit:
            break
else:
    for elem, value in attrs.items():
        try:
            value.__menu_button__
        except AttributeError:
            continue
        else:
            buttons.append(value)

new_cls.__menu_buttons__ = buttons
return new_cls

Conclusion

Any and all suggestions are welcome, feel free to judge the code and suggest changes.