{
    "file_diffs": [
        {
            "old_file_content": "\"\"\"\nFeature Constructor\n\nA widget for defining (constructing) new features from values\nof other variables.\n\n\"\"\"\nimport re\nimport copy\nimport functools\nimport builtins\nimport math\nimport random\nimport logging\nimport ast\nimport types\n\nfrom traceback import format_exception_only\nfrom collections import namedtuple, OrderedDict\nfrom itertools import chain, count\nfrom typing import List, Dict, Any\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QSizePolicy, QAbstractItemView, QComboBox, QFormLayout, QLineEdit,\n    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,\n    QPushButton, QMenu, QListView, QFrame, QLabel)\nfrom AnyQt.QtGui import QKeySequence\nfrom AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property\nfrom orangewidget.utils.combobox import ComboBoxSearch\n\nimport Orange\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import ContextSetting, DomainContextHandler\nfrom Orange.widgets.utils import itemmodels, vartype\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets import report\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\n\nFeatureDescriptor = \\\n    namedtuple(\"FeatureDescriptor\", [\"name\", \"expression\"])\n\nContinuousDescriptor = \\\n    namedtuple(\"ContinuousDescriptor\",\n               [\"name\", \"expression\", \"number_of_decimals\"])\nDateTimeDescriptor = \\\n    namedtuple(\"DateTimeDescriptor\",\n               [\"name\", \"expression\"])\nDiscreteDescriptor = \\\n    namedtuple(\"DiscreteDescriptor\",\n               [\"name\", \"expression\", \"values\", \"ordered\"])\n\nStringDescriptor = namedtuple(\"StringDescriptor\", [\"name\", \"expression\"])\n\n\ndef make_variable(descriptor, compute_value):\n    if isinstance(descriptor, ContinuousDescriptor):\n        return Orange.data.ContinuousVariable(\n            descriptor.name,\n            descriptor.number_of_decimals,\n            compute_value)\n    if isinstance(descriptor, DateTimeDescriptor):\n        return Orange.data.TimeVariable(\n            descriptor.name,\n            compute_value=compute_value, have_date=True, have_time=True)\n    elif isinstance(descriptor, DiscreteDescriptor):\n        return Orange.data.DiscreteVariable(\n            descriptor.name,\n            values=descriptor.values,\n            ordered=descriptor.ordered,\n            compute_value=compute_value)\n    elif isinstance(descriptor, StringDescriptor):\n        return Orange.data.StringVariable(\n            descriptor.name,\n            compute_value=compute_value)\n    else:\n        raise TypeError\n\n\ndef selected_row(view):\n    \"\"\"\n    Return the index of selected row in a `view` (:class:`QListView`)\n\n    The view's selection mode must be a QAbstractItemView.SingleSelction\n    \"\"\"\n    if view.selectionMode() in [QAbstractItemView.MultiSelection,\n                                QAbstractItemView.ExtendedSelection]:\n        raise ValueError(\"invalid 'selectionMode'\")\n\n    sel_model = view.selectionModel()\n    indexes = sel_model.selectedRows()\n    if indexes:\n        assert len(indexes) == 1\n        return indexes[0].row()\n    else:\n        return None\n\n\nclass FeatureEditor(QFrame):\n    FUNCTIONS = dict(chain([(key, val) for key, val in math.__dict__.items()\n                            if not key.startswith(\"_\")],\n                           [(key, val) for key, val in builtins.__dict__.items()\n                            if key in {\"str\", \"float\", \"int\", \"len\",\n                                       \"abs\", \"max\", \"min\"}]))\n    featureChanged = Signal()\n    featureEdited = Signal()\n\n    modifiedChanged = Signal(bool)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        layout = QFormLayout(\n            fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow\n        )\n        layout.setContentsMargins(0, 0, 0, 0)\n        self.nameedit = QLineEdit(\n            placeholderText=\"Name...\",\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.Fixed)\n        )\n        self.expressionedit = QLineEdit(\n            placeholderText=\"Expression...\",\n            toolTip=self.ExpressionTooltip)\n\n        self.attrs_model = itemmodels.VariableListModel(\n            [\"Select Feature\"], parent=self)\n        self.attributescb = ComboBoxSearch(\n            minimumContentsLength=16,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)\n        )\n        self.attributescb.setModel(self.attrs_model)\n\n        sorted_funcs = sorted(self.FUNCTIONS)\n        self.funcs_model = itemmodels.PyListModelTooltip()\n        self.funcs_model.setParent(self)\n\n        self.funcs_model[:] = chain([\"Select Function\"], sorted_funcs)\n        self.funcs_model.tooltips[:] = chain(\n            [''],\n            [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])\n\n        self.functionscb = ComboBoxSearch(\n            minimumContentsLength=16,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))\n        self.functionscb.setModel(self.funcs_model)\n\n        hbox = QHBoxLayout()\n        hbox.addWidget(self.attributescb)\n        hbox.addWidget(self.functionscb)\n\n        layout.addRow(self.nameedit, self.expressionedit)\n        layout.addRow(self.tr(\"\"), hbox)\n        self.setLayout(layout)\n\n        self.nameedit.editingFinished.connect(self._invalidate)\n        self.expressionedit.textChanged.connect(self._invalidate)\n        self.attributescb.currentIndexChanged.connect(self.on_attrs_changed)\n        self.functionscb.currentIndexChanged.connect(self.on_funcs_changed)\n\n        self._modified = False\n\n    def setModified(self, modified):\n        if not isinstance(modified, bool):\n            raise TypeError\n\n        if self._modified != modified:\n            self._modified = modified\n            self.modifiedChanged.emit(modified)\n\n    def modified(self):\n        return self._modified\n\n    modified = Property(bool, modified, setModified,\n                        notify=modifiedChanged)\n\n    def setEditorData(self, data, domain):\n        self.nameedit.setText(data.name)\n        self.expressionedit.setText(data.expression)\n        self.setModified(False)\n        self.featureChanged.emit()\n        self.attrs_model[:] = [\"Select Feature\"]\n        if domain is not None and not domain.empty():\n            self.attrs_model[:] += chain(domain.attributes,\n                                         domain.class_vars,\n                                         domain.metas)\n\n    def editorData(self):\n        return FeatureDescriptor(name=self.nameedit.text(),\n                                 expression=self.nameedit.text())\n\n    def _invalidate(self):\n        self.setModified(True)\n        self.featureEdited.emit()\n        self.featureChanged.emit()\n\n    def on_attrs_changed(self):\n        index = self.attributescb.currentIndex()\n        if index > 0:\n            attr = sanitized_name(self.attrs_model[index].name)\n            self.insert_into_expression(attr)\n            self.attributescb.setCurrentIndex(0)\n\n    def on_funcs_changed(self):\n        index = self.functionscb.currentIndex()\n        if index > 0:\n            func = self.funcs_model[index]\n            if func in [\"atan2\", \"fmod\", \"ldexp\", \"log\",\n                        \"pow\", \"copysign\", \"hypot\"]:\n                self.insert_into_expression(func + \"(,)\")\n                self.expressionedit.cursorBackward(False, 2)\n            elif func in [\"e\", \"pi\"]:\n                self.insert_into_expression(func)\n            else:\n                self.insert_into_expression(func + \"()\")\n                self.expressionedit.cursorBackward(False)\n            self.functionscb.setCurrentIndex(0)\n\n    def insert_into_expression(self, what):\n        cp = self.expressionedit.cursorPosition()\n        ct = self.expressionedit.text()\n        text = ct[:cp] + what + ct[cp:]\n        self.expressionedit.setText(text)\n        self.expressionedit.setFocus()\n\n\nclass ContinuousFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A numeric expression\"\n\n    def editorData(self):\n        return ContinuousDescriptor(\n            name=self.nameedit.text(),\n            number_of_decimals=None,\n            expression=self.expressionedit.text()\n        )\n\n\nclass DateTimeFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \\\n        \"Result must be a string in ISO-8601 format \" \\\n        \"(e.g. 2019-07-30T15:37:27 or a part thereof),\\n\" \\\n        \"or a number of seconds since Jan 1, 1970.\"\n\n    def editorData(self):\n        return DateTimeDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text()\n        )\n\n\nclass DiscreteFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \\\n        \"Result must be a string, if values are not explicitly given\\n\" \\\n        \"or a zero-based integer indices into a list of values given below.\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        tooltip = \\\n            \"If values are given, above expression must return zero-based \" \\\n            \"integer indices into that list.\"\n        self.valuesedit = QLineEdit(placeholderText=\"A, B ...\", toolTip=tooltip)\n        self.valuesedit.textChanged.connect(self._invalidate)\n\n        layout = self.layout()\n        label = QLabel(self.tr(\"Values (optional)\"))\n        label.setToolTip(tooltip)\n        layout.addRow(label, self.valuesedit)\n\n    def setEditorData(self, data, domain):\n        self.valuesedit.setText(\n            \", \".join(v.replace(\",\", r\"\\,\") for v in data.values))\n\n        super().setEditorData(data, domain)\n\n    def editorData(self):\n        values = self.valuesedit.text()\n        values = re.split(r\"(?<!\\\\),\", values)\n        values = tuple(filter(None, [v.replace(r\"\\,\", \",\").strip() for v in values]))\n        return DiscreteDescriptor(\n            name=self.nameedit.text(),\n            values=values,\n            ordered=False,\n            expression=self.expressionedit.text()\n        )\n\n\nclass StringFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A string expression\"\n\n    def editorData(self):\n        return StringDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text()\n        )\n\n\n_VarMap = {\n    DiscreteDescriptor: vartype(Orange.data.DiscreteVariable(\"d\")),\n    ContinuousDescriptor: vartype(Orange.data.ContinuousVariable(\"c\")),\n    DateTimeDescriptor: vartype(Orange.data.TimeVariable(\"t\")),\n    StringDescriptor: vartype(Orange.data.StringVariable(\"s\"))\n}\n\n\n@functools.lru_cache(20)\ndef variable_icon(dtype):\n    vtype = _VarMap.get(dtype, dtype)\n    return gui.attributeIconDict[vtype]\n\n\nclass FeatureItemDelegate(QStyledItemDelegate):\n    @staticmethod\n    def displayText(value, _):\n        return value.name + \" := \" + value.expression\n\n\nclass DescriptorModel(itemmodels.PyListModel):\n    def data(self, index, role=Qt.DisplayRole):\n        if role == Qt.DecorationRole:\n            value = self[index.row()]\n            return variable_icon(type(value))\n        else:\n            return super().data(index, role)\n\n\nclass FeatureConstructorHandler(DomainContextHandler):\n    \"\"\"Context handler that filters descriptors\"\"\"\n\n    def is_valid_item(self, setting, item, attrs, metas):\n        \"\"\"Check if descriptor `item` can be used with given domain.\n\n        Return True if descriptor's expression contains only\n        available variables and descriptors name does not clash with\n        existing variables.\n        \"\"\"\n        if item.name in attrs or item.name in metas:\n            return False\n\n        try:\n            exp_ast = ast.parse(item.expression, mode=\"eval\")\n        # ast.parse can return arbitrary errors, not only SyntaxError\n        # pylint: disable=broad-except\n        except Exception:\n            return False\n\n        available = dict(globals()[\"__GLOBALS\"])\n        for var in attrs:\n            available[sanitized_name(var)] = None\n        for var in metas:\n            available[sanitized_name(var)] = None\n\n        if freevars(exp_ast, available):\n            return False\n        return True\n\n\nclass OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)\n\n\ndef freevars(exp, env):\n    \"\"\"\n    Return names of all free variables in a parsed (expression) AST.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        An expression ast (ast.parse(..., mode=\"single\"))\n    env : List[str]\n        Environment\n\n    Returns\n    -------\n    freevars : List[str]\n\n    See also\n    --------\n    ast\n\n    \"\"\"\n    # pylint: disable=too-many-return-statements,too-many-branches\n    etype = type(exp)\n    if etype in [ast.Expr, ast.Expression]:\n        return freevars(exp.body, env)\n    elif etype == ast.BoolOp:\n        return sum((freevars(v, env) for v in exp.values), [])\n    elif etype == ast.BinOp:\n        return freevars(exp.left, env) + freevars(exp.right, env)\n    elif etype == ast.UnaryOp:\n        return freevars(exp.operand, env)\n    elif etype == ast.Lambda:\n        args = exp.args\n        assert isinstance(args, ast.arguments)\n        argnames = [a.arg for a in args.args]\n        argnames += [args.vararg.arg] if args.vararg else []\n        argnames += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []\n        argnames += [args.kwarg] if args.kwarg else []\n        return freevars(exp.body, env + argnames)\n    elif etype == ast.IfExp:\n        return (freevars(exp.test, env) + freevars(exp.body, env) +\n                freevars(exp.orelse, env))\n    elif etype == ast.Dict:\n        return sum((freevars(v, env)\n                    for v in chain(exp.keys, exp.values)), [])\n    elif etype == ast.Set:\n        return sum((freevars(v, env) for v in exp.elts), [])\n    elif etype in [ast.SetComp, ast.ListComp, ast.GeneratorExp, ast.DictComp]:\n        env_ext = []\n        vars_ = []\n        for gen in exp.generators:\n            target_names = freevars(gen.target, [])  # assigned names\n            vars_iter = freevars(gen.iter, env)\n            env_ext += target_names\n            vars_ifs = list(chain(*(freevars(ifexp, env + target_names)\n                                    for ifexp in gen.ifs or [])))\n            vars_ += vars_iter + vars_ifs\n\n        if etype == ast.DictComp:\n            vars_ = (freevars(exp.key, env_ext) +\n                     freevars(exp.value, env_ext) +\n                     vars_)\n        else:\n            vars_ = freevars(exp.elt, env + env_ext) + vars_\n        return vars_\n    # Yield, YieldFrom???\n    elif etype == ast.Compare:\n        return sum((freevars(v, env)\n                    for v in [exp.left] + exp.comparators), [])\n    elif etype == ast.Call:\n        return sum(map(lambda e: freevars(e, env),\n                       chain([exp.func],\n                             exp.args or [],\n                             [k.value for k in exp.keywords or []])),\n                   [])\n    elif etype == ast.Starred:\n        # a 'starred' call parameter (e.g. a and b in `f(x, *a, *b)`\n        return freevars(exp.value, env)\n    elif etype in [ast.Num, ast.Str, ast.Ellipsis, ast.Bytes, ast.NameConstant]:\n        return []\n    elif etype == ast.Constant:\n        return []\n    elif etype == ast.Attribute:\n        return freevars(exp.value, env)\n    elif etype == ast.Subscript:\n        return freevars(exp.value, env) + freevars(exp.slice, env)\n    elif etype == ast.Name:\n        return [exp.id] if exp.id not in env else []\n    elif etype == ast.List:\n        return sum((freevars(e, env) for e in exp.elts), [])\n    elif etype == ast.Tuple:\n        return sum((freevars(e, env) for e in exp.elts), [])\n    elif etype == ast.Slice:\n        return sum((freevars(e, env)\n                    for e in filter(None, [exp.lower, exp.upper, exp.step])),\n                   [])\n    elif etype == ast.ExtSlice:\n        return sum((freevars(e, env) for e in exp.dims), [])\n    elif etype == ast.Index:\n        return freevars(exp.value, env)\n    elif etype == ast.keyword:\n        return freevars(exp.value, env)\n    else:\n        raise ValueError(exp)\n\n\ndef validate_exp(exp):\n    \"\"\"\n    Validate an `ast.AST` expression.\n\n    Only expressions with no list,set,dict,generator comprehensions\n    are accepted.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        A parsed abstract syntax tree\n\n    \"\"\"\n    # pylint: disable=too-many-branches\n    if not isinstance(exp, ast.AST):\n        raise TypeError(\"exp is not a 'ast.AST' instance\")\n\n    etype = type(exp)\n    if etype in [ast.Expr, ast.Expression]:\n        return validate_exp(exp.body)\n    elif etype == ast.BoolOp:\n        return all(map(validate_exp, exp.values))\n    elif etype == ast.BinOp:\n        return all(map(validate_exp, [exp.left, exp.right]))\n    elif etype == ast.UnaryOp:\n        return validate_exp(exp.operand)\n    elif etype == ast.IfExp:\n        return all(map(validate_exp, [exp.test, exp.body, exp.orelse]))\n    elif etype == ast.Dict:\n        return all(map(validate_exp, chain(exp.keys, exp.values)))\n    elif etype == ast.Set:\n        return all(map(validate_exp, exp.elts))\n    elif etype == ast.Compare:\n        return all(map(validate_exp, [exp.left] + exp.comparators))\n    elif etype == ast.Call:\n        subexp = chain([exp.func], exp.args or [],\n                       [k.value for k in exp.keywords or []])\n        return all(map(validate_exp, subexp))\n    elif etype == ast.Starred:\n        assert isinstance(exp.ctx, ast.Load)\n        return validate_exp(exp.value)\n    elif etype in [ast.Num, ast.Str, ast.Bytes, ast.Ellipsis, ast.NameConstant]:\n        return True\n    elif etype == ast.Constant:\n        return True\n    elif etype == ast.Attribute:\n        return True\n    elif etype == ast.Subscript:\n        return all(map(validate_exp, [exp.value, exp.slice]))\n    elif etype in {ast.List, ast.Tuple}:\n        assert isinstance(exp.ctx, ast.Load)\n        return all(map(validate_exp, exp.elts))\n    elif etype == ast.Name:\n        return True\n    elif etype == ast.Slice:\n        return all(map(validate_exp,\n                       filter(None, [exp.lower, exp.upper, exp.step])))\n    elif etype == ast.ExtSlice:\n        return all(map(validate_exp, exp.dims))\n    elif etype == ast.Index:\n        return validate_exp(exp.value)\n    elif etype == ast.keyword:\n        return validate_exp(exp.value)\n    else:\n        raise ValueError(exp)\n\n\ndef construct_variables(descriptions, data):\n    # subs\n    variables = []\n    source_vars = data.domain.variables + data.domain.metas\n    for desc in descriptions:\n        desc, func = bind_variable(desc, source_vars, data)\n        var = make_variable(desc, func)\n        variables.append(var)\n    return variables\n\n\ndef sanitized_name(name):\n    sanitized = re.sub(r\"\\W\", \"_\", name)\n    if sanitized[0].isdigit():\n        sanitized = \"_\" + sanitized\n    return sanitized\n\n\ndef bind_variable(descriptor, env, data):\n    \"\"\"\n    (descriptor, env) ->\n        (descriptor, (instance -> value) | (table -> value list))\n    \"\"\"\n    if not descriptor.expression.strip():\n        return descriptor, FeatureFunc(\"nan\", [], {\"nan\": float(\"nan\")})\n\n    exp_ast = ast.parse(descriptor.expression, mode=\"eval\")\n    freev = unique(freevars(exp_ast, []))\n    variables = {sanitized_name(v.name): v for v in env}\n    source_vars = [(name, variables[name]) for name in freev\n                   if name in variables]\n\n    values = {}\n    cast = None\n    nan = float(\"nan\")\n\n    if isinstance(descriptor, DiscreteDescriptor):\n        if not descriptor.values:\n            str_func = FeatureFunc(descriptor.expression, source_vars)\n            values = sorted({str(x) for x in str_func(data)})\n            values = {name: i for i, name in enumerate(values)}\n            descriptor = descriptor._replace(values=values)\n\n            def cast(x):  # pylint: disable=function-redefined\n                return values.get(x, nan)\n\n        else:\n            values = [sanitized_name(v) for v in descriptor.values]\n            values = {name: i for i, name in enumerate(values)}\n\n    if isinstance(descriptor, DateTimeDescriptor):\n        parse = Orange.data.TimeVariable(\"_\").parse\n\n        def cast(e):  # pylint: disable=function-redefined\n            if isinstance(e, (int, float)):\n                return e\n            if e == \"\" or e is None:\n                return np.nan\n            return parse(e)\n\n    func = FeatureFunc(descriptor.expression, source_vars, values, cast)\n    return descriptor, func\n\n\ndef make_lambda(expression, args, env=None):\n    # type: (ast.Expression, List[str], Dict[str, Any]) -> types.FunctionType\n    \"\"\"\n    Create an lambda function from a expression AST.\n\n    Parameters\n    ----------\n    expression : ast.Expression\n        The body of the lambda.\n    args : List[str]\n        A list of positional argument names\n    env : Optional[Dict[str, Any]]\n        Extra environment to capture in the lambda's closure.\n\n    Returns\n    -------\n    func : types.FunctionType\n    \"\"\"\n    # lambda *{args}* : EXPRESSION\n    lambda_ = ast.Lambda(\n        args=ast.arguments(\n            posonlyargs=[],\n            args=[ast.arg(arg=arg, annotation=None) for arg in args],\n            varargs=None,\n            varargannotation=None,\n            kwonlyargs=[],\n            kwarg=None,\n            kwargannotation=None,\n            defaults=[],\n            kw_defaults=[]),\n        body=expression.body,\n    )\n    lambda_ = ast.copy_location(lambda_, expression.body)\n    # lambda **{env}** : lambda *{args}*: EXPRESSION\n    outer = ast.Lambda(\n        args=ast.arguments(\n            posonlyargs=[],\n            args=[ast.arg(arg=name, annotation=None) for name in (env or {})],\n            varargs=None,\n            varargannotation=None,\n            kwonlyargs=[],\n            kwarg=None,\n            kwargannotation=None,\n            defaults=[],\n            kw_defaults=[],\n        ),\n        body=lambda_,\n    )\n    exp = ast.Expression(body=outer, lineno=1, col_offset=0)\n    ast.fix_missing_locations(exp)\n    GLOBALS = __GLOBALS.copy()\n    GLOBALS[\"__builtins__\"] = {}\n    # pylint: disable=eval-used\n    fouter = eval(compile(exp, \"<lambda>\", \"eval\"), GLOBALS)\n    assert isinstance(fouter, types.FunctionType)\n    finner = fouter(**env)\n    assert isinstance(finner, types.FunctionType)\n    return finner\n\n\n__ALLOWED = [\n    \"Ellipsis\", \"False\", \"None\", \"True\", \"abs\", \"all\", \"any\", \"acsii\",\n    \"bin\", \"bool\", \"bytearray\", \"bytes\", \"chr\", \"complex\", \"dict\",\n    \"divmod\", \"enumerate\", \"filter\", \"float\", \"format\", \"frozenset\",\n    \"getattr\", \"hasattr\", \"hash\", \"hex\", \"id\", \"int\", \"iter\", \"len\",\n    \"list\", \"map\", \"memoryview\", \"next\", \"object\",\n    \"oct\", \"ord\", \"pow\", \"range\", \"repr\", \"reversed\", \"round\",\n    \"set\", \"slice\", \"sorted\", \"str\", \"tuple\", \"type\",\n    \"zip\"\n]\n\n__GLOBALS = {name: getattr(builtins, name) for name in __ALLOWED\n             if hasattr(builtins, name)}\n\n__GLOBALS.update({name: getattr(math, name) for name in dir(math)\n                  if not name.startswith(\"_\")})\n\n__GLOBALS.update({\n    \"normalvariate\": random.normalvariate,\n    \"gauss\": random.gauss,\n    \"expovariate\": random.expovariate,\n    \"gammavariate\": random.gammavariate,\n    \"betavariate\": random.betavariate,\n    \"lognormvariate\": random.lognormvariate,\n    \"paretovariate\": random.paretovariate,\n    \"vonmisesvariate\": random.vonmisesvariate,\n    \"weibullvariate\": random.weibullvariate,\n    \"triangular\": random.triangular,\n    \"uniform\": random.uniform,\n    \"nanmean\": lambda *args: np.nanmean(args),\n    \"nanmin\": lambda *args: np.nanmin(args),\n    \"nanmax\": lambda *args: np.nanmax(args),\n    \"nansum\": lambda *args: np.nansum(args),\n    \"nanstd\": lambda *args: np.nanstd(args),\n    \"nanmedian\": lambda *args: np.nanmedian(args),\n    \"nancumsum\": lambda *args: np.nancumsum(args),\n    \"nancumprod\": lambda *args: np.nancumprod(args),\n    \"nanargmax\": lambda *args: np.nanargmax(args),\n    \"nanargmin\": lambda *args: np.nanargmin(args),\n    \"nanvar\": lambda *args: np.nanvar(args),\n    \"mean\": lambda *args: np.mean(args),\n    \"min\": lambda *args: np.min(args),\n    \"max\": lambda *args: np.max(args),\n    \"sum\": lambda *args: np.sum(args),\n    \"std\": lambda *args: np.std(args),\n    \"median\": lambda *args: np.median(args),\n    \"cumsum\": lambda *args: np.cumsum(args),\n    \"cumprod\": lambda *args: np.cumprod(args),\n    \"argmax\": lambda *args: np.argmax(args),\n    \"argmin\": lambda *args: np.argmin(args),\n    \"var\": lambda *args: np.var(args)})\n\n\nclass FeatureFunc:\n    \"\"\"\n    Parameters\n    ----------\n    expression : str\n        An expression string\n    args : List[Tuple[str, Orange.data.Variable]]\n        A list of (`name`, `variable`) tuples where `name` is the name of\n        a variable as used in `expression`, and `variable` is the variable\n        instance used to extract the corresponding column/value from a\n        Table/Instance.\n    extra_env : Optional[Dict[str, Any]]\n        Extra environment specifying constant values to be made available\n        in expression. It must not shadow names in `args`\n    cast: Optional[Callable]\n        A function for casting the expressions result to the appropriate\n        type (e.g. string representation of date/time variables to floats)\n    \"\"\"\n    def __init__(self, expression, args, extra_env=None, cast=None):\n        self.expression = expression\n        self.args = args\n        self.extra_env = dict(extra_env or {})\n        self.func = make_lambda(ast.parse(expression, mode=\"eval\"),\n                                [name for name, _ in args], self.extra_env)\n        self.cast = cast\n        self.mask_exceptions = True\n\n    def __call__(self, instance, *_):\n        if isinstance(instance, Orange.data.Table):\n            return [self(inst) for inst in instance]\n        else:\n            try:\n                args = [str(instance[var])\n                        if instance.domain[var].is_string else instance[var]\n                        for _, var in self.args]\n                y = self.func(*args)\n            # user's expression can contain arbitrary errors\n            # this also covers missing attributes\n            except:  # pylint: disable=bare-except\n                if not self.mask_exceptions:\n                    raise\n                return np.nan\n            if self.cast:\n                y = self.cast(y)\n            return y\n\n    def __reduce__(self):\n        return type(self), (self.expression, self.args,\n                            self.extra_env, self.cast)\n\n    def __repr__(self):\n        return \"{0.__name__}{1!r}\".format(*self.__reduce__())\n\n\ndef unique(seq):\n    seen = set()\n    unique_el = []\n    for el in seq:\n        if el not in seen:\n            unique_el.append(el)\n            seen.add(el)\n    return unique_el\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWFeatureConstructor).run(Orange.data.Table(\"iris\"))\n",
            "new_file_content": "\"\"\"\nFeature Constructor\n\nA widget for defining (constructing) new features from values\nof other variables.\n\n\"\"\"\nimport re\nimport copy\nimport functools\nimport builtins\nimport math\nimport random\nimport logging\nimport ast\nimport types\n\nfrom traceback import format_exception_only\nfrom collections import namedtuple, OrderedDict\nfrom itertools import chain, count\nfrom typing import List, Dict, Any\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QSizePolicy, QAbstractItemView, QComboBox, QFormLayout, QLineEdit,\n    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,\n    QPushButton, QMenu, QListView, QFrame, QLabel)\nfrom AnyQt.QtGui import QKeySequence, QColor\nfrom AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property\nfrom orangewidget.utils.combobox import ComboBoxSearch\n\nimport Orange\nfrom Orange.data.util import get_unique_names\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import ContextSetting, DomainContextHandler\nfrom Orange.widgets.utils import itemmodels, vartype\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets import report\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\n\nFeatureDescriptor = \\\n    namedtuple(\"FeatureDescriptor\", [\"name\", \"expression\"])\n\nContinuousDescriptor = \\\n    namedtuple(\"ContinuousDescriptor\",\n               [\"name\", \"expression\", \"number_of_decimals\"])\nDateTimeDescriptor = \\\n    namedtuple(\"DateTimeDescriptor\",\n               [\"name\", \"expression\"])\nDiscreteDescriptor = \\\n    namedtuple(\"DiscreteDescriptor\",\n               [\"name\", \"expression\", \"values\", \"ordered\"])\n\nStringDescriptor = namedtuple(\"StringDescriptor\", [\"name\", \"expression\"])\n\n#warningIcon = gui.createAttributePixmap('!', QColor((202, 0, 32)))\n\ndef make_variable(descriptor, compute_value):\n    if isinstance(descriptor, ContinuousDescriptor):\n        return Orange.data.ContinuousVariable(\n            descriptor.name,\n            descriptor.number_of_decimals,\n            compute_value)\n    if isinstance(descriptor, DateTimeDescriptor):\n        return Orange.data.TimeVariable(\n            descriptor.name,\n            compute_value=compute_value, have_date=True, have_time=True)\n    elif isinstance(descriptor, DiscreteDescriptor):\n        return Orange.data.DiscreteVariable(\n            descriptor.name,\n            values=descriptor.values,\n            ordered=descriptor.ordered,\n            compute_value=compute_value)\n    elif isinstance(descriptor, StringDescriptor):\n        return Orange.data.StringVariable(\n            descriptor.name,\n            compute_value=compute_value)\n    else:\n        raise TypeError\n\n\ndef selected_row(view):\n    \"\"\"\n    Return the index of selected row in a `view` (:class:`QListView`)\n\n    The view's selection mode must be a QAbstractItemView.SingleSelction\n    \"\"\"\n    if view.selectionMode() in [QAbstractItemView.MultiSelection,\n                                QAbstractItemView.ExtendedSelection]:\n        raise ValueError(\"invalid 'selectionMode'\")\n\n    sel_model = view.selectionModel()\n    indexes = sel_model.selectedRows()\n    if indexes:\n        assert len(indexes) == 1\n        return indexes[0].row()\n    else:\n        return None\n\n\nclass FeatureEditor(QFrame):\n    FUNCTIONS = dict(chain([(key, val) for key, val in math.__dict__.items()\n                            if not key.startswith(\"_\")],\n                           [(key, val) for key, val in builtins.__dict__.items()\n                            if key in {\"str\", \"float\", \"int\", \"len\",\n                                       \"abs\", \"max\", \"min\"}]))\n    featureChanged = Signal()\n    featureEdited = Signal()\n\n    modifiedChanged = Signal(bool)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        layout = QFormLayout(\n            fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow\n        )\n        layout.setContentsMargins(0, 0, 0, 0)\n        self.nameedit = QLineEdit(\n            placeholderText=\"Name...\",\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.Fixed)\n        )\n        self.expressionedit = QLineEdit(\n            placeholderText=\"Expression...\",\n            toolTip=self.ExpressionTooltip)\n\n        self.attrs_model = itemmodels.VariableListModel(\n            [\"Select Feature\"], parent=self)\n        self.attributescb = ComboBoxSearch(\n            minimumContentsLength=16,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)\n        )\n        self.attributescb.setModel(self.attrs_model)\n\n        sorted_funcs = sorted(self.FUNCTIONS)\n        self.funcs_model = itemmodels.PyListModelTooltip()\n        self.funcs_model.setParent(self)\n\n        self.funcs_model[:] = chain([\"Select Function\"], sorted_funcs)\n        self.funcs_model.tooltips[:] = chain(\n            [''],\n            [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])\n\n        self.functionscb = ComboBoxSearch(\n            minimumContentsLength=16,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))\n        self.functionscb.setModel(self.funcs_model)\n\n        hbox = QHBoxLayout()\n        hbox.addWidget(self.attributescb)\n        hbox.addWidget(self.functionscb)\n\n        layout.addRow(self.nameedit, self.expressionedit)\n        layout.addRow(self.tr(\"\"), hbox)\n        self.setLayout(layout)\n\n        self.nameedit.editingFinished.connect(self._invalidate)\n        self.expressionedit.textChanged.connect(self._invalidate)\n        self.attributescb.currentIndexChanged.connect(self.on_attrs_changed)\n        self.functionscb.currentIndexChanged.connect(self.on_funcs_changed)\n\n        self._modified = False\n\n    def setModified(self, modified):\n        if not isinstance(modified, bool):\n            raise TypeError\n\n        if self._modified != modified:\n            self._modified = modified\n            self.modifiedChanged.emit(modified)\n\n    def modified(self):\n        return self._modified\n\n    modified = Property(bool, modified, setModified,\n                        notify=modifiedChanged)\n\n    def setEditorData(self, data, domain):\n        self.nameedit.setText(data.name)\n        self.expressionedit.setText(data.expression)\n        self.setModified(False)\n        self.featureChanged.emit()\n        self.attrs_model[:] = [\"Select Feature\"]\n        if domain is not None and not domain.empty():\n            self.attrs_model[:] += chain(domain.attributes,\n                                         domain.class_vars,\n                                         domain.metas)\n\n    def editorData(self):\n        return FeatureDescriptor(name=self.nameedit.text(),\n                                 expression=self.nameedit.text())\n\n    def _invalidate(self):\n        self.setModified(True)\n        self.featureEdited.emit()\n        self.featureChanged.emit()\n\n    def on_attrs_changed(self):\n        index = self.attributescb.currentIndex()\n        if index > 0:\n            attr = sanitized_name(self.attrs_model[index].name)\n            self.insert_into_expression(attr)\n            self.attributescb.setCurrentIndex(0)\n\n    def on_funcs_changed(self):\n        index = self.functionscb.currentIndex()\n        if index > 0:\n            func = self.funcs_model[index]\n            if func in [\"atan2\", \"fmod\", \"ldexp\", \"log\",\n                        \"pow\", \"copysign\", \"hypot\"]:\n                self.insert_into_expression(func + \"(,)\")\n                self.expressionedit.cursorBackward(False, 2)\n            elif func in [\"e\", \"pi\"]:\n                self.insert_into_expression(func)\n            else:\n                self.insert_into_expression(func + \"()\")\n                self.expressionedit.cursorBackward(False)\n            self.functionscb.setCurrentIndex(0)\n\n    def insert_into_expression(self, what):\n        cp = self.expressionedit.cursorPosition()\n        ct = self.expressionedit.text()\n        text = ct[:cp] + what + ct[cp:]\n        self.expressionedit.setText(text)\n        self.expressionedit.setFocus()\n\n\nclass ContinuousFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A numeric expression\"\n\n    def editorData(self):\n        return ContinuousDescriptor(\n            name=self.nameedit.text(),\n            number_of_decimals=None,\n            expression=self.expressionedit.text()\n        )\n\n\nclass DateTimeFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \\\n        \"Result must be a string in ISO-8601 format \" \\\n        \"(e.g. 2019-07-30T15:37:27 or a part thereof),\\n\" \\\n        \"or a number of seconds since Jan 1, 1970.\"\n\n    def editorData(self):\n        return DateTimeDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text()\n        )\n\n\nclass DiscreteFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \\\n        \"Result must be a string, if values are not explicitly given\\n\" \\\n        \"or a zero-based integer indices into a list of values given below.\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        tooltip = \\\n            \"If values are given, above expression must return zero-based \" \\\n            \"integer indices into that list.\"\n        self.valuesedit = QLineEdit(placeholderText=\"A, B ...\", toolTip=tooltip)\n        self.valuesedit.textChanged.connect(self._invalidate)\n\n        layout = self.layout()\n        label = QLabel(self.tr(\"Values (optional)\"))\n        label.setToolTip(tooltip)\n        layout.addRow(label, self.valuesedit)\n\n    def setEditorData(self, data, domain):\n        self.valuesedit.setText(\n            \", \".join(v.replace(\",\", r\"\\,\") for v in data.values))\n\n        super().setEditorData(data, domain)\n\n    def editorData(self):\n        values = self.valuesedit.text()\n        values = re.split(r\"(?<!\\\\),\", values)\n        values = tuple(filter(None, [v.replace(r\"\\,\", \",\").strip() for v in values]))\n        return DiscreteDescriptor(\n            name=self.nameedit.text(),\n            values=values,\n            ordered=False,\n            expression=self.expressionedit.text()\n        )\n\n\nclass StringFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A string expression\"\n\n    def editorData(self):\n        return StringDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text()\n        )\n\n\n_VarMap = {\n    DiscreteDescriptor: vartype(Orange.data.DiscreteVariable(\"d\")),\n    ContinuousDescriptor: vartype(Orange.data.ContinuousVariable(\"c\")),\n    DateTimeDescriptor: vartype(Orange.data.TimeVariable(\"t\")),\n    StringDescriptor: vartype(Orange.data.StringVariable(\"s\"))\n}\n\n\n@functools.lru_cache(20)\ndef variable_icon(dtype):\n    vtype = _VarMap.get(dtype, dtype)\n    return gui.attributeIconDict[vtype]\n\n\nclass FeatureItemDelegate(QStyledItemDelegate):\n    @staticmethod\n    def displayText(value, _):\n        return value.name + \" := \" + value.expression\n\n\nclass DescriptorModel(itemmodels.PyListModel):\n    def data(self, index, role=Qt.DisplayRole):\n        if role == Qt.DecorationRole:\n            value = self[index.row()]\n            return variable_icon(type(value))\n        else:\n            return super().data(index, role)\n\n\nclass FeatureConstructorHandler(DomainContextHandler):\n    \"\"\"Context handler that filters descriptors\"\"\"\n\n    def is_valid_item(self, setting, item, attrs, metas):\n        \"\"\"Check if descriptor `item` can be used with given domain.\n\n        Return True if descriptor's expression contains only\n        available variables and descriptors name does not clash with\n        existing variables.\n        \"\"\"\n        if item.name in attrs or item.name in metas:\n            return False\n\n        try:\n            exp_ast = ast.parse(item.expression, mode=\"eval\")\n        # ast.parse can return arbitrary errors, not only SyntaxError\n        # pylint: disable=broad-except\n        except Exception:\n            return False\n\n        available = dict(globals()[\"__GLOBALS\"])\n        for var in attrs:\n            available[sanitized_name(var)] = None\n        for var in metas:\n            available[sanitized_name(var)] = None\n\n        if freevars(exp_ast, available):\n            return False\n        return True\n\n\nclass OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    class Warning(OWWidget.Warning):\n        renamed_var = Msg(\"Recently added variable has been renamed, \"\n                           \"to avoid duplicates.\\n\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, self.reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            self.Warning.clear()\n            editor = self.editorstack.currentWidget()\n            proposed = editor.editorData().name\n            unique = get_unique_names(self.reserved_names(self.currentIndex),\n                                      proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != unique:\n                self.Warning.renamed_var()\n                feature = feature.__class__(unique, *feature[1:])\n                \n            self.featuremodel[self.currentIndex] = feature\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    def reserved_names(self, idx_=None):\n        varnames = []\n        if self.data is not None:\n            varnames = [var.name for var in\n                        self.data.domain.variables + self.data.domain.metas]\n        varnames += [desc.name for idx, desc in enumerate(self.featuremodel)\n                     if idx != idx_]\n        return set(varnames)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)\n\n\ndef freevars(exp, env):\n    \"\"\"\n    Return names of all free variables in a parsed (expression) AST.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        An expression ast (ast.parse(..., mode=\"single\"))\n    env : List[str]\n        Environment\n\n    Returns\n    -------\n    freevars : List[str]\n\n    See also\n    --------\n    ast\n\n    \"\"\"\n    # pylint: disable=too-many-return-statements,too-many-branches\n    etype = type(exp)\n    if etype in [ast.Expr, ast.Expression]:\n        return freevars(exp.body, env)\n    elif etype == ast.BoolOp:\n        return sum((freevars(v, env) for v in exp.values), [])\n    elif etype == ast.BinOp:\n        return freevars(exp.left, env) + freevars(exp.right, env)\n    elif etype == ast.UnaryOp:\n        return freevars(exp.operand, env)\n    elif etype == ast.Lambda:\n        args = exp.args\n        assert isinstance(args, ast.arguments)\n        argnames = [a.arg for a in args.args]\n        argnames += [args.vararg.arg] if args.vararg else []\n        argnames += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []\n        argnames += [args.kwarg] if args.kwarg else []\n        return freevars(exp.body, env + argnames)\n    elif etype == ast.IfExp:\n        return (freevars(exp.test, env) + freevars(exp.body, env) +\n                freevars(exp.orelse, env))\n    elif etype == ast.Dict:\n        return sum((freevars(v, env)\n                    for v in chain(exp.keys, exp.values)), [])\n    elif etype == ast.Set:\n        return sum((freevars(v, env) for v in exp.elts), [])\n    elif etype in [ast.SetComp, ast.ListComp, ast.GeneratorExp, ast.DictComp]:\n        env_ext = []\n        vars_ = []\n        for gen in exp.generators:\n            target_names = freevars(gen.target, [])  # assigned names\n            vars_iter = freevars(gen.iter, env)\n            env_ext += target_names\n            vars_ifs = list(chain(*(freevars(ifexp, env + target_names)\n                                    for ifexp in gen.ifs or [])))\n            vars_ += vars_iter + vars_ifs\n\n        if etype == ast.DictComp:\n            vars_ = (freevars(exp.key, env_ext) +\n                     freevars(exp.value, env_ext) +\n                     vars_)\n        else:\n            vars_ = freevars(exp.elt, env + env_ext) + vars_\n        return vars_\n    # Yield, YieldFrom???\n    elif etype == ast.Compare:\n        return sum((freevars(v, env)\n                    for v in [exp.left] + exp.comparators), [])\n    elif etype == ast.Call:\n        return sum(map(lambda e: freevars(e, env),\n                       chain([exp.func],\n                             exp.args or [],\n                             [k.value for k in exp.keywords or []])),\n                   [])\n    elif etype == ast.Starred:\n        # a 'starred' call parameter (e.g. a and b in `f(x, *a, *b)`\n        return freevars(exp.value, env)\n    elif etype in [ast.Num, ast.Str, ast.Ellipsis, ast.Bytes, ast.NameConstant]:\n        return []\n    elif etype == ast.Constant:\n        return []\n    elif etype == ast.Attribute:\n        return freevars(exp.value, env)\n    elif etype == ast.Subscript:\n        return freevars(exp.value, env) + freevars(exp.slice, env)\n    elif etype == ast.Name:\n        return [exp.id] if exp.id not in env else []\n    elif etype == ast.List:\n        return sum((freevars(e, env) for e in exp.elts), [])\n    elif etype == ast.Tuple:\n        return sum((freevars(e, env) for e in exp.elts), [])\n    elif etype == ast.Slice:\n        return sum((freevars(e, env)\n                    for e in filter(None, [exp.lower, exp.upper, exp.step])),\n                   [])\n    elif etype == ast.ExtSlice:\n        return sum((freevars(e, env) for e in exp.dims), [])\n    elif etype == ast.Index:\n        return freevars(exp.value, env)\n    elif etype == ast.keyword:\n        return freevars(exp.value, env)\n    else:\n        raise ValueError(exp)\n\n\ndef validate_exp(exp):\n    \"\"\"\n    Validate an `ast.AST` expression.\n\n    Only expressions with no list,set,dict,generator comprehensions\n    are accepted.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        A parsed abstract syntax tree\n\n    \"\"\"\n    # pylint: disable=too-many-branches\n    if not isinstance(exp, ast.AST):\n        raise TypeError(\"exp is not a 'ast.AST' instance\")\n\n    etype = type(exp)\n    if etype in [ast.Expr, ast.Expression]:\n        return validate_exp(exp.body)\n    elif etype == ast.BoolOp:\n        return all(map(validate_exp, exp.values))\n    elif etype == ast.BinOp:\n        return all(map(validate_exp, [exp.left, exp.right]))\n    elif etype == ast.UnaryOp:\n        return validate_exp(exp.operand)\n    elif etype == ast.IfExp:\n        return all(map(validate_exp, [exp.test, exp.body, exp.orelse]))\n    elif etype == ast.Dict:\n        return all(map(validate_exp, chain(exp.keys, exp.values)))\n    elif etype == ast.Set:\n        return all(map(validate_exp, exp.elts))\n    elif etype == ast.Compare:\n        return all(map(validate_exp, [exp.left] + exp.comparators))\n    elif etype == ast.Call:\n        subexp = chain([exp.func], exp.args or [],\n                       [k.value for k in exp.keywords or []])\n        return all(map(validate_exp, subexp))\n    elif etype == ast.Starred:\n        assert isinstance(exp.ctx, ast.Load)\n        return validate_exp(exp.value)\n    elif etype in [ast.Num, ast.Str, ast.Bytes, ast.Ellipsis, ast.NameConstant]:\n        return True\n    elif etype == ast.Constant:\n        return True\n    elif etype == ast.Attribute:\n        return True\n    elif etype == ast.Subscript:\n        return all(map(validate_exp, [exp.value, exp.slice]))\n    elif etype in {ast.List, ast.Tuple}:\n        assert isinstance(exp.ctx, ast.Load)\n        return all(map(validate_exp, exp.elts))\n    elif etype == ast.Name:\n        return True\n    elif etype == ast.Slice:\n        return all(map(validate_exp,\n                       filter(None, [exp.lower, exp.upper, exp.step])))\n    elif etype == ast.ExtSlice:\n        return all(map(validate_exp, exp.dims))\n    elif etype == ast.Index:\n        return validate_exp(exp.value)\n    elif etype == ast.keyword:\n        return validate_exp(exp.value)\n    else:\n        raise ValueError(exp)\n\n\ndef construct_variables(descriptions, data):\n    # subs\n    variables = []\n    source_vars = data.domain.variables + data.domain.metas\n    for desc in descriptions:\n        desc, func = bind_variable(desc, source_vars, data)\n        var = make_variable(desc, func)\n        variables.append(var)\n    return variables\n\n\ndef sanitized_name(name):\n    sanitized = re.sub(r\"\\W\", \"_\", name)\n    if sanitized[0].isdigit():\n        sanitized = \"_\" + sanitized\n    return sanitized\n\n\ndef bind_variable(descriptor, env, data):\n    \"\"\"\n    (descriptor, env) ->\n        (descriptor, (instance -> value) | (table -> value list))\n    \"\"\"\n    if not descriptor.expression.strip():\n        return descriptor, FeatureFunc(\"nan\", [], {\"nan\": float(\"nan\")})\n\n    exp_ast = ast.parse(descriptor.expression, mode=\"eval\")\n    freev = unique(freevars(exp_ast, []))\n    variables = {sanitized_name(v.name): v for v in env}\n    source_vars = [(name, variables[name]) for name in freev\n                   if name in variables]\n\n    values = {}\n    cast = None\n    nan = float(\"nan\")\n\n    if isinstance(descriptor, DiscreteDescriptor):\n        if not descriptor.values:\n            str_func = FeatureFunc(descriptor.expression, source_vars)\n            values = sorted({str(x) for x in str_func(data)})\n            values = {name: i for i, name in enumerate(values)}\n            descriptor = descriptor._replace(values=values)\n\n            def cast(x):  # pylint: disable=function-redefined\n                return values.get(x, nan)\n\n        else:\n            values = [sanitized_name(v) for v in descriptor.values]\n            values = {name: i for i, name in enumerate(values)}\n\n    if isinstance(descriptor, DateTimeDescriptor):\n        parse = Orange.data.TimeVariable(\"_\").parse\n\n        def cast(e):  # pylint: disable=function-redefined\n            if isinstance(e, (int, float)):\n                return e\n            if e == \"\" or e is None:\n                return np.nan\n            return parse(e)\n\n    func = FeatureFunc(descriptor.expression, source_vars, values, cast)\n    return descriptor, func\n\n\ndef make_lambda(expression, args, env=None):\n    # type: (ast.Expression, List[str], Dict[str, Any]) -> types.FunctionType\n    \"\"\"\n    Create an lambda function from a expression AST.\n\n    Parameters\n    ----------\n    expression : ast.Expression\n        The body of the lambda.\n    args : List[str]\n        A list of positional argument names\n    env : Optional[Dict[str, Any]]\n        Extra environment to capture in the lambda's closure.\n\n    Returns\n    -------\n    func : types.FunctionType\n    \"\"\"\n    # lambda *{args}* : EXPRESSION\n    lambda_ = ast.Lambda(\n        args=ast.arguments(\n            posonlyargs=[],\n            args=[ast.arg(arg=arg, annotation=None) for arg in args],\n            varargs=None,\n            varargannotation=None,\n            kwonlyargs=[],\n            kwarg=None,\n            kwargannotation=None,\n            defaults=[],\n            kw_defaults=[]),\n        body=expression.body,\n    )\n    lambda_ = ast.copy_location(lambda_, expression.body)\n    # lambda **{env}** : lambda *{args}*: EXPRESSION\n    outer = ast.Lambda(\n        args=ast.arguments(\n            posonlyargs=[],\n            args=[ast.arg(arg=name, annotation=None) for name in (env or {})],\n            varargs=None,\n            varargannotation=None,\n            kwonlyargs=[],\n            kwarg=None,\n            kwargannotation=None,\n            defaults=[],\n            kw_defaults=[],\n        ),\n        body=lambda_,\n    )\n    exp = ast.Expression(body=outer, lineno=1, col_offset=0)\n    ast.fix_missing_locations(exp)\n    GLOBALS = __GLOBALS.copy()\n    GLOBALS[\"__builtins__\"] = {}\n    # pylint: disable=eval-used\n    fouter = eval(compile(exp, \"<lambda>\", \"eval\"), GLOBALS)\n    assert isinstance(fouter, types.FunctionType)\n    finner = fouter(**env)\n    assert isinstance(finner, types.FunctionType)\n    return finner\n\n\n__ALLOWED = [\n    \"Ellipsis\", \"False\", \"None\", \"True\", \"abs\", \"all\", \"any\", \"acsii\",\n    \"bin\", \"bool\", \"bytearray\", \"bytes\", \"chr\", \"complex\", \"dict\",\n    \"divmod\", \"enumerate\", \"filter\", \"float\", \"format\", \"frozenset\",\n    \"getattr\", \"hasattr\", \"hash\", \"hex\", \"id\", \"int\", \"iter\", \"len\",\n    \"list\", \"map\", \"memoryview\", \"next\", \"object\",\n    \"oct\", \"ord\", \"pow\", \"range\", \"repr\", \"reversed\", \"round\",\n    \"set\", \"slice\", \"sorted\", \"str\", \"tuple\", \"type\",\n    \"zip\"\n]\n\n__GLOBALS = {name: getattr(builtins, name) for name in __ALLOWED\n             if hasattr(builtins, name)}\n\n__GLOBALS.update({name: getattr(math, name) for name in dir(math)\n                  if not name.startswith(\"_\")})\n\n__GLOBALS.update({\n    \"normalvariate\": random.normalvariate,\n    \"gauss\": random.gauss,\n    \"expovariate\": random.expovariate,\n    \"gammavariate\": random.gammavariate,\n    \"betavariate\": random.betavariate,\n    \"lognormvariate\": random.lognormvariate,\n    \"paretovariate\": random.paretovariate,\n    \"vonmisesvariate\": random.vonmisesvariate,\n    \"weibullvariate\": random.weibullvariate,\n    \"triangular\": random.triangular,\n    \"uniform\": random.uniform,\n    \"nanmean\": lambda *args: np.nanmean(args),\n    \"nanmin\": lambda *args: np.nanmin(args),\n    \"nanmax\": lambda *args: np.nanmax(args),\n    \"nansum\": lambda *args: np.nansum(args),\n    \"nanstd\": lambda *args: np.nanstd(args),\n    \"nanmedian\": lambda *args: np.nanmedian(args),\n    \"nancumsum\": lambda *args: np.nancumsum(args),\n    \"nancumprod\": lambda *args: np.nancumprod(args),\n    \"nanargmax\": lambda *args: np.nanargmax(args),\n    \"nanargmin\": lambda *args: np.nanargmin(args),\n    \"nanvar\": lambda *args: np.nanvar(args),\n    \"mean\": lambda *args: np.mean(args),\n    \"min\": lambda *args: np.min(args),\n    \"max\": lambda *args: np.max(args),\n    \"sum\": lambda *args: np.sum(args),\n    \"std\": lambda *args: np.std(args),\n    \"median\": lambda *args: np.median(args),\n    \"cumsum\": lambda *args: np.cumsum(args),\n    \"cumprod\": lambda *args: np.cumprod(args),\n    \"argmax\": lambda *args: np.argmax(args),\n    \"argmin\": lambda *args: np.argmin(args),\n    \"var\": lambda *args: np.var(args)})\n\n\nclass FeatureFunc:\n    \"\"\"\n    Parameters\n    ----------\n    expression : str\n        An expression string\n    args : List[Tuple[str, Orange.data.Variable]]\n        A list of (`name`, `variable`) tuples where `name` is the name of\n        a variable as used in `expression`, and `variable` is the variable\n        instance used to extract the corresponding column/value from a\n        Table/Instance.\n    extra_env : Optional[Dict[str, Any]]\n        Extra environment specifying constant values to be made available\n        in expression. It must not shadow names in `args`\n    cast: Optional[Callable]\n        A function for casting the expressions result to the appropriate\n        type (e.g. string representation of date/time variables to floats)\n    \"\"\"\n    def __init__(self, expression, args, extra_env=None, cast=None):\n        self.expression = expression\n        self.args = args\n        self.extra_env = dict(extra_env or {})\n        self.func = make_lambda(ast.parse(expression, mode=\"eval\"),\n                                [name for name, _ in args], self.extra_env)\n        self.cast = cast\n        self.mask_exceptions = True\n\n    def __call__(self, instance, *_):\n        if isinstance(instance, Orange.data.Table):\n            return [self(inst) for inst in instance]\n        else:\n            try:\n                args = [str(instance[var])\n                        if instance.domain[var].is_string else instance[var]\n                        for _, var in self.args]\n                y = self.func(*args)\n            # user's expression can contain arbitrary errors\n            # this also covers missing attributes\n            except:  # pylint: disable=bare-except\n                if not self.mask_exceptions:\n                    raise\n                return np.nan\n            if self.cast:\n                y = self.cast(y)\n            return y\n\n    def __reduce__(self):\n        return type(self), (self.expression, self.args,\n                            self.extra_env, self.cast)\n\n    def __repr__(self):\n        return \"{0.__name__}{1!r}\".format(*self.__reduce__())\n\n\ndef unique(seq):\n    seen = set()\n    unique_el = []\n    for el in seq:\n        if el not in seen:\n            unique_el.append(el)\n            seen.add(el)\n    return unique_el\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWFeatureConstructor).run(Orange.data.Table(\"iris\"))\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/owfeatureconstructor.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "a31e895be",
                "new_commit_hash": "e628fc3ab",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/owfeatureconstructor.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/owfeatureconstructor.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 26,
                            "length": 11
                        },
                        "new_range": {
                            "start": 26,
                            "length": 12
                        },
                        "section": "from AnyQt.QtWidgets import ("
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    QSizePolicy, QAbstractItemView, QComboBox, QFormLayout, QLineEdit,",
                                "type": "context"
                            },
                            {
                                "content": "    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,",
                                "type": "context"
                            },
                            {
                                "content": "    QPushButton, QMenu, QListView, QFrame, QLabel)",
                                "type": "context"
                            },
                            {
                                "content": "from AnyQt.QtGui import QKeySequence",
                                "type": "deleted"
                            },
                            {
                                "content": "from AnyQt.QtGui import QKeySequence, QColor",
                                "type": "added"
                            },
                            {
                                "content": "from AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property",
                                "type": "context"
                            },
                            {
                                "content": "from orangewidget.utils.combobox import ComboBoxSearch",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "import Orange",
                                "type": "context"
                            },
                            {
                                "content": "from Orange.data.util import get_unique_names",
                                "type": "added"
                            },
                            {
                                "content": "from Orange.widgets import gui",
                                "type": "context"
                            },
                            {
                                "content": "from Orange.widgets.settings import ContextSetting, DomainContextHandler",
                                "type": "context"
                            },
                            {
                                "content": "from Orange.widgets.utils import itemmodels, vartype",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "statement",
                            "name": "",
                            "content": "from AnyQt.QtGui import QKeySequence, QColor",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 29,
                            "end_lineno": 29,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "statement",
                            "name": "",
                            "content": "from Orange.data.util import get_unique_names",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 34,
                            "end_lineno": 34,
                            "parent": null
                        }
                    ],
                    "deleted_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "statement",
                            "name": "",
                            "content": "from AnyQt.QtGui import QKeySequence",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 29,
                            "end_lineno": 29,
                            "parent": null
                        }
                    ]
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 55,
                            "length": 6
                        },
                        "new_range": {
                            "start": 56,
                            "length": 7
                        },
                        "section": "DiscreteDescriptor = \\"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "StringDescriptor = namedtuple(\"StringDescriptor\", [\"name\", \"expression\"])",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "#warningIcon = gui.createAttributePixmap('!', QColor((202, 0, 32)))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def make_variable(descriptor, compute_value):",
                                "type": "context"
                            },
                            {
                                "content": "    if isinstance(descriptor, ContinuousDescriptor):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 390,
                            "length": 6
                        },
                        "new_range": {
                            "start": 392,
                            "length": 10
                        },
                        "section": "class OWFeatureConstructor(OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        more_values_needed = Msg(\"Categorical feature {} needs more values.\")",
                                "type": "context"
                            },
                            {
                                "content": "        invalid_expressions = Msg(\"Invalid expressions: {}.\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    class Warning(OWWidget.Warning):",
                                "type": "added"
                            },
                            {
                                "content": "        renamed_var = Msg(\"Recently added variable has been renamed, \"",
                                "type": "added"
                            },
                            {
                                "content": "                           \"to avoid duplicates.\\n\")",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def __init__(self):",
                                "type": "context"
                            },
                            {
                                "content": "        super().__init__()",
                                "type": "context"
                            },
                            {
                                "content": "        self.data = None",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "class",
                            "name": "OWFeatureConstructor",
                            "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    class Warning(OWWidget.Warning):\n        renamed_var = Msg(\"Recently added variable has been renamed, \"\n                           \"to avoid duplicates.\\n\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, self.reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            self.Warning.clear()\n            editor = self.editorstack.currentWidget()\n            proposed = editor.editorData().name\n            unique = get_unique_names(self.reserved_names(self.currentIndex),\n                                      proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != unique:\n                self.Warning.renamed_var()\n                feature = feature.__class__(unique, *feature[1:])\n                \n            self.featuremodel[self.currentIndex] = feature\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    def reserved_names(self, idx_=None):\n        varnames = []\n        if self.data is not None:\n            varnames = [var.name for var in\n                        self.data.domain.variables + self.data.domain.metas]\n        varnames += [desc.name for idx, desc in enumerate(self.featuremodel)\n                     if idx != idx_]\n        return set(varnames)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 365,
                            "end_lineno": 722,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 427,
                            "length": 16
                        },
                        "new_range": {
                            "start": 433,
                            "length": 8
                        },
                        "section": "class OWFeatureConstructor(OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            candidates = (fmt.format(i) for i in count(1))",
                                "type": "context"
                            },
                            {
                                "content": "            return next(c for c in candidates if c not in reserved)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        def reserved_names():",
                                "type": "deleted"
                            },
                            {
                                "content": "            varnames = []",
                                "type": "deleted"
                            },
                            {
                                "content": "            if self.data is not None:",
                                "type": "deleted"
                            },
                            {
                                "content": "                varnames = [var.name for var in",
                                "type": "deleted"
                            },
                            {
                                "content": "                            self.data.domain.variables + self.data.domain.metas]",
                                "type": "deleted"
                            },
                            {
                                "content": "            varnames += [desc.name for desc in self.featuremodel]",
                                "type": "deleted"
                            },
                            {
                                "content": "            return set(varnames)",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        def generate_newname(fmt):",
                                "type": "context"
                            },
                            {
                                "content": "            return unique_name(fmt, reserved_names())",
                                "type": "deleted"
                            },
                            {
                                "content": "            return unique_name(fmt, self.reserved_names())",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        menu = QMenu(self.addbutton)",
                                "type": "context"
                            },
                            {
                                "content": "        cont = menu.addAction(\"Numeric\")",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "class",
                            "name": "OWFeatureConstructor",
                            "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 363,
                            "end_lineno": 705,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "method",
                            "name": "OWFeatureConstructor.__init__",
                            "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 393,
                            "end_lineno": 510,
                            "parent": {
                                "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                                "type": "class",
                                "name": "OWFeatureConstructor",
                                "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 363,
                                "end_lineno": 705,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 531,
                            "length": 8
                        },
                        "new_range": {
                            "start": 529,
                            "length": 18
                        },
                        "section": "class OWFeatureConstructor(OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def _on_modified(self):",
                                "type": "context"
                            },
                            {
                                "content": "        if self.currentIndex >= 0:",
                                "type": "context"
                            },
                            {
                                "content": "            self.Warning.clear()",
                                "type": "added"
                            },
                            {
                                "content": "            editor = self.editorstack.currentWidget()",
                                "type": "context"
                            },
                            {
                                "content": "            self.featuremodel[self.currentIndex] = editor.editorData()",
                                "type": "deleted"
                            },
                            {
                                "content": "            proposed = editor.editorData().name",
                                "type": "added"
                            },
                            {
                                "content": "            unique = get_unique_names(self.reserved_names(self.currentIndex),",
                                "type": "added"
                            },
                            {
                                "content": "                                      proposed)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "            feature = editor.editorData()",
                                "type": "added"
                            },
                            {
                                "content": "            if editor.editorData().name != unique:",
                                "type": "added"
                            },
                            {
                                "content": "                self.Warning.renamed_var()",
                                "type": "added"
                            },
                            {
                                "content": "                feature = feature.__class__(unique, *feature[1:])",
                                "type": "added"
                            },
                            {
                                "content": "                ",
                                "type": "added"
                            },
                            {
                                "content": "            self.featuremodel[self.currentIndex] = feature",
                                "type": "added"
                            },
                            {
                                "content": "            self.descriptors = list(self.featuremodel)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def setDescriptors(self, descriptors):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "method",
                            "name": "OWFeatureConstructor._on_modified",
                            "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 532,
                            "end_lineno": 536,
                            "parent": {
                                "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                                "type": "class",
                                "name": "OWFeatureConstructor",
                                "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 363,
                                "end_lineno": 705,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "class",
                            "name": "OWFeatureConstructor",
                            "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 363,
                            "end_lineno": 705,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 542,
                            "length": 6
                        },
                        "new_range": {
                            "start": 550,
                            "length": 15
                        },
                        "section": "class OWFeatureConstructor(OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.descriptors = descriptors",
                                "type": "context"
                            },
                            {
                                "content": "        self.featuremodel[:] = list(self.descriptors)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def reserved_names(self, idx_=None):",
                                "type": "added"
                            },
                            {
                                "content": "        varnames = []",
                                "type": "added"
                            },
                            {
                                "content": "        if self.data is not None:",
                                "type": "added"
                            },
                            {
                                "content": "            varnames = [var.name for var in",
                                "type": "added"
                            },
                            {
                                "content": "                        self.data.domain.variables + self.data.domain.metas]",
                                "type": "added"
                            },
                            {
                                "content": "        varnames += [desc.name for idx, desc in enumerate(self.featuremodel)",
                                "type": "added"
                            },
                            {
                                "content": "                     if idx != idx_]",
                                "type": "added"
                            },
                            {
                                "content": "        return set(varnames)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    @Inputs.data",
                                "type": "context"
                            },
                            {
                                "content": "    @check_sql_input",
                                "type": "context"
                            },
                            {
                                "content": "    def setData(self, data=None):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "class",
                            "name": "OWFeatureConstructor",
                            "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    class Warning(OWWidget.Warning):\n        renamed_var = Msg(\"Recently added variable has been renamed, \"\n                           \"to avoid duplicates.\\n\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, self.reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            self.Warning.clear()\n            editor = self.editorstack.currentWidget()\n            proposed = editor.editorData().name\n            unique = get_unique_names(self.reserved_names(self.currentIndex),\n                                      proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != unique:\n                self.Warning.renamed_var()\n                feature = feature.__class__(unique, *feature[1:])\n                \n            self.featuremodel[self.currentIndex] = feature\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    def reserved_names(self, idx_=None):\n        varnames = []\n        if self.data is not None:\n            varnames = [var.name for var in\n                        self.data.domain.variables + self.data.domain.metas]\n        varnames += [desc.name for idx, desc in enumerate(self.featuremodel)\n                     if idx != idx_]\n        return set(varnames)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 365,
                            "end_lineno": 722,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "method",
                            "name": "OWFeatureConstructor.reserved_names",
                            "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    class Warning(OWWidget.Warning):\n        renamed_var = Msg(\"Recently added variable has been renamed, \"\n                           \"to avoid duplicates.\\n\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, self.reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            self.Warning.clear()\n            editor = self.editorstack.currentWidget()\n            proposed = editor.editorData().name\n            unique = get_unique_names(self.reserved_names(self.currentIndex),\n                                      proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != unique:\n                self.Warning.renamed_var()\n                feature = feature.__class__(unique, *feature[1:])\n                \n            self.featuremodel[self.currentIndex] = feature\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    def reserved_names(self, idx_=None):\n        varnames = []\n        if self.data is not None:\n            varnames = [var.name for var in\n                        self.data.domain.variables + self.data.domain.metas]\n        varnames += [desc.name for idx, desc in enumerate(self.featuremodel)\n                     if idx != idx_]\n        return set(varnames)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 553,
                            "end_lineno": 560,
                            "parent": {
                                "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                                "type": "class",
                                "name": "OWFeatureConstructor",
                                "content": "class OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    class Warning(OWWidget.Warning):\n        renamed_var = Msg(\"Recently added variable has been renamed, \"\n                           \"to avoid duplicates.\\n\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, self.reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            self.Warning.clear()\n            editor = self.editorstack.currentWidget()\n            proposed = editor.editorData().name\n            unique = get_unique_names(self.reserved_names(self.currentIndex),\n                                      proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != unique:\n                self.Warning.renamed_var()\n                feature = feature.__class__(unique, *feature[1:])\n                \n            self.featuremodel[self.currentIndex] = feature\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    def reserved_names(self, idx_=None):\n        varnames = []\n        if self.data is not None:\n            varnames = [var.name for var in\n                        self.data.domain.variables + self.data.domain.metas]\n        varnames += [desc.name for idx, desc in enumerate(self.featuremodel)\n                     if idx != idx_]\n        return set(varnames)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 365,
                                "end_lineno": 722,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# pylint: disable=unsubscriptable-object\nimport unittest\nimport ast\nimport sys\nimport math\nimport pickle\nimport copy\n\nfrom unittest.mock import Mock\nimport numpy as np\n\nfrom Orange.data import (Table, Domain, StringVariable,\n                         ContinuousVariable, DiscreteVariable, TimeVariable)\nfrom Orange.widgets.tests.base import WidgetTest\nfrom Orange.widgets.utils import vartype\nfrom Orange.widgets.utils.itemmodels import PyListModel\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.data.owfeatureconstructor import (\n    DiscreteDescriptor, ContinuousDescriptor, StringDescriptor,\n    construct_variables, OWFeatureConstructor,\n    FeatureEditor, DiscreteFeatureEditor, FeatureConstructorHandler,\n    DateTimeDescriptor)\n\nfrom Orange.widgets.data.owfeatureconstructor import (\n    freevars, validate_exp, FeatureFunc\n)\n\n\nclass FeatureConstructorTest(unittest.TestCase):\n    def test_construct_variables_discrete(self):\n        data = Table(\"iris\")\n        name = 'Discrete Variable'\n        expression = \"iris_one if iris == 'Iris-setosa' else iris_two \" \\\n                     \"if iris == 'Iris-versicolor' else iris_three\"\n        values = ('iris one', 'iris two', 'iris three')\n        desc = PyListModel(\n            [DiscreteDescriptor(name=name, expression=expression,\n                                values=values, ordered=False)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(desc, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], DiscreteVariable))\n        self.assertEqual(data.domain[name].values, values)\n        for i in range(3):\n            self.assertEqual(data[i * 50, name], values[i])\n\n    def test_construct_variables_discrete_no_values(self):\n        data = Table(\"iris\")\n        name = 'Discrete Variable'\n        expression = \"str(iris)[-1]\"  # last letter - a or r\n        values = ()\n        desc = PyListModel(\n            [DiscreteDescriptor(name=name, expression=expression,\n                                values=values, ordered=False)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(desc, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        newvar = data.domain[name]\n        self.assertTrue(isinstance(newvar, DiscreteVariable))\n        self.assertEqual(set(data.domain[name].values), set(\"ar\"))\n        for i in range(3):\n            inst = data[i * 50]\n            self.assertEqual(str(inst[name]), str(inst[\"iris\"])[-1])\n\n    def test_construct_variables_continuous(self):\n        data = Table(\"iris\")\n        name = 'Continuous Variable'\n        expression = \"pow(sepal_length + sepal_width, 2)\"\n        featuremodel = PyListModel(\n            [ContinuousDescriptor(name=name, expression=expression,\n                                  number_of_decimals=2)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(featuremodel, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], ContinuousVariable))\n        for i in range(3):\n            self.assertEqual(data[i * 50, name],\n                             pow(data[i * 50, 0] + data[i * 50, 1], 2))\n\n    def test_construct_variables_datetime(self):\n        data = Table(\"housing\")\n        name = 'Date'\n        expression = '\"2019-07-{:02}\".format(int(MEDV/3))'\n        featuremodel = PyListModel(\n            [DateTimeDescriptor(name=name, expression=expression)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(featuremodel, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], TimeVariable))\n        for row in data:\n            self.assertEqual(\"2019-07-{:02}\".format(int(row[\"MEDV\"] / 3)),\n                             str(row[\"Date\"])[:10])\n\n    def test_construct_variables_string(self):\n        data = Table(\"iris\")\n        name = 'String Variable'\n        expression = \"str(iris) + '_name'\"\n        desc = PyListModel(\n            [StringDescriptor(name=name, expression=expression)]\n        )\n        data = data.transform(Domain(data.domain.attributes,\n                                     data.domain.class_vars,\n                                     list(data.domain.metas) +\n                                     construct_variables(desc, data)))\n        self.assertTrue(isinstance(data.domain[name], StringVariable))\n        for i in range(3):\n            self.assertEqual(data[i * 50, name],\n                             str(data[i * 50, \"iris\"]) + \"_name\")\n\n    @staticmethod\n    def test_construct_numeric_names():\n        data = Table(\"iris\")\n        newdomain = Domain((ContinuousVariable(\"0.1\"), ContinuousVariable(\"1\"))\n                           + data.domain.attributes[2:], data.domain.class_var)\n        data = Table.from_numpy(newdomain, data.X, data.Y)\n        desc = PyListModel(\n            [ContinuousDescriptor(name=\"S\",\n                                  expression=\"_0_1 + _1\",\n                                  number_of_decimals=3)]\n        )\n        nv = construct_variables(desc, data)\n        ndata = data.transform(Domain(nv))\n        np.testing.assert_array_equal(ndata.X[:, 0],\n                                      data.X[:, :2].sum(axis=1))\n\n\nclass TestTools(unittest.TestCase):\n    def test_free_vars(self):\n        stmt = ast.parse(\"foo\", \"\", \"single\")\n        with self.assertRaises(ValueError):\n            freevars(stmt, [])\n\n        suite = ast.parse(\"foo; bar();\", \"exec\")\n        with self.assertRaises(ValueError):\n            freevars(suite, [])\n\n        def freevars_(source, env=None):\n            return freevars(ast.parse(source, \"\", \"eval\"), env or [])\n\n        self.assertEqual(freevars_(\"1\"), [])\n        self.assertEqual(freevars_(\"...\"), [])\n        self.assertEqual(freevars_(\"a\"), [\"a\"])\n        self.assertEqual(freevars_(\"a\", [\"a\"]), [])\n        self.assertEqual(freevars_(\"f(1)\"), [\"f\"])\n        self.assertEqual(freevars_(\"f(x)\"), [\"f\", \"x\"])\n        self.assertEqual(freevars_(\"f(x)\", [\"f\"]), [\"x\"])\n        self.assertEqual(freevars_(\"a + 1\"), [\"a\"])\n        self.assertEqual(freevars_(\"a + b\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a + b\", [\"a\", \"b\"]), [])\n        self.assertEqual(freevars_(\"a[b]\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a[b]\", [\"a\", \"b\"]), [])\n        self.assertEqual(freevars_(\"f(x, *a)\", [\"f\"]), [\"x\", \"a\"])\n        self.assertEqual(freevars_(\"f(x, *a, y=1)\", [\"f\"]), [\"x\", \"a\"])\n        self.assertEqual(freevars_(\"f(x, *a, y=1, **k)\", [\"f\"]),\n                         [\"x\", \"a\", \"k\"])\n        if sys.version_info >= (3, 5):\n            self.assertEqual(freevars_(\"f(*a, *b, k=c, **d, **e)\", [\"f\"]),\n                             [\"a\", \"b\", \"c\", \"d\", \"e\"])\n\n        self.assertEqual(freevars_(\"True\"), [])\n        self.assertEqual(freevars_(\"'True'\"), [])\n        self.assertEqual(freevars_(\"None\"), [])\n        self.assertEqual(freevars_(\"b'None'\"), [])\n\n        self.assertEqual(freevars_(\"a < b\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a < b <= c\"), [\"a\", \"b\", \"c\"])\n        self.assertEqual(freevars_(\"1 < a <= 3\"), [\"a\"])\n\n        self.assertEqual(freevars_(\"{}\"), [])\n        self.assertEqual(freevars_(\"[]\"), [])\n        self.assertEqual(freevars_(\"()\"), [])\n        self.assertEqual(freevars_(\"[a, 1]\"), [\"a\"])\n        self.assertEqual(freevars_(\"{a: b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a, b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"0 if abs(a) < 0.1 else b\", [\"abs\"]),\n                         [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"lambda a: b + 1\"), [\"b\"])\n        self.assertEqual(freevars_(\"lambda a: b + 1\", [\"b\"]), [])\n        self.assertEqual(freevars_(\"lambda a: a + 1\"), [])\n        self.assertEqual(freevars_(\"(lambda a: a + 1)(a)\"), [\"a\"])\n        self.assertEqual(freevars_(\"lambda a, *arg: arg + (a,)\"), [])\n        self.assertEqual(freevars_(\"lambda a, *arg, **kwargs: arg + (a,)\"), [])\n\n        self.assertEqual(freevars_(\"[a for a in b]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[1 + a for c in b if c]\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a for _ in [] if b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a for _ in [] if b}\", [\"a\", \"b\"]), [])\n\n    def test_validate_exp(self):\n\n        stmt = ast.parse(\"1\", mode=\"single\")\n        with self.assertRaises(ValueError):\n            validate_exp(stmt)\n        suite = ast.parse(\"a; b\", mode=\"exec\")\n        with self.assertRaises(ValueError):\n            validate_exp(suite)\n\n        def validate_(source):\n            return validate_exp(ast.parse(source, mode=\"eval\"))\n\n        self.assertTrue(validate_(\"a\"))\n        self.assertTrue(validate_(\"a + 1\"))\n        self.assertTrue(validate_(\"a < 1\"))\n        self.assertTrue(validate_(\"1 < a\"))\n        self.assertTrue(validate_(\"1 < a < 10\"))\n        self.assertTrue(validate_(\"a and b\"))\n        self.assertTrue(validate_(\"not a\"))\n        self.assertTrue(validate_(\"a if b else c\"))\n\n        self.assertTrue(validate_(\"f(x)\"))\n        self.assertTrue(validate_(\"f(g(x)) + g(x)\"))\n\n        self.assertTrue(validate_(\"f(x, r=b)\"))\n        self.assertTrue(validate_(\"a[b]\"))\n\n        self.assertTrue(validate_(\"a in {'a', 'b'}\"))\n        self.assertTrue(validate_(\"{}\"))\n        self.assertTrue(validate_(\"{'a': 1}\"))\n        self.assertTrue(validate_(\"()\"))\n        self.assertTrue(validate_(\"[]\"))\n\n        with self.assertRaises(ValueError):\n            validate_(\"[a for a in s]\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"(a for a in s)\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"{a for a in s}\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"{a:1 for a in s}\")\n\n\nclass FeatureFuncTest(unittest.TestCase):\n    def test_reconstruct(self):\n        iris = Table(\"iris\")\n        inst1 = iris[0]\n        val1 = 2 * inst1[\"sepal width\"] + 10\n        inst2 = iris[100]\n        val2 = 2 * inst2[\"sepal width\"] + 10\n\n        f = FeatureFunc(\"a * sepal_width + c\",\n                        [(\"sepal_width\", iris.domain[\"sepal width\"])],\n                        {\"a\": 2, \"c\": 10})\n        self.assertAlmostEqual(f(inst1), val1)\n        f1 = pickle.loads(pickle.dumps(f))\n        self.assertAlmostEqual(f1(inst1), val1)\n        fc = copy.copy(f)\n        self.assertEqual(fc(inst2), val2)\n\n    def test_repr(self):\n        self.assertEqual(repr(FeatureFunc(\"a + 1\", [(\"a\", 2)])),\n                         \"FeatureFunc('a + 1', [('a', 2)], {}, None)\")\n\n    def test_call(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"sepal_width + 10\",\n                        [(\"sepal_width\", iris.domain[\"sepal width\"])])\n        r = f(iris)\n        np.testing.assert_array_equal(r, iris.X[:, 1] + 10)\n        self.assertEqual(f(iris[0]), iris[0][\"sepal width\"] + 10)\n\n    def test_string_casting(self):\n        zoo = Table(\"zoo\")\n        f = FeatureFunc(\"name[0]\",\n                        [(\"name\", zoo.domain[\"name\"])])\n        r = f(zoo)\n        self.assertEqual(r, [x[0] for x in zoo.metas[:, 0]])\n        self.assertEqual(f(zoo[0]), str(zoo[0, \"name\"])[0])\n\n    def test_missing_variable(self):\n        zoo = Table(\"zoo\")\n        assert zoo.domain.class_var.name == \"type\"\n        f = FeatureFunc(\"type[0]\",\n                        [(\"type\", zoo.domain[\"type\"])])\n        no_class = Domain(zoo.domain.attributes, None, zoo.domain.metas)\n        data2 = zoo.transform(no_class)\n        r = f(data2)\n        self.assertTrue(np.all(np.isnan(r)))\n        self.assertTrue(np.isnan(f(data2[0])))\n\n    def test_invalid_expression_variable(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"1 / petal_length\",\n                        [(\"petal_length\", iris.domain[\"petal length\"])])\n        iris[0][\"petal length\"] = 0\n\n        f.mask_exceptions = False\n        self.assertRaises(Exception, f, iris)\n        self.assertRaises(Exception, f, iris[0])\n        _ = f(iris[1])\n\n        f.mask_exceptions = True\n        r = f(iris)\n        self.assertTrue(np.isnan(r[0]))\n        self.assertFalse(np.isnan(r[1]))\n        self.assertTrue(np.isnan(f(iris[0])))\n        self.assertFalse(np.isnan(f(iris[1])))\n\n\nclass OWFeatureConstructorTests(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWFeatureConstructor)\n\n    def test_create_variable_with_no_data(self):\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X1\", \"\", 3))\n\n    def test_error_invalid_expression(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0\", 3)\n        )\n        self.widget.apply()\n        self.assertFalse(self.widget.Error.invalid_expressions.is_shown())\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0a\", 3)\n        )\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.invalid_expressions.is_shown())\n\n    def test_discrete_no_values(self):\n        \"\"\"\n        Should not fail when there are no values set.\n        GH-2417\n        \"\"\"\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        discreteFeatureEditor = DiscreteFeatureEditor()\n\n        discreteFeatureEditor.valuesedit.setText(\"A\")\n        discreteFeatureEditor.nameedit.setText(\"D1\")\n        discreteFeatureEditor.expressionedit.setText(\"iris\")\n        self.widget.addFeature(\n            discreteFeatureEditor.editorData()\n        )\n        self.assertFalse(self.widget.Error.more_values_needed.is_shown())\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(len(data), format_summary_details(data))\n        output = self.get_output(self.widget.Outputs.data)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")\n\n\nclass TestFeatureEditor(unittest.TestCase):\n    def test_has_functions(self):\n        self.assertIs(FeatureEditor.FUNCTIONS[\"abs\"], abs)\n        self.assertIs(FeatureEditor.FUNCTIONS[\"sqrt\"], math.sqrt)\n\n\nclass FeatureConstructorHandlerTests(unittest.TestCase):\n    def test_handles_builtins_in_expression(self):\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str(A) + str(B)\"),\n                {\"A\": vartype(DiscreteVariable)},\n                {\"B\": vartype(DiscreteVariable)}\n            )\n        )\n\n        # no variables is also ok\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str('foo')\"),\n                {},\n                {}\n            )\n        )\n\n        # should fail on unknown variables\n        self.assertFalse(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str(X)\"),\n                {},\n                {}\n            )\n        )\n\n    def test_handles_special_characters_in_var_names(self):\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"A_2_f\"),\n                {\"A.2 f\": vartype(DiscreteVariable)},\n                {}\n            )\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "new_file_content": "# pylint: disable=unsubscriptable-object\nimport unittest\nimport ast\nimport sys\nimport math\nimport pickle\nimport copy\n\nfrom unittest.mock import Mock\nimport numpy as np\n\nfrom Orange.data import (Table, Domain, StringVariable,\n                         ContinuousVariable, DiscreteVariable, TimeVariable)\nfrom Orange.widgets.tests.base import WidgetTest\nfrom Orange.widgets.utils import vartype\nfrom Orange.widgets.utils.itemmodels import PyListModel\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.data.owfeatureconstructor import (\n    DiscreteDescriptor, ContinuousDescriptor, StringDescriptor,\n    construct_variables, OWFeatureConstructor,\n    FeatureEditor, DiscreteFeatureEditor, FeatureConstructorHandler,\n    DateTimeDescriptor)\n\nfrom Orange.widgets.data.owfeatureconstructor import (\n    freevars, validate_exp, FeatureFunc\n)\n\n\nclass FeatureConstructorTest(unittest.TestCase):\n    def test_construct_variables_discrete(self):\n        data = Table(\"iris\")\n        name = 'Discrete Variable'\n        expression = \"iris_one if iris == 'Iris-setosa' else iris_two \" \\\n                     \"if iris == 'Iris-versicolor' else iris_three\"\n        values = ('iris one', 'iris two', 'iris three')\n        desc = PyListModel(\n            [DiscreteDescriptor(name=name, expression=expression,\n                                values=values, ordered=False)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(desc, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], DiscreteVariable))\n        self.assertEqual(data.domain[name].values, values)\n        for i in range(3):\n            self.assertEqual(data[i * 50, name], values[i])\n\n    def test_construct_variables_discrete_no_values(self):\n        data = Table(\"iris\")\n        name = 'Discrete Variable'\n        expression = \"str(iris)[-1]\"  # last letter - a or r\n        values = ()\n        desc = PyListModel(\n            [DiscreteDescriptor(name=name, expression=expression,\n                                values=values, ordered=False)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(desc, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        newvar = data.domain[name]\n        self.assertTrue(isinstance(newvar, DiscreteVariable))\n        self.assertEqual(set(data.domain[name].values), set(\"ar\"))\n        for i in range(3):\n            inst = data[i * 50]\n            self.assertEqual(str(inst[name]), str(inst[\"iris\"])[-1])\n\n    def test_construct_variables_continuous(self):\n        data = Table(\"iris\")\n        name = 'Continuous Variable'\n        expression = \"pow(sepal_length + sepal_width, 2)\"\n        featuremodel = PyListModel(\n            [ContinuousDescriptor(name=name, expression=expression,\n                                  number_of_decimals=2)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(featuremodel, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], ContinuousVariable))\n        for i in range(3):\n            self.assertEqual(data[i * 50, name],\n                             pow(data[i * 50, 0] + data[i * 50, 1], 2))\n\n    def test_construct_variables_datetime(self):\n        data = Table(\"housing\")\n        name = 'Date'\n        expression = '\"2019-07-{:02}\".format(int(MEDV/3))'\n        featuremodel = PyListModel(\n            [DateTimeDescriptor(name=name, expression=expression)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(featuremodel, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], TimeVariable))\n        for row in data:\n            self.assertEqual(\"2019-07-{:02}\".format(int(row[\"MEDV\"] / 3)),\n                             str(row[\"Date\"])[:10])\n\n    def test_construct_variables_string(self):\n        data = Table(\"iris\")\n        name = 'String Variable'\n        expression = \"str(iris) + '_name'\"\n        desc = PyListModel(\n            [StringDescriptor(name=name, expression=expression)]\n        )\n        data = data.transform(Domain(data.domain.attributes,\n                                     data.domain.class_vars,\n                                     list(data.domain.metas) +\n                                     construct_variables(desc, data)))\n        self.assertTrue(isinstance(data.domain[name], StringVariable))\n        for i in range(3):\n            self.assertEqual(data[i * 50, name],\n                             str(data[i * 50, \"iris\"]) + \"_name\")\n\n    @staticmethod\n    def test_construct_numeric_names():\n        data = Table(\"iris\")\n        newdomain = Domain((ContinuousVariable(\"0.1\"), ContinuousVariable(\"1\"))\n                           + data.domain.attributes[2:], data.domain.class_var)\n        data = Table.from_numpy(newdomain, data.X, data.Y)\n        desc = PyListModel(\n            [ContinuousDescriptor(name=\"S\",\n                                  expression=\"_0_1 + _1\",\n                                  number_of_decimals=3)]\n        )\n        nv = construct_variables(desc, data)\n        ndata = data.transform(Domain(nv))\n        np.testing.assert_array_equal(ndata.X[:, 0],\n                                      data.X[:, :2].sum(axis=1))\n\n\nclass TestTools(unittest.TestCase):\n    def test_free_vars(self):\n        stmt = ast.parse(\"foo\", \"\", \"single\")\n        with self.assertRaises(ValueError):\n            freevars(stmt, [])\n\n        suite = ast.parse(\"foo; bar();\", \"exec\")\n        with self.assertRaises(ValueError):\n            freevars(suite, [])\n\n        def freevars_(source, env=None):\n            return freevars(ast.parse(source, \"\", \"eval\"), env or [])\n\n        self.assertEqual(freevars_(\"1\"), [])\n        self.assertEqual(freevars_(\"...\"), [])\n        self.assertEqual(freevars_(\"a\"), [\"a\"])\n        self.assertEqual(freevars_(\"a\", [\"a\"]), [])\n        self.assertEqual(freevars_(\"f(1)\"), [\"f\"])\n        self.assertEqual(freevars_(\"f(x)\"), [\"f\", \"x\"])\n        self.assertEqual(freevars_(\"f(x)\", [\"f\"]), [\"x\"])\n        self.assertEqual(freevars_(\"a + 1\"), [\"a\"])\n        self.assertEqual(freevars_(\"a + b\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a + b\", [\"a\", \"b\"]), [])\n        self.assertEqual(freevars_(\"a[b]\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a[b]\", [\"a\", \"b\"]), [])\n        self.assertEqual(freevars_(\"f(x, *a)\", [\"f\"]), [\"x\", \"a\"])\n        self.assertEqual(freevars_(\"f(x, *a, y=1)\", [\"f\"]), [\"x\", \"a\"])\n        self.assertEqual(freevars_(\"f(x, *a, y=1, **k)\", [\"f\"]),\n                         [\"x\", \"a\", \"k\"])\n        if sys.version_info >= (3, 5):\n            self.assertEqual(freevars_(\"f(*a, *b, k=c, **d, **e)\", [\"f\"]),\n                             [\"a\", \"b\", \"c\", \"d\", \"e\"])\n\n        self.assertEqual(freevars_(\"True\"), [])\n        self.assertEqual(freevars_(\"'True'\"), [])\n        self.assertEqual(freevars_(\"None\"), [])\n        self.assertEqual(freevars_(\"b'None'\"), [])\n\n        self.assertEqual(freevars_(\"a < b\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a < b <= c\"), [\"a\", \"b\", \"c\"])\n        self.assertEqual(freevars_(\"1 < a <= 3\"), [\"a\"])\n\n        self.assertEqual(freevars_(\"{}\"), [])\n        self.assertEqual(freevars_(\"[]\"), [])\n        self.assertEqual(freevars_(\"()\"), [])\n        self.assertEqual(freevars_(\"[a, 1]\"), [\"a\"])\n        self.assertEqual(freevars_(\"{a: b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a, b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"0 if abs(a) < 0.1 else b\", [\"abs\"]),\n                         [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"lambda a: b + 1\"), [\"b\"])\n        self.assertEqual(freevars_(\"lambda a: b + 1\", [\"b\"]), [])\n        self.assertEqual(freevars_(\"lambda a: a + 1\"), [])\n        self.assertEqual(freevars_(\"(lambda a: a + 1)(a)\"), [\"a\"])\n        self.assertEqual(freevars_(\"lambda a, *arg: arg + (a,)\"), [])\n        self.assertEqual(freevars_(\"lambda a, *arg, **kwargs: arg + (a,)\"), [])\n\n        self.assertEqual(freevars_(\"[a for a in b]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[1 + a for c in b if c]\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a for _ in [] if b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a for _ in [] if b}\", [\"a\", \"b\"]), [])\n\n    def test_validate_exp(self):\n\n        stmt = ast.parse(\"1\", mode=\"single\")\n        with self.assertRaises(ValueError):\n            validate_exp(stmt)\n        suite = ast.parse(\"a; b\", mode=\"exec\")\n        with self.assertRaises(ValueError):\n            validate_exp(suite)\n\n        def validate_(source):\n            return validate_exp(ast.parse(source, mode=\"eval\"))\n\n        self.assertTrue(validate_(\"a\"))\n        self.assertTrue(validate_(\"a + 1\"))\n        self.assertTrue(validate_(\"a < 1\"))\n        self.assertTrue(validate_(\"1 < a\"))\n        self.assertTrue(validate_(\"1 < a < 10\"))\n        self.assertTrue(validate_(\"a and b\"))\n        self.assertTrue(validate_(\"not a\"))\n        self.assertTrue(validate_(\"a if b else c\"))\n\n        self.assertTrue(validate_(\"f(x)\"))\n        self.assertTrue(validate_(\"f(g(x)) + g(x)\"))\n\n        self.assertTrue(validate_(\"f(x, r=b)\"))\n        self.assertTrue(validate_(\"a[b]\"))\n\n        self.assertTrue(validate_(\"a in {'a', 'b'}\"))\n        self.assertTrue(validate_(\"{}\"))\n        self.assertTrue(validate_(\"{'a': 1}\"))\n        self.assertTrue(validate_(\"()\"))\n        self.assertTrue(validate_(\"[]\"))\n\n        with self.assertRaises(ValueError):\n            validate_(\"[a for a in s]\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"(a for a in s)\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"{a for a in s}\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"{a:1 for a in s}\")\n\n\nclass FeatureFuncTest(unittest.TestCase):\n    def test_reconstruct(self):\n        iris = Table(\"iris\")\n        inst1 = iris[0]\n        val1 = 2 * inst1[\"sepal width\"] + 10\n        inst2 = iris[100]\n        val2 = 2 * inst2[\"sepal width\"] + 10\n\n        f = FeatureFunc(\"a * sepal_width + c\",\n                        [(\"sepal_width\", iris.domain[\"sepal width\"])],\n                        {\"a\": 2, \"c\": 10})\n        self.assertAlmostEqual(f(inst1), val1)\n        f1 = pickle.loads(pickle.dumps(f))\n        self.assertAlmostEqual(f1(inst1), val1)\n        fc = copy.copy(f)\n        self.assertEqual(fc(inst2), val2)\n\n    def test_repr(self):\n        self.assertEqual(repr(FeatureFunc(\"a + 1\", [(\"a\", 2)])),\n                         \"FeatureFunc('a + 1', [('a', 2)], {}, None)\")\n\n    def test_call(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"sepal_width + 10\",\n                        [(\"sepal_width\", iris.domain[\"sepal width\"])])\n        r = f(iris)\n        np.testing.assert_array_equal(r, iris.X[:, 1] + 10)\n        self.assertEqual(f(iris[0]), iris[0][\"sepal width\"] + 10)\n\n    def test_string_casting(self):\n        zoo = Table(\"zoo\")\n        f = FeatureFunc(\"name[0]\",\n                        [(\"name\", zoo.domain[\"name\"])])\n        r = f(zoo)\n        self.assertEqual(r, [x[0] for x in zoo.metas[:, 0]])\n        self.assertEqual(f(zoo[0]), str(zoo[0, \"name\"])[0])\n\n    def test_missing_variable(self):\n        zoo = Table(\"zoo\")\n        assert zoo.domain.class_var.name == \"type\"\n        f = FeatureFunc(\"type[0]\",\n                        [(\"type\", zoo.domain[\"type\"])])\n        no_class = Domain(zoo.domain.attributes, None, zoo.domain.metas)\n        data2 = zoo.transform(no_class)\n        r = f(data2)\n        self.assertTrue(np.all(np.isnan(r)))\n        self.assertTrue(np.isnan(f(data2[0])))\n\n    def test_invalid_expression_variable(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"1 / petal_length\",\n                        [(\"petal_length\", iris.domain[\"petal length\"])])\n        iris[0][\"petal length\"] = 0\n\n        f.mask_exceptions = False\n        self.assertRaises(Exception, f, iris)\n        self.assertRaises(Exception, f, iris[0])\n        _ = f(iris[1])\n\n        f.mask_exceptions = True\n        r = f(iris)\n        self.assertTrue(np.isnan(r[0]))\n        self.assertFalse(np.isnan(r[1]))\n        self.assertTrue(np.isnan(f(iris[0])))\n        self.assertFalse(np.isnan(f(iris[1])))\n\n\nclass OWFeatureConstructorTests(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWFeatureConstructor)\n\n    def test_create_variable_with_no_data(self):\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X1\", \"\", 3))\n\n    def test_error_invalid_expression(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0\", 3)\n        )\n        self.widget.apply()\n        self.assertFalse(self.widget.Error.invalid_expressions.is_shown())\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0a\", 3)\n        )\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.invalid_expressions.is_shown())\n\n    def test_renaming_duplicate_vars(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"iris\", \"0\", 3)\n        )\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        self.assertEqual(len(set(var.name for var in output.domain.variables)),\n                         len(output.domain.variables))\n\n    def test_discrete_no_values(self):\n        \"\"\"\n        Should not fail when there are no values set.\n        GH-2417\n        \"\"\"\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        discreteFeatureEditor = DiscreteFeatureEditor()\n\n        discreteFeatureEditor.valuesedit.setText(\"A\")\n        discreteFeatureEditor.nameedit.setText(\"D1\")\n        discreteFeatureEditor.expressionedit.setText(\"iris\")\n        self.widget.addFeature(\n            discreteFeatureEditor.editorData()\n        )\n        self.assertFalse(self.widget.Error.more_values_needed.is_shown())\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(len(data), format_summary_details(data))\n        output = self.get_output(self.widget.Outputs.data)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")\n\n\nclass TestFeatureEditor(unittest.TestCase):\n    def test_has_functions(self):\n        self.assertIs(FeatureEditor.FUNCTIONS[\"abs\"], abs)\n        self.assertIs(FeatureEditor.FUNCTIONS[\"sqrt\"], math.sqrt)\n\n\nclass FeatureConstructorHandlerTests(unittest.TestCase):\n    def test_handles_builtins_in_expression(self):\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str(A) + str(B)\"),\n                {\"A\": vartype(DiscreteVariable)},\n                {\"B\": vartype(DiscreteVariable)}\n            )\n        )\n\n        # no variables is also ok\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str('foo')\"),\n                {},\n                {}\n            )\n        )\n\n        # should fail on unknown variables\n        self.assertFalse(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str(X)\"),\n                {},\n                {}\n            )\n        )\n\n    def test_handles_special_characters_in_var_names(self):\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"A_2_f\"),\n                {\"A.2 f\": vartype(DiscreteVariable)},\n                {}\n            )\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/tests/test_owfeatureconstructor.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "2f6435ab5",
                "new_commit_hash": "a3acf48b4",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/tests/test_owfeatureconstructor.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/tests/test_owfeatureconstructor.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 329,
                            "length": 6
                        },
                        "new_range": {
                            "start": 329,
                            "length": 17
                        },
                        "section": "class OWFeatureConstructorTests(WidgetTest):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.widget.apply()",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(self.widget.Error.invalid_expressions.is_shown())",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_renaming_duplicate_vars(self):",
                                "type": "added"
                            },
                            {
                                "content": "        data = Table(\"iris\")",
                                "type": "added"
                            },
                            {
                                "content": "        self.widget.setData(data)",
                                "type": "added"
                            },
                            {
                                "content": "        self.widget.addFeature(",
                                "type": "added"
                            },
                            {
                                "content": "            ContinuousDescriptor(\"iris\", \"0\", 3)",
                                "type": "added"
                            },
                            {
                                "content": "        )",
                                "type": "added"
                            },
                            {
                                "content": "        self.widget.apply()",
                                "type": "added"
                            },
                            {
                                "content": "        output = self.get_output(self.widget.Outputs.data)",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(len(set(var.name for var in output.domain.variables)),",
                                "type": "added"
                            },
                            {
                                "content": "                         len(output.domain.variables))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def test_discrete_no_values(self):",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "        Should not fail when there are no values set.",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                            "type": "class",
                            "name": "OWFeatureConstructorTests",
                            "content": "class OWFeatureConstructorTests(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWFeatureConstructor)\n\n    def test_create_variable_with_no_data(self):\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X1\", \"\", 3))\n\n    def test_error_invalid_expression(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0\", 3)\n        )\n        self.widget.apply()\n        self.assertFalse(self.widget.Error.invalid_expressions.is_shown())\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0a\", 3)\n        )\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.invalid_expressions.is_shown())\n\n    def test_renaming_duplicate_vars(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"iris\", \"0\", 3)\n        )\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        self.assertEqual(len(set(var.name for var in output.domain.variables)),\n                         len(output.domain.variables))\n\n    def test_discrete_no_values(self):\n        \"\"\"\n        Should not fail when there are no values set.\n        GH-2417\n        \"\"\"\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        discreteFeatureEditor = DiscreteFeatureEditor()\n\n        discreteFeatureEditor.valuesedit.setText(\"A\")\n        discreteFeatureEditor.nameedit.setText(\"D1\")\n        discreteFeatureEditor.expressionedit.setText(\"iris\")\n        self.widget.addFeature(\n            discreteFeatureEditor.editorData()\n        )\n        self.assertFalse(self.widget.Error.more_values_needed.is_shown())\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(len(data), format_summary_details(data))\n        output = self.get_output(self.widget.Outputs.data)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 310,
                            "end_lineno": 380,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                            "type": "method",
                            "name": "OWFeatureConstructorTests.test_renaming_duplicate_vars",
                            "content": "class OWFeatureConstructorTests(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWFeatureConstructor)\n\n    def test_create_variable_with_no_data(self):\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X1\", \"\", 3))\n\n    def test_error_invalid_expression(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0\", 3)\n        )\n        self.widget.apply()\n        self.assertFalse(self.widget.Error.invalid_expressions.is_shown())\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0a\", 3)\n        )\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.invalid_expressions.is_shown())\n\n    def test_renaming_duplicate_vars(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"iris\", \"0\", 3)\n        )\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        self.assertEqual(len(set(var.name for var in output.domain.variables)),\n                         len(output.domain.variables))\n\n    def test_discrete_no_values(self):\n        \"\"\"\n        Should not fail when there are no values set.\n        GH-2417\n        \"\"\"\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        discreteFeatureEditor = DiscreteFeatureEditor()\n\n        discreteFeatureEditor.valuesedit.setText(\"A\")\n        discreteFeatureEditor.nameedit.setText(\"D1\")\n        discreteFeatureEditor.expressionedit.setText(\"iris\")\n        self.widget.addFeature(\n            discreteFeatureEditor.editorData()\n        )\n        self.assertFalse(self.widget.Error.more_values_needed.is_shown())\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(len(data), format_summary_details(data))\n        output = self.get_output(self.widget.Outputs.data)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 332,
                            "end_lineno": 341,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                                "type": "class",
                                "name": "OWFeatureConstructorTests",
                                "content": "class OWFeatureConstructorTests(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWFeatureConstructor)\n\n    def test_create_variable_with_no_data(self):\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X1\", \"\", 3))\n\n    def test_error_invalid_expression(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0\", 3)\n        )\n        self.widget.apply()\n        self.assertFalse(self.widget.Error.invalid_expressions.is_shown())\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0a\", 3)\n        )\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.invalid_expressions.is_shown())\n\n    def test_renaming_duplicate_vars(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"iris\", \"0\", 3)\n        )\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        self.assertEqual(len(set(var.name for var in output.domain.variables)),\n                         len(output.domain.variables))\n\n    def test_discrete_no_values(self):\n        \"\"\"\n        Should not fail when there are no values set.\n        GH-2417\n        \"\"\"\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        discreteFeatureEditor = DiscreteFeatureEditor()\n\n        discreteFeatureEditor.valuesedit.setText(\"A\")\n        discreteFeatureEditor.nameedit.setText(\"D1\")\n        discreteFeatureEditor.expressionedit.setText(\"iris\")\n        self.widget.addFeature(\n            discreteFeatureEditor.editorData()\n        )\n        self.assertFalse(self.widget.Error.more_values_needed.is_shown())\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(len(data), format_summary_details(data))\n        output = self.get_output(self.widget.Outputs.data)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 310,
                                "end_lineno": 380,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "f961edaeed8ada5ce32fcb8b737100e00a7782da^",
    "new_commit_hash": "f961edaeed8ada5ce32fcb8b737100e00a7782da",
    "commit_message": "Owfeatureconstructor: rename new var if nonunique",
    "commit_date": "2020-05-16T18:40:27+02:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "f961edaeed8ada5ce32fcb8b737100e00a7782da",
    "test_file_codes": [
        "# pylint: disable=unsubscriptable-object\nimport unittest\nimport ast\nimport sys\nimport math\nimport pickle\nimport copy\n\nfrom unittest.mock import Mock\nimport numpy as np\n\nfrom Orange.data import (Table, Domain, StringVariable,\n                         ContinuousVariable, DiscreteVariable, TimeVariable)\nfrom Orange.widgets.tests.base import WidgetTest\nfrom Orange.widgets.utils import vartype\nfrom Orange.widgets.utils.itemmodels import PyListModel\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.data.owfeatureconstructor import (\n    DiscreteDescriptor, ContinuousDescriptor, StringDescriptor,\n    construct_variables, OWFeatureConstructor,\n    FeatureEditor, DiscreteFeatureEditor, FeatureConstructorHandler,\n    DateTimeDescriptor)\n\nfrom Orange.widgets.data.owfeatureconstructor import (\n    freevars, validate_exp, FeatureFunc\n)\n\n\nclass FeatureConstructorTest(unittest.TestCase):\n    def test_construct_variables_discrete(self):\n        data = Table(\"iris\")\n        name = 'Discrete Variable'\n        expression = \"iris_one if iris == 'Iris-setosa' else iris_two \" \\\n                     \"if iris == 'Iris-versicolor' else iris_three\"\n        values = ('iris one', 'iris two', 'iris three')\n        desc = PyListModel(\n            [DiscreteDescriptor(name=name, expression=expression,\n                                values=values, ordered=False)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(desc, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], DiscreteVariable))\n        self.assertEqual(data.domain[name].values, values)\n        for i in range(3):\n            self.assertEqual(data[i * 50, name], values[i])\n\n    def test_construct_variables_discrete_no_values(self):\n        data = Table(\"iris\")\n        name = 'Discrete Variable'\n        expression = \"str(iris)[-1]\"  # last letter - a or r\n        values = ()\n        desc = PyListModel(\n            [DiscreteDescriptor(name=name, expression=expression,\n                                values=values, ordered=False)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(desc, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        newvar = data.domain[name]\n        self.assertTrue(isinstance(newvar, DiscreteVariable))\n        self.assertEqual(set(data.domain[name].values), set(\"ar\"))\n        for i in range(3):\n            inst = data[i * 50]\n            self.assertEqual(str(inst[name]), str(inst[\"iris\"])[-1])\n\n    def test_construct_variables_continuous(self):\n        data = Table(\"iris\")\n        name = 'Continuous Variable'\n        expression = \"pow(sepal_length + sepal_width, 2)\"\n        featuremodel = PyListModel(\n            [ContinuousDescriptor(name=name, expression=expression,\n                                  number_of_decimals=2)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(featuremodel, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], ContinuousVariable))\n        for i in range(3):\n            self.assertEqual(data[i * 50, name],\n                             pow(data[i * 50, 0] + data[i * 50, 1], 2))\n\n    def test_construct_variables_datetime(self):\n        data = Table(\"housing\")\n        name = 'Date'\n        expression = '\"2019-07-{:02}\".format(int(MEDV/3))'\n        featuremodel = PyListModel(\n            [DateTimeDescriptor(name=name, expression=expression)]\n        )\n        data = data.transform(Domain(list(data.domain.attributes) +\n                                     construct_variables(featuremodel, data),\n                                     data.domain.class_vars,\n                                     data.domain.metas))\n        self.assertTrue(isinstance(data.domain[name], TimeVariable))\n        for row in data:\n            self.assertEqual(\"2019-07-{:02}\".format(int(row[\"MEDV\"] / 3)),\n                             str(row[\"Date\"])[:10])\n\n    def test_construct_variables_string(self):\n        data = Table(\"iris\")\n        name = 'String Variable'\n        expression = \"str(iris) + '_name'\"\n        desc = PyListModel(\n            [StringDescriptor(name=name, expression=expression)]\n        )\n        data = data.transform(Domain(data.domain.attributes,\n                                     data.domain.class_vars,\n                                     list(data.domain.metas) +\n                                     construct_variables(desc, data)))\n        self.assertTrue(isinstance(data.domain[name], StringVariable))\n        for i in range(3):\n            self.assertEqual(data[i * 50, name],\n                             str(data[i * 50, \"iris\"]) + \"_name\")\n\n    @staticmethod\n    def test_construct_numeric_names():\n        data = Table(\"iris\")\n        newdomain = Domain((ContinuousVariable(\"0.1\"), ContinuousVariable(\"1\"))\n                           + data.domain.attributes[2:], data.domain.class_var)\n        data = Table.from_numpy(newdomain, data.X, data.Y)\n        desc = PyListModel(\n            [ContinuousDescriptor(name=\"S\",\n                                  expression=\"_0_1 + _1\",\n                                  number_of_decimals=3)]\n        )\n        nv = construct_variables(desc, data)\n        ndata = data.transform(Domain(nv))\n        np.testing.assert_array_equal(ndata.X[:, 0],\n                                      data.X[:, :2].sum(axis=1))\n\n\nclass TestTools(unittest.TestCase):\n    def test_free_vars(self):\n        stmt = ast.parse(\"foo\", \"\", \"single\")\n        with self.assertRaises(ValueError):\n            freevars(stmt, [])\n\n        suite = ast.parse(\"foo; bar();\", \"exec\")\n        with self.assertRaises(ValueError):\n            freevars(suite, [])\n\n        def freevars_(source, env=None):\n            return freevars(ast.parse(source, \"\", \"eval\"), env or [])\n\n        self.assertEqual(freevars_(\"1\"), [])\n        self.assertEqual(freevars_(\"...\"), [])\n        self.assertEqual(freevars_(\"a\"), [\"a\"])\n        self.assertEqual(freevars_(\"a\", [\"a\"]), [])\n        self.assertEqual(freevars_(\"f(1)\"), [\"f\"])\n        self.assertEqual(freevars_(\"f(x)\"), [\"f\", \"x\"])\n        self.assertEqual(freevars_(\"f(x)\", [\"f\"]), [\"x\"])\n        self.assertEqual(freevars_(\"a + 1\"), [\"a\"])\n        self.assertEqual(freevars_(\"a + b\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a + b\", [\"a\", \"b\"]), [])\n        self.assertEqual(freevars_(\"a[b]\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a[b]\", [\"a\", \"b\"]), [])\n        self.assertEqual(freevars_(\"f(x, *a)\", [\"f\"]), [\"x\", \"a\"])\n        self.assertEqual(freevars_(\"f(x, *a, y=1)\", [\"f\"]), [\"x\", \"a\"])\n        self.assertEqual(freevars_(\"f(x, *a, y=1, **k)\", [\"f\"]),\n                         [\"x\", \"a\", \"k\"])\n        if sys.version_info >= (3, 5):\n            self.assertEqual(freevars_(\"f(*a, *b, k=c, **d, **e)\", [\"f\"]),\n                             [\"a\", \"b\", \"c\", \"d\", \"e\"])\n\n        self.assertEqual(freevars_(\"True\"), [])\n        self.assertEqual(freevars_(\"'True'\"), [])\n        self.assertEqual(freevars_(\"None\"), [])\n        self.assertEqual(freevars_(\"b'None'\"), [])\n\n        self.assertEqual(freevars_(\"a < b\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"a < b <= c\"), [\"a\", \"b\", \"c\"])\n        self.assertEqual(freevars_(\"1 < a <= 3\"), [\"a\"])\n\n        self.assertEqual(freevars_(\"{}\"), [])\n        self.assertEqual(freevars_(\"[]\"), [])\n        self.assertEqual(freevars_(\"()\"), [])\n        self.assertEqual(freevars_(\"[a, 1]\"), [\"a\"])\n        self.assertEqual(freevars_(\"{a: b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a, b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"0 if abs(a) < 0.1 else b\", [\"abs\"]),\n                         [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"lambda a: b + 1\"), [\"b\"])\n        self.assertEqual(freevars_(\"lambda a: b + 1\", [\"b\"]), [])\n        self.assertEqual(freevars_(\"lambda a: a + 1\"), [])\n        self.assertEqual(freevars_(\"(lambda a: a + 1)(a)\"), [\"a\"])\n        self.assertEqual(freevars_(\"lambda a, *arg: arg + (a,)\"), [])\n        self.assertEqual(freevars_(\"lambda a, *arg, **kwargs: arg + (a,)\"), [])\n\n        self.assertEqual(freevars_(\"[a for a in b]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[1 + a for c in b if c]\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a for _ in [] if b}\"), [\"a\", \"b\"])\n        self.assertEqual(freevars_(\"{a for _ in [] if b}\", [\"a\", \"b\"]), [])\n\n    def test_validate_exp(self):\n\n        stmt = ast.parse(\"1\", mode=\"single\")\n        with self.assertRaises(ValueError):\n            validate_exp(stmt)\n        suite = ast.parse(\"a; b\", mode=\"exec\")\n        with self.assertRaises(ValueError):\n            validate_exp(suite)\n\n        def validate_(source):\n            return validate_exp(ast.parse(source, mode=\"eval\"))\n\n        self.assertTrue(validate_(\"a\"))\n        self.assertTrue(validate_(\"a + 1\"))\n        self.assertTrue(validate_(\"a < 1\"))\n        self.assertTrue(validate_(\"1 < a\"))\n        self.assertTrue(validate_(\"1 < a < 10\"))\n        self.assertTrue(validate_(\"a and b\"))\n        self.assertTrue(validate_(\"not a\"))\n        self.assertTrue(validate_(\"a if b else c\"))\n\n        self.assertTrue(validate_(\"f(x)\"))\n        self.assertTrue(validate_(\"f(g(x)) + g(x)\"))\n\n        self.assertTrue(validate_(\"f(x, r=b)\"))\n        self.assertTrue(validate_(\"a[b]\"))\n\n        self.assertTrue(validate_(\"a in {'a', 'b'}\"))\n        self.assertTrue(validate_(\"{}\"))\n        self.assertTrue(validate_(\"{'a': 1}\"))\n        self.assertTrue(validate_(\"()\"))\n        self.assertTrue(validate_(\"[]\"))\n\n        with self.assertRaises(ValueError):\n            validate_(\"[a for a in s]\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"(a for a in s)\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"{a for a in s}\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"{a:1 for a in s}\")\n\n\nclass FeatureFuncTest(unittest.TestCase):\n    def test_reconstruct(self):\n        iris = Table(\"iris\")\n        inst1 = iris[0]\n        val1 = 2 * inst1[\"sepal width\"] + 10\n        inst2 = iris[100]\n        val2 = 2 * inst2[\"sepal width\"] + 10\n\n        f = FeatureFunc(\"a * sepal_width + c\",\n                        [(\"sepal_width\", iris.domain[\"sepal width\"])],\n                        {\"a\": 2, \"c\": 10})\n        self.assertAlmostEqual(f(inst1), val1)\n        f1 = pickle.loads(pickle.dumps(f))\n        self.assertAlmostEqual(f1(inst1), val1)\n        fc = copy.copy(f)\n        self.assertEqual(fc(inst2), val2)\n\n    def test_repr(self):\n        self.assertEqual(repr(FeatureFunc(\"a + 1\", [(\"a\", 2)])),\n                         \"FeatureFunc('a + 1', [('a', 2)], {}, None)\")\n\n    def test_call(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"sepal_width + 10\",\n                        [(\"sepal_width\", iris.domain[\"sepal width\"])])\n        r = f(iris)\n        np.testing.assert_array_equal(r, iris.X[:, 1] + 10)\n        self.assertEqual(f(iris[0]), iris[0][\"sepal width\"] + 10)\n\n    def test_string_casting(self):\n        zoo = Table(\"zoo\")\n        f = FeatureFunc(\"name[0]\",\n                        [(\"name\", zoo.domain[\"name\"])])\n        r = f(zoo)\n        self.assertEqual(r, [x[0] for x in zoo.metas[:, 0]])\n        self.assertEqual(f(zoo[0]), str(zoo[0, \"name\"])[0])\n\n    def test_missing_variable(self):\n        zoo = Table(\"zoo\")\n        assert zoo.domain.class_var.name == \"type\"\n        f = FeatureFunc(\"type[0]\",\n                        [(\"type\", zoo.domain[\"type\"])])\n        no_class = Domain(zoo.domain.attributes, None, zoo.domain.metas)\n        data2 = zoo.transform(no_class)\n        r = f(data2)\n        self.assertTrue(np.all(np.isnan(r)))\n        self.assertTrue(np.isnan(f(data2[0])))\n\n    def test_invalid_expression_variable(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"1 / petal_length\",\n                        [(\"petal_length\", iris.domain[\"petal length\"])])\n        iris[0][\"petal length\"] = 0\n\n        f.mask_exceptions = False\n        self.assertRaises(Exception, f, iris)\n        self.assertRaises(Exception, f, iris[0])\n        _ = f(iris[1])\n\n        f.mask_exceptions = True\n        r = f(iris)\n        self.assertTrue(np.isnan(r[0]))\n        self.assertFalse(np.isnan(r[1]))\n        self.assertTrue(np.isnan(f(iris[0])))\n        self.assertFalse(np.isnan(f(iris[1])))\n\n\nclass OWFeatureConstructorTests(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWFeatureConstructor)\n\n    def test_create_variable_with_no_data(self):\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X1\", \"\", 3))\n\n    def test_error_invalid_expression(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0\", 3)\n        )\n        self.widget.apply()\n        self.assertFalse(self.widget.Error.invalid_expressions.is_shown())\n        self.widget.addFeature(\n            ContinuousDescriptor(\"X\", \"0a\", 3)\n        )\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.invalid_expressions.is_shown())\n\n    def test_renaming_duplicate_vars(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"iris\", \"0\", 3)\n        )\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        self.assertEqual(len(set(var.name for var in output.domain.variables)),\n                         len(output.domain.variables))\n\n    def test_discrete_no_values(self):\n        \"\"\"\n        Should not fail when there are no values set.\n        GH-2417\n        \"\"\"\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        discreteFeatureEditor = DiscreteFeatureEditor()\n\n        discreteFeatureEditor.valuesedit.setText(\"A\")\n        discreteFeatureEditor.nameedit.setText(\"D1\")\n        discreteFeatureEditor.expressionedit.setText(\"iris\")\n        self.widget.addFeature(\n            discreteFeatureEditor.editorData()\n        )\n        self.assertFalse(self.widget.Error.more_values_needed.is_shown())\n        self.widget.apply()\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(len(data), format_summary_details(data))\n        output = self.get_output(self.widget.Outputs.data)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")\n\n\nclass TestFeatureEditor(unittest.TestCase):\n    def test_has_functions(self):\n        self.assertIs(FeatureEditor.FUNCTIONS[\"abs\"], abs)\n        self.assertIs(FeatureEditor.FUNCTIONS[\"sqrt\"], math.sqrt)\n\n\nclass FeatureConstructorHandlerTests(unittest.TestCase):\n    def test_handles_builtins_in_expression(self):\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str(A) + str(B)\"),\n                {\"A\": vartype(DiscreteVariable)},\n                {\"B\": vartype(DiscreteVariable)}\n            )\n        )\n\n        # no variables is also ok\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str('foo')\"),\n                {},\n                {}\n            )\n        )\n\n        # should fail on unknown variables\n        self.assertFalse(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"str(X)\"),\n                {},\n                {}\n            )\n        )\n\n    def test_handles_special_characters_in_var_names(self):\n        self.assertTrue(\n            FeatureConstructorHandler().is_valid_item(\n                OWFeatureConstructor.descriptors,\n                StringDescriptor(\"X\", \"A_2_f\"),\n                {\"A.2 f\": vartype(DiscreteVariable)},\n                {}\n            )\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nrunning build_ext\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpb8gweqii/tmp\ncreating /tmp/tmpb8gweqii/tmp/tmpb8gweqii\ncompile options: '-MMD -MF /tmp/tmpb8gweqii/file.c.d -c'\ncc: /tmp/tmpb8gweqii/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmplau_m51x/tmp\ncreating /tmp/tmplau_m51x/tmp/tmplau_m51x\ncompile options: '-MMD -MF /tmp/tmplau_m51x/file.c.d -c'\ncc: /tmp/tmplau_m51x/file.c\ncustomize UnixCCompiler using build_ext\nbuilding 'Orange.classification._simple_tree' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_simple_tree.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._tree_scorers' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_tree_scorers.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_valuecount.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_contingency.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_io.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_variable.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/distance/_distance.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/preprocess/_discretize.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/preprocess/_relieff.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/projection/_som.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/widgets/utils/_grid_density.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\nrunning develop\nrunning build_scripts\nrunning egg_info\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncreating Orange3.egg-info\nwriting Orange3.egg-info/PKG-INFO\nwriting dependency_links to Orange3.egg-info/dependency_links.txt\nwriting entry points to Orange3.egg-info/entry_points.txt\nwriting requirements to Orange3.egg-info/requires.txt\nwriting top-level names to Orange3.egg-info/top_level.txt\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nwarning: no files found matching 'README*' under directory 'Orange'\nwarning: no files found matching 'LICENSE' under directory 'Orange'\nwarning: no files found matching '*.basket' under directory 'Orange/datasets'\nwarning: no files found matching '*.js' under directory 'Orange/widgets'\nwarning: no files found matching '*.css' under directory 'Orange/widgets'\nwarning: no files found matching '*.html' under directory 'Orange/widgets'\nwarning: no files found matching 'COPYING'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmp1ql21aau/tmp\ncreating /tmp/tmp1ql21aau/tmp/tmp1ql21aau\ncompile options: '-MMD -MF /tmp/tmp1ql21aau/file.c.d -c'\ncc: /tmp/tmp1ql21aau/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpgt02olb4/tmp\ncreating /tmp/tmpgt02olb4/tmp/tmpgt02olb4\ncompile options: '-MMD -MF /tmp/tmpgt02olb4/file.c.d -c'\ncc: /tmp/tmpgt02olb4/file.c\ncustomize UnixCCompiler using build_ext\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.26.0.dev0+f961eda to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da\nProcessing dependencies for Orange3==3.26.0.dev0+f961eda\nSearching for XlsxWriter==3.2.0\nBest match: XlsxWriter 3.2.0\nAdding XlsxWriter 3.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for xlrd==2.0.1\nBest match: xlrd 2.0.1\nAdding xlrd 2.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for setuptools==59.8.0\nBest match: setuptools 59.8.0\nAdding setuptools 59.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for serverfiles==0.3.1\nBest match: serverfiles 0.3.1\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for scipy==1.7.3\nBest match: scipy 1.7.3\nAdding scipy 1.7.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for scikit-learn==0.22.2.post1\nBest match: scikit-learn 0.22.2.post1\nAdding scikit-learn 0.22.2.post1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for requests==2.31.0\nBest match: requests 2.31.0\nAdding requests 2.31.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for PyYAML==6.0.1\nBest match: PyYAML 6.0.1\nAdding PyYAML 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for python-louvain==0.16\nBest match: python-louvain 0.16\nAdding python-louvain 0.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for pyqtgraph==0.12.4\nBest match: pyqtgraph 0.12.4\nAdding pyqtgraph 0.12.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for pip==24.0\nBest match: pip 24.0\nAdding pip 24.0 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for pandas==1.3.5\nBest match: pandas 1.3.5\nAdding pandas 1.3.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.22.0\nBest match: orange-widget-base 4.22.0\nAdding orange-widget-base 4.22.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for orange-canvas-core==0.1.35\nBest match: orange-canvas-core 0.1.35\nAdding orange-canvas-core 0.1.35 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for openpyxl==3.1.3\nBest match: openpyxl 3.1.3\nAdding openpyxl 3.1.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for openTSNE==1.0.1\nBest match: openTSNE 1.0.1\nAdding openTSNE 1.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for numpy==1.17.5\nBest match: numpy 1.17.5\nAdding numpy 1.17.5 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for networkx==2.6.3\nBest match: networkx 2.6.3\nAdding networkx 2.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for matplotlib==3.5.3\nBest match: matplotlib 3.5.3\nAdding matplotlib 3.5.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for keyrings.alt==4.2.0\nBest match: keyrings.alt 4.2.0\nAdding keyrings.alt 4.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for keyring==24.1.1\nBest match: keyring 24.1.1\nAdding keyring 24.1.1 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for joblib==1.3.2\nBest match: joblib 1.3.2\nAdding joblib 1.3.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for httpx==0.24.1\nBest match: httpx 0.24.1\nAdding httpx 0.24.1 to easy-install.pth file\nInstalling httpx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for docutils==0.20.1\nBest match: docutils 0.20.1\nAdding docutils 0.20.1 to easy-install.pth file\nInstalling docutils script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for chardet==5.2.0\nBest match: chardet 5.2.0\nAdding chardet 5.2.0 to easy-install.pth file\nInstalling chardetect script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for Bottleneck==1.4.0\nBest match: Bottleneck 1.4.0\nAdding Bottleneck 1.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for baycomp==1.0.3\nBest match: baycomp 1.0.3\nAdding baycomp 1.0.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for AnyQt==0.2.0\nBest match: AnyQt 0.2.0\nAdding AnyQt 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for certifi==2024.12.14\nBest match: certifi 2024.12.14\nAdding certifi 2024.12.14 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for idna==3.10\nBest match: idna 3.10\nAdding idna 3.10 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for charset-normalizer==3.4.1\nBest match: charset-normalizer 3.4.1\nAdding charset-normalizer 3.4.1 to easy-install.pth file\nInstalling normalizer script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for urllib3==2.0.7\nBest match: urllib3 2.0.7\nAdding urllib3 2.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for pytz==2024.2\nBest match: pytz 2024.2\nAdding pytz 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for python-dateutil==2.9.0.post0\nBest match: python-dateutil 2.9.0.post0\nAdding python-dateutil 2.9.0.post0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for typing-extensions==4.7.1\nBest match: typing-extensions 4.7.1\nAdding typing-extensions 4.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for packaging==24.0\nBest match: packaging 24.0\nAdding packaging 24.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for qasync==0.25.0\nBest match: qasync 0.25.0\nAdding qasync 0.25.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for commonmark==0.9.1\nBest match: commonmark 0.9.1\nAdding commonmark 0.9.1 to easy-install.pth file\nInstalling cmark script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for importlib-metadata==6.7.0\nBest match: importlib-metadata 6.7.0\nAdding importlib-metadata 6.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for requests-cache==1.1.1\nBest match: requests-cache 1.1.1\nAdding requests-cache 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for dictdiffer==0.9.0\nBest match: dictdiffer 0.9.0\nAdding dictdiffer 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for et-xmlfile==1.1.0\nBest match: et-xmlfile 1.1.0\nAdding et-xmlfile 1.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for cycler==0.11.0\nBest match: cycler 0.11.0\nAdding cycler 0.11.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for kiwisolver==1.4.5\nBest match: kiwisolver 1.4.5\nAdding kiwisolver 1.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for pyparsing==3.1.4\nBest match: pyparsing 3.1.4\nAdding pyparsing 3.1.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for Pillow==9.5.0\nBest match: Pillow 9.5.0\nAdding Pillow 9.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for fonttools==4.38.0\nBest match: fonttools 4.38.0\nAdding fonttools 4.38.0 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for jaraco.classes==3.2.3\nBest match: jaraco.classes 3.2.3\nAdding jaraco.classes 3.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for importlib-resources==5.12.0\nBest match: importlib-resources 5.12.0\nAdding importlib-resources 5.12.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for jeepney==0.8.0\nBest match: jeepney 0.8.0\nAdding jeepney 0.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for SecretStorage==3.3.3\nBest match: SecretStorage 3.3.3\nAdding SecretStorage 3.3.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for httpcore==0.17.3\nBest match: httpcore 0.17.3\nAdding httpcore 0.17.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for sniffio==1.3.1\nBest match: sniffio 1.3.1\nAdding sniffio 1.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for six==1.17.0\nBest match: six 1.17.0\nAdding six 1.17.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for zipp==3.15.0\nBest match: zipp 3.15.0\nAdding zipp 3.15.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for cattrs==23.1.2\nBest match: cattrs 23.1.2\nAdding cattrs 23.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for platformdirs==4.0.0\nBest match: platformdirs 4.0.0\nAdding platformdirs 4.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for url-normalize==1.4.3\nBest match: url-normalize 1.4.3\nAdding url-normalize 1.4.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for attrs==24.2.0\nBest match: attrs 24.2.0\nAdding attrs 24.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for more-itertools==9.1.0\nBest match: more-itertools 9.1.0\nAdding more-itertools 9.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for cryptography==44.0.0\nBest match: cryptography 44.0.0\nAdding cryptography 44.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for h11==0.14.0\nBest match: h11 0.14.0\nAdding h11 0.14.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for anyio==3.7.1\nBest match: anyio 3.7.1\nAdding anyio 3.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for exceptiongroup==1.2.2\nBest match: exceptiongroup 1.2.2\nAdding exceptiongroup 1.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for cffi==1.15.1\nBest match: cffi 1.15.1\nAdding cffi 1.15.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nSearching for pycparser==2.21\nBest match: pycparser 2.21\nAdding pycparser 2.21 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.26.0.dev0+f961eda\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 257ms\nPrepared 18 packages in 1ms\nInstalled 18 packages in 544ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + numpy==1.17.5\n + packaging==24.0\n + pluggy==1.2.0\n + pyqt5==5.15.10\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.13.0\n + pyqtwebengine==5.15.6\n + pyqtwebengine-qt5==5.15.16\n + pytest==7.4.4\n + setuptools==59.8.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + wheel==0.42.0\n + zipp==3.15.0\nResolved 53 packages in 487ms\nInstalled 46 packages in 720ms\n + anyio==3.7.1\n + baycomp==1.0.3\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + cycler==0.11.0\n + et-xmlfile==1.1.0\n + fonttools==4.38.0\n + h11==0.14.0\n + httpcore==0.17.3\n + httpx==0.24.1\n + idna==3.10\n + importlib-resources==5.12.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + joblib==1.3.2\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + more-itertools==9.1.0\n + networkx==2.6.3\n + openpyxl==3.1.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pillow==9.5.0\n + pip==24.0\n + pycparser==2.21\n + pyparsing==3.1.4\n + python-dateutil==2.9.0.post0\n + python-louvain==0.16\n + pytz==2024.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + scikit-learn==0.22.2.post1\n + scipy==1.7.3\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + sniffio==1.3.1\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 34 packages in 70ms\nInstalled 13 packages in 51ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + platformdirs==4.0.0\n + pyqtgraph==0.12.4\n + qasync==0.25.0\n + requests-cache==1.1.1\n + url-normalize==1.4.3\nResolved 2 packages in 15ms\nInstalled 2 packages in 79ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nAudited 1 package in 1ms\nAudited 2 packages in 1ms\nOrange/classification/_simple_tree.c: In function ‘gain_ratio_c’:\nOrange/classification/_simple_tree.c:63:9: warning: implicit declaration of function ‘qsort_r’; did you mean ‘qsort’? [-Wimplicit-function-declaration]\n   63 |         qsort_r(base, nel, size, compar, thunk)\n      |         ^~~~~~~\nOrange/classification/_simple_tree.c:147:9: note: in expansion of macro ‘QSORT_R_FUNC’\n  147 |         QSORT_R_FUNC(examples, size, sizeof(struct Example), (void*) &compar_attr, compar_examples);\n      |         ^~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_2find_threshold_entropy’:\nOrange/classification/_tree_scorers.c:3115:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3115 |   __pyx_t_7 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_6find_threshold_MSE’:\nOrange/classification/_tree_scorers.c:4619:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4619 |   __pyx_t_1 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_8find_binarization_MSE’:\nOrange/classification/_tree_scorers.c:5396:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5396 |           for (__pyx_t_22 = 0; __pyx_t_22 < __pyx_t_21; __pyx_t_22+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5517:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5517 |           __pyx_t_27 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5523:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5523 |           __pyx_t_27 = (((__pyx_v_N - __pyx_v_left) >= __pyx_v_min_leaf) != 0);\n      |                                                     ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_14compute_predictions_csr’:\nOrange/classification/_tree_scorers.c:7051:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7051 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7741:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7741 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7801:143: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7801 |               __pyx_t_17 = (((*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_18 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                               ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/data/_io.c: In function ‘__pyx_pf_6Orange_4data_3_io_sparse_read_float.constprop’:\nOrange/data/_io.c:3881:24: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3881 |           __pyx_v_decs = (__pyx_v_decs / 10.0);\n      |           ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:4070:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4070 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 221, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:480:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  480 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1301:7: note: ‘__pyx_v_attr_index’ was declared here\n 1301 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:2935:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2935 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:627:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_5update_hex’:\nOrange/projection/_som.c:3655:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3655 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3654:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3654 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4547:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4547 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_38, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_39, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4114:7: note: ‘__pyx_v_win_y’ was declared here\n 4114 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4536:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4536 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_36, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_37, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4113:7: note: ‘__pyx_v_win_x’ was declared here\n 4113 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_8update_sparse.constprop’:\nOrange/projection/_som.c:4746:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4746 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4745:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4745 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3141:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3141 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:2964:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2964 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_28, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2665:7: note: ‘__pyx_v_win_y’ was declared here\n 2665 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:2953:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2953 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_25, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2664:7: note: ‘__pyx_v_win_x’ was declared here\n 2664 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_10update_sparse_hex.constprop’:\nOrange/projection/_som.c:5515:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5515 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5514:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5514 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/setuptools/command/easy_install.py:159: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.\n  EasyInstallDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da/.venv/lib/python3.7/site-packages/setuptools/command/egg_info.py:628: SetuptoolsDeprecationWarning: Custom 'build_py' does not implement 'get_data_files_without_manifest'.\nPlease extend command classes from setuptools instead of distutils.\n  SetuptoolsDeprecationWarning\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da\nplugins: anyio-3.7.1\ncollected 28 items\n\nr2e_tests/test_1.py sss..................F......                         [100%]\n\n=================================== FAILURES ===================================\n_________________ OWFeatureConstructorTests.test_minimum_size __________________\n\nself = <r2e_tests.test_1.OWFeatureConstructorTests testMethod=test_minimum_size>\n\n    def test_minimum_size(self):\n        widget = getattr(self, \"widget\", None)\n        if widget is None:\n            self.skipTest(\"minimum size not tested as .widget was not set\")\n>       self.check_minimum_size(widget)\n\n.venv/lib/python3.7/site-packages/orangewidget/tests/base.py:242: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n.venv/lib/python3.7/site-packages/orangewidget/tests/base.py:615: in check_minimum_size\n    self.assertLess(min_size.width(), 800)\nE   AssertionError: 1107 not less than 800\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_numeric_names\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_continuous\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_datetime\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_discrete\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_discrete_no_values\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_string\nPASSED r2e_tests/test_1.py::TestTools::test_free_vars\nPASSED r2e_tests/test_1.py::TestTools::test_validate_exp\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_call\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_invalid_expression_variable\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_missing_variable\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_reconstruct\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_repr\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_string_casting\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_create_variable_with_no_data\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_discrete_no_values\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_error_invalid_expression\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_image_export\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_msg_base_class\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_renaming_duplicate_vars\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_summary\nPASSED r2e_tests/test_1.py::TestFeatureEditor::test_has_functions\nPASSED r2e_tests/test_1.py::FeatureConstructorHandlerTests::test_handles_builtins_in_expression\nPASSED r2e_tests/test_1.py::FeatureConstructorHandlerTests::test_handles_special_characters_in_var_names\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::OWFeatureConstructorTests::test_minimum_size - As...\n=================== 1 failed, 24 passed, 3 skipped in 4.55s ====================\nWidgets left: 0    Max widgets: 1088 \n\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_f961edaeed8ada5ce32fcb8b737100e00a7782da\nplugins: anyio-3.7.1\ncollected 28 items\n\nr2e_tests/test_1.py sss..................F.F....                         [100%]\n\n=================================== FAILURES ===================================\n_________________ OWFeatureConstructorTests.test_minimum_size __________________\n\nself = <r2e_tests.test_1.OWFeatureConstructorTests testMethod=test_minimum_size>\n\n    def test_minimum_size(self):\n        widget = getattr(self, \"widget\", None)\n        if widget is None:\n            self.skipTest(\"minimum size not tested as .widget was not set\")\n>       self.check_minimum_size(widget)\n\n.venv/lib/python3.7/site-packages/orangewidget/tests/base.py:242: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n.venv/lib/python3.7/site-packages/orangewidget/tests/base.py:615: in check_minimum_size\n    self.assertLess(min_size.width(), 800)\nE   AssertionError: 1107 not less than 800\n____________ OWFeatureConstructorTests.test_renaming_duplicate_vars ____________\n\nself = <r2e_tests.test_1.OWFeatureConstructorTests testMethod=test_renaming_duplicate_vars>\n\n    def test_renaming_duplicate_vars(self):\n        data = Table(\"iris\")\n        self.widget.setData(data)\n        self.widget.addFeature(\n            ContinuousDescriptor(\"iris\", \"0\", 3)\n        )\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        self.assertEqual(len(set(var.name for var in output.domain.variables)),\n>                        len(output.domain.variables))\nE       AssertionError: 5 != 6\n\nr2e_tests/test_1.py:341: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_numeric_names\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_continuous\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_datetime\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_discrete\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_discrete_no_values\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_construct_variables_string\nPASSED r2e_tests/test_1.py::TestTools::test_free_vars\nPASSED r2e_tests/test_1.py::TestTools::test_validate_exp\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_call\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_invalid_expression_variable\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_missing_variable\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_reconstruct\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_repr\nPASSED r2e_tests/test_1.py::FeatureFuncTest::test_string_casting\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_create_variable_with_no_data\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_discrete_no_values\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_error_invalid_expression\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_image_export\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_msg_base_class\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_summary\nPASSED r2e_tests/test_1.py::TestFeatureEditor::test_has_functions\nPASSED r2e_tests/test_1.py::FeatureConstructorHandlerTests::test_handles_builtins_in_expression\nPASSED r2e_tests/test_1.py::FeatureConstructorHandlerTests::test_handles_special_characters_in_var_names\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::OWFeatureConstructorTests::test_minimum_size - As...\nFAILED r2e_tests/test_1.py::OWFeatureConstructorTests::test_renaming_duplicate_vars\n=================== 2 failed, 23 passed, 3 skipped in 2.72s ====================\nWidgets left: 0    Max widgets: 1088 \n\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/data/owfeatureconstructor.py Orange/widgets/data/tests/test_owfeatureconstructor.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Adding a Feature with a Duplicate Name Results in Multiple Variables Sharing the Same Name

**Description:**
When using the Feature Constructor to add a new variable with a name that already exists in the dataset, the system does not automatically rename the new variable to ensure uniqueness. This leads to multiple variables having the same name, which can cause confusion and potential errors in data analysis.

**Example Code:**
```python
data = Table("iris")
widget.setData(data)
widget.addFeature(ContinuousDescriptor("iris", "0", 3))
widget.apply()
output = widget.get_output()
```

**Expected Behavior:**
The new variable should be automatically renamed to a unique name, such as "iris1", to avoid duplication of variable names.

**Actual Behavior:**
The new variable retains the original name "iris", resulting in duplicate variable names within the dataset.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/data/owfeatureconstructor.py
"""
Feature Constructor

A widget for defining (constructing) new features from values
of other variables.

"""
import re
import copy
import functools
import builtins
import math
import random
import logging
import ast
import types

from traceback import format_exception_only
from collections import namedtuple, OrderedDict
from itertools import chain, count
from typing import List, Dict, Any

import numpy as np

from AnyQt.QtWidgets import (
    QSizePolicy, QAbstractItemView, QComboBox, QFormLayout, QLineEdit,
    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,
    QPushButton, QMenu, QListView, QFrame, QLabel)
from AnyQt.QtGui import QKeySequence
from AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property
from orangewidget.utils.combobox import ComboBoxSearch

import Orange
from Orange.widgets import gui
from Orange.widgets.settings import ContextSetting, DomainContextHandler
from Orange.widgets.utils import itemmodels, vartype
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets import report
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.utils.state_summary import format_summary_details
from Orange.widgets.widget import OWWidget, Msg, Input, Output

FeatureDescriptor = \
    namedtuple("FeatureDescriptor", ["name", "expression"])

ContinuousDescriptor = \
    namedtuple("ContinuousDescriptor",
               ["name", "expression", "number_of_decimals"])
DateTimeDescriptor = \
    namedtuple("DateTimeDescriptor",
               ["name", "expression"])
DiscreteDescriptor = \
    namedtuple("DiscreteDescriptor",
               ["name", "expression", "values", "ordered"])

StringDescriptor = namedtuple("StringDescriptor", ["name", "expression"])


def make_variable(descriptor, compute_value):
    if isinstance(descriptor, ContinuousDescriptor):
        return Orange.data.ContinuousVariable(
            descriptor.name,
            descriptor.number_of_decimals,
            compute_value)
    if isinstance(descriptor, DateTimeDescriptor):
        return Orange.data.TimeVariable(
            descriptor.name,
            compute_value=compute_value, have_date=True, have_time=True)
    elif isinstance(descriptor, DiscreteDescriptor):
        return Orange.data.DiscreteVariable(
            descriptor.name,
            values=descriptor.values,
            ordered=descriptor.ordered,
            compute_value=compute_value)
    elif isinstance(descriptor, StringDescriptor):
        return Orange.data.StringVariable(
            descriptor.name,
            compute_value=compute_value)
    else:
        raise TypeError


def selected_row(view):
    """
    Return the index of selected row in a `view` (:class:`QListView`)

    The view's selection mode must be a QAbstractItemView.SingleSelction
    """
    if view.selectionMode() in [QAbstractItemView.MultiSelection,
                                QAbstractItemView.ExtendedSelection]:
        raise ValueError("invalid 'selectionMode'")

    sel_model = view.selectionModel()
    indexes = sel_model.selectedRows()
    if indexes:
        assert len(indexes) == 1
        return indexes[0].row()
    else:
        return None


class FeatureEditor(QFrame):
    FUNCTIONS = dict(chain([(key, val) for key, val in math.__dict__.items()
                            if not key.startswith("_")],
                           [(key, val) for key, val in builtins.__dict__.items()
                            if key in {"str", "float", "int", "len",
                                       "abs", "max", "min"}]))
    featureChanged = Signal()
    featureEdited = Signal()

    modifiedChanged = Signal(bool)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QFormLayout(
            fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow
        )
        layout.setContentsMargins(0, 0, 0, 0)
        self.nameedit = QLineEdit(
            placeholderText="Name...",
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.Fixed)
        )
        self.expressionedit = QLineEdit(
            placeholderText="Expression...",
            toolTip=self.ExpressionTooltip)

        self.attrs_model = itemmodels.VariableListModel(
            ["Select Feature"], parent=self)
        self.attributescb = ComboBoxSearch(
            minimumContentsLength=16,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        )
        self.attributescb.setModel(self.attrs_model)

        sorted_funcs = sorted(self.FUNCTIONS)
        self.funcs_model = itemmodels.PyListModelTooltip()
        self.funcs_model.setParent(self)

        self.funcs_model[:] = chain(["Select Function"], sorted_funcs)
        self.funcs_model.tooltips[:] = chain(
            [''],
            [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])

        self.functionscb = ComboBoxSearch(
            minimumContentsLength=16,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
        self.functionscb.setModel(self.funcs_model)

        hbox = QHBoxLayout()
        hbox.addWidget(self.attributescb)
        hbox.addWidget(self.functionscb)

        layout.addRow(self.nameedit, self.expressionedit)
        layout.addRow(self.tr(""), hbox)
        self.setLayout(layout)

        self.nameedit.editingFinished.connect(self._invalidate)
        self.expressionedit.textChanged.connect(self._invalidate)
        self.attributescb.currentIndexChanged.connect(self.on_attrs_changed)
        self.functionscb.currentIndexChanged.connect(self.on_funcs_changed)

        self._modified = False

    def setModified(self, modified):
        if not isinstance(modified, bool):
            raise TypeError

        if self._modified != modified:
            self._modified = modified
            self.modifiedChanged.emit(modified)

    def modified(self):
        return self._modified

    modified = Property(bool, modified, setModified,
                        notify=modifiedChanged)

    def setEditorData(self, data, domain):
        self.nameedit.setText(data.name)
        self.expressionedit.setText(data.expression)
        self.setModified(False)
        self.featureChanged.emit()
        self.attrs_model[:] = ["Select Feature"]
        if domain is not None and not domain.empty():
            self.attrs_model[:] += chain(domain.attributes,
                                         domain.class_vars,
                                         domain.metas)

    def editorData(self):
        return FeatureDescriptor(name=self.nameedit.text(),
                                 expression=self.nameedit.text())

    def _invalidate(self):
        self.setModified(True)
        self.featureEdited.emit()
        self.featureChanged.emit()

    def on_attrs_changed(self):
        index = self.attributescb.currentIndex()
        if index > 0:
            attr = sanitized_name(self.attrs_model[index].name)
            self.insert_into_expression(attr)
            self.attributescb.setCurrentIndex(0)

    def on_funcs_changed(self):
        index = self.functionscb.currentIndex()
        if index > 0:
            func = self.funcs_model[index]
            if func in ["atan2", "fmod", "ldexp", "log",
                        "pow", "copysign", "hypot"]:
                self.insert_into_expression(func + "(,)")
                self.expressionedit.cursorBackward(False, 2)
            elif func in ["e", "pi"]:
                self.insert_into_expression(func)
            else:
                self.insert_into_expression(func + "()")
                self.expressionedit.cursorBackward(False)
            self.functionscb.setCurrentIndex(0)

    def insert_into_expression(self, what):
        cp = self.expressionedit.cursorPosition()
        ct = self.expressionedit.text()
        text = ct[:cp] + what + ct[cp:]
        self.expressionedit.setText(text)
        self.expressionedit.setFocus()


class ContinuousFeatureEditor(FeatureEditor):
    ExpressionTooltip = "A numeric expression"

    def editorData(self):
        return ContinuousDescriptor(
            name=self.nameedit.text(),
            number_of_decimals=None,
            expression=self.expressionedit.text()
        )


class DateTimeFeatureEditor(FeatureEditor):
    ExpressionTooltip = \
        "Result must be a string in ISO-8601 format " \
        "(e.g. 2019-07-30T15:37:27 or a part thereof),\n" \
        "or a number of seconds since Jan 1, 1970."

    def editorData(self):
        return DateTimeDescriptor(
            name=self.nameedit.text(),
            expression=self.expressionedit.text()
        )


class DiscreteFeatureEditor(FeatureEditor):
    ExpressionTooltip = \
        "Result must be a string, if values are not explicitly given\n" \
        "or a zero-based integer indices into a list of values given below."

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        tooltip = \
            "If values are given, above expression must return zero-based " \
            "integer indices into that list."
        self.valuesedit = QLineEdit(placeholderText="A, B ...", toolTip=tooltip)
        self.valuesedit.textChanged.connect(self._invalidate)

        layout = self.layout()
        label = QLabel(self.tr("Values (optional)"))
        label.setToolTip(tooltip)
        layout.addRow(label, self.valuesedit)

    def setEditorData(self, data, domain):
        self.valuesedit.setText(
            ", ".join(v.replace(",", r"\,") for v in data.values))

        super().setEditorData(data, domain)

    def editorData(self):
        values = self.valuesedit.text()
        values = re.split(r"(?<!\\),", values)
        values = tuple(filter(None, [v.replace(r"\,", ",").strip() for v in values]))
        return DiscreteDescriptor(
            name=self.nameedit.text(),
            values=values,
            ordered=False,
            expression=self.expressionedit.text()
        )


class StringFeatureEditor(FeatureEditor):
    ExpressionTooltip = "A string expression"

    def editorData(self):
        return StringDescriptor(
            name=self.nameedit.text(),
            expression=self.expressionedit.text()
        )


_VarMap = {
    DiscreteDescriptor: vartype(Orange.data.DiscreteVariable("d")),
    ContinuousDescriptor: vartype(Orange.data.ContinuousVariable("c")),
    DateTimeDescriptor: vartype(Orange.data.TimeVariable("t")),
    StringDescriptor: vartype(Orange.data.StringVariable("s"))
}


@functools.lru_cache(20)
def variable_icon(dtype):
    vtype = _VarMap.get(dtype, dtype)
    return gui.attributeIconDict[vtype]


class FeatureItemDelegate(QStyledItemDelegate):
    @staticmethod
    def displayText(value, _):
        return value.name + " := " + value.expression


class DescriptorModel(itemmodels.PyListModel):
    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DecorationRole:
            value = self[index.row()]
            return variable_icon(type(value))
        else:
            return super().data(index, role)


class FeatureConstructorHandler(DomainContextHandler):
    """Context handler that filters descriptors"""

    def is_valid_item(self, setting, item, attrs, metas):
        """Check if descriptor `item` can be used with given domain.

        Return True if descriptor's expression contains only
        available variables and descriptors name does not clash with
        existing variables.
        """
        if item.name in attrs or item.name in metas:
            return False

        try:
            exp_ast = ast.parse(item.expression, mode="eval")
        # ast.parse can return arbitrary errors, not only SyntaxError
        # pylint: disable=broad-except
        except Exception:
            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


class OWFeatureConstructor(OWWidget):
    name = "Feature Constructor"
    description = "Construct new features (data columns) from a set of " \
                  "existing features in the input dataset."
    icon = "icons/FeatureConstructor.svg"
    keywords = []

    class Inputs:
        data = Input("Data", Orange.data.Table)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    want_main_area = False

    settingsHandler = FeatureConstructorHandler()
    descriptors = ContextSetting([])
    currentIndex = ContextSetting(-1)

    EDITORS = [
        (ContinuousDescriptor, ContinuousFeatureEditor),
        (DateTimeDescriptor, DateTimeFeatureEditor),
        (DiscreteDescriptor, DiscreteFeatureEditor),
        (StringDescriptor, StringFeatureEditor)
    ]

    class Error(OWWidget.Error):
        more_values_needed = Msg("Categorical feature {} needs more values.")
        invalid_expressions = Msg("Invalid expressions: {}.")

    def __init__(self):
        super().__init__()
        self.data = None
        self.editors = {}

        box = gui.vBox(self.controlArea, "Variable Definitions")

        toplayout = QHBoxLayout()
        toplayout.setContentsMargins(0, 0, 0, 0)
        box.layout().addLayout(toplayout)

        self.editorstack = QStackedWidget(
            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                   QSizePolicy.MinimumExpanding)
        )

        for descclass, editorclass in self.EDITORS:
            editor = editorclass()
            editor.featureChanged.connect(self._on_modified)
            self.editors[descclass] = editor
            self.editorstack.addWidget(editor)

        self.editorstack.setEnabled(False)

        buttonlayout = QVBoxLayout(spacing=10)
        buttonlayout.setContentsMargins(0, 0, 0, 0)

        self.addbutton = QPushButton(
            "New", toolTip="Create a new variable",
            minimumWidth=120,
            shortcut=QKeySequence.New
        )

        def unique_name(fmt, reserved):
            candidates = (fmt.format(i) for i in count(1))
            return next(c for c in candidates if c not in reserved)

        def reserved_names():
            varnames = []
            if self.data is not None:
                varnames = [var.name for var in
                            self.data.domain.variables + self.data.domain.metas]
            varnames += [desc.name for desc in self.featuremodel]
            return set(varnames)

        def generate_newname(fmt):
            return unique_name(fmt, reserved_names())

        menu = QMenu(self.addbutton)
        cont = menu.addAction("Numeric")
        cont.triggered.connect(
            lambda: self.addFeature(
                ContinuousDescriptor(generate_newname("X{}"), "", 3))
        )
        disc = menu.addAction("Categorical")
        disc.triggered.connect(
            lambda: self.addFeature(
                DiscreteDescriptor(generate_newname("D{}"), "", (), False))
        )
        string = menu.addAction("Text")
        string.triggered.connect(
            lambda: self.addFeature(
                StringDescriptor(generate_newname("S{}"), ""))
        )
        datetime = menu.addAction("Date/Time")
        datetime.triggered.connect(
            lambda: self.addFeature(
                DateTimeDescriptor(generate_newname("T{}"), ""))
        )

        menu.addSeparator()
        self.duplicateaction = menu.addAction("Duplicate Selected Variable")
        self.duplicateaction.triggered.connect(self.duplicateFeature)
        self.duplicateaction.setEnabled(False)
        self.addbutton.setMenu(menu)

        self.removebutton = QPushButton(
            "Remove", toolTip="Remove selected variable",
            minimumWidth=120,
            shortcut=QKeySequence.Delete
        )
        self.removebutton.clicked.connect(self.removeSelectedFeature)

        buttonlayout.addWidget(self.addbutton)
        buttonlayout.addWidget(self.removebutton)
        buttonlayout.addStretch(10)

        toplayout.addLayout(buttonlayout, 0)
        toplayout.addWidget(self.editorstack, 10)

        # Layout for the list view
        layout = QVBoxLayout(spacing=1, margin=0)
        self.featuremodel = DescriptorModel(parent=self)

        self.featureview = QListView(
            minimumWidth=200, minimumHeight=50,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.MinimumExpanding)
        )

        self.featureview.setItemDelegate(FeatureItemDelegate(self))
        self.featureview.setModel(self.featuremodel)
        self.featureview.selectionModel().selectionChanged.connect(
            self._on_selectedVariableChanged
        )

        self.info.set_input_summary(self.info.NoInput)
        self.info.set_output_summary(self.info.NoOutput)

        layout.addWidget(self.featureview)

        box.layout().addLayout(layout, 1)

        box = gui.hBox(self.controlArea)
        gui.rubber(box)
        commit = gui.button(box, self, "Send", callback=self.apply,
                            default=True)
        commit.setMinimumWidth(180)

    def setCurrentIndex(self, index):
        index = min(index, len(self.featuremodel) - 1)
        self.currentIndex = index
        if index >= 0:
            itemmodels.select_row(self.featureview, index)
            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]
            editor = self.editors[type(desc)]
            self.editorstack.setCurrentWidget(editor)
            editor.setEditorData(desc, self.data.domain if self.data else None)
        self.editorstack.setEnabled(index >= 0)
        self.duplicateaction.setEnabled(index >= 0)
        self.removebutton.setEnabled(index >= 0)

    def _on_selectedVariableChanged(self, selected, *_):
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        else:
            self.setCurrentIndex(-1)

    def _on_modified(self):
        if self.currentIndex >= 0:
            editor = self.editorstack.currentWidget()
            self.featuremodel[self.currentIndex] = editor.editorData()
            self.descriptors = list(self.featuremodel)

    def setDescriptors(self, descriptors):
        """
        Set a list of variable descriptors to edit.
        """
        self.descriptors = descriptors
        self.featuremodel[:] = list(self.descriptors)

    @Inputs.data
    @check_sql_input
    def setData(self, data=None):
        """Set the input dataset."""
        self.closeContext()

        self.data = data

        self.info.set_input_summary(self.info.NoInput)
        if self.data is not None:
            descriptors = list(self.descriptors)
            currindex = self.currentIndex
            self.descriptors = []
            self.currentIndex = -1
            self.openContext(data)
            self.info.set_input_summary(len(data), format_summary_details(data))

            if descriptors != self.descriptors or \
                    self.currentIndex != currindex:
                # disconnect from the selection model while reseting the model
                selmodel = self.featureview.selectionModel()
                selmodel.selectionChanged.disconnect(
                    self._on_selectedVariableChanged)

                self.featuremodel[:] = list(self.descriptors)
                self.setCurrentIndex(self.currentIndex)

                selmodel.selectionChanged.connect(
                    self._on_selectedVariableChanged)

        self.editorstack.setEnabled(self.currentIndex >= 0)

    def handleNewSignals(self):
        if self.data is not None:
            self.apply()
        else:
            self.info.set_output_summary(self.info.NoOutput)
            self.Outputs.data.send(None)

    def addFeature(self, descriptor):
        self.featuremodel.append(descriptor)
        self.setCurrentIndex(len(self.featuremodel) - 1)
        editor = self.editorstack.currentWidget()
        editor.nameedit.setFocus()
        editor.nameedit.selectAll()

    def removeFeature(self, index):
        del self.featuremodel[index]
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        elif index is None and self.featuremodel.rowCount():
            # Deleting the last item clears selection
            self.setCurrentIndex(self.featuremodel.rowCount() - 1)

    def removeSelectedFeature(self):
        if self.currentIndex >= 0:
            self.removeFeature(self.currentIndex)

    def duplicateFeature(self):
        desc = self.featuremodel[self.currentIndex]
        self.addFeature(copy.deepcopy(desc))

    @staticmethod
    def check_attrs_values(attr, data):
        for i in range(len(data)):
            for var in attr:
                if not math.isnan(data[i, var]) \
                        and int(data[i, var]) >= len(var.values):
                    return var.name
        return None

    def _validate_descriptors(self, desc):

        def validate(source):
            try:
                return validate_exp(ast.parse(source, mode="eval"))
            # ast.parse can return arbitrary errors, not only SyntaxError
            # pylint: disable=broad-except
            except Exception:
                return False

        final = []
        invalid = []
        for d in desc:
            if validate(d.expression):
                final.append(d)
            else:
                final.append(d._replace(expression=""))
                invalid.append(d)

        if invalid:
            self.Error.invalid_expressions(", ".join(s.name for s in invalid))

        return final

    def apply(self):
        def report_error(err):
            log = logging.getLogger(__name__)
            log.error("", exc_info=True)
            self.error("".join(format_exception_only(type(err), err)).rstrip())

        self.Error.clear()

        if self.data is None:
            return

        desc = list(self.featuremodel)
        desc = self._validate_descriptors(desc)
        try:
            new_variables = construct_variables(desc, self.data)
        # user's expression can contain arbitrary errors
        except Exception as err:  # pylint: disable=broad-except
            report_error(err)
            return

        attrs = [var for var in new_variables if var.is_primitive()]
        metas = [var for var in new_variables if not var.is_primitive()]
        new_domain = Orange.data.Domain(
            self.data.domain.attributes + tuple(attrs),
            self.data.domain.class_vars,
            metas=self.data.domain.metas + tuple(metas)
        )

        try:
            for variable in new_variables:
                variable.compute_value.mask_exceptions = False
            data = self.data.transform(new_domain)
        # user's expression can contain arbitrary errors
        # pylint: disable=broad-except
        except Exception as err:
            report_error(err)
            return
        finally:
            for variable in new_variables:
                variable.compute_value.mask_exceptions = True

        disc_attrs_not_ok = self.check_attrs_values(
            [var for var in attrs if var.is_discrete], data)
        if disc_attrs_not_ok:
            self.Error.more_values_needed(disc_attrs_not_ok)
            return

        self.info.set_output_summary(len(data), format_summary_details(data))
        self.Outputs.data.send(data)

    def send_report(self):
        items = OrderedDict()
        for feature in self.featuremodel:
            if isinstance(feature, DiscreteDescriptor):
                items[feature.name] = "{} (categorical with values {}{})".format(
                    feature.expression, feature.values,
                    "; ordered" * feature.ordered)
            elif isinstance(feature, ContinuousDescriptor):
                items[feature.name] = "{} (numeric)".format(feature.expression)
            elif isinstance(feature, DateTimeDescriptor):
                items[feature.name] = "{} (date/time)".format(feature.expression)
            else:
                items[feature.name] = "{} (text)".format(feature.expression)
        self.report_items(
            report.plural("Constructed feature{s}", len(items)), items)


def freevars(exp, env):
    """
    Return names of all free variables in a parsed (expression) AST.

    Parameters
    ----------
    exp : ast.AST
        An expression ast (ast.parse(..., mode="single"))
    env : List[str]
        Environment

    Returns
    -------
    freevars : List[str]

    See also
    --------
    ast

    """
    # pylint: disable=too-many-return-statements,too-many-branches
    etype = type(exp)
    if etype in [ast.Expr, ast.Expression]:
        return freevars(exp.body, env)
    elif etype == ast.BoolOp:
        return sum((freevars(v, env) for v in exp.values), [])
    elif etype == ast.BinOp:
        return freevars(exp.left, env) + freevars(exp.right, env)
    elif etype == ast.UnaryOp:
        return freevars(exp.operand, env)
    elif etype == ast.Lambda:
        args = exp.args
        assert isinstance(args, ast.arguments)
        argnames = [a.arg for a in args.args]
        argnames += [args.vararg.arg] if args.vararg else []
        argnames += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []
        argnames += [args.kwarg] if args.kwarg else []
        return freevars(exp.body, env + argnames)
    elif etype == ast.IfExp:
        return (freevars(exp.test, env) + freevars(exp.body, env) +
                freevars(exp.orelse, env))
    elif etype == ast.Dict:
        return sum((freevars(v, env)
                    for v in chain(exp.keys, exp.values)), [])
    elif etype == ast.Set:
        return sum((freevars(v, env) for v in exp.elts), [])
    elif etype in [ast.SetComp, ast.ListComp, ast.GeneratorExp, ast.DictComp]:
        env_ext = []
        vars_ = []
        for gen in exp.generators:
            target_names = freevars(gen.target, [])  # assigned names
            vars_iter = freevars(gen.iter, env)
            env_ext += target_names
            vars_ifs = list(chain(*(freevars(ifexp, env + target_names)
                                    for ifexp in gen.ifs or [])))
            vars_ += vars_iter + vars_ifs

        if etype == ast.DictComp:
            vars_ = (freevars(exp.key, env_ext) +
                     freevars(exp.value, env_ext) +
                     vars_)
        else:
            vars_ = freevars(exp.elt, env + env_ext) + vars_
        return vars_
    # Yield, YieldFrom???
    elif etype == ast.Compare:
        return sum((freevars(v, env)
                    for v in [exp.left] + exp.comparators), [])
    elif etype == ast.Call:
        return sum(map(lambda e: freevars(e, env),
                       chain([exp.func],
                             exp.args or [],
                             [k.value for k in exp.keywords or []])),
                   [])
    elif etype == ast.Starred:
        # a 'starred' call parameter (e.g. a and b in `f(x, *a, *b)`
        return freevars(exp.value, env)
    elif etype in [ast.Num, ast.Str, ast.Ellipsis, ast.Bytes, ast.NameConstant]:
        return []
    elif etype == ast.Constant:
        return []
    elif etype == ast.Attribute:
        return freevars(exp.value, env)
    elif etype == ast.Subscript:
        return freevars(exp.value, env) + freevars(exp.slice, env)
    elif etype == ast.Name:
        return [exp.id] if exp.id not in env else []
    elif etype == ast.List:
        return sum((freevars(e, env) for e in exp.elts), [])
    elif etype == ast.Tuple:
        return sum((freevars(e, env) for e in exp.elts), [])
    elif etype == ast.Slice:
        return sum((freevars(e, env)
                    for e in filter(None, [exp.lower, exp.upper, exp.step])),
                   [])
    elif etype == ast.ExtSlice:
        return sum((freevars(e, env) for e in exp.dims), [])
    elif etype == ast.Index:
        return freevars(exp.value, env)
    elif etype == ast.keyword:
        return freevars(exp.value, env)
    else:
        raise ValueError(exp)


def validate_exp(exp):
    """
    Validate an `ast.AST` expression.

    Only expressions with no list,set,dict,generator comprehensions
    are accepted.

    Parameters
    ----------
    exp : ast.AST
        A parsed abstract syntax tree

    """
    # pylint: disable=too-many-branches
    if not isinstance(exp, ast.AST):
        raise TypeError("exp is not a 'ast.AST' instance")

    etype = type(exp)
    if etype in [ast.Expr, ast.Expression]:
        return validate_exp(exp.body)
    elif etype == ast.BoolOp:
        return all(map(validate_exp, exp.values))
    elif etype == ast.BinOp:
        return all(map(validate_exp, [exp.left, exp.right]))
    elif etype == ast.UnaryOp:
        return validate_exp(exp.operand)
    elif etype == ast.IfExp:
        return all(map(validate_exp, [exp.test, exp.body, exp.orelse]))
    elif etype == ast.Dict:
        return all(map(validate_exp, chain(exp.keys, exp.values)))
    elif etype == ast.Set:
        return all(map(validate_exp, exp.elts))
    elif etype == ast.Compare:
        return all(map(validate_exp, [exp.left] + exp.comparators))
    elif etype == ast.Call:
        subexp = chain([exp.func], exp.args or [],
                       [k.value for k in exp.keywords or []])
        return all(map(validate_exp, subexp))
    elif etype == ast.Starred:
        assert isinstance(exp.ctx, ast.Load)
        return validate_exp(exp.value)
    elif etype in [ast.Num, ast.Str, ast.Bytes, ast.Ellipsis, ast.NameConstant]:
        return True
    elif etype == ast.Constant:
        return True
    elif etype == ast.Attribute:
        return True
    elif etype == ast.Subscript:
        return all(map(validate_exp, [exp.value, exp.slice]))
    elif etype in {ast.List, ast.Tuple}:
        assert isinstance(exp.ctx, ast.Load)
        return all(map(validate_exp, exp.elts))
    elif etype == ast.Name:
        return True
    elif etype == ast.Slice:
        return all(map(validate_exp,
                       filter(None, [exp.lower, exp.upper, exp.step])))
    elif etype == ast.ExtSlice:
        return all(map(validate_exp, exp.dims))
    elif etype == ast.Index:
        return validate_exp(exp.value)
    elif etype == ast.keyword:
        return validate_exp(exp.value)
    else:
        raise ValueError(exp)


def construct_variables(descriptions, data):
    # subs
    variables = []
    source_vars = data.domain.variables + data.domain.metas
    for desc in descriptions:
        desc, func = bind_variable(desc, source_vars, data)
        var = make_variable(desc, func)
        variables.append(var)
    return variables


def sanitized_name(name):
    sanitized = re.sub(r"\W", "_", name)
    if sanitized[0].isdigit():
        sanitized = "_" + sanitized
    return sanitized


def bind_variable(descriptor, env, data):
    """
    (descriptor, env) ->
        (descriptor, (instance -> value) | (table -> value list))
    """
    if not descriptor.expression.strip():
        return descriptor, FeatureFunc("nan", [], {"nan": float("nan")})

    exp_ast = ast.parse(descriptor.expression, mode="eval")
    freev = unique(freevars(exp_ast, []))
    variables = {sanitized_name(v.name): v for v in env}
    source_vars = [(name, variables[name]) for name in freev
                   if name in variables]

    values = {}
    cast = None
    nan = float("nan")

    if isinstance(descriptor, DiscreteDescriptor):
        if not descriptor.values:
            str_func = FeatureFunc(descriptor.expression, source_vars)
            values = sorted({str(x) for x in str_func(data)})
            values = {name: i for i, name in enumerate(values)}
            descriptor = descriptor._replace(values=values)

            def cast(x):  # pylint: disable=function-redefined
                return values.get(x, nan)

        else:
            values = [sanitized_name(v) for v in descriptor.values]
            values = {name: i for i, name in enumerate(values)}

    if isinstance(descriptor, DateTimeDescriptor):
        parse = Orange.data.TimeVariable("_").parse

        def cast(e):  # pylint: disable=function-redefined
            if isinstance(e, (int, float)):
                return e
            if e == "" or e is None:
                return np.nan
            return parse(e)

    func = FeatureFunc(descriptor.expression, source_vars, values, cast)
    return descriptor, func


def make_lambda(expression, args, env=None):
    # type: (ast.Expression, List[str], Dict[str, Any]) -> types.FunctionType
    """
    Create an lambda function from a expression AST.

    Parameters
    ----------
    expression : ast.Expression
        The body of the lambda.
    args : List[str]
        A list of positional argument names
    env : Optional[Dict[str, Any]]
        Extra environment to capture in the lambda's closure.

    Returns
    -------
    func : types.FunctionType
    """
    # lambda *{args}* : EXPRESSION
    lambda_ = ast.Lambda(
        args=ast.arguments(
            posonlyargs=[],
            args=[ast.arg(arg=arg, annotation=None) for arg in args],
            varargs=None,
            varargannotation=None,
            kwonlyargs=[],
            kwarg=None,
            kwargannotation=None,
            defaults=[],
            kw_defaults=[]),
        body=expression.body,
    )
    lambda_ = ast.copy_location(lambda_, expression.body)
    # lambda **{env}** : lambda *{args}*: EXPRESSION
    outer = ast.Lambda(
        args=ast.arguments(
            posonlyargs=[],
            args=[ast.arg(arg=name, annotation=None) for name in (env or {})],
            varargs=None,
            varargannotation=None,
            kwonlyargs=[],
            kwarg=None,
            kwargannotation=None,
            defaults=[],
            kw_defaults=[],
        ),
        body=lambda_,
    )
    exp = ast.Expression(body=outer, lineno=1, col_offset=0)
    ast.fix_missing_locations(exp)
    GLOBALS = __GLOBALS.copy()
    GLOBALS["__builtins__"] = {}
    # pylint: disable=eval-used
    fouter = eval(compile(exp, "<lambda>", "eval"), GLOBALS)
    assert isinstance(fouter, types.FunctionType)
    finner = fouter(**env)
    assert isinstance(finner, types.FunctionType)
    return finner


__ALLOWED = [
    "Ellipsis", "False", "None", "True", "abs", "all", "any", "acsii",
    "bin", "bool", "bytearray", "bytes", "chr", "complex", "dict",
    "divmod", "enumerate", "filter", "float", "format", "frozenset",
    "getattr", "hasattr", "hash", "hex", "id", "int", "iter", "len",
    "list", "map", "memoryview", "next", "object",
    "oct", "ord", "pow", "range", "repr", "reversed", "round",
    "set", "slice", "sorted", "str", "tuple", "type",
    "zip"
]

__GLOBALS = {name: getattr(builtins, name) for name in __ALLOWED
             if hasattr(builtins, name)}

__GLOBALS.update({name: getattr(math, name) for name in dir(math)
                  if not name.startswith("_")})

__GLOBALS.update({
    "normalvariate": random.normalvariate,
    "gauss": random.gauss,
    "expovariate": random.expovariate,
    "gammavariate": random.gammavariate,
    "betavariate": random.betavariate,
    "lognormvariate": random.lognormvariate,
    "paretovariate": random.paretovariate,
    "vonmisesvariate": random.vonmisesvariate,
    "weibullvariate": random.weibullvariate,
    "triangular": random.triangular,
    "uniform": random.uniform,
    "nanmean": lambda *args: np.nanmean(args),
    "nanmin": lambda *args: np.nanmin(args),
    "nanmax": lambda *args: np.nanmax(args),
    "nansum": lambda *args: np.nansum(args),
    "nanstd": lambda *args: np.nanstd(args),
    "nanmedian": lambda *args: np.nanmedian(args),
    "nancumsum": lambda *args: np.nancumsum(args),
    "nancumprod": lambda *args: np.nancumprod(args),
    "nanargmax": lambda *args: np.nanargmax(args),
    "nanargmin": lambda *args: np.nanargmin(args),
    "nanvar": lambda *args: np.nanvar(args),
    "mean": lambda *args: np.mean(args),
    "min": lambda *args: np.min(args),
    "max": lambda *args: np.max(args),
    "sum": lambda *args: np.sum(args),
    "std": lambda *args: np.std(args),
    "median": lambda *args: np.median(args),
    "cumsum": lambda *args: np.cumsum(args),
    "cumprod": lambda *args: np.cumprod(args),
    "argmax": lambda *args: np.argmax(args),
    "argmin": lambda *args: np.argmin(args),
    "var": lambda *args: np.var(args)})


class FeatureFunc:
    """
    Parameters
    ----------
    expression : str
        An expression string
    args : List[Tuple[str, Orange.data.Variable]]
        A list of (`name`, `variable`) tuples where `name` is the name of
        a variable as used in `expression`, and `variable` is the variable
        instance used to extract the corresponding column/value from a
        Table/Instance.
    extra_env : Optional[Dict[str, Any]]
        Extra environment specifying constant values to be made available
        in expression. It must not shadow names in `args`
    cast: Optional[Callable]
        A function for casting the expressions result to the appropriate
        type (e.g. string representation of date/time variables to floats)
    """
    def __init__(self, expression, args, extra_env=None, cast=None):
        self.expression = expression
        self.args = args
        self.extra_env = dict(extra_env or {})
        self.func = make_lambda(ast.parse(expression, mode="eval"),
                                [name for name, _ in args], self.extra_env)
        self.cast = cast
        self.mask_exceptions = True

    def __call__(self, instance, *_):
        if isinstance(instance, Orange.data.Table):
            return [self(inst) for inst in instance]
        else:
            try:
                args = [str(instance[var])
                        if instance.domain[var].is_string else instance[var]
                        for _, var in self.args]
                y = self.func(*args)
            # user's expression can contain arbitrary errors
            # this also covers missing attributes
            except:  # pylint: disable=bare-except
                if not self.mask_exceptions:
                    raise
                return np.nan
            if self.cast:
                y = self.cast(y)
            return y

    def __reduce__(self):
        return type(self), (self.expression, self.args,
                            self.extra_env, self.cast)

    def __repr__(self):
        return "{0.__name__}{1!r}".format(*self.__reduce__())


def unique(seq):
    seen = set()
    unique_el = []
    for el in seq:
        if el not in seen:
            unique_el.append(el)
            seen.add(el)
    return unique_el


if __name__ == "__main__":  # pragma: no cover
    WidgetPreview(OWFeatureConstructor).run(Orange.data.Table("iris"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Adding a Feature with a Duplicate Name Results in Multiple Variables Sharing the Same Name

**Description:**
When using the Feature Constructor to add a new variable with a name that already exists in the dataset, the system does not automatically rename the new variable to ensure uniqueness. This leads to multiple variables having the same name, which can cause confusion and potential errors in data analysis.

**Example Code:**
```python
data = Table("iris")
widget.setData(data)
widget.addFeature(ContinuousDescriptor("iris", "0", 3))
widget.apply()
output = widget.get_output()
```

**Expected Behavior:**
The new variable should be automatically renamed to a unique name, such as "iris1", to avoid duplication of variable names.

**Actual Behavior:**
The new variable retains the original name "iris", resulting in duplicate variable names within the dataset.
[/ISSUE]
{
    "FeatureConstructorTest.test_construct_numeric_names": "PASSED",
    "FeatureConstructorTest.test_construct_variables_continuous": "PASSED",
    "FeatureConstructorTest.test_construct_variables_datetime": "PASSED",
    "FeatureConstructorTest.test_construct_variables_discrete": "PASSED",
    "FeatureConstructorTest.test_construct_variables_discrete_no_values": "PASSED",
    "FeatureConstructorTest.test_construct_variables_string": "PASSED",
    "TestTools.test_free_vars": "PASSED",
    "TestTools.test_validate_exp": "PASSED",
    "FeatureFuncTest.test_call": "PASSED",
    "FeatureFuncTest.test_invalid_expression_variable": "PASSED",
    "FeatureFuncTest.test_missing_variable": "PASSED",
    "FeatureFuncTest.test_reconstruct": "PASSED",
    "FeatureFuncTest.test_repr": "PASSED",
    "FeatureFuncTest.test_string_casting": "PASSED",
    "OWFeatureConstructorTests.test_create_variable_with_no_data": "PASSED",
    "OWFeatureConstructorTests.test_discrete_no_values": "PASSED",
    "OWFeatureConstructorTests.test_error_invalid_expression": "PASSED",
    "OWFeatureConstructorTests.test_image_export": "PASSED",
    "OWFeatureConstructorTests.test_msg_base_class": "PASSED",
    "OWFeatureConstructorTests.test_renaming_duplicate_vars": "PASSED",
    "OWFeatureConstructorTests.test_summary": "PASSED",
    "TestFeatureEditor.test_has_functions": "PASSED",
    "FeatureConstructorHandlerTests.test_handles_builtins_in_expression": "PASSED",
    "FeatureConstructorHandlerTests.test_handles_special_characters_in_var_names": "PASSED",
    "OWFeatureConstructorTests.test_minimum_size": "FAILED"
}
diff --git a/Orange/widgets/data/owfeatureconstructor.py b/Orange/widgets/data/owfeatureconstructor.py
index a31e895be..e628fc3ab 100644
--- a/Orange/widgets/data/owfeatureconstructor.py
+++ b/Orange/widgets/data/owfeatureconstructor.py
@@ -26,11 +26,12 @@ from AnyQt.QtWidgets import (
     QSizePolicy, QAbstractItemView, QComboBox, QFormLayout, QLineEdit,
     QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,
     QPushButton, QMenu, QListView, QFrame, QLabel)
-from AnyQt.QtGui import QKeySequence
+from AnyQt.QtGui import QKeySequence, QColor
 from AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property
 from orangewidget.utils.combobox import ComboBoxSearch
 
 import Orange
+from Orange.data.util import get_unique_names
 from Orange.widgets import gui
 from Orange.widgets.settings import ContextSetting, DomainContextHandler
 from Orange.widgets.utils import itemmodels, vartype
@@ -55,6 +56,7 @@ DiscreteDescriptor = \
 
 StringDescriptor = namedtuple("StringDescriptor", ["name", "expression"])
 
+#warningIcon = gui.createAttributePixmap('!', QColor((202, 0, 32)))
 
 def make_variable(descriptor, compute_value):
     if isinstance(descriptor, ContinuousDescriptor):
@@ -390,6 +392,10 @@ class OWFeatureConstructor(OWWidget):
         more_values_needed = Msg("Categorical feature {} needs more values.")
         invalid_expressions = Msg("Invalid expressions: {}.")
 
+    class Warning(OWWidget.Warning):
+        renamed_var = Msg("Recently added variable has been renamed, "
+                           "to avoid duplicates.\n")
+
     def __init__(self):
         super().__init__()
         self.data = None
@@ -427,16 +433,8 @@ class OWFeatureConstructor(OWWidget):
             candidates = (fmt.format(i) for i in count(1))
             return next(c for c in candidates if c not in reserved)
 
-        def reserved_names():
-            varnames = []
-            if self.data is not None:
-                varnames = [var.name for var in
-                            self.data.domain.variables + self.data.domain.metas]
-            varnames += [desc.name for desc in self.featuremodel]
-            return set(varnames)
-
         def generate_newname(fmt):
-            return unique_name(fmt, reserved_names())
+            return unique_name(fmt, self.reserved_names())
 
         menu = QMenu(self.addbutton)
         cont = menu.addAction("Numeric")
@@ -531,8 +529,18 @@ class OWFeatureConstructor(OWWidget):
 
     def _on_modified(self):
         if self.currentIndex >= 0:
+            self.Warning.clear()
             editor = self.editorstack.currentWidget()
-            self.featuremodel[self.currentIndex] = editor.editorData()
+            proposed = editor.editorData().name
+            unique = get_unique_names(self.reserved_names(self.currentIndex),
+                                      proposed)
+
+            feature = editor.editorData()
+            if editor.editorData().name != unique:
+                self.Warning.renamed_var()
+                feature = feature.__class__(unique, *feature[1:])
+                
+            self.featuremodel[self.currentIndex] = feature
             self.descriptors = list(self.featuremodel)
 
     def setDescriptors(self, descriptors):
@@ -542,6 +550,15 @@ class OWFeatureConstructor(OWWidget):
         self.descriptors = descriptors
         self.featuremodel[:] = list(self.descriptors)
 
+    def reserved_names(self, idx_=None):
+        varnames = []
+        if self.data is not None:
+            varnames = [var.name for var in
+                        self.data.domain.variables + self.data.domain.metas]
+        varnames += [desc.name for idx, desc in enumerate(self.featuremodel)
+                     if idx != idx_]
+        return set(varnames)
+
     @Inputs.data
     @check_sql_input
     def setData(self, data=None):

{"Orange/widgets/data/owfeatureconstructor.py": "\"\"\"\nFeature Constructor\n\nA widget for defining (constructing) new features from values\nof other variables.\n\n\"\"\"\nimport re\nimport copy\nimport functools\nimport builtins\nimport math\nimport random\nimport logging\nimport ast\nimport types\n\nfrom traceback import format_exception_only\nfrom collections import namedtuple, OrderedDict\nfrom itertools import chain, count\nfrom typing import List, Dict, Any\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QSizePolicy, QAbstractItemView, QComboBox, QFormLayout, QLineEdit,\n    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,\n    QPushButton, QMenu, QListView, QFrame, QLabel)\nfrom AnyQt.QtGui import QKeySequence\nfrom AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property\nfrom orangewidget.utils.combobox import ComboBoxSearch\n\nimport Orange\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import ContextSetting, DomainContextHandler\nfrom Orange.widgets.utils import itemmodels, vartype\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets import report\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.utils.state_summary import format_summary_details\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\n\nFeatureDescriptor = \\\n    namedtuple(\"FeatureDescriptor\", [\"name\", \"expression\"])\n\nContinuousDescriptor = \\\n    namedtuple(\"ContinuousDescriptor\",\n               [\"name\", \"expression\", \"number_of_decimals\"])\nDateTimeDescriptor = \\\n    namedtuple(\"DateTimeDescriptor\",\n               [\"name\", \"expression\"])\nDiscreteDescriptor = \\\n    namedtuple(\"DiscreteDescriptor\",\n               [\"name\", \"expression\", \"values\", \"ordered\"])\n\nStringDescriptor = namedtuple(\"StringDescriptor\", [\"name\", \"expression\"])\n\n\ndef make_variable(descriptor, compute_value):\n    if isinstance(descriptor, ContinuousDescriptor):\n        return Orange.data.ContinuousVariable(\n            descriptor.name,\n            descriptor.number_of_decimals,\n            compute_value)\n    if isinstance(descriptor, DateTimeDescriptor):\n        return Orange.data.TimeVariable(\n            descriptor.name,\n            compute_value=compute_value, have_date=True, have_time=True)\n    elif isinstance(descriptor, DiscreteDescriptor):\n        return Orange.data.DiscreteVariable(\n            descriptor.name,\n            values=descriptor.values,\n            ordered=descriptor.ordered,\n            compute_value=compute_value)\n    elif isinstance(descriptor, StringDescriptor):\n        return Orange.data.StringVariable(\n            descriptor.name,\n            compute_value=compute_value)\n    else:\n        raise TypeError\n\n\ndef selected_row(view):\n    \"\"\"\n    Return the index of selected row in a `view` (:class:`QListView`)\n\n    The view's selection mode must be a QAbstractItemView.SingleSelction\n    \"\"\"\n    if view.selectionMode() in [QAbstractItemView.MultiSelection,\n                                QAbstractItemView.ExtendedSelection]:\n        raise ValueError(\"invalid 'selectionMode'\")\n\n    sel_model = view.selectionModel()\n    indexes = sel_model.selectedRows()\n    if indexes:\n        assert len(indexes) == 1\n        return indexes[0].row()\n    else:\n        return None\n\n\nclass FeatureEditor(QFrame):\n    FUNCTIONS = dict(chain([(key, val) for key, val in math.__dict__.items()\n                            if not key.startswith(\"_\")],\n                           [(key, val) for key, val in builtins.__dict__.items()\n                            if key in {\"str\", \"float\", \"int\", \"len\",\n                                       \"abs\", \"max\", \"min\"}]))\n    featureChanged = Signal()\n    featureEdited = Signal()\n\n    modifiedChanged = Signal(bool)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        layout = QFormLayout(\n            fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow\n        )\n        layout.setContentsMargins(0, 0, 0, 0)\n        self.nameedit = QLineEdit(\n            placeholderText=\"Name...\",\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.Fixed)\n        )\n        self.expressionedit = QLineEdit(\n            placeholderText=\"Expression...\",\n            toolTip=self.ExpressionTooltip)\n\n        self.attrs_model = itemmodels.VariableListModel(\n            [\"Select Feature\"], parent=self)\n        self.attributescb = ComboBoxSearch(\n            minimumContentsLength=16,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)\n        )\n        self.attributescb.setModel(self.attrs_model)\n\n        sorted_funcs = sorted(self.FUNCTIONS)\n        self.funcs_model = itemmodels.PyListModelTooltip()\n        self.funcs_model.setParent(self)\n\n        self.funcs_model[:] = chain([\"Select Function\"], sorted_funcs)\n        self.funcs_model.tooltips[:] = chain(\n            [''],\n            [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])\n\n        self.functionscb = ComboBoxSearch(\n            minimumContentsLength=16,\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))\n        self.functionscb.setModel(self.funcs_model)\n\n        hbox = QHBoxLayout()\n        hbox.addWidget(self.attributescb)\n        hbox.addWidget(self.functionscb)\n\n        layout.addRow(self.nameedit, self.expressionedit)\n        layout.addRow(self.tr(\"\"), hbox)\n        self.setLayout(layout)\n\n        self.nameedit.editingFinished.connect(self._invalidate)\n        self.expressionedit.textChanged.connect(self._invalidate)\n        self.attributescb.currentIndexChanged.connect(self.on_attrs_changed)\n        self.functionscb.currentIndexChanged.connect(self.on_funcs_changed)\n\n        self._modified = False\n\n    def setModified(self, modified):\n        if not isinstance(modified, bool):\n            raise TypeError\n\n        if self._modified != modified:\n            self._modified = modified\n            self.modifiedChanged.emit(modified)\n\n    def modified(self):\n        return self._modified\n\n    modified = Property(bool, modified, setModified,\n                        notify=modifiedChanged)\n\n    def setEditorData(self, data, domain):\n        self.nameedit.setText(data.name)\n        self.expressionedit.setText(data.expression)\n        self.setModified(False)\n        self.featureChanged.emit()\n        self.attrs_model[:] = [\"Select Feature\"]\n        if domain is not None and not domain.empty():\n            self.attrs_model[:] += chain(domain.attributes,\n                                         domain.class_vars,\n                                         domain.metas)\n\n    def editorData(self):\n        return FeatureDescriptor(name=self.nameedit.text(),\n                                 expression=self.nameedit.text())\n\n    def _invalidate(self):\n        self.setModified(True)\n        self.featureEdited.emit()\n        self.featureChanged.emit()\n\n    def on_attrs_changed(self):\n        index = self.attributescb.currentIndex()\n        if index > 0:\n            attr = sanitized_name(self.attrs_model[index].name)\n            self.insert_into_expression(attr)\n            self.attributescb.setCurrentIndex(0)\n\n    def on_funcs_changed(self):\n        index = self.functionscb.currentIndex()\n        if index > 0:\n            func = self.funcs_model[index]\n            if func in [\"atan2\", \"fmod\", \"ldexp\", \"log\",\n                        \"pow\", \"copysign\", \"hypot\"]:\n                self.insert_into_expression(func + \"(,)\")\n                self.expressionedit.cursorBackward(False, 2)\n            elif func in [\"e\", \"pi\"]:\n                self.insert_into_expression(func)\n            else:\n                self.insert_into_expression(func + \"()\")\n                self.expressionedit.cursorBackward(False)\n            self.functionscb.setCurrentIndex(0)\n\n    def insert_into_expression(self, what):\n        cp = self.expressionedit.cursorPosition()\n        ct = self.expressionedit.text()\n        text = ct[:cp] + what + ct[cp:]\n        self.expressionedit.setText(text)\n        self.expressionedit.setFocus()\n\n\nclass ContinuousFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A numeric expression\"\n\n    def editorData(self):\n        return ContinuousDescriptor(\n            name=self.nameedit.text(),\n            number_of_decimals=None,\n            expression=self.expressionedit.text()\n        )\n\n\nclass DateTimeFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \\\n        \"Result must be a string in ISO-8601 format \" \\\n        \"(e.g. 2019-07-30T15:37:27 or a part thereof),\\n\" \\\n        \"or a number of seconds since Jan 1, 1970.\"\n\n    def editorData(self):\n        return DateTimeDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text()\n        )\n\n\nclass DiscreteFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \\\n        \"Result must be a string, if values are not explicitly given\\n\" \\\n        \"or a zero-based integer indices into a list of values given below.\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        tooltip = \\\n            \"If values are given, above expression must return zero-based \" \\\n            \"integer indices into that list.\"\n        self.valuesedit = QLineEdit(placeholderText=\"A, B ...\", toolTip=tooltip)\n        self.valuesedit.textChanged.connect(self._invalidate)\n\n        layout = self.layout()\n        label = QLabel(self.tr(\"Values (optional)\"))\n        label.setToolTip(tooltip)\n        layout.addRow(label, self.valuesedit)\n\n    def setEditorData(self, data, domain):\n        self.valuesedit.setText(\n            \", \".join(v.replace(\",\", r\"\\,\") for v in data.values))\n\n        super().setEditorData(data, domain)\n\n    def editorData(self):\n        values = self.valuesedit.text()\n        values = re.split(r\"(?<!\\\\),\", values)\n        values = tuple(filter(None, [v.replace(r\"\\,\", \",\").strip() for v in values]))\n        return DiscreteDescriptor(\n            name=self.nameedit.text(),\n            values=values,\n            ordered=False,\n            expression=self.expressionedit.text()\n        )\n\n\nclass StringFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A string expression\"\n\n    def editorData(self):\n        return StringDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text()\n        )\n\n\n_VarMap = {\n    DiscreteDescriptor: vartype(Orange.data.DiscreteVariable(\"d\")),\n    ContinuousDescriptor: vartype(Orange.data.ContinuousVariable(\"c\")),\n    DateTimeDescriptor: vartype(Orange.data.TimeVariable(\"t\")),\n    StringDescriptor: vartype(Orange.data.StringVariable(\"s\"))\n}\n\n\n@functools.lru_cache(20)\ndef variable_icon(dtype):\n    vtype = _VarMap.get(dtype, dtype)\n    return gui.attributeIconDict[vtype]\n\n\nclass FeatureItemDelegate(QStyledItemDelegate):\n    @staticmethod\n    def displayText(value, _):\n        return value.name + \" := \" + value.expression\n\n\nclass DescriptorModel(itemmodels.PyListModel):\n    def data(self, index, role=Qt.DisplayRole):\n        if role == Qt.DecorationRole:\n            value = self[index.row()]\n            return variable_icon(type(value))\n        else:\n            return super().data(index, role)\n\n\nclass FeatureConstructorHandler(DomainContextHandler):\n    \"\"\"Context handler that filters descriptors\"\"\"\n\n    def is_valid_item(self, setting, item, attrs, metas):\n        \"\"\"Check if descriptor `item` can be used with given domain.\n\n        Return True if descriptor's expression contains only\n        available variables and descriptors name does not clash with\n        existing variables.\n        \"\"\"\n        if item.name in attrs or item.name in metas:\n            return False\n\n        try:\n            exp_ast = ast.parse(item.expression, mode=\"eval\")\n        # ast.parse can return arbitrary errors, not only SyntaxError\n        # pylint: disable=broad-except\n        except Exception:\n            return False\n\n        available = dict(globals()[\"__GLOBALS\"])\n        for var in attrs:\n            available[sanitized_name(var)] = None\n        for var in metas:\n            available[sanitized_name(var)] = None\n\n        if freevars(exp_ast, available):\n            return False\n        return True\n\n\nclass OWFeatureConstructor(OWWidget):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = []\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        data = Output(\"Data\", Orange.data.Table)\n\n    want_main_area = False\n\n    settingsHandler = FeatureConstructorHandler()\n    descriptors = ContextSetting([])\n    currentIndex = ContextSetting(-1)\n\n    EDITORS = [\n        (ContinuousDescriptor, ContinuousFeatureEditor),\n        (DateTimeDescriptor, DateTimeFeatureEditor),\n        (DiscreteDescriptor, DiscreteFeatureEditor),\n        (StringDescriptor, StringFeatureEditor)\n    ]\n\n    class Error(OWWidget.Error):\n        more_values_needed = Msg(\"Categorical feature {} needs more values.\")\n        invalid_expressions = Msg(\"Invalid expressions: {}.\")\n\n    def __init__(self):\n        super().__init__()\n        self.data = None\n        self.editors = {}\n\n        box = gui.vBox(self.controlArea, \"Variable Definitions\")\n\n        toplayout = QHBoxLayout()\n        toplayout.setContentsMargins(0, 0, 0, 0)\n        box.layout().addLayout(toplayout)\n\n        self.editorstack = QStackedWidget(\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        for descclass, editorclass in self.EDITORS:\n            editor = editorclass()\n            editor.featureChanged.connect(self._on_modified)\n            self.editors[descclass] = editor\n            self.editorstack.addWidget(editor)\n\n        self.editorstack.setEnabled(False)\n\n        buttonlayout = QVBoxLayout(spacing=10)\n        buttonlayout.setContentsMargins(0, 0, 0, 0)\n\n        self.addbutton = QPushButton(\n            \"New\", toolTip=\"Create a new variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.New\n        )\n\n        def unique_name(fmt, reserved):\n            candidates = (fmt.format(i) for i in count(1))\n            return next(c for c in candidates if c not in reserved)\n\n        def reserved_names():\n            varnames = []\n            if self.data is not None:\n                varnames = [var.name for var in\n                            self.data.domain.variables + self.data.domain.metas]\n            varnames += [desc.name for desc in self.featuremodel]\n            return set(varnames)\n\n        def generate_newname(fmt):\n            return unique_name(fmt, reserved_names())\n\n        menu = QMenu(self.addbutton)\n        cont = menu.addAction(\"Numeric\")\n        cont.triggered.connect(\n            lambda: self.addFeature(\n                ContinuousDescriptor(generate_newname(\"X{}\"), \"\", 3))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\"))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\"))\n        )\n\n        menu.addSeparator()\n        self.duplicateaction = menu.addAction(\"Duplicate Selected Variable\")\n        self.duplicateaction.triggered.connect(self.duplicateFeature)\n        self.duplicateaction.setEnabled(False)\n        self.addbutton.setMenu(menu)\n\n        self.removebutton = QPushButton(\n            \"Remove\", toolTip=\"Remove selected variable\",\n            minimumWidth=120,\n            shortcut=QKeySequence.Delete\n        )\n        self.removebutton.clicked.connect(self.removeSelectedFeature)\n\n        buttonlayout.addWidget(self.addbutton)\n        buttonlayout.addWidget(self.removebutton)\n        buttonlayout.addStretch(10)\n\n        toplayout.addLayout(buttonlayout, 0)\n        toplayout.addWidget(self.editorstack, 10)\n\n        # Layout for the list view\n        layout = QVBoxLayout(spacing=1, margin=0)\n        self.featuremodel = DescriptorModel(parent=self)\n\n        self.featureview = QListView(\n            minimumWidth=200, minimumHeight=50,\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.MinimumExpanding)\n        )\n\n        self.featureview.setItemDelegate(FeatureItemDelegate(self))\n        self.featureview.setModel(self.featuremodel)\n        self.featureview.selectionModel().selectionChanged.connect(\n            self._on_selectedVariableChanged\n        )\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        box = gui.hBox(self.controlArea)\n        gui.rubber(box)\n        commit = gui.button(box, self, \"Send\", callback=self.apply,\n                            default=True)\n        commit.setMinimumWidth(180)\n\n    def setCurrentIndex(self, index):\n        index = min(index, len(self.featuremodel) - 1)\n        self.currentIndex = index\n        if index >= 0:\n            itemmodels.select_row(self.featureview, index)\n            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]\n            editor = self.editors[type(desc)]\n            self.editorstack.setCurrentWidget(editor)\n            editor.setEditorData(desc, self.data.domain if self.data else None)\n        self.editorstack.setEnabled(index >= 0)\n        self.duplicateaction.setEnabled(index >= 0)\n        self.removebutton.setEnabled(index >= 0)\n\n    def _on_selectedVariableChanged(self, selected, *_):\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        else:\n            self.setCurrentIndex(-1)\n\n    def _on_modified(self):\n        if self.currentIndex >= 0:\n            editor = self.editorstack.currentWidget()\n            self.featuremodel[self.currentIndex] = editor.editorData()\n            self.descriptors = list(self.featuremodel)\n\n    def setDescriptors(self, descriptors):\n        \"\"\"\n        Set a list of variable descriptors to edit.\n        \"\"\"\n        self.descriptors = descriptors\n        self.featuremodel[:] = list(self.descriptors)\n\n    @Inputs.data\n    @check_sql_input\n    def setData(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.closeContext()\n\n        self.data = data\n\n        self.info.set_input_summary(self.info.NoInput)\n        if self.data is not None:\n            descriptors = list(self.descriptors)\n            currindex = self.currentIndex\n            self.descriptors = []\n            self.currentIndex = -1\n            self.openContext(data)\n            self.info.set_input_summary(len(data), format_summary_details(data))\n\n            if descriptors != self.descriptors or \\\n                    self.currentIndex != currindex:\n                # disconnect from the selection model while reseting the model\n                selmodel = self.featureview.selectionModel()\n                selmodel.selectionChanged.disconnect(\n                    self._on_selectedVariableChanged)\n\n                self.featuremodel[:] = list(self.descriptors)\n                self.setCurrentIndex(self.currentIndex)\n\n                selmodel.selectionChanged.connect(\n                    self._on_selectedVariableChanged)\n\n        self.editorstack.setEnabled(self.currentIndex >= 0)\n\n    def handleNewSignals(self):\n        if self.data is not None:\n            self.apply()\n        else:\n            self.info.set_output_summary(self.info.NoOutput)\n            self.Outputs.data.send(None)\n\n    def addFeature(self, descriptor):\n        self.featuremodel.append(descriptor)\n        self.setCurrentIndex(len(self.featuremodel) - 1)\n        editor = self.editorstack.currentWidget()\n        editor.nameedit.setFocus()\n        editor.nameedit.selectAll()\n\n    def removeFeature(self, index):\n        del self.featuremodel[index]\n        index = selected_row(self.featureview)\n        if index is not None:\n            self.setCurrentIndex(index)\n        elif index is None and self.featuremodel.rowCount():\n            # Deleting the last item clears selection\n            self.setCurrentIndex(self.featuremodel.rowCount() - 1)\n\n    def removeSelectedFeature(self):\n        if self.currentIndex >= 0:\n            self.removeFeature(self.currentIndex)\n\n    def duplicateFeature(self):\n        desc = self.featuremodel[self.currentIndex]\n        self.addFeature(copy.deepcopy(desc))\n\n    @staticmethod\n    def check_attrs_values(attr, data):\n        for i in range(len(data)):\n            for var in attr:\n                if not math.isnan(data[i, var]) \\\n                        and int(data[i, var]) >= len(var.values):\n                    return var.name\n        return None\n\n    def _validate_descriptors(self, desc):\n\n        def validate(source):\n            try:\n                return validate_exp(ast.parse(source, mode=\"eval\"))\n            # ast.parse can return arbitrary errors, not only SyntaxError\n            # pylint: disable=broad-except\n            except Exception:\n                return False\n\n        final = []\n        invalid = []\n        for d in desc:\n            if validate(d.expression):\n                final.append(d)\n            else:\n                final.append(d._replace(expression=\"\"))\n                invalid.append(d)\n\n        if invalid:\n            self.Error.invalid_expressions(\", \".join(s.name for s in invalid))\n\n        return final\n\n    def apply(self):\n        def report_error(err):\n            log = logging.getLogger(__name__)\n            log.error(\"\", exc_info=True)\n            self.error(\"\".join(format_exception_only(type(err), err)).rstrip())\n\n        self.Error.clear()\n\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        try:\n            new_variables = construct_variables(desc, self.data)\n        # user's expression can contain arbitrary errors\n        except Exception as err:  # pylint: disable=broad-except\n            report_error(err)\n            return\n\n        attrs = [var for var in new_variables if var.is_primitive()]\n        metas = [var for var in new_variables if not var.is_primitive()]\n        new_domain = Orange.data.Domain(\n            self.data.domain.attributes + tuple(attrs),\n            self.data.domain.class_vars,\n            metas=self.data.domain.metas + tuple(metas)\n        )\n\n        try:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = False\n            data = self.data.transform(new_domain)\n        # user's expression can contain arbitrary errors\n        # pylint: disable=broad-except\n        except Exception as err:\n            report_error(err)\n            return\n        finally:\n            for variable in new_variables:\n                variable.compute_value.mask_exceptions = True\n\n        disc_attrs_not_ok = self.check_attrs_values(\n            [var for var in attrs if var.is_discrete], data)\n        if disc_attrs_not_ok:\n            self.Error.more_values_needed(disc_attrs_not_ok)\n            return\n\n        self.info.set_output_summary(len(data), format_summary_details(data))\n        self.Outputs.data.send(data)\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                items[feature.name] = \"{} (categorical with values {}{})\".format(\n                    feature.expression, feature.values,\n                    \"; ordered\" * feature.ordered)\n            elif isinstance(feature, ContinuousDescriptor):\n                items[feature.name] = \"{} (numeric)\".format(feature.expression)\n            elif isinstance(feature, DateTimeDescriptor):\n                items[feature.name] = \"{} (date/time)\".format(feature.expression)\n            else:\n                items[feature.name] = \"{} (text)\".format(feature.expression)\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)\n\n\ndef freevars(exp, env):\n    \"\"\"\n    Return names of all free variables in a parsed (expression) AST.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        An expression ast (ast.parse(..., mode=\"single\"))\n    env : List[str]\n        Environment\n\n    Returns\n    -------\n    freevars : List[str]\n\n    See also\n    --------\n    ast\n\n    \"\"\"\n    # pylint: disable=too-many-return-statements,too-many-branches\n    etype = type(exp)\n    if etype in [ast.Expr, ast.Expression]:\n        return freevars(exp.body, env)\n    elif etype == ast.BoolOp:\n        return sum((freevars(v, env) for v in exp.values), [])\n    elif etype == ast.BinOp:\n        return freevars(exp.left, env) + freevars(exp.right, env)\n    elif etype == ast.UnaryOp:\n        return freevars(exp.operand, env)\n    elif etype == ast.Lambda:\n        args = exp.args\n        assert isinstance(args, ast.arguments)\n        argnames = [a.arg for a in args.args]\n        argnames += [args.vararg.arg] if args.vararg else []\n        argnames += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []\n        argnames += [args.kwarg] if args.kwarg else []\n        return freevars(exp.body, env + argnames)\n    elif etype == ast.IfExp:\n        return (freevars(exp.test, env) + freevars(exp.body, env) +\n                freevars(exp.orelse, env))\n    elif etype == ast.Dict:\n        return sum((freevars(v, env)\n                    for v in chain(exp.keys, exp.values)), [])\n    elif etype == ast.Set:\n        return sum((freevars(v, env) for v in exp.elts), [])\n    elif etype in [ast.SetComp, ast.ListComp, ast.GeneratorExp, ast.DictComp]:\n        env_ext = []\n        vars_ = []\n        for gen in exp.generators:\n            target_names = freevars(gen.target, [])  # assigned names\n            vars_iter = freevars(gen.iter, env)\n            env_ext += target_names\n            vars_ifs = list(chain(*(freevars(ifexp, env + target_names)\n                                    for ifexp in gen.ifs or [])))\n            vars_ += vars_iter + vars_ifs\n\n        if etype == ast.DictComp:\n            vars_ = (freevars(exp.key, env_ext) +\n                     freevars(exp.value, env_ext) +\n                     vars_)\n        else:\n            vars_ = freevars(exp.elt, env + env_ext) + vars_\n        return vars_\n    # Yield, YieldFrom???\n    elif etype == ast.Compare:\n        return sum((freevars(v, env)\n                    for v in [exp.left] + exp.comparators), [])\n    elif etype == ast.Call:\n        return sum(map(lambda e: freevars(e, env),\n                       chain([exp.func],\n                             exp.args or [],\n                             [k.value for k in exp.keywords or []])),\n                   [])\n    elif etype == ast.Starred:\n        # a 'starred' call parameter (e.g. a and b in `f(x, *a, *b)`\n        return freevars(exp.value, env)\n    elif etype in [ast.Num, ast.Str, ast.Ellipsis, ast.Bytes, ast.NameConstant]:\n        return []\n    elif etype == ast.Constant:\n        return []\n    elif etype == ast.Attribute:\n        return freevars(exp.value, env)\n    elif etype == ast.Subscript:\n        return freevars(exp.value, env) + freevars(exp.slice, env)\n    elif etype == ast.Name:\n        return [exp.id] if exp.id not in env else []\n    elif etype == ast.List:\n        return sum((freevars(e, env) for e in exp.elts), [])\n    elif etype == ast.Tuple:\n        return sum((freevars(e, env) for e in exp.elts), [])\n    elif etype == ast.Slice:\n        return sum((freevars(e, env)\n                    for e in filter(None, [exp.lower, exp.upper, exp.step])),\n                   [])\n    elif etype == ast.ExtSlice:\n        return sum((freevars(e, env) for e in exp.dims), [])\n    elif etype == ast.Index:\n        return freevars(exp.value, env)\n    elif etype == ast.keyword:\n        return freevars(exp.value, env)\n    else:\n        raise ValueError(exp)\n\n\ndef validate_exp(exp):\n    \"\"\"\n    Validate an `ast.AST` expression.\n\n    Only expressions with no list,set,dict,generator comprehensions\n    are accepted.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        A parsed abstract syntax tree\n\n    \"\"\"\n    # pylint: disable=too-many-branches\n    if not isinstance(exp, ast.AST):\n        raise TypeError(\"exp is not a 'ast.AST' instance\")\n\n    etype = type(exp)\n    if etype in [ast.Expr, ast.Expression]:\n        return validate_exp(exp.body)\n    elif etype == ast.BoolOp:\n        return all(map(validate_exp, exp.values))\n    elif etype == ast.BinOp:\n        return all(map(validate_exp, [exp.left, exp.right]))\n    elif etype == ast.UnaryOp:\n        return validate_exp(exp.operand)\n    elif etype == ast.IfExp:\n        return all(map(validate_exp, [exp.test, exp.body, exp.orelse]))\n    elif etype == ast.Dict:\n        return all(map(validate_exp, chain(exp.keys, exp.values)))\n    elif etype == ast.Set:\n        return all(map(validate_exp, exp.elts))\n    elif etype == ast.Compare:\n        return all(map(validate_exp, [exp.left] + exp.comparators))\n    elif etype == ast.Call:\n        subexp = chain([exp.func], exp.args or [],\n                       [k.value for k in exp.keywords or []])\n        return all(map(validate_exp, subexp))\n    elif etype == ast.Starred:\n        assert isinstance(exp.ctx, ast.Load)\n        return validate_exp(exp.value)\n    elif etype in [ast.Num, ast.Str, ast.Bytes, ast.Ellipsis, ast.NameConstant]:\n        return True\n    elif etype == ast.Constant:\n        return True\n    elif etype == ast.Attribute:\n        return True\n    elif etype == ast.Subscript:\n        return all(map(validate_exp, [exp.value, exp.slice]))\n    elif etype in {ast.List, ast.Tuple}:\n        assert isinstance(exp.ctx, ast.Load)\n        return all(map(validate_exp, exp.elts))\n    elif etype == ast.Name:\n        return True\n    elif etype == ast.Slice:\n        return all(map(validate_exp,\n                       filter(None, [exp.lower, exp.upper, exp.step])))\n    elif etype == ast.ExtSlice:\n        return all(map(validate_exp, exp.dims))\n    elif etype == ast.Index:\n        return validate_exp(exp.value)\n    elif etype == ast.keyword:\n        return validate_exp(exp.value)\n    else:\n        raise ValueError(exp)\n\n\ndef construct_variables(descriptions, data):\n    # subs\n    variables = []\n    source_vars = data.domain.variables + data.domain.metas\n    for desc in descriptions:\n        desc, func = bind_variable(desc, source_vars, data)\n        var = make_variable(desc, func)\n        variables.append(var)\n    return variables\n\n\ndef sanitized_name(name):\n    sanitized = re.sub(r\"\\W\", \"_\", name)\n    if sanitized[0].isdigit():\n        sanitized = \"_\" + sanitized\n    return sanitized\n\n\ndef bind_variable(descriptor, env, data):\n    \"\"\"\n    (descriptor, env) ->\n        (descriptor, (instance -> value) | (table -> value list))\n    \"\"\"\n    if not descriptor.expression.strip():\n        return descriptor, FeatureFunc(\"nan\", [], {\"nan\": float(\"nan\")})\n\n    exp_ast = ast.parse(descriptor.expression, mode=\"eval\")\n    freev = unique(freevars(exp_ast, []))\n    variables = {sanitized_name(v.name): v for v in env}\n    source_vars = [(name, variables[name]) for name in freev\n                   if name in variables]\n\n    values = {}\n    cast = None\n    nan = float(\"nan\")\n\n    if isinstance(descriptor, DiscreteDescriptor):\n        if not descriptor.values:\n            str_func = FeatureFunc(descriptor.expression, source_vars)\n            values = sorted({str(x) for x in str_func(data)})\n            values = {name: i for i, name in enumerate(values)}\n            descriptor = descriptor._replace(values=values)\n\n            def cast(x):  # pylint: disable=function-redefined\n                return values.get(x, nan)\n\n        else:\n            values = [sanitized_name(v) for v in descriptor.values]\n            values = {name: i for i, name in enumerate(values)}\n\n    if isinstance(descriptor, DateTimeDescriptor):\n        parse = Orange.data.TimeVariable(\"_\").parse\n\n        def cast(e):  # pylint: disable=function-redefined\n            if isinstance(e, (int, float)):\n                return e\n            if e == \"\" or e is None:\n                return np.nan\n            return parse(e)\n\n    func = FeatureFunc(descriptor.expression, source_vars, values, cast)\n    return descriptor, func\n\n\ndef make_lambda(expression, args, env=None):\n    # type: (ast.Expression, List[str], Dict[str, Any]) -> types.FunctionType\n    \"\"\"\n    Create an lambda function from a expression AST.\n\n    Parameters\n    ----------\n    expression : ast.Expression\n        The body of the lambda.\n    args : List[str]\n        A list of positional argument names\n    env : Optional[Dict[str, Any]]\n        Extra environment to capture in the lambda's closure.\n\n    Returns\n    -------\n    func : types.FunctionType\n    \"\"\"\n    # lambda *{args}* : EXPRESSION\n    lambda_ = ast.Lambda(\n        args=ast.arguments(\n            posonlyargs=[],\n            args=[ast.arg(arg=arg, annotation=None) for arg in args],\n            varargs=None,\n            varargannotation=None,\n            kwonlyargs=[],\n            kwarg=None,\n            kwargannotation=None,\n            defaults=[],\n            kw_defaults=[]),\n        body=expression.body,\n    )\n    lambda_ = ast.copy_location(lambda_, expression.body)\n    # lambda **{env}** : lambda *{args}*: EXPRESSION\n    outer = ast.Lambda(\n        args=ast.arguments(\n            posonlyargs=[],\n            args=[ast.arg(arg=name, annotation=None) for name in (env or {})],\n            varargs=None,\n            varargannotation=None,\n            kwonlyargs=[],\n            kwarg=None,\n            kwargannotation=None,\n            defaults=[],\n            kw_defaults=[],\n        ),\n        body=lambda_,\n    )\n    exp = ast.Expression(body=outer, lineno=1, col_offset=0)\n    ast.fix_missing_locations(exp)\n    GLOBALS = __GLOBALS.copy()\n    GLOBALS[\"__builtins__\"] = {}\n    # pylint: disable=eval-used\n    fouter = eval(compile(exp, \"<lambda>\", \"eval\"), GLOBALS)\n    assert isinstance(fouter, types.FunctionType)\n    finner = fouter(**env)\n    assert isinstance(finner, types.FunctionType)\n    return finner\n\n\n__ALLOWED = [\n    \"Ellipsis\", \"False\", \"None\", \"True\", \"abs\", \"all\", \"any\", \"acsii\",\n    \"bin\", \"bool\", \"bytearray\", \"bytes\", \"chr\", \"complex\", \"dict\",\n    \"divmod\", \"enumerate\", \"filter\", \"float\", \"format\", \"frozenset\",\n    \"getattr\", \"hasattr\", \"hash\", \"hex\", \"id\", \"int\", \"iter\", \"len\",\n    \"list\", \"map\", \"memoryview\", \"next\", \"object\",\n    \"oct\", \"ord\", \"pow\", \"range\", \"repr\", \"reversed\", \"round\",\n    \"set\", \"slice\", \"sorted\", \"str\", \"tuple\", \"type\",\n    \"zip\"\n]\n\n__GLOBALS = {name: getattr(builtins, name) for name in __ALLOWED\n             if hasattr(builtins, name)}\n\n__GLOBALS.update({name: getattr(math, name) for name in dir(math)\n                  if not name.startswith(\"_\")})\n\n__GLOBALS.update({\n    \"normalvariate\": random.normalvariate,\n    \"gauss\": random.gauss,\n    \"expovariate\": random.expovariate,\n    \"gammavariate\": random.gammavariate,\n    \"betavariate\": random.betavariate,\n    \"lognormvariate\": random.lognormvariate,\n    \"paretovariate\": random.paretovariate,\n    \"vonmisesvariate\": random.vonmisesvariate,\n    \"weibullvariate\": random.weibullvariate,\n    \"triangular\": random.triangular,\n    \"uniform\": random.uniform,\n    \"nanmean\": lambda *args: np.nanmean(args),\n    \"nanmin\": lambda *args: np.nanmin(args),\n    \"nanmax\": lambda *args: np.nanmax(args),\n    \"nansum\": lambda *args: np.nansum(args),\n    \"nanstd\": lambda *args: np.nanstd(args),\n    \"nanmedian\": lambda *args: np.nanmedian(args),\n    \"nancumsum\": lambda *args: np.nancumsum(args),\n    \"nancumprod\": lambda *args: np.nancumprod(args),\n    \"nanargmax\": lambda *args: np.nanargmax(args),\n    \"nanargmin\": lambda *args: np.nanargmin(args),\n    \"nanvar\": lambda *args: np.nanvar(args),\n    \"mean\": lambda *args: np.mean(args),\n    \"min\": lambda *args: np.min(args),\n    \"max\": lambda *args: np.max(args),\n    \"sum\": lambda *args: np.sum(args),\n    \"std\": lambda *args: np.std(args),\n    \"median\": lambda *args: np.median(args),\n    \"cumsum\": lambda *args: np.cumsum(args),\n    \"cumprod\": lambda *args: np.cumprod(args),\n    \"argmax\": lambda *args: np.argmax(args),\n    \"argmin\": lambda *args: np.argmin(args),\n    \"var\": lambda *args: np.var(args)})\n\n\nclass FeatureFunc:\n    \"\"\"\n    Parameters\n    ----------\n    expression : str\n        An expression string\n    args : List[Tuple[str, Orange.data.Variable]]\n        A list of (`name`, `variable`) tuples where `name` is the name of\n        a variable as used in `expression`, and `variable` is the variable\n        instance used to extract the corresponding column/value from a\n        Table/Instance.\n    extra_env : Optional[Dict[str, Any]]\n        Extra environment specifying constant values to be made available\n        in expression. It must not shadow names in `args`\n    cast: Optional[Callable]\n        A function for casting the expressions result to the appropriate\n        type (e.g. string representation of date/time variables to floats)\n    \"\"\"\n    def __init__(self, expression, args, extra_env=None, cast=None):\n        self.expression = expression\n        self.args = args\n        self.extra_env = dict(extra_env or {})\n        self.func = make_lambda(ast.parse(expression, mode=\"eval\"),\n                                [name for name, _ in args], self.extra_env)\n        self.cast = cast\n        self.mask_exceptions = True\n\n    def __call__(self, instance, *_):\n        if isinstance(instance, Orange.data.Table):\n            return [self(inst) for inst in instance]\n        else:\n            try:\n                args = [str(instance[var])\n                        if instance.domain[var].is_string else instance[var]\n                        for _, var in self.args]\n                y = self.func(*args)\n            # user's expression can contain arbitrary errors\n            # this also covers missing attributes\n            except:  # pylint: disable=bare-except\n                if not self.mask_exceptions:\n                    raise\n                return np.nan\n            if self.cast:\n                y = self.cast(y)\n            return y\n\n    def __reduce__(self):\n        return type(self), (self.expression, self.args,\n                            self.extra_env, self.cast)\n\n    def __repr__(self):\n        return \"{0.__name__}{1!r}\".format(*self.__reduce__())\n\n\ndef unique(seq):\n    seen = set()\n    unique_el = []\n    for el in seq:\n        if el not in seen:\n            unique_el.append(el)\n            seen.add(el)\n    return unique_el\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWFeatureConstructor).run(Orange.data.Table(\"iris\"))\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Adding a Feature with a Duplicate Name Results in Multiple Variables Sharing the Same Name

**Description:**
When using the Feature Constructor to add a new variable with a name that already exists in the dataset, the system does not automatically rename the new variable to ensure uniqueness. This leads to multiple variables having the same name, which can cause confusion and potential errors in data analysis.

**Example Code:**
```python
data = Table("iris")
widget.setData(data)
widget.addFeature(ContinuousDescriptor("iris", "0", 3))
widget.apply()
output = widget.get_output()
```

**Expected Behavior:**
The new variable should be automatically renamed to a unique name, such as "iris1", to avoid duplication of variable names.

**Actual Behavior:**
The new variable retains the original name "iris", resulting in duplicate variable names within the dataset.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/data/owfeatureconstructor.py
"""
Feature Constructor

A widget for defining (constructing) new features from values
of other variables.

"""
import re
import copy
import functools
import builtins
import math
import random
import logging
import ast
import types

from traceback import format_exception_only
from collections import namedtuple, OrderedDict
from itertools import chain, count
from typing import List, Dict, Any

import numpy as np

from AnyQt.QtWidgets import (
    QSizePolicy, QAbstractItemView, QComboBox, QFormLayout, QLineEdit,
    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,
    QPushButton, QMenu, QListView, QFrame, QLabel)
from AnyQt.QtGui import QKeySequence
from AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property
from orangewidget.utils.combobox import ComboBoxSearch

import Orange
from Orange.widgets import gui
from Orange.widgets.settings import ContextSetting, DomainContextHandler
from Orange.widgets.utils import itemmodels, vartype
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets import report
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.utils.state_summary import format_summary_details
from Orange.widgets.widget import OWWidget, Msg, Input, Output

FeatureDescriptor = \
    namedtuple("FeatureDescriptor", ["name", "expression"])

ContinuousDescriptor = \
    namedtuple("ContinuousDescriptor",
               ["name", "expression", "number_of_decimals"])
DateTimeDescriptor = \
    namedtuple("DateTimeDescriptor",
               ["name", "expression"])
DiscreteDescriptor = \
    namedtuple("DiscreteDescriptor",
               ["name", "expression", "values", "ordered"])

StringDescriptor = namedtuple("StringDescriptor", ["name", "expression"])


def make_variable(descriptor, compute_value):
    if isinstance(descriptor, ContinuousDescriptor):
        return Orange.data.ContinuousVariable(
            descriptor.name,
            descriptor.number_of_decimals,
            compute_value)
    if isinstance(descriptor, DateTimeDescriptor):
        return Orange.data.TimeVariable(
            descriptor.name,
            compute_value=compute_value, have_date=True, have_time=True)
    elif isinstance(descriptor, DiscreteDescriptor):
        return Orange.data.DiscreteVariable(
            descriptor.name,
            values=descriptor.values,
            ordered=descriptor.ordered,
            compute_value=compute_value)
    elif isinstance(descriptor, StringDescriptor):
        return Orange.data.StringVariable(
            descriptor.name,
            compute_value=compute_value)
    else:
        raise TypeError


def selected_row(view):
    """
    Return the index of selected row in a `view` (:class:`QListView`)

    The view's selection mode must be a QAbstractItemView.SingleSelction
    """
    if view.selectionMode() in [QAbstractItemView.MultiSelection,
                                QAbstractItemView.ExtendedSelection]:
        raise ValueError("invalid 'selectionMode'")

    sel_model = view.selectionModel()
    indexes = sel_model.selectedRows()
    if indexes:
        assert len(indexes) == 1
        return indexes[0].row()
    else:
        return None


class FeatureEditor(QFrame):
    FUNCTIONS = dict(chain([(key, val) for key, val in math.__dict__.items()
                            if not key.startswith("_")],
                           [(key, val) for key, val in builtins.__dict__.items()
                            if key in {"str", "float", "int", "len",
                                       "abs", "max", "min"}]))
    featureChanged = Signal()
    featureEdited = Signal()

    modifiedChanged = Signal(bool)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QFormLayout(
            fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow
        )
        layout.setContentsMargins(0, 0, 0, 0)
        self.nameedit = QLineEdit(
            placeholderText="Name...",
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.Fixed)
        )
        self.expressionedit = QLineEdit(
            placeholderText="Expression...",
            toolTip=self.ExpressionTooltip)

        self.attrs_model = itemmodels.VariableListModel(
            ["Select Feature"], parent=self)
        self.attributescb = ComboBoxSearch(
            minimumContentsLength=16,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        )
        self.attributescb.setModel(self.attrs_model)

        sorted_funcs = sorted(self.FUNCTIONS)
        self.funcs_model = itemmodels.PyListModelTooltip()
        self.funcs_model.setParent(self)

        self.funcs_model[:] = chain(["Select Function"], sorted_funcs)
        self.funcs_model.tooltips[:] = chain(
            [''],
            [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])

        self.functionscb = ComboBoxSearch(
            minimumContentsLength=16,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
        self.functionscb.setModel(self.funcs_model)

        hbox = QHBoxLayout()
        hbox.addWidget(self.attributescb)
        hbox.addWidget(self.functionscb)

        layout.addRow(self.nameedit, self.expressionedit)
        layout.addRow(self.tr(""), hbox)
        self.setLayout(layout)

        self.nameedit.editingFinished.connect(self._invalidate)
        self.expressionedit.textChanged.connect(self._invalidate)
        self.attributescb.currentIndexChanged.connect(self.on_attrs_changed)
        self.functionscb.currentIndexChanged.connect(self.on_funcs_changed)

        self._modified = False

    def setModified(self, modified):
        if not isinstance(modified, bool):
            raise TypeError

        if self._modified != modified:
            self._modified = modified
            self.modifiedChanged.emit(modified)

    def modified(self):
        return self._modified

    modified = Property(bool, modified, setModified,
                        notify=modifiedChanged)

    def setEditorData(self, data, domain):
        self.nameedit.setText(data.name)
        self.expressionedit.setText(data.expression)
        self.setModified(False)
        self.featureChanged.emit()
        self.attrs_model[:] = ["Select Feature"]
        if domain is not None and not domain.empty():
            self.attrs_model[:] += chain(domain.attributes,
                                         domain.class_vars,
                                         domain.metas)

    def editorData(self):
        return FeatureDescriptor(name=self.nameedit.text(),
                                 expression=self.nameedit.text())

    def _invalidate(self):
        self.setModified(True)
        self.featureEdited.emit()
        self.featureChanged.emit()

    def on_attrs_changed(self):
        index = self.attributescb.currentIndex()
        if index > 0:
            attr = sanitized_name(self.attrs_model[index].name)
            self.insert_into_expression(attr)
            self.attributescb.setCurrentIndex(0)

    def on_funcs_changed(self):
        index = self.functionscb.currentIndex()
        if index > 0:
            func = self.funcs_model[index]
            if func in ["atan2", "fmod", "ldexp", "log",
                        "pow", "copysign", "hypot"]:
                self.insert_into_expression(func + "(,)")
                self.expressionedit.cursorBackward(False, 2)
            elif func in ["e", "pi"]:
                self.insert_into_expression(func)
            else:
                self.insert_into_expression(func + "()")
                self.expressionedit.cursorBackward(False)
            self.functionscb.setCurrentIndex(0)

    def insert_into_expression(self, what):
        cp = self.expressionedit.cursorPosition()
        ct = self.expressionedit.text()
        text = ct[:cp] + what + ct[cp:]
        self.expressionedit.setText(text)
        self.expressionedit.setFocus()


class ContinuousFeatureEditor(FeatureEditor):
    ExpressionTooltip = "A numeric expression"

    def editorData(self):
        return ContinuousDescriptor(
            name=self.nameedit.text(),
            number_of_decimals=None,
            expression=self.expressionedit.text()
        )


class DateTimeFeatureEditor(FeatureEditor):
    ExpressionTooltip = \
        "Result must be a string in ISO-8601 format " \
        "(e.g. 2019-07-30T15:37:27 or a part thereof),\n" \
        "or a number of seconds since Jan 1, 1970."

    def editorData(self):
        return DateTimeDescriptor(
            name=self.nameedit.text(),
            expression=self.expressionedit.text()
        )


class DiscreteFeatureEditor(FeatureEditor):
    ExpressionTooltip = \
        "Result must be a string, if values are not explicitly given\n" \
        "or a zero-based integer indices into a list of values given below."

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        tooltip = \
            "If values are given, above expression must return zero-based " \
            "integer indices into that list."
        self.valuesedit = QLineEdit(placeholderText="A, B ...", toolTip=tooltip)
        self.valuesedit.textChanged.connect(self._invalidate)

        layout = self.layout()
        label = QLabel(self.tr("Values (optional)"))
        label.setToolTip(tooltip)
        layout.addRow(label, self.valuesedit)

    def setEditorData(self, data, domain):
        self.valuesedit.setText(
            ", ".join(v.replace(",", r"\,") for v in data.values))

        super().setEditorData(data, domain)

    def editorData(self):
        values = self.valuesedit.text()
        values = re.split(r"(?<!\\),", values)
        values = tuple(filter(None, [v.replace(r"\,", ",").strip() for v in values]))
        return DiscreteDescriptor(
            name=self.nameedit.text(),
            values=values,
            ordered=False,
            expression=self.expressionedit.text()
        )


class StringFeatureEditor(FeatureEditor):
    ExpressionTooltip = "A string expression"

    def editorData(self):
        return StringDescriptor(
            name=self.nameedit.text(),
            expression=self.expressionedit.text()
        )


_VarMap = {
    DiscreteDescriptor: vartype(Orange.data.DiscreteVariable("d")),
    ContinuousDescriptor: vartype(Orange.data.ContinuousVariable("c")),
    DateTimeDescriptor: vartype(Orange.data.TimeVariable("t")),
    StringDescriptor: vartype(Orange.data.StringVariable("s"))
}


@functools.lru_cache(20)
def variable_icon(dtype):
    vtype = _VarMap.get(dtype, dtype)
    return gui.attributeIconDict[vtype]


class FeatureItemDelegate(QStyledItemDelegate):
    @staticmethod
    def displayText(value, _):
        return value.name + " := " + value.expression


class DescriptorModel(itemmodels.PyListModel):
    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DecorationRole:
            value = self[index.row()]
            return variable_icon(type(value))
        else:
            return super().data(index, role)


class FeatureConstructorHandler(DomainContextHandler):
    """Context handler that filters descriptors"""

    def is_valid_item(self, setting, item, attrs, metas):
        """Check if descriptor `item` can be used with given domain.

        Return True if descriptor's expression contains only
        available variables and descriptors name does not clash with
        existing variables.
        """
        if item.name in attrs or item.name in metas:
            return False

        try:
            exp_ast = ast.parse(item.expression, mode="eval")
        # ast.parse can return arbitrary errors, not only SyntaxError
        # pylint: disable=broad-except
        except Exception:
            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


class OWFeatureConstructor(OWWidget):
    name = "Feature Constructor"
    description = "Construct new features (data columns) from a set of " \
                  "existing features in the input dataset."
    icon = "icons/FeatureConstructor.svg"
    keywords = []

    class Inputs:
        data = Input("Data", Orange.data.Table)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    want_main_area = False

    settingsHandler = FeatureConstructorHandler()
    descriptors = ContextSetting([])
    currentIndex = ContextSetting(-1)

    EDITORS = [
        (ContinuousDescriptor, ContinuousFeatureEditor),
        (DateTimeDescriptor, DateTimeFeatureEditor),
        (DiscreteDescriptor, DiscreteFeatureEditor),
        (StringDescriptor, StringFeatureEditor)
    ]

    class Error(OWWidget.Error):
        more_values_needed = Msg("Categorical feature {} needs more values.")
        invalid_expressions = Msg("Invalid expressions: {}.")

    def __init__(self):
        super().__init__()
        self.data = None
        self.editors = {}

        box = gui.vBox(self.controlArea, "Variable Definitions")

        toplayout = QHBoxLayout()
        toplayout.setContentsMargins(0, 0, 0, 0)
        box.layout().addLayout(toplayout)

        self.editorstack = QStackedWidget(
            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                   QSizePolicy.MinimumExpanding)
        )

        for descclass, editorclass in self.EDITORS:
            editor = editorclass()
            editor.featureChanged.connect(self._on_modified)
            self.editors[descclass] = editor
            self.editorstack.addWidget(editor)

        self.editorstack.setEnabled(False)

        buttonlayout = QVBoxLayout(spacing=10)
        buttonlayout.setContentsMargins(0, 0, 0, 0)

        self.addbutton = QPushButton(
            "New", toolTip="Create a new variable",
            minimumWidth=120,
            shortcut=QKeySequence.New
        )

        def unique_name(fmt, reserved):
            candidates = (fmt.format(i) for i in count(1))
            return next(c for c in candidates if c not in reserved)

        def reserved_names():
            varnames = []
            if self.data is not None:
                varnames = [var.name for var in
                            self.data.domain.variables + self.data.domain.metas]
            varnames += [desc.name for desc in self.featuremodel]
            return set(varnames)

        def generate_newname(fmt):
            return unique_name(fmt, reserved_names())

        menu = QMenu(self.addbutton)
        cont = menu.addAction("Numeric")
        cont.triggered.connect(
            lambda: self.addFeature(
                ContinuousDescriptor(generate_newname("X{}"), "", 3))
        )
        disc = menu.addAction("Categorical")
        disc.triggered.connect(
            lambda: self.addFeature(
                DiscreteDescriptor(generate_newname("D{}"), "", (), False))
        )
        string = menu.addAction("Text")
        string.triggered.connect(
            lambda: self.addFeature(
                StringDescriptor(generate_newname("S{}"), ""))
        )
        datetime = menu.addAction("Date/Time")
        datetime.triggered.connect(
            lambda: self.addFeature(
                DateTimeDescriptor(generate_newname("T{}"), ""))
        )

        menu.addSeparator()
        self.duplicateaction = menu.addAction("Duplicate Selected Variable")
        self.duplicateaction.triggered.connect(self.duplicateFeature)
        self.duplicateaction.setEnabled(False)
        self.addbutton.setMenu(menu)

        self.removebutton = QPushButton(
            "Remove", toolTip="Remove selected variable",
            minimumWidth=120,
            shortcut=QKeySequence.Delete
        )
        self.removebutton.clicked.connect(self.removeSelectedFeature)

        buttonlayout.addWidget(self.addbutton)
        buttonlayout.addWidget(self.removebutton)
        buttonlayout.addStretch(10)

        toplayout.addLayout(buttonlayout, 0)
        toplayout.addWidget(self.editorstack, 10)

        # Layout for the list view
        layout = QVBoxLayout(spacing=1, margin=0)
        self.featuremodel = DescriptorModel(parent=self)

        self.featureview = QListView(
            minimumWidth=200, minimumHeight=50,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.MinimumExpanding)
        )

        self.featureview.setItemDelegate(FeatureItemDelegate(self))
        self.featureview.setModel(self.featuremodel)
        self.featureview.selectionModel().selectionChanged.connect(
            self._on_selectedVariableChanged
        )

        self.info.set_input_summary(self.info.NoInput)
        self.info.set_output_summary(self.info.NoOutput)

        layout.addWidget(self.featureview)

        box.layout().addLayout(layout, 1)

        box = gui.hBox(self.controlArea)
        gui.rubber(box)
        commit = gui.button(box, self, "Send", callback=self.apply,
                            default=True)
        commit.setMinimumWidth(180)

    def setCurrentIndex(self, index):
        index = min(index, len(self.featuremodel) - 1)
        self.currentIndex = index
        if index >= 0:
            itemmodels.select_row(self.featureview, index)
            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]
            editor = self.editors[type(desc)]
            self.editorstack.setCurrentWidget(editor)
            editor.setEditorData(desc, self.data.domain if self.data else None)
        self.editorstack.setEnabled(index >= 0)
        self.duplicateaction.setEnabled(index >= 0)
        self.removebutton.setEnabled(index >= 0)

    def _on_selectedVariableChanged(self, selected, *_):
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        else:
            self.setCurrentIndex(-1)

    def _on_modified(self):
        if self.currentIndex >= 0:
            editor = self.editorstack.currentWidget()
            self.featuremodel[self.currentIndex] = editor.editorData()
            self.descriptors = list(self.featuremodel)

    def setDescriptors(self, descriptors):
        """
        Set a list of variable descriptors to edit.
        """
        self.descriptors = descriptors
        self.featuremodel[:] = list(self.descriptors)

    @Inputs.data
    @check_sql_input
    def setData(self, data=None):
        """Set the input dataset."""
        self.closeContext()

        self.data = data

        self.info.set_input_summary(self.info.NoInput)
        if self.data is not None:
            descriptors = list(self.descriptors)
            currindex = self.currentIndex
            self.descriptors = []
            self.currentIndex = -1
            self.openContext(data)
            self.info.set_input_summary(len(data), format_summary_details(data))

            if descriptors != self.descriptors or \
                    self.currentIndex != currindex:
                # disconnect from the selection model while reseting the model
                selmodel = self.featureview.selectionModel()
                selmodel.selectionChanged.disconnect(
                    self._on_selectedVariableChanged)

                self.featuremodel[:] = list(self.descriptors)
                self.setCurrentIndex(self.currentIndex)

                selmodel.selectionChanged.connect(
                    self._on_selectedVariableChanged)

        self.editorstack.setEnabled(self.currentIndex >= 0)

    def handleNewSignals(self):
        if self.data is not None:
            self.apply()
        else:
            self.info.set_output_summary(self.info.NoOutput)
            self.Outputs.data.send(None)

    def addFeature(self, descriptor):
        self.featuremodel.append(descriptor)
        self.setCurrentIndex(len(self.featuremodel) - 1)
        editor = self.editorstack.currentWidget()
        editor.nameedit.setFocus()
        editor.nameedit.selectAll()

    def removeFeature(self, index):
        del self.featuremodel[index]
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        elif index is None and self.featuremodel.rowCount():
            # Deleting the last item clears selection
            self.setCurrentIndex(self.featuremodel.rowCount() - 1)

    def removeSelectedFeature(self):
        if self.currentIndex >= 0:
            self.removeFeature(self.currentIndex)

    def duplicateFeature(self):
        desc = self.featuremodel[self.currentIndex]
        self.addFeature(copy.deepcopy(desc))

    @staticmethod
    def check_attrs_values(attr, data):
        for i in range(len(data)):
            for var in attr:
                if not math.isnan(data[i, var]) \
                        and int(data[i, var]) >= len(var.values):
                    return var.name
        return None

    def _validate_descriptors(self, desc):

        def validate(source):
            try:
                return validate_exp(ast.parse(source, mode="eval"))
            # ast.parse can return arbitrary errors, not only SyntaxError
            # pylint: disable=broad-except
            except Exception:
                return False

        final = []
        invalid = []
        for d in desc:
            if validate(d.expression):
                final.append(d)
            else:
                final.append(d._replace(expression=""))
                invalid.append(d)

        if invalid:
            self.Error.invalid_expressions(", ".join(s.name for s in invalid))

        return final

    def apply(self):
        def report_error(err):
            log = logging.getLogger(__name__)
            log.error("", exc_info=True)
            self.error("".join(format_exception_only(type(err), err)).rstrip())

        self.Error.clear()

        if self.data is None:
            return

        desc = list(self.featuremodel)
        desc = self._validate_descriptors(desc)
        try:
            new_variables = construct_variables(desc, self.data)
        # user's expression can contain arbitrary errors
        except Exception as err:  # pylint: disable=broad-except
            report_error(err)
            return

        attrs = [var for var in new_variables if var.is_primitive()]
        metas = [var for var in new_variables if not var.is_primitive()]
        new_domain = Orange.data.Domain(
            self.data.domain.attributes + tuple(attrs),
            self.data.domain.class_vars,
            metas=self.data.domain.metas + tuple(metas)
        )

        try:
            for variable in new_variables:
                variable.compute_value.mask_exceptions = False
            data = self.data.transform(new_domain)
        # user's expression can contain arbitrary errors
        # pylint: disable=broad-except
        except Exception as err:
            report_error(err)
            return
        finally:
            for variable in new_variables:
                variable.compute_value.mask_exceptions = True

        disc_attrs_not_ok = self.check_attrs_values(
            [var for var in attrs if var.is_discrete], data)
        if disc_attrs_not_ok:
            self.Error.more_values_needed(disc_attrs_not_ok)
            return

        self.info.set_output_summary(len(data), format_summary_details(data))
        self.Outputs.data.send(data)

    def send_report(self):
        items = OrderedDict()
        for feature in self.featuremodel:
            if isinstance(feature, DiscreteDescriptor):
                items[feature.name] = "{} (categorical with values {}{})".format(
                    feature.expression, feature.values,
                    "; ordered" * feature.ordered)
            elif isinstance(feature, ContinuousDescriptor):
                items[feature.name] = "{} (numeric)".format(feature.expression)
            elif isinstance(feature, DateTimeDescriptor):
                items[feature.name] = "{} (date/time)".format(feature.expression)
            else:
                items[feature.name] = "{} (text)".format(feature.expression)
        self.report_items(
            report.plural("Constructed feature{s}", len(items)), items)


def freevars(exp, env):
    """
    Return names of all free variables in a parsed (expression) AST.

    Parameters
    ----------
    exp : ast.AST
        An expression ast (ast.parse(..., mode="single"))
    env : List[str]
        Environment

    Returns
    -------
    freevars : List[str]

    See also
    --------
    ast

    """
    # pylint: disable=too-many-return-statements,too-many-branches
    etype = type(exp)
    if etype in [ast.Expr, ast.Expression]:
        return freevars(exp.body, env)
    elif etype == ast.BoolOp:
        return sum((freevars(v, env) for v in exp.values), [])
    elif etype == ast.BinOp:
        return freevars(exp.left, env) + freevars(exp.right, env)
    elif etype == ast.UnaryOp:
        return freevars(exp.operand, env)
    elif etype == ast.Lambda:
        args = exp.args
        assert isinstance(args, ast.arguments)
        argnames = [a.arg for a in args.args]
        argnames += [args.vararg.arg] if args.vararg else []
        argnames += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []
        argnames += [args.kwarg] if args.kwarg else []
        return freevars(exp.body, env + argnames)
    elif etype == ast.IfExp:
        return (freevars(exp.test, env) + freevars(exp.body, env) +
                freevars(exp.orelse, env))
    elif etype == ast.Dict:
        return sum((freevars(v, env)
                    for v in chain(exp.keys, exp.values)), [])
    elif etype == ast.Set:
        return sum((freevars(v, env) for v in exp.elts), [])
    elif etype in [ast.SetComp, ast.ListComp, ast.GeneratorExp, ast.DictComp]:
        env_ext = []
        vars_ = []
        for gen in exp.generators:
            target_names = freevars(gen.target, [])  # assigned names
            vars_iter = freevars(gen.iter, env)
            env_ext += target_names
            vars_ifs = list(chain(*(freevars(ifexp, env + target_names)
                                    for ifexp in gen.ifs or [])))
            vars_ += vars_iter + vars_ifs

        if etype == ast.DictComp:
            vars_ = (freevars(exp.key, env_ext) +
                     freevars(exp.value, env_ext) +
                     vars_)
        else:
            vars_ = freevars(exp.elt, env + env_ext) + vars_
        return vars_
    # Yield, YieldFrom???
    elif etype == ast.Compare:
        return sum((freevars(v, env)
                    for v in [exp.left] + exp.comparators), [])
    elif etype == ast.Call:
        return sum(map(lambda e: freevars(e, env),
                       chain([exp.func],
                             exp.args or [],
                             [k.value for k in exp.keywords or []])),
                   [])
    elif etype == ast.Starred:
        # a 'starred' call parameter (e.g. a and b in `f(x, *a, *b)`
        return freevars(exp.value, env)
    elif etype in [ast.Num, ast.Str, ast.Ellipsis, ast.Bytes, ast.NameConstant]:
        return []
    elif etype == ast.Constant:
        return []
    elif etype == ast.Attribute:
        return freevars(exp.value, env)
    elif etype == ast.Subscript:
        return freevars(exp.value, env) + freevars(exp.slice, env)
    elif etype == ast.Name:
        return [exp.id] if exp.id not in env else []
    elif etype == ast.List:
        return sum((freevars(e, env) for e in exp.elts), [])
    elif etype == ast.Tuple:
        return sum((freevars(e, env) for e in exp.elts), [])
    elif etype == ast.Slice:
        return sum((freevars(e, env)
                    for e in filter(None, [exp.lower, exp.upper, exp.step])),
                   [])
    elif etype == ast.ExtSlice:
        return sum((freevars(e, env) for e in exp.dims), [])
    elif etype == ast.Index:
        return freevars(exp.value, env)
    elif etype == ast.keyword:
        return freevars(exp.value, env)
    else:
        raise ValueError(exp)


def validate_exp(exp):
    """
    Validate an `ast.AST` expression.

    Only expressions with no list,set,dict,generator comprehensions
    are accepted.

    Parameters
    ----------
    exp : ast.AST
        A parsed abstract syntax tree

    """
    # pylint: disable=too-many-branches
    if not isinstance(exp, ast.AST):
        raise TypeError("exp is not a 'ast.AST' instance")

    etype = type(exp)
    if etype in [ast.Expr, ast.Expression]:
        return validate_exp(exp.body)
    elif etype == ast.BoolOp:
        return all(map(validate_exp, exp.values))
    elif etype == ast.BinOp:
        return all(map(validate_exp, [exp.left, exp.right]))
    elif etype == ast.UnaryOp:
        return validate_exp(exp.operand)
    elif etype == ast.IfExp:
        return all(map(validate_exp, [exp.test, exp.body, exp.orelse]))
    elif etype == ast.Dict:
        return all(map(validate_exp, chain(exp.keys, exp.values)))
    elif etype == ast.Set:
        return all(map(validate_exp, exp.elts))
    elif etype == ast.Compare:
        return all(map(validate_exp, [exp.left] + exp.comparators))
    elif etype == ast.Call:
        subexp = chain([exp.func], exp.args or [],
                       [k.value for k in exp.keywords or []])
        return all(map(validate_exp, subexp))
    elif etype == ast.Starred:
        assert isinstance(exp.ctx, ast.Load)
        return validate_exp(exp.value)
    elif etype in [ast.Num, ast.Str, ast.Bytes, ast.Ellipsis, ast.NameConstant]:
        return True
    elif etype == ast.Constant:
        return True
    elif etype == ast.Attribute:
        return True
    elif etype == ast.Subscript:
        return all(map(validate_exp, [exp.value, exp.slice]))
    elif etype in {ast.List, ast.Tuple}:
        assert isinstance(exp.ctx, ast.Load)
        return all(map(validate_exp, exp.elts))
    elif etype == ast.Name:
        return True
    elif etype == ast.Slice:
        return all(map(validate_exp,
                       filter(None, [exp.lower, exp.upper, exp.step])))
    elif etype == ast.ExtSlice:
        return all(map(validate_exp, exp.dims))
    elif etype == ast.Index:
        return validate_exp(exp.value)
    elif etype == ast.keyword:
        return validate_exp(exp.value)
    else:
        raise ValueError(exp)


def construct_variables(descriptions, data):
    # subs
    variables = []
    source_vars = data.domain.variables + data.domain.metas
    for desc in descriptions:
        desc, func = bind_variable(desc, source_vars, data)
        var = make_variable(desc, func)
        variables.append(var)
    return variables


def sanitized_name(name):
    sanitized = re.sub(r"\W", "_", name)
    if sanitized[0].isdigit():
        sanitized = "_" + sanitized
    return sanitized


def bind_variable(descriptor, env, data):
    """
    (descriptor, env) ->
        (descriptor, (instance -> value) | (table -> value list))
    """
    if not descriptor.expression.strip():
        return descriptor, FeatureFunc("nan", [], {"nan": float("nan")})

    exp_ast = ast.parse(descriptor.expression, mode="eval")
    freev = unique(freevars(exp_ast, []))
    variables = {sanitized_name(v.name): v for v in env}
    source_vars = [(name, variables[name]) for name in freev
                   if name in variables]

    values = {}
    cast = None
    nan = float("nan")

    if isinstance(descriptor, DiscreteDescriptor):
        if not descriptor.values:
            str_func = FeatureFunc(descriptor.expression, source_vars)
            values = sorted({str(x) for x in str_func(data)})
            values = {name: i for i, name in enumerate(values)}
            descriptor = descriptor._replace(values=values)

            def cast(x):  # pylint: disable=function-redefined
                return values.get(x, nan)

        else:
            values = [sanitized_name(v) for v in descriptor.values]
            values = {name: i for i, name in enumerate(values)}

    if isinstance(descriptor, DateTimeDescriptor):
        parse = Orange.data.TimeVariable("_").parse

        def cast(e):  # pylint: disable=function-redefined
            if isinstance(e, (int, float)):
                return e
            if e == "" or e is None:
                return np.nan
            return parse(e)

    func = FeatureFunc(descriptor.expression, source_vars, values, cast)
    return descriptor, func


def make_lambda(expression, args, env=None):
    # type: (ast.Expression, List[str], Dict[str, Any]) -> types.FunctionType
    """
    Create an lambda function from a expression AST.

    Parameters
    ----------
    expression : ast.Expression
        The body of the lambda.
    args : List[str]
        A list of positional argument names
    env : Optional[Dict[str, Any]]
        Extra environment to capture in the lambda's closure.

    Returns
    -------
    func : types.FunctionType
    """
    # lambda *{args}* : EXPRESSION
    lambda_ = ast.Lambda(
        args=ast.arguments(
            posonlyargs=[],
            args=[ast.arg(arg=arg, annotation=None) for arg in args],
            varargs=None,
            varargannotation=None,
            kwonlyargs=[],
            kwarg=None,
            kwargannotation=None,
            defaults=[],
            kw_defaults=[]),
        body=expression.body,
    )
    lambda_ = ast.copy_location(lambda_, expression.body)
    # lambda **{env}** : lambda *{args}*: EXPRESSION
    outer = ast.Lambda(
        args=ast.arguments(
            posonlyargs=[],
            args=[ast.arg(arg=name, annotation=None) for name in (env or {})],
            varargs=None,
            varargannotation=None,
            kwonlyargs=[],
            kwarg=None,
            kwargannotation=None,
            defaults=[],
            kw_defaults=[],
        ),
        body=lambda_,
    )
    exp = ast.Expression(body=outer, lineno=1, col_offset=0)
    ast.fix_missing_locations(exp)
    GLOBALS = __GLOBALS.copy()
    GLOBALS["__builtins__"] = {}
    # pylint: disable=eval-used
    fouter = eval(compile(exp, "<lambda>", "eval"), GLOBALS)
    assert isinstance(fouter, types.FunctionType)
    finner = fouter(**env)
    assert isinstance(finner, types.FunctionType)
    return finner


__ALLOWED = [
    "Ellipsis", "False", "None", "True", "abs", "all", "any", "acsii",
    "bin", "bool", "bytearray", "bytes", "chr", "complex", "dict",
    "divmod", "enumerate", "filter", "float", "format", "frozenset",
    "getattr", "hasattr", "hash", "hex", "id", "int", "iter", "len",
    "list", "map", "memoryview", "next", "object",
    "oct", "ord", "pow", "range", "repr", "reversed", "round",
    "set", "slice", "sorted", "str", "tuple", "type",
    "zip"
]

__GLOBALS = {name: getattr(builtins, name) for name in __ALLOWED
             if hasattr(builtins, name)}

__GLOBALS.update({name: getattr(math, name) for name in dir(math)
                  if not name.startswith("_")})

__GLOBALS.update({
    "normalvariate": random.normalvariate,
    "gauss": random.gauss,
    "expovariate": random.expovariate,
    "gammavariate": random.gammavariate,
    "betavariate": random.betavariate,
    "lognormvariate": random.lognormvariate,
    "paretovariate": random.paretovariate,
    "vonmisesvariate": random.vonmisesvariate,
    "weibullvariate": random.weibullvariate,
    "triangular": random.triangular,
    "uniform": random.uniform,
    "nanmean": lambda *args: np.nanmean(args),
    "nanmin": lambda *args: np.nanmin(args),
    "nanmax": lambda *args: np.nanmax(args),
    "nansum": lambda *args: np.nansum(args),
    "nanstd": lambda *args: np.nanstd(args),
    "nanmedian": lambda *args: np.nanmedian(args),
    "nancumsum": lambda *args: np.nancumsum(args),
    "nancumprod": lambda *args: np.nancumprod(args),
    "nanargmax": lambda *args: np.nanargmax(args),
    "nanargmin": lambda *args: np.nanargmin(args),
    "nanvar": lambda *args: np.nanvar(args),
    "mean": lambda *args: np.mean(args),
    "min": lambda *args: np.min(args),
    "max": lambda *args: np.max(args),
    "sum": lambda *args: np.sum(args),
    "std": lambda *args: np.std(args),
    "median": lambda *args: np.median(args),
    "cumsum": lambda *args: np.cumsum(args),
    "cumprod": lambda *args: np.cumprod(args),
    "argmax": lambda *args: np.argmax(args),
    "argmin": lambda *args: np.argmin(args),
    "var": lambda *args: np.var(args)})


class FeatureFunc:
    """
    Parameters
    ----------
    expression : str
        An expression string
    args : List[Tuple[str, Orange.data.Variable]]
        A list of (`name`, `variable`) tuples where `name` is the name of
        a variable as used in `expression`, and `variable` is the variable
        instance used to extract the corresponding column/value from a
        Table/Instance.
    extra_env : Optional[Dict[str, Any]]
        Extra environment specifying constant values to be made available
        in expression. It must not shadow names in `args`
    cast: Optional[Callable]
        A function for casting the expressions result to the appropriate
        type (e.g. string representation of date/time variables to floats)
    """
    def __init__(self, expression, args, extra_env=None, cast=None):
        self.expression = expression
        self.args = args
        self.extra_env = dict(extra_env or {})
        self.func = make_lambda(ast.parse(expression, mode="eval"),
                                [name for name, _ in args], self.extra_env)
        self.cast = cast
        self.mask_exceptions = True

    def __call__(self, instance, *_):
        if isinstance(instance, Orange.data.Table):
            return [self(inst) for inst in instance]
        else:
            try:
                args = [str(instance[var])
                        if instance.domain[var].is_string else instance[var]
                        for _, var in self.args]
                y = self.func(*args)
            # user's expression can contain arbitrary errors
            # this also covers missing attributes
            except:  # pylint: disable=bare-except
                if not self.mask_exceptions:
                    raise
                return np.nan
            if self.cast:
                y = self.cast(y)
            return y

    def __reduce__(self):
        return type(self), (self.expression, self.args,
                            self.extra_env, self.cast)

    def __repr__(self):
        return "{0.__name__}{1!r}".format(*self.__reduce__())


def unique(seq):
    seen = set()
    unique_el = []
    for el in seq:
        if el not in seen:
            unique_el.append(el)
            seen.add(el)
    return unique_el


if __name__ == "__main__":  # pragma: no cover
    WidgetPreview(OWFeatureConstructor).run(Orange.data.Table("iris"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user