diff --git a/Orange/widgets/data/owfeatureconstructor.py b/Orange/widgets/data/owfeatureconstructor.py index d458f755c00..4b6109b7151 100644 --- a/Orange/widgets/data/owfeatureconstructor.py +++ b/Orange/widgets/data/owfeatureconstructor.py @@ -317,9 +317,14 @@ def is_valid_item(self, setting, descriptor, attrs, metas): except Exception: return False - for name in freevars(exp_ast, []): - if not (name in attrs or name in metas): - return False + available = dict(globals()["__GLOBALS"]) + for var in attrs: + available[sanitized_name(var)] = None + for var in metas: + available[sanitized_name(var)] = None + + if freevars(exp_ast, available): + return False return True diff --git a/Orange/widgets/data/tests/test_owfeatureconstructor.py b/Orange/widgets/data/tests/test_owfeatureconstructor.py index ede084a5393..e4d00cf6df5 100644 --- a/Orange/widgets/data/tests/test_owfeatureconstructor.py +++ b/Orange/widgets/data/tests/test_owfeatureconstructor.py @@ -10,11 +10,12 @@ from Orange.data import (Table, Domain, StringVariable, ContinuousVariable, DiscreteVariable) from Orange.widgets.tests.base import WidgetTest +from Orange.widgets.utils import vartype from Orange.widgets.utils.itemmodels import PyListModel from Orange.widgets.data.owfeatureconstructor import ( DiscreteDescriptor, ContinuousDescriptor, StringDescriptor, construct_variables, OWFeatureConstructor, - FeatureEditor, DiscreteFeatureEditor) + FeatureEditor, DiscreteFeatureEditor, FeatureConstructorHandler) from Orange.widgets.data.owfeatureconstructor import ( freevars, validate_exp, FeatureFunc @@ -269,3 +270,45 @@ class TestFeatureEditor(unittest.TestCase): def test_has_functions(self): self.assertIs(FeatureEditor.FUNCTIONS["abs"], abs) self.assertIs(FeatureEditor.FUNCTIONS["sqrt"], math.sqrt) + + +class FeatureConstructorHandlerTests(unittest.TestCase): + def test_handles_builtins_in_expression(self): + self.assertTrue( + FeatureConstructorHandler().is_valid_item( + OWFeatureConstructor.descriptors, + StringDescriptor("X", "str(A) + str(B)"), + {"A": vartype(DiscreteVariable)}, + {"B": vartype(DiscreteVariable)} + ) + ) + + # no variables is also ok + self.assertTrue( + FeatureConstructorHandler().is_valid_item( + OWFeatureConstructor.descriptors, + StringDescriptor("X", "str('foo')"), + {}, + {} + ) + ) + + # should fail on unknown variables + self.assertFalse( + FeatureConstructorHandler().is_valid_item( + OWFeatureConstructor.descriptors, + StringDescriptor("X", "str(X)"), + {}, + {} + ) + ) + + def test_handles_special_characters_in_var_names(self): + self.assertTrue( + FeatureConstructorHandler().is_valid_item( + OWFeatureConstructor.descriptors, + StringDescriptor("X", "A_2_f"), + {"A.2 f": vartype(DiscreteVariable)}, + {} + ) + )