Skip to content

Commit 2146396

Browse files
authored
Merge pull request #4860 from ales-erjavec/explicit-close-signal
[FIX] Use explicit ordered multiple inputs
2 parents aa647cd + 8b63366 commit 2146396

File tree

18 files changed

+523
-287
lines changed

18 files changed

+523
-287
lines changed

Orange/widgets/data/owconcatenate.py

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
Concatenate (append) two or more datasets.
66
77
"""
8-
98
from collections import OrderedDict, namedtuple, defaultdict
109
from functools import reduce
1110
from itertools import chain, count
12-
from typing import List
11+
from typing import List, Optional, Sequence
1312

1413
import numpy as np
1514
from AnyQt.QtWidgets import QFormLayout
@@ -21,9 +20,9 @@
2120
from Orange.widgets import widget, gui, settings
2221
from Orange.widgets.settings import Setting
2322
from Orange.widgets.utils.annotated_data import add_columns
24-
from Orange.widgets.utils.sql import check_sql_input
23+
from Orange.widgets.utils.sql import check_sql_input, check_sql_input_sequence
2524
from Orange.widgets.utils.widgetpreview import WidgetPreview
26-
from Orange.widgets.widget import Input, Output, Msg
25+
from Orange.widgets.widget import Input, MultiInput, Output, Msg
2726

2827

2928
class OWConcatenate(widget.OWWidget):
@@ -35,10 +34,9 @@ class OWConcatenate(widget.OWWidget):
3534

3635
class Inputs:
3736
primary_data = Input("Primary Data", Orange.data.Table)
38-
additional_data = Input("Additional Data",
39-
Orange.data.Table,
40-
multiple=True,
41-
default=True)
37+
additional_data = MultiInput(
38+
"Additional Data", Orange.data.Table, default=True
39+
)
4240

4341
class Outputs:
4442
data = Output("Data", Orange.data.Table)
@@ -86,7 +84,7 @@ def __init__(self):
8684
super().__init__()
8785

8886
self.primary_data = None
89-
self.more_data = OrderedDict()
87+
self._more_data_input: List[Optional[Orange.data.Table]] = []
9088

9189
self.mergebox = gui.vBox(self.controlArea, "Variable Merging")
9290
box = gui.radioButtons(
@@ -158,12 +156,22 @@ def set_primary_data(self, data):
158156
self.primary_data = data
159157

160158
@Inputs.additional_data
161-
@check_sql_input
162-
def set_more_data(self, data=None, sig_id=None):
163-
if data is not None:
164-
self.more_data[sig_id] = data
165-
elif sig_id in self.more_data:
166-
del self.more_data[sig_id]
159+
@check_sql_input_sequence
160+
def set_more_data(self, index, data):
161+
self._more_data_input[index] = data
162+
163+
@Inputs.additional_data.insert
164+
@check_sql_input_sequence
165+
def insert_more_data(self, index, data):
166+
self._more_data_input.insert(index, data)
167+
168+
@Inputs.additional_data.remove
169+
def remove_more_data(self, index):
170+
self._more_data_input.pop(index)
171+
172+
@property
173+
def more_data(self) -> Sequence[Orange.data.Table]:
174+
return [t for t in self._more_data_input if t is not None]
167175

168176
def handleNewSignals(self):
169177
self.mergebox.setDisabled(self.primary_data is not None)
@@ -177,8 +185,8 @@ def incompatible_types(self):
177185
types_ = set()
178186
if self.primary_data is not None:
179187
types_.add(type(self.primary_data))
180-
for key in self.more_data:
181-
types_.add(type(self.more_data[key]))
188+
for table in self.more_data:
189+
types_.add(type(table))
182190
if len(types_) > 1:
183191
return True
184192

@@ -188,13 +196,13 @@ def apply(self):
188196
self.Warning.renamed_variables.clear()
189197
tables, domain, source_var = [], None, None
190198
if self.primary_data is not None:
191-
tables = [self.primary_data] + list(self.more_data.values())
199+
tables = [self.primary_data] + list(self.more_data)
192200
domain = self.primary_data.domain
193201
elif self.more_data:
194202
if self.ignore_compute_value:
195203
tables = self._dumb_tables()
196204
else:
197-
tables = self.more_data.values()
205+
tables = self.more_data
198206
domains = [table.domain for table in tables]
199207
domain = self.merge_domains(domains)
200208

@@ -230,7 +238,7 @@ def enumerated_parts(domain):
230238
return enumerate((domain.attributes, domain.class_vars, domain.metas))
231239

232240
compute_value_groups = defaultdict(set)
233-
for table in self.more_data.values():
241+
for table in self.more_data:
234242
for part, part_vars in enumerated_parts(table.domain):
235243
for var in part_vars:
236244
desc = (var.name, type(var), part)
@@ -240,7 +248,7 @@ def enumerated_parts(domain):
240248
if len(compute_values) > 1}
241249

242250
dumb_tables = []
243-
for table in self.more_data.values():
251+
for table in self.more_data:
244252
dumb_domain = Orange.data.Domain(
245253
*[[var.copy(compute_value=None)
246254
if (var.name, type(var), part) in to_dumbify
@@ -352,5 +360,5 @@ def _unique_vars(seq: List[Orange.data.Variable]):
352360

353361
if __name__ == "__main__": # pragma: no cover
354362
WidgetPreview(OWConcatenate).run(
355-
set_more_data=[(Orange.data.Table("iris"), 0),
356-
(Orange.data.Table("zoo"), 1)])
363+
insert_more_data=[(0, Orange.data.Table("iris")),
364+
(1, Orange.data.Table("zoo"))])

Orange/widgets/data/owpythonscript.py

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from Orange.widgets.utils import itemmodels
3636
from Orange.widgets.settings import Setting
3737
from Orange.widgets.utils.widgetpreview import WidgetPreview
38-
from Orange.widgets.widget import OWWidget, Input, Output
38+
from Orange.widgets.widget import OWWidget, MultiInput, Output
3939

4040
if TYPE_CHECKING:
4141
from typing_extensions import TypedDict
@@ -526,14 +526,18 @@ class OWPythonScript(OWWidget):
526526
keywords = ["program", "function"]
527527

528528
class Inputs:
529-
data = Input("Data", Table, replaces=["in_data"],
530-
default=True, multiple=True)
531-
learner = Input("Learner", Learner, replaces=["in_learner"],
532-
default=True, multiple=True)
533-
classifier = Input("Classifier", Model, replaces=["in_classifier"],
534-
default=True, multiple=True)
535-
object = Input("Object", object, replaces=["in_object"],
536-
default=False, multiple=True)
529+
data = MultiInput(
530+
"Data", Table, replaces=["in_data"], default=True
531+
)
532+
learner = MultiInput(
533+
"Learner", Learner, replaces=["in_learner"], default=True
534+
)
535+
classifier = MultiInput(
536+
"Classifier", Model, replaces=["in_classifier"], default=True
537+
)
538+
object = MultiInput(
539+
"Object", object, replaces=["in_object"], default=False
540+
)
537541

538542
class Outputs:
539543
data = Output("Data", Table, replaces=["out_data"])
@@ -562,7 +566,7 @@ def __init__(self):
562566
super().__init__()
563567

564568
for name in self.signal_names:
565-
setattr(self, name, {})
569+
setattr(self, name, [])
566570

567571
self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
568572
self.mainArea.layout().addWidget(self.splitCanvas)
@@ -779,29 +783,65 @@ def _saveState(self):
779783
self.scriptText = self.text.toPlainText()
780784
self.splitterState = bytes(self.splitCanvas.saveState())
781785

782-
def handle_input(self, obj, sig_id, signal):
786+
def set_input(self, index, obj, signal):
783787
dic = getattr(self, signal)
784-
if obj is None:
785-
if sig_id in dic.keys():
786-
del dic[sig_id]
787-
else:
788-
dic[sig_id] = obj
788+
dic[index] = obj
789+
790+
def insert_input(self, index, obj, signal):
791+
dic = getattr(self, signal)
792+
dic.insert(index, obj)
793+
794+
def remove_input(self, index, signal):
795+
dic = getattr(self, signal)
796+
dic.pop(index)
789797

790798
@Inputs.data
791-
def set_data(self, data, sig_id):
792-
self.handle_input(data, sig_id, "data")
799+
def set_data(self, index, data):
800+
self.set_input(index, data, "data")
801+
802+
@Inputs.data.insert
803+
def insert_data(self, index, data):
804+
self.insert_input(index, data, "data")
805+
806+
@Inputs.data.remove
807+
def remove_data(self, index):
808+
self.remove_input(index, "data")
793809

794810
@Inputs.learner
795-
def set_learner(self, data, sig_id):
796-
self.handle_input(data, sig_id, "learner")
811+
def set_learner(self, index, learner):
812+
self.set_input(index, learner, "learner")
813+
814+
@Inputs.learner.insert
815+
def insert_learner(self, index, learner):
816+
self.insert_input(index, learner, "learner")
817+
818+
@Inputs.learner.remove
819+
def remove_learner(self, index):
820+
self.remove_input(index, "learner")
797821

798822
@Inputs.classifier
799-
def set_classifier(self, data, sig_id):
800-
self.handle_input(data, sig_id, "classifier")
823+
def set_classifier(self, index, classifier):
824+
self.set_input(index, classifier, "classifier")
825+
826+
@Inputs.classifier.insert
827+
def insert_classifier(self, index, classifier):
828+
self.insert_input(index, classifier, "classifier")
829+
830+
@Inputs.classifier.remove
831+
def remove_classifier(self, index):
832+
self.remove_input(index, "classifier")
801833

802834
@Inputs.object
803-
def set_object(self, data, sig_id):
804-
self.handle_input(data, sig_id, "object")
835+
def set_object(self, index, object):
836+
self.set_input(index, object, "object")
837+
838+
@Inputs.object.insert
839+
def insert_object(self, index, object):
840+
self.insert_input(index, object, "object")
841+
842+
@Inputs.object.remove
843+
def remove_object(self, index):
844+
self.remove_input(index, "object")
805845

806846
def handleNewSignals(self):
807847
# update fake signature labels
@@ -925,7 +965,7 @@ def initial_locals_state(self):
925965
d = {}
926966
for name in self.signal_names:
927967
value = getattr(self, name)
928-
all_values = list(value.values())
968+
all_values = list(value)
929969
one_value = all_values[0] if len(all_values) == 1 else None
930970
d["in_" + name + "s"] = all_values
931971
d["in_" + name] = one_value

Orange/widgets/data/owrank.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
import warnings
3-
from collections import OrderedDict, namedtuple
3+
from collections import namedtuple
44
from functools import partial
55
from itertools import chain
66
from types import SimpleNamespace
@@ -33,7 +33,7 @@
3333
from Orange.widgets.utils.itemmodels import PyTableModel
3434
from Orange.widgets.utils.sql import check_sql_input
3535
from Orange.widgets.utils.widgetpreview import WidgetPreview
36-
from Orange.widgets.widget import AttributeList, Input, Msg, Output, OWWidget
36+
from Orange.widgets.widget import AttributeList, Input, MultiInput, Output, Msg, OWWidget
3737

3838
log = logging.getLogger(__name__)
3939

@@ -256,7 +256,7 @@ class OWRank(OWWidget, ConcurrentWidgetMixin):
256256

257257
class Inputs:
258258
data = Input("Data", Table)
259-
scorer = Input("Scorer", score.Scorer, multiple=True)
259+
scorer = MultiInput("Scorer", score.Scorer, filter_none=True)
260260

261261
class Outputs:
262262
reduced_data = Output("Reduced Data", Table, default=True)
@@ -292,7 +292,7 @@ class Warning(OWWidget.Warning):
292292
def __init__(self):
293293
OWWidget.__init__(self)
294294
ConcurrentWidgetMixin.__init__(self)
295-
self.scorers = OrderedDict()
295+
self.scorers: List[ScoreMeta] = []
296296
self.out_domain_desc = None
297297
self.data = None
298298
self.problem_type_mode = ProblemType.CLASSIFICATION
@@ -440,18 +440,27 @@ def handleNewSignals(self):
440440
self.on_select()
441441

442442
@Inputs.scorer
443-
def set_learner(self, scorer, id): # pylint: disable=redefined-builtin
444-
if scorer is None:
445-
self.scorers.pop(id, None)
446-
else:
447-
# Avoid caching a (possibly stale) previous instance of the same
448-
# Scorer passed via the same signal
449-
if id in self.scorers:
450-
self.scorers_results = {}
443+
def set_learner(self, index, scorer):
444+
self.scorers[index] = ScoreMeta(
445+
scorer.name, scorer.name, scorer,
446+
ProblemType.from_variable(scorer.class_type),
447+
False
448+
)
449+
self.scorers_results = {}
451450

452-
self.scorers[id] = ScoreMeta(scorer.name, scorer.name, scorer,
453-
ProblemType.from_variable(scorer.class_type),
454-
False)
451+
@Inputs.scorer.insert
452+
def insert_learner(self, index: int, scorer):
453+
self.scorers.insert(index, ScoreMeta(
454+
scorer.name, scorer.name, scorer,
455+
ProblemType.from_variable(scorer.class_type),
456+
False
457+
))
458+
self.scorers_results = {}
459+
460+
@Inputs.scorer.remove
461+
def remove_learner(self, index):
462+
self.scorers.pop(index)
463+
self.scorers_results = {}
455464

456465
def _get_methods(self):
457466
return [
@@ -469,7 +478,7 @@ def _get_methods(self):
469478

470479
def _get_scorers(self):
471480
scorers = []
472-
for scorer in self.scorers.values():
481+
for scorer in self.scorers:
473482
if scorer.problem_type in (
474483
self.problem_type_mode,
475484
ProblemType.UNSUPERVISED,

0 commit comments

Comments
 (0)