Skip to content

Commit c8ab897

Browse files
authored
Merge pull request #4643 from janezd/remove-str-settings
[FIX] Fixes for deprecations in 3.26, and for changed behaviour of file dialog
2 parents cf781b1 + d89e896 commit c8ab897

7 files changed

Lines changed: 133 additions & 168 deletions

File tree

Orange/widgets/data/contexthandlers.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

Orange/widgets/data/owcorrelations.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,9 @@ class Outputs:
248248

249249
correlation_type: int
250250

251-
settings_version = 2
251+
settings_version = 3
252252
settingsHandler = DomainContextHandler()
253-
selection = ContextSetting(())
253+
selection = ContextSetting([])
254254
feature = ContextSetting(None)
255255
correlation_type = Setting(0)
256256

@@ -307,7 +307,7 @@ def _feature_combo_changed(self):
307307
self.apply()
308308

309309
def _vizrank_selection_changed(self, *args):
310-
self.selection = [(var.name, vartype(var)) for var in args]
310+
self.selection = list(args)
311311
self.commit()
312312

313313
def _vizrank_stopped(self):
@@ -323,7 +323,7 @@ def _vizrank_select(self):
323323
# filtered by a feature and therefore selection could not be found
324324
selection_in_model = False
325325
if self.selection:
326-
sel_names = sorted(name for name, _ in self.selection)
326+
sel_names = sorted(var.name for var in self.selection)
327327
for i in range(model.rowCount()):
328328
# pylint: disable=protected-access
329329
names = sorted(x.name for x in model.data(
@@ -345,7 +345,7 @@ def set_data(self, data):
345345
self.clear_messages()
346346
self.data = data
347347
self.cont_data = None
348-
self.selection = ()
348+
self.selection = []
349349
if data is not None:
350350
if len(data) < 2:
351351
self.Warning.not_enough_inst()
@@ -415,7 +415,7 @@ def commit(self):
415415

416416
# data has been imputed; send original attributes
417417
self.Outputs.features.send(AttributeList(
418-
[self.data.domain[name] for name, _ in self.selection]))
418+
[self.data.domain[var.name] for var in self.selection]))
419419
self.Outputs.correlations.send(corr_table)
420420

421421
def send_report(self):
@@ -426,8 +426,12 @@ def send_report(self):
426426
def migrate_context(cls, context, version):
427427
if version < 2:
428428
sel = context.values["selection"]
429-
context.values["selection"] = ([(var.name, vartype(var))
430-
for var in sel[0]], sel[1])
429+
context.values["selection"] = [(var.name, vartype(var))
430+
for var in sel[0]]
431+
if version < 3:
432+
sel = context.values["selection"]
433+
context.values["selection"] = ([(name, vtype + 100)
434+
for name, vtype in sel], -3)
431435

432436

433437
if __name__ == "__main__": # pragma: no cover

Orange/widgets/data/owselectcolumns.py

Lines changed: 80 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99
)
1010

1111
from Orange.widgets import gui, widget
12-
from Orange.widgets.data.contexthandlers import \
13-
SelectAttributesDomainContextHandler
14-
from Orange.widgets.settings import ContextSetting, Setting
15-
from Orange.widgets.utils.listfilter import VariablesListItemView, slices, variables_filter
12+
from Orange.widgets.settings import (
13+
ContextSetting, Setting, DomainContextHandler
14+
)
15+
from Orange.widgets.utils import vartype
16+
from Orange.widgets.utils.listfilter import (
17+
VariablesListItemView, slices, variables_filter
18+
)
1619
from Orange.widgets.utils.widgetpreview import WidgetPreview
1720
from Orange.widgets.utils.state_summary import format_summary_details
1821
from Orange.widgets.widget import Input, Output, AttributeList, Msg
1922
from Orange.data.table import Table
20-
from Orange.widgets.utils import vartype
2123
from Orange.widgets.utils.itemmodels import VariableListModel
2224
import Orange
2325

@@ -99,6 +101,45 @@ def dropMimeData(self, mime, action, row, column, parent):
99101
return True
100102

101103

