diff --git a/docs/module_docs.py b/docs/module_docs.py index 5aff0eb..3687728 100644 --- a/docs/module_docs.py +++ b/docs/module_docs.py @@ -52,7 +52,8 @@ def process_docstring(app, what, name, obj, options, lines): self.doc = setting[1] else: self.name = setting - if self.name in cls.required: + _, required = cls.get_merged_settings() + if self.name in required: self.required = True elif hasattr(cls, self.name): default = getattr(cls, self.name) @@ -83,7 +84,7 @@ def process_docstring(app, what, name, obj, options, lines): lines.append(".. rubric:: Settings") lines.append("") - settings = [Setting(obj, setting) for setting in obj.settings] + settings = [Setting(obj, setting) for setting in obj.get_merged_settings()[0]] lines += map(str, settings) diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index c9b66d9..e08db08 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -8,11 +8,12 @@ class Module(SettingsBase): output = None position = 0 - settings = ('on_leftclick', "Callback called on left click (string)", - 'on_rightclick', "Callback called on right click (string)", - 'on_upscroll', "Callback called on scrolling up (string)", - 'on_downscroll', "Callback called on scrolling down (string)", - ) + settings = ( + ('on_leftclick', "Callback called on left click (string)"), + ('on_rightclick', "Callback called on right click (string)"), + ('on_upscroll', "Callback called on scrolling up (string)"), + ('on_downscroll', "Callback called on scrolling down (string)"), + ) on_leftclick = None on_rightclick = None diff --git a/i3pystatus/core/settings.py b/i3pystatus/core/settings.py index 9406b82..fbf0dc0 100644 --- a/i3pystatus/core/settings.py +++ b/i3pystatus/core/settings.py @@ -37,6 +37,24 @@ class SettingsBase: log_level = logging.NOTSET logger = None + @classmethod + def get_merged_settings(cls): + def unique(settings): + def name(s): + return s[0] if isinstance(s, tuple) else s + seen = set() + return [setting for setting in settings if not ( + name(setting) in seen or seen.add(name(setting)))] + + settings = tuple() + required = tuple() + # getmro returns base classes according to Method Resolution Order, + # which always includes the class itself as the first element. + for base in inspect.getmro(cls): + settings += getattr(base, "settings", tuple()) + required += getattr(base, "required", tuple()) + return unique(settings), required + def __init__(self, *args, **kwargs): def get_argument_dict(args, kwargs): if len(args) == 1 and not kwargs: @@ -45,21 +63,12 @@ class SettingsBase: return args[0] return kwargs - def merge_with_parents_settings(): - settings = tuple() - # getmro returns base classes according to Method Resolution Order - for cls in inspect.getmro(self.__class__): - if hasattr(cls, "settings"): - settings = settings + cls.settings - return settings + self.__name__ = "{}.{}".format(self.__module__, self.__class__.__name__) - self.__name__ = "{}.{}".format( - self.__module__, self.__class__.__name__) - - settings = merge_with_parents_settings() + settings, required = self.get_merged_settings() settings = self.flatten_settings(settings) - sm = KeyConstraintDict(settings, self.required) + sm = KeyConstraintDict(settings, required) settings_source = get_argument_dict(args, kwargs) protected = self.get_protected_settings(settings_source)