diff --git a/Orange/widgets/utils/signals.py b/Orange/widgets/utils/signals.py index 58956bd774c..4511da4a37e 100644 --- a/Orange/widgets/utils/signals.py +++ b/Orange/widgets/utils/signals.py @@ -1,7 +1,13 @@ import copy +import itertools from Orange.canvas.registry.description import InputSignal, OutputSignal +# increasing counter for ensuring the order of Input/Output definitions +# is preserved when going through the unordered class namespace of +# WidgetSignalsMixin.Inputs/Outputs. +_counter = itertools.count() + class _Signal: @staticmethod @@ -61,6 +67,7 @@ def __init__(self, name, type, id=None, doc=None, replaces=None, *, multiple=False, default=False, explicit=False): flags = self.get_flags(multiple, default, explicit, False) super().__init__(name, type, "", flags, id, doc, replaces or []) + self._seq_id = next(_counter) def __call__(self, method): """ @@ -118,6 +125,7 @@ def __init__(self, name, type, id=None, doc=None, replaces=None, *, flags = self.get_flags(False, default, explicit, dynamic) super().__init__(name, type, flags, id, doc, replaces or []) self.widget = None + self._seq_id = next(_counter) def bound_signal(self, widget): """ @@ -234,8 +242,9 @@ def get_signals(cls, direction): return old_style signal_class = getattr(cls, direction.title()) - return [signal for signal in signal_class.__dict__.values() - if isinstance(signal, _Signal)] + signals = [signal for signal in signal_class.__dict__.values() + if isinstance(signal, _Signal)] + return list(sorted(signals, key=lambda s: s._seq_id)) class AttributeList(list): diff --git a/Orange/widgets/utils/tests/test_signals.py b/Orange/widgets/utils/tests/test_signals.py index 84aaf2d452c..34026b4684d 100644 --- a/Orange/widgets/utils/tests/test_signals.py +++ b/Orange/widgets/utils/tests/test_signals.py @@ -12,7 +12,8 @@ Single, Multiple, Default, NonDefault, Explicit, Dynamic, InputSignal, \ OutputSignal from Orange.widgets.tests.base import GuiTest -from Orange.widgets.utils.signals import _Signal, Input, Output +from Orange.widgets.utils.signals import _Signal, Input, Output, \ + WidgetSignalsMixin from Orange.widgets.widget import OWWidget @@ -186,6 +187,26 @@ def foo(self): self.assertIsInstance(output, OutputSignal) self.assertEqual(output.name, "another name") + def test_get_signals_order(self): + class TestWidget(WidgetSignalsMixin): + class Inputs: + input_1 = Input("1", int) + input_2 = Input("2", int) + input_3 = Input("3", int) + input_a = Input("a", object) + + class Outputs: + output_1 = Output("1", int) + output_2 = Output("2", int) + output_3 = Output("3", int) + output_a = Output("a", object) + + inputs = TestWidget.get_signals("inputs") + self.assertTrue(all(isinstance(s, Input) for s in inputs)) + self.assertSequenceEqual([s.name for s in inputs], list("123a")) + outputs = TestWidget.get_signals("outputs") + self.assertTrue(all(isinstance(s, Output) for s in outputs)) + self.assertSequenceEqual([s.name for s in outputs], list("123a")) if __name__ == "__main__": unittest.main()