104+
class SelectAttributesDomainContextHandler(DomainContextHandler):
105+
def encode_setting(self, context, setting, value):
106+
if setting.name == 'domain_role_hints':
107+
value = {(var.name, vartype(var)): role_i
108+
for var, role_i in value.items()}
109+
return super().encode_setting(context, setting, value)
110+
111+
def decode_setting(self, setting, value, domain=None):
112+
decoded = super().decode_setting(setting, value, domain)
113+
if setting.name == 'domain_role_hints':
114+
decoded = {domain[name]: role_i
115+
for (name, _), role_i in decoded.items()}
116+
return decoded
117+
118+
def match(self, context, domain, attrs, metas):
119+
if not "domain_role_hints" in context.values:
120+
return self.NO_MATCH
121+
122+
all_vars = attrs.copy()
123+
all_vars.update(metas)
124+
value = context.values["domain_role_hints"][0]
125+
assigned = [desc for desc, (role, _) in value.items()
126+
if role != "available"]
127+
return assigned and \
128+
sum(all_vars.get(attr) == vtype for attr, vtype in assigned) \
129+
/ len(assigned)
130+
131+
def filter_value(self, setting, data, domain, attrs, metas):
132+
if setting.name != "domain_role_hints":
133+
return super().filter_value(setting, data, domain, attrs, metas)
134+
135+
all_vars = attrs.copy()
136+
all_vars.update(metas)
137+
value = data["domain_role_hints"][0].items()
138+
data["domain_role_hints"] = {desc: role_i
139+
for desc, role_i in value
140+
if all_vars.get(desc[0]) == desc[1]}
141+
142+
102143
class OWSelectAttributes(widget.OWWidget):
103144
# pylint: disable=too-many-instance-attributes
104145
name = "Select Columns"
@@ -305,68 +346,52 @@ def __used_attrs_changed(self):
305346
def set_data(self, data=None):
306347
self.update_domain_role_hints()
307348
self.closeContext()
349+
self.domain_role_hints = {}
350+
308351
self.data = data
309-
if data is not None:
310-
self.openContext(data)
311-
all_vars = data.domain.variables + data.domain.metas
312-
313-
var_sig = lambda attr: (attr.name, vartype(attr))
314-
315-
domain_hints = {var_sig(attr): ("attribute", i)
316-
for i, attr in enumerate(data.domain.attributes)}
317-
318-
domain_hints.update({var_sig(attr): ("meta", i)
319-
for i, attr in enumerate(data.domain.metas)})
320-
321-
if data.domain.class_vars:
322-
domain_hints.update(
323-
{var_sig(attr): ("class", i)
324-
for i, attr in enumerate(data.domain.class_vars)})
325-
326-
# update the hints from context settings
327-
domain_hints.update(self.domain_role_hints)
328-
329-
attrs_for_role = lambda role: [
330-
(domain_hints[var_sig(attr)][1], attr)
331-
for attr in all_vars if domain_hints[var_sig(attr)][0] == role]
332-
333-
attributes = [
334-
attr for place, attr in sorted(attrs_for_role("attribute"),
335-
key=lambda a: a[0])]
336-
classes = [
337-
attr for place, attr in sorted(attrs_for_role("class"),
338-
key=lambda a: a[0])]
339-
metas = [
340-
attr for place, attr in sorted(attrs_for_role("meta"),
341-
key=lambda a: a[0])]
342-
available = [
343-
attr for place, attr in sorted(attrs_for_role("available"),
344-
key=lambda a: a[0])]
345-
346-
self.used_attrs[:] = attributes
347-
self.class_attrs[:] = classes
348-
self.meta_attrs[:] = metas
349-
self.available_attrs[:] = available
350-
self.info.set_input_summary(len(data), format_summary_details(data))
351-
else:
352+
if data is None:
352353
self.used_attrs[:] = []
353354
self.class_attrs[:] = []
354355
self.meta_attrs[:] = []
355356
self.available_attrs[:] = []
356357
self.info.set_input_summary(self.info.NoInput)
358+
return
359+
360+
self.openContext(data)
361+
all_vars = data.domain.variables + data.domain.metas
362+
363+
def attrs_for_role(role):
364+
return [attr for _, attr in sorted(
365+
(domain_hints[attr][1], attr)
366+
for attr in all_vars if domain_hints[attr][0] == role)]
367+
368+
domain = data.domain
369+
domain_hints = {}
370+
domain_hints.update(self._hints_from_seq("attribute", domain.attributes))
371+
domain_hints.update(self._hints_from_seq("meta", domain.metas))
372+
domain_hints.update(self._hints_from_seq("class", domain.class_vars))
373+
domain_hints.update(self.domain_role_hints)
374+
375+
self.used_attrs[:] = attrs_for_role("attribute")
376+
self.class_attrs[:] = attrs_for_role("class")
377+
self.meta_attrs[:] = attrs_for_role("meta")
378+
self.available_attrs[:] = attrs_for_role("available")
379+
self.info.set_input_summary(len(data), format_summary_details(data))
357380

358381
def update_domain_role_hints(self):
359382
""" Update the domain hints to be stored in the widgets settings.
360383
"""
361-
hints_from_model = lambda role, model: [
362-
((attr.name, vartype(attr)), (role, i))
363-
for i, attr in enumerate(model)]
364-
hints = dict(hints_from_model("available", self.available_attrs))
365-
hints.update(hints_from_model("attribute", self.used_attrs))
366-
hints.update(hints_from_model("class", self.class_attrs))
367-
hints.update(hints_from_model("meta", self.meta_attrs))
384+
hints = {}
385+
hints.update(self._hints_from_seq("available", self.available_attrs))
386+
hints.update(self._hints_from_seq("attribute", self.used_attrs))
387+
hints.update(self._hints_from_seq("class", self.class_attrs))
388+
hints.update(self._hints_from_seq("meta", self.meta_attrs))
368389
self.domain_role_hints = hints
369390

391+
@staticmethod
392+
def _hints_from_seq(role, model):
393+
return [(attr, (role, i)) for i, attr in enumerate(model)]
394+
370395
@Inputs.features
371396
def set_features(self, features):
372397
self.features = features

