From 1c69e6fb2f778c9eb6e9719a58f7e7751f5231b3 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 6 Dec 2019 14:22:14 +0100 Subject: [PATCH] Select Rows: Fix crash on changed variable type --- Orange/widgets/data/owselectrows.py | 45 ++++++++++++++----- .../widgets/data/tests/test_owselectrows.py | 42 ++++++++++++++--- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/Orange/widgets/data/owselectrows.py b/Orange/widgets/data/owselectrows.py index 0a09f035730..9083fa1f6c7 100644 --- a/Orange/widgets/data/owselectrows.py +++ b/Orange/widgets/data/owselectrows.py @@ -44,26 +44,39 @@ def is_valid_item(self, setting, condition, attrs, metas): return varname in attrs or varname in metas def encode_setting(self, context, setting, value): - if setting.name == 'conditions': - CONTINUOUS = vartype(ContinuousVariable("x")) - for i, (attr, op, values) in enumerate(value): - if context.attributes.get(attr) == CONTINUOUS: - if values and isinstance(values[0], str): - values = [QLocale().toDouble(v)[0] for v in values] - value[i] = (attr, op, values) - return super().encode_setting(context, setting, value) + if setting.name != 'conditions': + return super().encode_settings(context, setting, value) + + encoded = [] + CONTINUOUS = vartype(ContinuousVariable("x")) + for attr, op, values in value: + vtype = context.attributes.get(attr) + if vtype == CONTINUOUS and values and isinstance(values[0], str): + values = [QLocale().toDouble(v)[0] for v in values] + encoded.append((attr, vtype, op, values)) + return encoded def decode_setting(self, setting, value, domain=None): value = super().decode_setting(setting, value, domain) if setting.name == 'conditions': - for i, (attr, op, values) in enumerate(value): + for i, (attr, _, op, values) in enumerate(value): var = attr in domain and domain[attr] if var and var.is_continuous and not isinstance(var, TimeVariable): - value[i] = (attr, op, - list([QLocale().toString(float(i), 'f') - for i in values])) + values = [QLocale().toString(float(i), 'f') for i in values] + value[i] = (attr, op, values) return value + def match(self, context, domain, attrs, metas): + if (attrs, metas) == (context.attributes, context.metas): + return self.PERFECT_MATCH + + conditions = context.values["conditions"] + all_vars = attrs + all_vars.update(metas) + if all(all_vars.get(name) == tpe for name, tpe, *_ in conditions): + return 0.5 + return self.NO_MATCH + class FilterDiscreteType(enum.Enum): Equal = "Equal" @@ -105,6 +118,8 @@ class Outputs: purge_classes = Setting(False, schema_only=True) auto_commit = Setting(True) + settings_version = 2 + Operators = { ContinuousVariable: [ (FilterContinuous.Equal, "equals"), @@ -692,6 +707,12 @@ def send_report(self): ("Non-matching data", nonmatch_inst > 0 and "{} instances".format(nonmatch_inst)))) + @classmethod + def migrate_context(cls, context, version): + if not version: + # Just remove; can't migrate because variables types are unknown + context.values["conditions"] = [] + class CheckBoxPopup(QWidget): def __init__(self, var, lc, widget_parent=None, widget=None): diff --git a/Orange/widgets/data/tests/test_owselectrows.py b/Orange/widgets/data/tests/test_owselectrows.py index e517cf3d426..255315a74e9 100644 --- a/Orange/widgets/data/tests/test_owselectrows.py +++ b/Orange/widgets/data/tests/test_owselectrows.py @@ -7,7 +7,7 @@ import numpy as np from Orange.data import ( - Table, ContinuousVariable, StringVariable, DiscreteVariable) + Table, ContinuousVariable, StringVariable, DiscreteVariable, Domain) from Orange.widgets.data.owselectrows import ( OWSelectRows, FilterDiscreteType, SelectRowsContextHandler) from Orange.widgets.tests.base import WidgetTest, datasets @@ -16,6 +16,7 @@ from Orange.widgets.tests.utils import simulate, override_locale from Orange.widgets.utils.annotated_data import ANNOTATED_DATA_FEATURE_NAME from Orange.tests import test_filename +from orangewidget.settings import VERSION_KEY CFValues = { FilterContinuous.Equal: ["5.4"], @@ -132,14 +133,14 @@ def test_stores_settings_in_invariant_locale(self): context = self.widget.current_context self.send_signal(self.widget.Inputs.data, None) saved_condition = context.values["conditions"][0] - self.assertEqual(saved_condition[2][0], 5.2) + self.assertEqual(saved_condition[3][0], 5.2) @override_locale(QLocale.C) def test_restores_continuous_filter_in_c_locale(self): iris = Table("iris")[:5] # Settings with string value self.widget = self.widget_with_context( - iris.domain, [["sepal length", 2, ("5.2",)]]) + iris.domain, [["sepal length", 102, 2, ("5.2",)]]) self.send_signal(self.widget.Inputs.data, iris) values = self.widget.conditions[0][2] @@ -147,7 +148,7 @@ def test_restores_continuous_filter_in_c_locale(self): # Settings with float value self.widget = self.widget_with_context( - iris.domain, [["sepal length", 2, (5.2,)]]) + iris.domain, [["sepal length", 102, 2, (5.2,)]]) self.send_signal(self.widget.Inputs.data, iris) values = self.widget.conditions[0][2] @@ -158,7 +159,7 @@ def test_restores_continuous_filter_in_sl_SI_locale(self): iris = Table("iris")[:5] # Settings with string value self.widget = self.widget_with_context( - iris.domain, [["sepal length", 2, ("5.2",)]]) + iris.domain, [["sepal length", 102, 2, ("5.2",)]]) self.send_signal(self.widget.Inputs.data, iris) values = self.widget.conditions[0][2] @@ -166,7 +167,7 @@ def test_restores_continuous_filter_in_sl_SI_locale(self): # Settings with float value self.widget = self.widget_with_context( - iris.domain, [["sepal length", 2, (5.2,)]]) + iris.domain, [["sepal length", 102, 2, (5.2,)]]) self.send_signal(self.widget.Inputs.data, iris) values = self.widget.conditions[0][2] @@ -242,10 +243,37 @@ def test_annotated_data(self): np.testing.assert_equal(annotations[:50], True) np.testing.assert_equal(annotations[50:], False) + def test_change_var_type(self): + iris = Table("iris") + domain = iris.domain + + self.send_signal(self.widget.Inputs.data, iris) + self.widget.remove_all_button.click() + self.enterFilter(domain[0], "is below", "5.2") + + var0vals = list({str(x) for x in iris.X[:, 0]}) + new_domain = Domain( + (DiscreteVariable(domain[0].name, values=var0vals), ) + + domain.attributes[1:], + domain.class_var) + new_iris = iris.transform(new_domain) + self.send_signal(self.widget.Inputs.data, new_iris) + + def test_migration_to_version_1(self): + iris = Table("iris") + + ch = SelectRowsContextHandler() + context = ch.new_context(iris.domain, *ch.encode_domain(iris.domain)) + context.values = dict(conditions=[["petal length", 2, (5.2,)]]) + settings = dict(context_settings=[context]) + widget = self.create_widget(OWSelectRows, settings) + self.assertEqual(widget.conditions, []) + def widget_with_context(self, domain, conditions): ch = SelectRowsContextHandler() context = ch.new_context(domain, *ch.encode_domain(domain)) - context.values = dict(conditions=conditions) + context.values = {"conditions": conditions, + VERSION_KEY: OWSelectRows.settings_version} settings = dict(context_settings=[context]) return self.create_widget(OWSelectRows, settings)