Orange/widgets/data/tests/test_owfile.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,22 @@ def test_read_format(self):
374374
def open_iris_with_no_spec_format(_a, _b, _c, filters, _e):
375375
return iris.__file__, filters.split(";;")[0]
376376

377+
"""
378+
orangewidgets.utils.filedialogs.open_filename_dialog changed behaviour
379+
in https://github.com/biolab/orange-widget-base/pull/53. Until it is
380+
released, one of the following tests fails, so this test is disabled.
381+
If you read this and orange-widget-base has already been released,
382+
uncomment this code and remove the first test
383+
377384
with patch("AnyQt.QtWidgets.QFileDialog.getOpenFileName",
378385
open_iris_with_no_spec_format):
379386
self.widget.browse_file()
380387
388+
# Worked before
381389
self.assertIsNone(self.widget.recent_paths[0].file_format)
390+
# Works after
391+
self.assertEqual(self.widget.recent_paths[0].file_format, "Orange.data.io.TabReader")
392+
"""
382393

383394
def open_iris_with_tab(*_):
384395
return iris.__file__, format_filter(TabReader)

Orange/widgets/data/tests/test_owselectcolumns.py

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@
66
from AnyQt.QtGui import QDragEnterEvent
77

88
from Orange.data import Table, ContinuousVariable, DiscreteVariable, Domain
9-
from Orange.widgets.data.contexthandlers import \
10-
SelectAttributesDomainContextHandler
119
from Orange.widgets.settings import ContextSetting
1210
from Orange.widgets.utils import vartype
1311
from Orange.widgets.utils.state_summary import format_summary_details
1412
from Orange.widgets.tests.base import WidgetTest
1513
from Orange.widgets.data.owselectcolumns \
16-
import OWSelectAttributes, VariablesListItemModel
14+
import OWSelectAttributes, VariablesListItemModel, \
15+
SelectAttributesDomainContextHandler
1716
from Orange.widgets.data.owrank import OWRank
1817
from Orange.widgets.widget import AttributeList
1918

@@ -56,20 +55,21 @@ def test_open_context(self):
5655

5756
widget = SimpleWidget()
5857
self.handler.initialize(widget)
59-
self.handler.open_context(widget, self.args[0])
58+
domain = self.args[0]
59+
self.handler.open_context(widget, domain)
6060
self.assertEqual(widget.domain_role_hints,
61-
{('d1', Discrete): ('available', 0),
62-
('d2', Discrete): ('meta', 0),
63-
('c1', Continuous): ('attribute', 0),
64-
('d3', Discrete): ('attribute', 1),
65-
('d4', Discrete): ('attribute', 2),
66-
('c2', Continuous): ('class', 0)})
61+
{domain['d1']: ('available', 0),
62+
domain['d2']: ('meta', 0),
63+
domain['c1']: ('attribute', 0),
64+
domain['d3']: ('attribute', 1),
65+
domain['d4']: ('attribute', 2),
66+
domain['c2']: ('class', 0)})
6767

6868
def test_open_context_with_imperfect_match(self):
6969
self.handler.bind(SimpleWidget)
7070
context1 = Mock(values=dict(
7171
domain_role_hints=({('d1', Discrete): ('attribute', 0),
72-
('m2', Discrete): ('meta', 0)})
72+
('m2', Discrete): ('meta', 0)}, -2)
7373
))
7474
context = Mock(values=dict(
7575
domain_role_hints=({('d1', Discrete): ('available', 0),
@@ -84,30 +84,14 @@ def test_open_context_with_imperfect_match(self):
8484

8585
widget = SimpleWidget()
8686
self.handler.initialize(widget)
87-
self.handler.open_context(widget, self.args[0])
87+
domain = self.args[0]
88+
self.handler.open_context(widget, domain)
8889

8990
self.assertEqual(widget.domain_role_hints,
90-
{('d1', Discrete): ('available', 0),
91-
('d2', Discrete): ('meta', 0),
92-
('c1', Continuous): ('attribute', 0),
93-
('c2', Continuous): ('class', 0)})
94-
95-
def test_open_context_with_no_match(self):
96-
self.handler.bind(SimpleWidget)
97-
context = Mock(values=dict(
98-
domain_role_hints=({('d1', Discrete): ('available', 0),
99-
('d2', Discrete): ('meta', 0),
100-
('c1', Continuous): ('attribute', 0),
101-
('d3', Discrete): ('attribute', 1),
102-
('d4', Discrete): ('attribute', 2),
103-
('c2', Continuous): ('class', 0)}, -2),
104-
required=('g1', Continuous),
105-
))
106-
self.handler.global_contexts = [context]
107-
widget = SimpleWidget()
108-
self.handler.initialize(widget)
109-
self.handler.open_context(widget, self.args[0])
110-
self.assertEqual(widget.domain_role_hints, {})
91+
{domain['d1']: ('available', 0),
92+
domain['d2']: ('meta', 0),
93+
domain['c1']: ('attribute', 0),
94+
domain['c2']: ('class', 0)})
11195

11296

11397
class TestModel(TestCase):

0 commit comments

Comments
 (0)