{
    "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\nimport unicodedata\n\nfrom concurrent.futures import CancelledError\nfrom dataclasses import dataclass\nfrom traceback import format_exception_only\nfrom collections import namedtuple, OrderedDict\nfrom itertools import chain, count, starmap\nfrom typing import List, Dict, Any, Mapping, Optional\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QSizePolicy, QAbstractItemView, QComboBox, QLineEdit,\n    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,\n    QPushButton, QMenu, QListView, QFrame, QLabel, QMessageBox,\n    QGridLayout, QWidget, QCheckBox)\nfrom AnyQt.QtGui import QKeySequence\nfrom AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property\n\nfrom orangewidget.utils.combobox import ComboBoxSearch\n\nimport Orange\nfrom Orange.preprocess.transformation import MappingTransform\nfrom Orange.util import frompyfunc\nfrom Orange.data import Variable, Table, Value, Instance\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 (\n    itemmodels, vartype, ftry, unique_everseen as unique\n)\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets import report\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\nfrom Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState\n\n\nFeatureDescriptor = \\\n    namedtuple(\"FeatureDescriptor\", [\"name\", \"expression\", \"meta\"],\n               defaults=[False])\n\nContinuousDescriptor = \\\n    namedtuple(\"ContinuousDescriptor\",\n               [\"name\", \"expression\", \"number_of_decimals\", \"meta\"],\n               defaults=[False])\nDateTimeDescriptor = \\\n    namedtuple(\"DateTimeDescriptor\",\n               [\"name\", \"expression\", \"meta\"],\n               defaults=[False])\nDiscreteDescriptor = \\\n    namedtuple(\"DiscreteDescriptor\",\n               [\"name\", \"expression\", \"values\", \"ordered\", \"meta\"],\n               defaults=[False])\n\nStringDescriptor = \\\n    namedtuple(\"StringDescriptor\",\n               [\"name\", \"expression\", \"meta\"],\n               defaults=[True])\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            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    ExpressionTooltip = \"\"\"\nUse variable names as values in expression.\nCategorical features are passed as strings\n(note the change in behaviour from Orange 3.30).\n\n\"\"\".lstrip()\n\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 = QGridLayout()\n        layout.setContentsMargins(0, 0, 0, 0)\n        self.nameedit = QLineEdit(\n            placeholderText=\"Name...\",\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.Fixed)\n        )\n\n        self.metaattributecb = QCheckBox(\"Meta attribute\")\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            chain([\"Select Function\"], sorted_funcs),\n            chain([''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])\n        )\n        self.funcs_model.setParent(self)\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        layout.addWidget(self.nameedit, 0, 0)\n        layout.addWidget(self.metaattributecb, 1, 0)\n        layout.addWidget(self.expressionedit, 0, 1, 1, 2)\n        layout.addWidget(self.attributescb, 1, 1)\n        layout.addWidget(self.functionscb, 1, 2)\n        layout.addWidget(QWidget(), 2, 0)\n\n        self.setLayout(layout)\n\n        self.nameedit.editingFinished.connect(self._invalidate)\n        self.metaattributecb.clicked.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.metaattributecb.setChecked(data.meta)\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                                 meta=self.metaattributecb.isChecked())\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\" \\\n                        + FeatureEditor.ExpressionTooltip\n\n    def editorData(self):\n        return ContinuousDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text(),\n            meta=self.metaattributecb.isChecked(),\n            number_of_decimals=None,\n        )\n\n\nclass DateTimeFeatureEditor(FeatureEditor):\n    ExpressionTooltip = FeatureEditor.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            meta=self.metaattributecb.isChecked(),\n        )\n\n\nclass DiscreteFeatureEditor(FeatureEditor):\n    ExpressionTooltip = FeatureEditor.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.addWidget(label, 2, 0)\n        layout.addWidget(self.valuesedit, 2, 1, 1, 2)\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            meta=self.metaattributecb.isChecked(),\n            values=values,\n            ordered=False,\n            expression=self.expressionedit.text()\n        )\n\n\nclass StringFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A string expression\\n\\n\" \\\n                        + FeatureEditor.ExpressionTooltip\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.metaattributecb.setChecked(True)\n        self.metaattributecb.setDisabled(True)\n\n    def editorData(self):\n        return StringDescriptor(\n            name=self.nameedit.text(),\n            meta=True,\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\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\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, ConcurrentWidgetMixin):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    category = \"Transform\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = ['function', 'lambda']\n    priority = 2240\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    expressions_with_values = ContextSetting(False)\n    settings_version = 3\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        transform_error = Msg(\"{}\")\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        ConcurrentWidgetMixin.__init__(self)\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, meta=False))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False, meta=False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\", meta=True))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\", meta=False))\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)\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        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        self.fix_button = gui.button(\n            self.buttonsArea, self, \"Upgrade Expressions\",\n            callback=self.fix_expressions)\n        self.fix_button.setHidden(True)\n        gui.button(self.buttonsArea, self, \"Send\", callback=self.apply, default=True)\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            uniq = get_unique_names(self.reserved_names(self.currentIndex),\n                                    proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != uniq:\n                self.Warning.renamed_var()\n                feature = feature.__class__(uniq, *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        self.expressions_with_values = False\n\n        self.descriptors = []\n        self.currentIndex = -1\n        if self.data is not None:\n            self.openContext(data)\n\n        # disconnect from the selection model while reseting the model\n        selmodel = self.featureview.selectionModel()\n        selmodel.selectionChanged.disconnect(self._on_selectedVariableChanged)\n\n        self.featuremodel[:] = list(self.descriptors)\n        self.setCurrentIndex(self.currentIndex)\n\n        selmodel.selectionChanged.connect(self._on_selectedVariableChanged)\n        self.fix_button.setHidden(not self.expressions_with_values)\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.cancel()\n            self.Outputs.data.send(None)\n            self.fix_button.setHidden(True)\n\n    def onDeleteWidget(self):\n        self.shutdown()\n        super().onDeleteWidget()\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 var in attr:\n            col = data.get_column(var)\n            mask = ~np.isnan(col)\n            grater_or_equal = np.greater_equal(\n                col, len(var.values), out=mask, where=mask\n            )\n            if grater_or_equal.any():\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        self.cancel()\n        self.Error.clear()\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        self.start(run, self.data, desc, self.expressions_with_values)\n\n    def on_done(self, result: \"Result\") -> None:\n        data, attrs = result.data, result.attributes\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.Outputs.data.send(data)\n\n    def on_exception(self, ex: Exception):\n        log = logging.getLogger(__name__)\n        log.error(\"\", exc_info=ex)\n        self.Error.transform_error(\n            \"\".join(format_exception_only(type(ex), ex)).rstrip(),\n            exc_info=ex\n        )\n\n    def on_partial_result(self, _):\n        pass\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                desc = \"categorical\"\n                if feature.values:\n                    desc += \" with values \" \\\n                            + \", \".join(f\"'{val}'\" for val in feature.values)\n                if feature.ordered:\n                    desc += \"; ordered\"\n            elif isinstance(feature, ContinuousDescriptor):\n                desc = \"numeric\"\n            elif isinstance(feature, DateTimeDescriptor):\n                desc = \"date/time\"\n            else:\n                desc = \"text\"\n            items[feature.name] = f\"{feature.expression} ({desc})\"\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)\n\n    def fix_expressions(self):\n        dlg = QMessageBox(\n            QMessageBox.Question,\n            \"Fix Expressions\",\n            \"This widget's behaviour has changed. Values of categorical \"\n            \"variables are now inserted as their textual representations \"\n            \"(strings); previously they appeared as integer numbers, with an \"\n            \"attribute '.value' that contained the text.\\n\\n\"\n            \"The widget currently runs in compatibility mode. After \"\n            \"expressions are updated, manually check for their correctness.\")\n        dlg.addButton(\"Update\", QMessageBox.ApplyRole)\n        dlg.addButton(\"Cancel\", QMessageBox.RejectRole)\n        if dlg.exec() == QMessageBox.RejectRole:\n            return\n\n        def fixer(mo):\n            var = domain[mo.group(2)]\n            if mo.group(3) == \".value\":  # uses string; remove `.value`\n                return \"\".join(mo.group(1, 2, 4))\n            # Uses ints: get them by indexing\n            return mo.group(1) + \"{\" + \\\n                   \", \".join(f\"'{val}': {i}\"\n                             for i, val in enumerate(var.values)) + \\\n                   f\"}}[{var.name}]\" + mo.group(4)\n\n        domain = self.data.domain\n        disc_vars = \"|\".join(f\"{var.name}\"\n                             for var in chain(domain.variables, domain.metas)\n                             if var.is_discrete)\n        expr = re.compile(r\"(^|\\W)(\" + disc_vars + r\")(\\.value)?(\\W|$)\")\n        self.descriptors[:] = [\n            descriptor._replace(\n                expression=expr.sub(fixer, descriptor.expression))\n            for descriptor in self.descriptors]\n\n        self.expressions_with_values = False\n        self.fix_button.hide()\n        index = self.currentIndex\n        self.featuremodel[:] = list(self.descriptors)\n        self.setCurrentIndex(index)\n        self.apply()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version is None or version < 2:\n            used_vars = set(chain(*(\n                freevars(ast.parse(descriptor.expression, mode=\"eval\"), [])\n                for descriptor in context.values[\"descriptors\"]\n                if descriptor.expression)))\n            disc_vars = {name\n                         for (name, vtype) in chain(context.attributes.items(),\n                                                    context.metas.items())\n                         if vtype == 1}\n            if used_vars & disc_vars:\n                context.values[\"expressions_with_values\"] = True\n\n\n@dataclass\nclass Result:\n    data: Table\n    attributes: List[Variable]\n    metas: List[Variable]\n\n\ndef run(data: Table, desc, use_values, task: TaskState) -> Result:\n    if task.is_interruption_requested():\n        raise CancelledError  # pragma: no cover\n    new_variables, new_metas = construct_variables(desc, data, use_values)\n    # Explicit cancellation point after `construct_variables` which can\n    # already run `compute_value`.\n    if task.is_interruption_requested():\n        raise CancelledError  # pragma: no cover\n    new_domain = Orange.data.Domain(\n        data.domain.attributes + new_variables,\n        data.domain.class_vars,\n        metas=data.domain.metas + new_metas\n    )\n    try:\n        for variable in new_variables:\n            variable.compute_value.mask_exceptions = False\n        data = data.transform(new_domain)\n    finally:\n        for variable in new_variables:\n            variable.compute_value.mask_exceptions = True\n    return Result(data, new_variables, new_metas)\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, use_values=False):\n    # subs\n    variables = []\n    metas = []\n    source_vars = data.domain.variables + data.domain.metas\n    for desc in descriptions:\n        desc, func = bind_variable(desc, source_vars, data, use_values)\n        var = make_variable(desc, func)\n        [variables, metas][desc.meta].append(var)\n    return tuple(variables), tuple(metas)\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, use_values):\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 = {unicodedata.normalize(\"NFKC\", sanitized_name(v.name)): v\n                 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    dtype = object if isinstance(descriptor, StringDescriptor) else float\n\n    if isinstance(descriptor, DiscreteDescriptor):\n        if not descriptor.values:\n            str_func = FeatureFunc(descriptor.expression, source_vars,\n                                   use_values=use_values)\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            cast = MappingTransformCast(values)\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        cast = DateTimeCast()\n\n    func = FeatureFunc(descriptor.expression, source_vars, values, cast,\n                       use_values=use_values, dtype=dtype)\n    return descriptor, func\n\n\n_parse_datetime = Orange.data.TimeVariable(\"_\").parse\n_cast_datetime_num_types = (int, float)\n\n\ndef cast_datetime(e):\n    if isinstance(e, _cast_datetime_num_types):\n        return e\n    if e == \"\" or e is None:\n        return np.nan\n    return _parse_datetime(e)\n\n\n_cast_datetime = frompyfunc(cast_datetime, 1, 1, dtype=float)\n\n\nclass DateTimeCast:\n    def __call__(self, values):\n        return _cast_datetime(values)\n\n    def __eq__(self, other):\n        return isinstance(other, DateTimeCast)\n\n    def __hash__(self):\n        return hash(cast_datetime)\n\n\nclass MappingTransformCast:\n    def __init__(self, mapping: Mapping):\n        self.t = MappingTransform(None, mapping)\n\n    def __reduce_ex__(self, protocol):\n        return type(self), (self.t.mapping, )\n\n    def __call__(self, values):\n        return self.t.transform(values)\n\n    def __eq__(self, other):\n        return isinstance(other, MappingTransformCast) and self.t == other.t\n\n    def __hash__(self):\n        return hash(self.t)\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    dtype: Optional['DType'] = None\n\n    def __init__(self, expression, args, extra_env=None, cast=None, use_values=False,\n                 dtype=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        self.use_values = use_values\n        self.dtype = dtype\n\n    def __call__(self, table, *_):\n        if isinstance(table, Table):\n            return self.__call_table(table)\n        else:\n            return self.__call_instance(table)\n\n    def __call_table(self, table):\n        try:\n            cols = [self.extract_column(table, var) for _, var in self.args]\n        except ValueError:\n            if self.mask_exceptions:\n                return np.full(len(table), np.nan)\n            else:\n                raise\n\n        if not cols:\n            args = [()] * len(table)\n        else:\n            args = zip(*cols)\n        f = self.func\n        if self.mask_exceptions:\n            y = list(starmap(ftry(f, Exception, np.nan), args))\n        else:\n            y = list(starmap(f, args))\n        if self.cast is not None:\n            y = self.cast(y)\n        return np.asarray(y, dtype=self.dtype)\n\n    def __call_instance(self, instance: Instance):\n        table = Table.from_numpy(\n            instance.domain,\n            np.array([instance.x]),\n            np.array([instance.y]),\n            np.array([instance.metas]),\n        )\n        return self.__call_table(table)[0]\n\n    def extract_column(self, table: Table, var: Variable):\n        data = table.get_column(var)\n        if var.is_string:\n            return data\n        elif var.is_discrete and not self.use_values:\n            values = np.array([*var.values, None], dtype=object)\n            idx = data.astype(int)\n            idx[~np.isfinite(data)] = len(values) - 1\n            return values[idx].tolist()\n        elif var.is_time:  # time always needs Values due to str(val) formatting\n            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access\n        elif not self.use_values:\n            return data.tolist()\n        else:\n            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access\n\n    def __reduce__(self):\n        return type(self), (self.expression, self.args,\n                            self.extra_env, self.cast, self.use_values,\n                            self.dtype)\n\n    def __repr__(self):\n        return \"{0.__name__}{1!r}\".format(*self.__reduce__())\n\n    def __hash__(self):\n        return hash((self.expression, tuple(self.args),\n                     tuple(sorted(self.extra_env.items())), self.cast))\n\n    def __eq__(self, other):\n        return type(self) is type(other) \\\n            and self.expression == other.expression and self.args == other.args \\\n            and self.extra_env == other.extra_env and self.cast == other.cast\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\nimport unicodedata\n\nfrom concurrent.futures import CancelledError\nfrom dataclasses import dataclass\nfrom traceback import format_exception_only\nfrom collections import namedtuple, OrderedDict\nfrom itertools import chain, count, starmap\nfrom typing import List, Dict, Any, Mapping, Optional\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QSizePolicy, QAbstractItemView, QComboBox, QLineEdit,\n    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,\n    QPushButton, QMenu, QListView, QFrame, QLabel, QMessageBox,\n    QGridLayout, QWidget, QCheckBox)\nfrom AnyQt.QtGui import QKeySequence\nfrom AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property\n\nfrom orangewidget.utils.combobox import ComboBoxSearch\n\nimport Orange\nfrom Orange.preprocess.transformation import MappingTransform\nfrom Orange.util import frompyfunc\nfrom Orange.data import Variable, Table, Value, Instance\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 (\n    itemmodels, vartype, ftry, unique_everseen as unique\n)\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets import report\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\nfrom Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState\n\n\nFeatureDescriptor = \\\n    namedtuple(\"FeatureDescriptor\", [\"name\", \"expression\", \"meta\"],\n               defaults=[False])\n\nContinuousDescriptor = \\\n    namedtuple(\"ContinuousDescriptor\",\n               [\"name\", \"expression\", \"number_of_decimals\", \"meta\"],\n               defaults=[False])\nDateTimeDescriptor = \\\n    namedtuple(\"DateTimeDescriptor\",\n               [\"name\", \"expression\", \"meta\"],\n               defaults=[False])\nDiscreteDescriptor = \\\n    namedtuple(\"DiscreteDescriptor\",\n               [\"name\", \"expression\", \"values\", \"ordered\", \"meta\"],\n               defaults=[False])\n\nStringDescriptor = \\\n    namedtuple(\"StringDescriptor\",\n               [\"name\", \"expression\", \"meta\"],\n               defaults=[True])\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            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    ExpressionTooltip = \"\"\"\nUse variable names as values in expression.\nCategorical features are passed as strings\n(note the change in behaviour from Orange 3.30).\n\n\"\"\".lstrip()\n\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 = QGridLayout()\n        layout.setContentsMargins(0, 0, 0, 0)\n        self.nameedit = QLineEdit(\n            placeholderText=\"Name...\",\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.Fixed)\n        )\n\n        self.metaattributecb = QCheckBox(\"Meta attribute\")\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            chain([\"Select Function\"], sorted_funcs),\n            chain([''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])\n        )\n        self.funcs_model.setParent(self)\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        layout.addWidget(self.nameedit, 0, 0)\n        layout.addWidget(self.metaattributecb, 1, 0)\n        layout.addWidget(self.expressionedit, 0, 1, 1, 2)\n        layout.addWidget(self.attributescb, 1, 1)\n        layout.addWidget(self.functionscb, 1, 2)\n        layout.addWidget(QWidget(), 2, 0)\n\n        self.setLayout(layout)\n\n        self.nameedit.editingFinished.connect(self._invalidate)\n        self.metaattributecb.clicked.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.metaattributecb.setChecked(data.meta)\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                                 meta=self.metaattributecb.isChecked())\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\" \\\n                        + FeatureEditor.ExpressionTooltip\n\n    def editorData(self):\n        return ContinuousDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text(),\n            meta=self.metaattributecb.isChecked(),\n            number_of_decimals=None,\n        )\n\n\nclass DateTimeFeatureEditor(FeatureEditor):\n    ExpressionTooltip = FeatureEditor.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            meta=self.metaattributecb.isChecked(),\n        )\n\n\nclass DiscreteFeatureEditor(FeatureEditor):\n    ExpressionTooltip = FeatureEditor.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.addWidget(label, 2, 0)\n        layout.addWidget(self.valuesedit, 2, 1, 1, 2)\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            meta=self.metaattributecb.isChecked(),\n            values=values,\n            ordered=False,\n            expression=self.expressionedit.text()\n        )\n\n\nclass StringFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A string expression\\n\\n\" \\\n                        + FeatureEditor.ExpressionTooltip\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.metaattributecb.setChecked(True)\n        self.metaattributecb.setDisabled(True)\n\n    def editorData(self):\n        return StringDescriptor(\n            name=self.nameedit.text(),\n            meta=True,\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\ndef freevars(exp: ast.AST, env: List[str]):\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        arg_names = [a.arg for a in chain(args.posonlyargs, args.args)]\n        arg_names += [args.vararg.arg] if args.vararg else []\n        arg_names += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []\n        arg_names += [args.kwarg.arg] if args.kwarg else []\n        vars_ = chain.from_iterable(\n            freevars(e, env) for e in chain(args.defaults, args.kw_defaults)\n        )\n        return list(vars_) + freevars(exp.body, env + arg_names)\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 + env_ext)\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\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, list(available)):\n            return False\n        return True\n\n\nclass OWFeatureConstructor(OWWidget, ConcurrentWidgetMixin):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    category = \"Transform\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = ['function', 'lambda']\n    priority = 2240\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    expressions_with_values = ContextSetting(False)\n    settings_version = 3\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        transform_error = Msg(\"{}\")\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        ConcurrentWidgetMixin.__init__(self)\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, meta=False))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False, meta=False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\", meta=True))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\", meta=False))\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)\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        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        self.fix_button = gui.button(\n            self.buttonsArea, self, \"Upgrade Expressions\",\n            callback=self.fix_expressions)\n        self.fix_button.setHidden(True)\n        gui.button(self.buttonsArea, self, \"Send\", callback=self.apply, default=True)\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            uniq = get_unique_names(self.reserved_names(self.currentIndex),\n                                    proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != uniq:\n                self.Warning.renamed_var()\n                feature = feature.__class__(uniq, *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        self.expressions_with_values = False\n\n        self.descriptors = []\n        self.currentIndex = -1\n        if self.data is not None:\n            self.openContext(data)\n\n        # disconnect from the selection model while reseting the model\n        selmodel = self.featureview.selectionModel()\n        selmodel.selectionChanged.disconnect(self._on_selectedVariableChanged)\n\n        self.featuremodel[:] = list(self.descriptors)\n        self.setCurrentIndex(self.currentIndex)\n\n        selmodel.selectionChanged.connect(self._on_selectedVariableChanged)\n        self.fix_button.setHidden(not self.expressions_with_values)\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.cancel()\n            self.Outputs.data.send(None)\n            self.fix_button.setHidden(True)\n\n    def onDeleteWidget(self):\n        self.shutdown()\n        super().onDeleteWidget()\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 var in attr:\n            col = data.get_column(var)\n            mask = ~np.isnan(col)\n            grater_or_equal = np.greater_equal(\n                col, len(var.values), out=mask, where=mask\n            )\n            if grater_or_equal.any():\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        self.cancel()\n        self.Error.clear()\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        self.start(run, self.data, desc, self.expressions_with_values)\n\n    def on_done(self, result: \"Result\") -> None:\n        data, attrs = result.data, result.attributes\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.Outputs.data.send(data)\n\n    def on_exception(self, ex: Exception):\n        log = logging.getLogger(__name__)\n        log.error(\"\", exc_info=ex)\n        self.Error.transform_error(\n            \"\".join(format_exception_only(type(ex), ex)).rstrip(),\n            exc_info=ex\n        )\n\n    def on_partial_result(self, _):\n        pass\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                desc = \"categorical\"\n                if feature.values:\n                    desc += \" with values \" \\\n                            + \", \".join(f\"'{val}'\" for val in feature.values)\n                if feature.ordered:\n                    desc += \"; ordered\"\n            elif isinstance(feature, ContinuousDescriptor):\n                desc = \"numeric\"\n            elif isinstance(feature, DateTimeDescriptor):\n                desc = \"date/time\"\n            else:\n                desc = \"text\"\n            items[feature.name] = f\"{feature.expression} ({desc})\"\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)\n\n    def fix_expressions(self):\n        dlg = QMessageBox(\n            QMessageBox.Question,\n            \"Fix Expressions\",\n            \"This widget's behaviour has changed. Values of categorical \"\n            \"variables are now inserted as their textual representations \"\n            \"(strings); previously they appeared as integer numbers, with an \"\n            \"attribute '.value' that contained the text.\\n\\n\"\n            \"The widget currently runs in compatibility mode. After \"\n            \"expressions are updated, manually check for their correctness.\")\n        dlg.addButton(\"Update\", QMessageBox.ApplyRole)\n        dlg.addButton(\"Cancel\", QMessageBox.RejectRole)\n        if dlg.exec() == QMessageBox.RejectRole:\n            return\n\n        def fixer(mo):\n            var = domain[mo.group(2)]\n            if mo.group(3) == \".value\":  # uses string; remove `.value`\n                return \"\".join(mo.group(1, 2, 4))\n            # Uses ints: get them by indexing\n            return mo.group(1) + \"{\" + \\\n                   \", \".join(f\"'{val}': {i}\"\n                             for i, val in enumerate(var.values)) + \\\n                   f\"}}[{var.name}]\" + mo.group(4)\n\n        domain = self.data.domain\n        disc_vars = \"|\".join(f\"{var.name}\"\n                             for var in chain(domain.variables, domain.metas)\n                             if var.is_discrete)\n        expr = re.compile(r\"(^|\\W)(\" + disc_vars + r\")(\\.value)?(\\W|$)\")\n        self.descriptors[:] = [\n            descriptor._replace(\n                expression=expr.sub(fixer, descriptor.expression))\n            for descriptor in self.descriptors]\n\n        self.expressions_with_values = False\n        self.fix_button.hide()\n        index = self.currentIndex\n        self.featuremodel[:] = list(self.descriptors)\n        self.setCurrentIndex(index)\n        self.apply()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version is None or version < 2:\n            used_vars = set(chain(*(\n                freevars(ast.parse(descriptor.expression, mode=\"eval\"), [])\n                for descriptor in context.values[\"descriptors\"]\n                if descriptor.expression)))\n            disc_vars = {name\n                         for (name, vtype) in chain(context.attributes.items(),\n                                                    context.metas.items())\n                         if vtype == 1}\n            if used_vars & disc_vars:\n                context.values[\"expressions_with_values\"] = True\n\n\n@dataclass\nclass Result:\n    data: Table\n    attributes: List[Variable]\n    metas: List[Variable]\n\n\ndef run(data: Table, desc, use_values, task: TaskState) -> Result:\n    if task.is_interruption_requested():\n        raise CancelledError  # pragma: no cover\n    new_variables, new_metas = construct_variables(desc, data, use_values)\n    # Explicit cancellation point after `construct_variables` which can\n    # already run `compute_value`.\n    if task.is_interruption_requested():\n        raise CancelledError  # pragma: no cover\n    new_domain = Orange.data.Domain(\n        data.domain.attributes + new_variables,\n        data.domain.class_vars,\n        metas=data.domain.metas + new_metas\n    )\n    try:\n        for variable in new_variables:\n            variable.compute_value.mask_exceptions = False\n        data = data.transform(new_domain)\n    finally:\n        for variable in new_variables:\n            variable.compute_value.mask_exceptions = True\n    return Result(data, new_variables, new_metas)\n\n\ndef validate_exp(exp):\n    \"\"\"\n    Validate an `ast.AST` expression.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        A parsed abstract syntax tree\n    \"\"\"\n    # pylint: disable=too-many-branches,too-many-return-statements\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.Lambda:\n        return all(validate_exp(e) for e in exp.args.defaults) and \\\n               all(validate_exp(e) for e in exp.args.kw_defaults) and \\\n               validate_exp(exp.body)\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 in (ast.SetComp, ast.ListComp, ast.GeneratorExp):\n        return validate_exp(exp.elt) and all(map(validate_exp, exp.generators))\n    elif etype == ast.DictComp:\n        return validate_exp(exp.key) and validate_exp(exp.value) and \\\n               all(map(validate_exp, exp.generators))\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        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        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    elif etype == ast.comprehension and not exp.is_async:\n        return validate_exp(exp.target) and validate_exp(exp.iter) and \\\n               all(map(validate_exp, exp.ifs))\n    else:\n        raise ValueError(exp)\n\n\ndef construct_variables(descriptions, data, use_values=False):\n    # subs\n    variables = []\n    metas = []\n    source_vars = data.domain.variables + data.domain.metas\n    for desc in descriptions:\n        desc, func = bind_variable(desc, source_vars, data, use_values)\n        var = make_variable(desc, func)\n        [variables, metas][desc.meta].append(var)\n    return tuple(variables), tuple(metas)\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, use_values):\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 = {unicodedata.normalize(\"NFKC\", sanitized_name(v.name)): v\n                 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    dtype = object if isinstance(descriptor, StringDescriptor) else float\n\n    if isinstance(descriptor, DiscreteDescriptor):\n        if not descriptor.values:\n            str_func = FeatureFunc(descriptor.expression, source_vars,\n                                   use_values=use_values)\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            cast = MappingTransformCast(values)\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        cast = DateTimeCast()\n\n    func = FeatureFunc(descriptor.expression, source_vars, values, cast,\n                       use_values=use_values, dtype=dtype)\n    return descriptor, func\n\n\n_parse_datetime = Orange.data.TimeVariable(\"_\").parse\n_cast_datetime_num_types = (int, float)\n\n\ndef cast_datetime(e):\n    if isinstance(e, _cast_datetime_num_types):\n        return e\n    if e == \"\" or e is None:\n        return np.nan\n    return _parse_datetime(e)\n\n\n_cast_datetime = frompyfunc(cast_datetime, 1, 1, dtype=float)\n\n\nclass DateTimeCast:\n    def __call__(self, values):\n        return _cast_datetime(values)\n\n    def __eq__(self, other):\n        return isinstance(other, DateTimeCast)\n\n    def __hash__(self):\n        return hash(cast_datetime)\n\n\nclass MappingTransformCast:\n    def __init__(self, mapping: Mapping):\n        self.t = MappingTransform(None, mapping)\n\n    def __reduce_ex__(self, protocol):\n        return type(self), (self.t.mapping, )\n\n    def __call__(self, values):\n        return self.t.transform(values)\n\n    def __eq__(self, other):\n        return isinstance(other, MappingTransformCast) and self.t == other.t\n\n    def __hash__(self):\n        return hash(self.t)\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\", \"max\", \"memoryview\", \"min\", \"next\", \"object\",\n    \"oct\", \"ord\", \"pow\", \"range\", \"repr\", \"reversed\", \"round\",\n    \"set\", \"slice\", \"sorted\", \"str\", \"sum\", \"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    \"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    dtype: Optional['DType'] = None\n\n    def __init__(self, expression, args, extra_env=None, cast=None, use_values=False,\n                 dtype=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        self.use_values = use_values\n        self.dtype = dtype\n\n    def __call__(self, table, *_):\n        if isinstance(table, Table):\n            return self.__call_table(table)\n        else:\n            return self.__call_instance(table)\n\n    def __call_table(self, table):\n        try:\n            cols = [self.extract_column(table, var) for _, var in self.args]\n        except ValueError:\n            if self.mask_exceptions:\n                return np.full(len(table), np.nan)\n            else:\n                raise\n\n        if not cols:\n            args = [()] * len(table)\n        else:\n            args = zip(*cols)\n        f = self.func\n        if self.mask_exceptions:\n            y = list(starmap(ftry(f, Exception, np.nan), args))\n        else:\n            y = list(starmap(f, args))\n        if self.cast is not None:\n            y = self.cast(y)\n        return np.asarray(y, dtype=self.dtype)\n\n    def __call_instance(self, instance: Instance):\n        table = Table.from_numpy(\n            instance.domain,\n            np.array([instance.x]),\n            np.array([instance.y]),\n            np.array([instance.metas]),\n        )\n        return self.__call_table(table)[0]\n\n    def extract_column(self, table: Table, var: Variable):\n        data = table.get_column(var)\n        if var.is_string:\n            return data\n        elif var.is_discrete and not self.use_values:\n            values = np.array([*var.values, None], dtype=object)\n            idx = data.astype(int)\n            idx[~np.isfinite(data)] = len(values) - 1\n            return values[idx].tolist()\n        elif var.is_time:  # time always needs Values due to str(val) formatting\n            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access\n        elif not self.use_values:\n            return data.tolist()\n        else:\n            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access\n\n    def __reduce__(self):\n        return type(self), (self.expression, self.args,\n                            self.extra_env, self.cast, self.use_values,\n                            self.dtype)\n\n    def __repr__(self):\n        return \"{0.__name__}{1!r}\".format(*self.__reduce__())\n\n    def __hash__(self):\n        return hash((self.expression, tuple(self.args),\n                     tuple(sorted(self.extra_env.items())), self.cast))\n\n    def __eq__(self, other):\n        return type(self) is type(other) \\\n            and self.expression == other.expression and self.args == other.args \\\n            and self.extra_env == other.extra_env and self.cast == other.cast\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": "4b85d06ba",
                "new_commit_hash": "143861424",
                "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": 369,
                            "length": 7
                        },
                        "new_range": {
                            "start": 369,
                            "length": 7
                        },
                        "section": "class DescriptorModel(itemmodels.PyListModel):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            return super().data(index, role)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def freevars(exp, env):",
                                "type": "deleted"
                            },
                            {
                                "content": "def freevars(exp: ast.AST, env: List[str]):",
                                "type": "added"
                            },
                            {
                                "content": "    \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "    Return names of all free variables in a parsed (expression) AST.",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "freevars",
                            "content": "def 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)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 372,
                            "end_lineno": 474,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 402,
                            "length": 11
                        },
                        "new_range": {
                            "start": 402,
                            "length": 14
                        },
                        "section": "def freevars(exp, env):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    elif etype == ast.Lambda:",
                                "type": "context"
                            },
                            {
                                "content": "        args = exp.args",
                                "type": "context"
                            },
                            {
                                "content": "        assert isinstance(args, ast.arguments)",
                                "type": "context"
                            },
                            {
                                "content": "        argnames = [a.arg for a in args.args]",
                                "type": "deleted"
                            },
                            {
                                "content": "        argnames += [args.vararg.arg] if args.vararg else []",
                                "type": "deleted"
                            },
                            {
                                "content": "        argnames += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []",
                                "type": "deleted"
                            },
                            {
                                "content": "        argnames += [args.kwarg] if args.kwarg else []",
                                "type": "deleted"
                            },
                            {
                                "content": "        return freevars(exp.body, env + argnames)",
                                "type": "deleted"
                            },
                            {
                                "content": "        arg_names = [a.arg for a in chain(args.posonlyargs, args.args)]",
                                "type": "added"
                            },
                            {
                                "content": "        arg_names += [args.vararg.arg] if args.vararg else []",
                                "type": "added"
                            },
                            {
                                "content": "        arg_names += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []",
                                "type": "added"
                            },
                            {
                                "content": "        arg_names += [args.kwarg.arg] if args.kwarg else []",
                                "type": "added"
                            },
                            {
                                "content": "        vars_ = chain.from_iterable(",
                                "type": "added"
                            },
                            {
                                "content": "            freevars(e, env) for e in chain(args.defaults, args.kw_defaults)",
                                "type": "added"
                            },
                            {
                                "content": "        )",
                                "type": "added"
                            },
                            {
                                "content": "        return list(vars_) + freevars(exp.body, env + arg_names)",
                                "type": "added"
                            },
                            {
                                "content": "    elif etype == ast.IfExp:",
                                "type": "context"
                            },
                            {
                                "content": "        return (freevars(exp.test, env) + freevars(exp.body, env) +",
                                "type": "context"
                            },
                            {
                                "content": "                freevars(exp.orelse, env))",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "freevars",
                            "content": "def 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)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 372,
                            "end_lineno": 474,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 420,
                            "length": 7
                        },
                        "new_range": {
                            "start": 423,
                            "length": 7
                        },
                        "section": "def freevars(exp, env):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        vars_ = []",
                                "type": "context"
                            },
                            {
                                "content": "        for gen in exp.generators:",
                                "type": "context"
                            },
                            {
                                "content": "            target_names = freevars(gen.target, [])  # assigned names",
                                "type": "context"
                            },
                            {
                                "content": "            vars_iter = freevars(gen.iter, env)",
                                "type": "deleted"
                            },
                            {
                                "content": "            vars_iter = freevars(gen.iter, env + env_ext)",
                                "type": "added"
                            },
                            {
                                "content": "            env_ext += target_names",
                                "type": "context"
                            },
                            {
                                "content": "            vars_ifs = list(chain(*(freevars(ifexp, env + target_names)",
                                "type": "context"
                            },
                            {
                                "content": "                                    for ifexp in gen.ifs or [])))",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "freevars",
                            "content": "def 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)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 372,
                            "end_lineno": 474,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 500,
                            "length": 7
                        },
                        "new_range": {
                            "start": 503,
                            "length": 7
                        },
                        "section": "class FeatureConstructorHandler(DomainContextHandler):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        for var in metas:",
                                "type": "context"
                            },
                            {
                                "content": "            available[sanitized_name(var)] = None",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        if freevars(exp_ast, available):",
                                "type": "deleted"
                            },
                            {
                                "content": "        if freevars(exp_ast, list(available)):",
                                "type": "added"
                            },
                            {
                                "content": "            return False",
                                "type": "context"
                            },
                            {
                                "content": "        return True",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "method",
                            "name": "FeatureConstructorHandler.is_valid_item",
                            "content": "class 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",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 480,
                            "end_lineno": 505,
                            "parent": {
                                "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                                "type": "class",
                                "name": "FeatureConstructorHandler",
                                "content": "class 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",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 477,
                                "end_lineno": 505,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "class",
                            "name": "FeatureConstructorHandler",
                            "content": "class 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",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 477,
                            "end_lineno": 505,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 944,
                            "length": 16
                        },
                        "new_range": {
                            "start": 947,
                            "length": 12
                        },
                        "section": "def validate_exp(exp):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "    Validate an `ast.AST` expression.",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    Only expressions with no list,set,dict,generator comprehensions",
                                "type": "deleted"
                            },
                            {
                                "content": "    are accepted.",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "    Parameters",
                                "type": "context"
                            },
                            {
                                "content": "    ----------",
                                "type": "context"
                            },
                            {
                                "content": "    exp : ast.AST",
                                "type": "context"
                            },
                            {
                                "content": "        A parsed abstract syntax tree",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "    \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "    # pylint: disable=too-many-branches",
                                "type": "deleted"
                            },
                            {
                                "content": "    # pylint: disable=too-many-branches,too-many-return-statements",
                                "type": "added"
                            },
                            {
                                "content": "    if not isinstance(exp, ast.AST):",
                                "type": "context"
                            },
                            {
                                "content": "        raise TypeError(\"exp is not a 'ast.AST' instance\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "validate_exp",
                            "content": "def 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)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 943,
                            "end_lineno": 1007,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 966,
                            "length": 12
                        },
                        "new_range": {
                            "start": 965,
                            "length": 21
                        },
                        "section": "def validate_exp(exp):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        return all(map(validate_exp, [exp.left, exp.right]))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.UnaryOp:",
                                "type": "context"
                            },
                            {
                                "content": "        return validate_exp(exp.operand)",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.Lambda:",
                                "type": "added"
                            },
                            {
                                "content": "        return all(validate_exp(e) for e in exp.args.defaults) and \\",
                                "type": "added"
                            },
                            {
                                "content": "               all(validate_exp(e) for e in exp.args.kw_defaults) and \\",
                                "type": "added"
                            },
                            {
                                "content": "               validate_exp(exp.body)",
                                "type": "added"
                            },
                            {
                                "content": "    elif etype == ast.IfExp:",
                                "type": "context"
                            },
                            {
                                "content": "        return all(map(validate_exp, [exp.test, exp.body, exp.orelse]))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.Dict:",
                                "type": "context"
                            },
                            {
                                "content": "        return all(map(validate_exp, chain(exp.keys, exp.values)))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.Set:",
                                "type": "context"
                            },
                            {
                                "content": "        return all(map(validate_exp, exp.elts))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype in (ast.SetComp, ast.ListComp, ast.GeneratorExp):",
                                "type": "added"
                            },
                            {
                                "content": "        return validate_exp(exp.elt) and all(map(validate_exp, exp.generators))",
                                "type": "added"
                            },
                            {
                                "content": "    elif etype == ast.DictComp:",
                                "type": "added"
                            },
                            {
                                "content": "        return validate_exp(exp.key) and validate_exp(exp.value) and \\",
                                "type": "added"
                            },
                            {
                                "content": "               all(map(validate_exp, exp.generators))",
                                "type": "added"
                            },
                            {
                                "content": "    elif etype == ast.Compare:",
                                "type": "context"
                            },
                            {
                                "content": "        return all(map(validate_exp, [exp.left] + exp.comparators))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.Call:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "validate_exp",
                            "content": "def validate_exp(exp):\n    \"\"\"\n    Validate an `ast.AST` expression.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        A parsed abstract syntax tree\n    \"\"\"\n    # pylint: disable=too-many-branches,too-many-return-statements\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.Lambda:\n        return all(validate_exp(e) for e in exp.args.defaults) and \\\n               all(validate_exp(e) for e in exp.args.kw_defaults) and \\\n               validate_exp(exp.body)\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 in (ast.SetComp, ast.ListComp, ast.GeneratorExp):\n        return validate_exp(exp.elt) and all(map(validate_exp, exp.generators))\n    elif etype == ast.DictComp:\n        return validate_exp(exp.key) and validate_exp(exp.value) and \\\n               all(map(validate_exp, exp.generators))\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        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        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    elif etype == ast.comprehension and not exp.is_async:\n        return validate_exp(exp.target) and validate_exp(exp.iter) and \\\n               all(map(validate_exp, exp.ifs))\n    else:\n        raise ValueError(exp)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 946,
                            "end_lineno": 1016,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 979,
                            "length": 7
                        },
                        "new_range": {
                            "start": 987,
                            "length": 6
                        },
                        "section": "def validate_exp(exp):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                       [k.value for k in exp.keywords or []])",
                                "type": "context"
                            },
                            {
                                "content": "        return all(map(validate_exp, subexp))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.Starred:",
                                "type": "context"
                            },
                            {
                                "content": "        assert isinstance(exp.ctx, ast.Load)",
                                "type": "deleted"
                            },
                            {
                                "content": "        return validate_exp(exp.value)",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype in [ast.Num, ast.Str, ast.Bytes, ast.Ellipsis, ast.NameConstant]:",
                                "type": "context"
                            },
                            {
                                "content": "        return True",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "validate_exp",
                            "content": "def 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)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 943,
                            "end_lineno": 1007,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 990,
                            "length": 7
                        },
                        "new_range": {
                            "start": 997,
                            "length": 6
                        },
                        "section": "def validate_exp(exp):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    elif etype == ast.Subscript:",
                                "type": "context"
                            },
                            {
                                "content": "        return all(map(validate_exp, [exp.value, exp.slice]))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype in {ast.List, ast.Tuple}:",
                                "type": "context"
                            },
                            {
                                "content": "        assert isinstance(exp.ctx, ast.Load)",
                                "type": "deleted"
                            },
                            {
                                "content": "        return all(map(validate_exp, exp.elts))",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.Name:",
                                "type": "context"
                            },
                            {
                                "content": "        return True",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "validate_exp",
                            "content": "def 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)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 943,
                            "end_lineno": 1007,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1003,
                            "length": 6
                        },
                        "new_range": {
                            "start": 1009,
                            "length": 9
                        },
                        "section": "def validate_exp(exp):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        return validate_exp(exp.value)",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.keyword:",
                                "type": "context"
                            },
                            {
                                "content": "        return validate_exp(exp.value)",
                                "type": "context"
                            },
                            {
                                "content": "    elif etype == ast.comprehension and not exp.is_async:",
                                "type": "added"
                            },
                            {
                                "content": "        return validate_exp(exp.target) and validate_exp(exp.iter) and \\",
                                "type": "added"
                            },
                            {
                                "content": "               all(map(validate_exp, exp.ifs))",
                                "type": "added"
                            },
                            {
                                "content": "    else:",
                                "type": "context"
                            },
                            {
                                "content": "        raise ValueError(exp)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "function",
                            "name": "validate_exp",
                            "content": "def validate_exp(exp):\n    \"\"\"\n    Validate an `ast.AST` expression.\n\n    Parameters\n    ----------\n    exp : ast.AST\n        A parsed abstract syntax tree\n    \"\"\"\n    # pylint: disable=too-many-branches,too-many-return-statements\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.Lambda:\n        return all(validate_exp(e) for e in exp.args.defaults) and \\\n               all(validate_exp(e) for e in exp.args.kw_defaults) and \\\n               validate_exp(exp.body)\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 in (ast.SetComp, ast.ListComp, ast.GeneratorExp):\n        return validate_exp(exp.elt) and all(map(validate_exp, exp.generators))\n    elif etype == ast.DictComp:\n        return validate_exp(exp.key) and validate_exp(exp.value) and \\\n               all(map(validate_exp, exp.generators))\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        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        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    elif etype == ast.comprehension and not exp.is_async:\n        return validate_exp(exp.target) and validate_exp(exp.iter) and \\\n               all(map(validate_exp, exp.ifs))\n    else:\n        raise ValueError(exp)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 946,
                            "end_lineno": 1016,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1173,
                            "length": 9
                        },
                        "new_range": {
                            "start": 1182,
                            "length": 9
                        },
                        "section": "__ALLOWED = ["
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    \"bin\", \"bool\", \"bytearray\", \"bytes\", \"chr\", \"complex\", \"dict\",",
                                "type": "context"
                            },
                            {
                                "content": "    \"divmod\", \"enumerate\", \"filter\", \"float\", \"format\", \"frozenset\",",
                                "type": "context"
                            },
                            {
                                "content": "    \"getattr\", \"hasattr\", \"hash\", \"hex\", \"id\", \"int\", \"iter\", \"len\",",
                                "type": "context"
                            },
                            {
                                "content": "    \"list\", \"map\", \"memoryview\", \"next\", \"object\",",
                                "type": "deleted"
                            },
                            {
                                "content": "    \"list\", \"map\", \"max\", \"memoryview\", \"min\", \"next\", \"object\",",
                                "type": "added"
                            },
                            {
                                "content": "    \"oct\", \"ord\", \"pow\", \"range\", \"repr\", \"reversed\", \"round\",",
                                "type": "context"
                            },
                            {
                                "content": "    \"set\", \"slice\", \"sorted\", \"str\", \"tuple\", \"type\",",
                                "type": "deleted"
                            },
                            {
                                "content": "    \"set\", \"slice\", \"sorted\", \"str\", \"sum\", \"tuple\", \"type\",",
                                "type": "added"
                            },
                            {
                                "content": "    \"zip\"",
                                "type": "context"
                            },
                            {
                                "content": "]",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "statement",
                            "name": "",
                            "content": "__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\", \"max\", \"memoryview\", \"min\", \"next\", \"object\",\n    \"oct\", \"ord\", \"pow\", \"range\", \"repr\", \"reversed\", \"round\",\n    \"set\", \"slice\", \"sorted\", \"str\", \"sum\", \"tuple\", \"type\",\n    \"zip\"\n]",
                            "ast_type_str": "Assign",
                            "start_lineno": 1180,
                            "end_lineno": 1189,
                            "parent": null
                        }
                    ],
                    "deleted_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "statement",
                            "name": "",
                            "content": "__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]",
                            "ast_type_str": "Assign",
                            "start_lineno": 1171,
                            "end_lineno": 1180,
                            "parent": null
                        }
                    ]
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1209,
                            "length": 9
                        },
                        "new_range": {
                            "start": 1218,
                            "length": 6
                        },
                        "section": "__GLOBALS.update({"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    \"nanargmin\": lambda *args: np.nanargmin(args),",
                                "type": "context"
                            },
                            {
                                "content": "    \"nanvar\": lambda *args: np.nanvar(args),",
                                "type": "context"
                            },
                            {
                                "content": "    \"mean\": lambda *args: np.mean(args),",
                                "type": "context"
                            },
                            {
                                "content": "    \"min\": lambda *args: np.min(args),",
                                "type": "deleted"
                            },
                            {
                                "content": "    \"max\": lambda *args: np.max(args),",
                                "type": "deleted"
                            },
                            {
                                "content": "    \"sum\": lambda *args: np.sum(args),",
                                "type": "deleted"
                            },
                            {
                                "content": "    \"std\": lambda *args: np.std(args),",
                                "type": "context"
                            },
                            {
                                "content": "    \"median\": lambda *args: np.median(args),",
                                "type": "context"
                            },
                            {
                                "content": "    \"cumsum\": lambda *args: np.cumsum(args),",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": [
                        {
                            "file_name": "Orange/widgets/data/owfeatureconstructor.py",
                            "type": "statement",
                            "name": "",
                            "content": "__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)})",
                            "ast_type_str": "Expr",
                            "start_lineno": 1188,
                            "end_lineno": 1221,
                            "parent": null
                        }
                    ]
                }
            ]
        },
        {
            "old_file_content": "# pylint: disable=unsubscriptable-object\nimport unittest\nimport ast\nimport sys\nimport math\nimport pickle\nimport copy\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\nfrom scipy import sparse as sp\n\nfrom orangewidget.settings import Context\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.data.owfeatureconstructor import (\n    DiscreteDescriptor, ContinuousDescriptor, StringDescriptor,\n    construct_variables, OWFeatureConstructor,\n    FeatureEditor, DiscreteFeatureEditor, FeatureConstructorHandler,\n    DateTimeDescriptor, StringFeatureEditor)\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=True)]\n        )\n        data = data.transform(Domain(data.domain.attributes +\n                                     construct_variables(desc, data)[0],\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(data.domain.attributes +\n                                     construct_variables(desc, data)[0],\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(data.domain.attributes +\n                                     construct_variables(featuremodel, data)[0],\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(data.domain.attributes +\n                                     construct_variables(featuremodel, data)[0],\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(f\"2019-07-{int(row['MEDV'] / 3):02}\",\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                                     data.domain.metas +\n                                     construct_variables(desc, data)[1]))\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                                  meta=False,\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    def test_construct_placement(self):\n        domain = Domain([ContinuousVariable(x) for x in \"ab\"])\n        data = Table.from_numpy(domain, np.arange(4).reshape(2, 2))\n        desc = PyListModel(\n            [ContinuousDescriptor(\"x\", \"a + b\", 1, False),\n             ContinuousDescriptor(\"y\", \"a + b\", 1, True),\n             StringDescriptor(\"z\", \"a + b\", True)])\n        attrs, metas = construct_variables(desc, data)\n        self.assertEqual([var.name for var in attrs], [\"x\"])\n        self.assertEqual([var.name for var in metas], [\"y\", \"z\"])\n\n    @staticmethod\n    def test_unicode_normalization():\n        micro = \"\\u00b5\"\n        domain = Domain([ContinuousVariable(micro)])\n        name = 'Micro Variable'\n        expression = micro\n        desc = PyListModel(\n            [ContinuousDescriptor(name=name, expression=expression,\n                                  number_of_decimals=2)]\n        )\n        data = Table.from_numpy(domain, np.arange(5).reshape(5, 1))\n        data = data.transform(Domain(data.domain.attributes,\n                                     [],\n                                     construct_variables(desc, data)[0]))\n        np.testing.assert_equal(data.X, data.metas)\n\n    @staticmethod\n    def test_transform_sparse():\n        domain = Domain([ContinuousVariable(\"A\")])\n        desc = [\n            ContinuousDescriptor(name=\"X\", expression=\"A\", number_of_decimals=2)\n        ]\n        X = sp.csc_matrix(np.arange(5).reshape(5, 1))\n        data = Table.from_numpy(domain, X)\n        data_ = data.transform(Domain(data.domain.attributes,\n                                      [],\n                                      construct_variables(desc, data)[0]))\n        np.testing.assert_equal(data.get_column(0), data_.get_column(0)\n        )\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, False, 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(list(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_time_str(self):\n        data = Table.from_numpy(Domain([TimeVariable(\"T\", have_date=True)]), [[0], [0]])\n        f = FeatureFunc(\"str(T)\", [(\"T\", data.domain[0])])\n        c = f(data)\n        self.assertEqual(list(c), [\"1970-01-01\", \"1970-01-01\"])\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        with iris.unlocked():\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    def test_hash_eq(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"1 / petal_length\",\n                        [(\"petal_length\", iris.domain[\"petal length\"])])\n        g = copy.deepcopy(f)\n        self.assertEqual(f, g)\n        self.assertEqual(hash(f), hash(g))\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_transform_error(self):\n        data = Table(\"iris\")[::5]\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.addFeature(ContinuousDescriptor(\"X\", \"1/0\", 3))\n        self.widget.apply()\n        self.wait_until_finished(self.widget)\n        self.assertTrue(self.widget.Error.transform_error.is_shown())\n        self.widget.removeFeature(0)\n        self.widget.addFeature(ContinuousDescriptor(\"X\", \"1\", 3))\n        self.widget.apply()\n        self.wait_until_finished(self.widget)\n        self.assertFalse(self.widget.Error.transform_error.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(\"1\")\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.wait_until_finished(self.widget)\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_missing_strings(self):\n        domain = Domain([], metas=[StringVariable(\"S1\")])\n        data = Table.from_list(domain, [[\"A\"], [\"B\"], [None]])\n        self.widget.setData(data)\n\n        editor = StringFeatureEditor()\n        editor.nameedit.setText(\"S2\")\n        editor.expressionedit.setText(\"S1 + S1\")\n        self.widget.addFeature(editor.editorData())\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        np.testing.assert_equal(output.metas,\n                                [[\"A\", \"AA\"], [\"B\", \"BB\"], [\"\", \"\"]])\n\n    @patch(\"Orange.widgets.data.owfeatureconstructor.QMessageBox\")\n    def test_fix_values(self, msgbox):\n        w = self.widget\n\n        msgbox.ApplyRole, msgbox.RejectRole = object(), object()\n        msgbox.return_value = Mock()\n        dlgexec = msgbox.return_value.exec = Mock()\n\n        v = [DiscreteVariable(name, values=tuple(\"abc\"))\n             for name in (\"ana\", \"berta\", \"cilka\")]\n        domain = Domain(v, [])\n        self.send_signal(w.Inputs.data, Table.from_numpy(domain, [[0, 1, 2]]))\n\n        w.descriptors = [StringDescriptor(\n            \"y\", \"ana.value + berta.value + cilka.value\")]\n\n        # Reject fixing - no changes\n        dlgexec.return_value=msgbox.RejectRole\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"ana.value + berta.value + cilka.value\")\n\n        dlgexec.return_value = Mock(return_value=msgbox.AcceptRole)\n\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression, \"ana + berta + cilka\")\n\n        w.descriptors = [StringDescriptor(\n            \"y\", \"ana.value + dani.value + cilka.value\")]\n        with patch.object(w, \"apply\"):  # dani doesn't exist and will fail\n            w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"ana + dani.value + cilka\")\n\n        w.descriptors = [ContinuousDescriptor(\"y\", \"sqrt(berta)\", 1)]\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"sqrt({'a': 0, 'b': 1, 'c': 2}[berta])\")\n\n    def test_migration_discrete_strings(self):\n        v = [DiscreteVariable(\"Ana\", values=tuple(\"012\")),\n             ContinuousVariable(\"Cilka\")]\n        domain = Domain(v)\n        data = Table.from_numpy(domain, [[1, 3.14]])\n\n        settings_w_discrete = {\n            \"context_settings\":\n            [Context(\n                attributes=dict(Ana=1, Cilka=2), metas={},\n                values=dict(\n                    descriptors=[\n                        ContinuousDescriptor(\"y\", \"Ana + int(Cilka)\", 1),\n                        StringDescriptor(\"u\", \"Ana.value + 'X'\")\n                    ],\n                    currentIndex=0)\n             )]\n        }\n        widget = self.create_widget(OWFeatureConstructor, settings_w_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.expressions_with_values)\n        self.assertFalse(widget.fix_button.isHidden())\n        out = self.get_output(widget.Outputs.data)\n        np.testing.assert_almost_equal(out.X, [[1, 3.14, 4]])\n        np.testing.assert_equal(out.metas, [[\"1X\"]])\n\n        settings_no_discrete = {\n            \"context_settings\":\n            [Context(\n                attributes=dict(Ana=1, Cilka=2), metas={},\n                values=dict(\n                    descriptors=[\n                        ContinuousDescriptor(\"y\", \"int(Cilka)\", 1),\n                    ],\n                    currentIndex=0)\n             )]\n        }\n        widget = self.create_widget(OWFeatureConstructor, settings_no_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertFalse(widget.expressions_with_values)\n        self.assertTrue(widget.fix_button.isHidden())\n        out = self.get_output(widget.Outputs.data)\n        np.testing.assert_almost_equal(out.X, [[1, 3.14, 3]])\n\n        widget = self.create_widget(OWFeatureConstructor, settings_w_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.expressions_with_values)\n        self.assertFalse(widget.fix_button.isHidden())\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.expressions_with_values)\n        self.assertTrue(widget.fix_button.isHidden())\n\n    def test_report(self):\n        settings = {\n            \"context_settings\":\n                [Context(\n                    attributes=dict(x=2, y=2, z=2), metas={},\n                    values=dict(\n                        descriptors=[\n                            ContinuousDescriptor(\"a\", \"x + 2\", 1),\n                            DiscreteDescriptor(\"b\", \"x < 3\", (), False),\n                            DiscreteDescriptor(\"c\", \"x > 15\", (), True),\n                            DiscreteDescriptor(\"d\", \"y > x\", (\"foo\", \"bar\"), False),\n                            DiscreteDescriptor(\"e\", \"x ** 2 + y == 5\", (\"foo\", \"bar\"), True),\n                            StringDescriptor(\"f\", \"str(x)\"),\n                            DateTimeDescriptor(\"g\", \"z\")\n                        ],\n                        currentIndex=0)\n                )]\n        }\n\n        w = self.create_widget(OWFeatureConstructor, settings)\n        v = [ContinuousVariable(name) for name in \"xyz\"]\n        domain = Domain(v, [])\n        self.send_signal(w.Inputs.data, Table.from_numpy(domain, [[0, 1, 2]]))\n        w.report_items = Mock()\n        w.send_report()\n        args = w.report_items.call_args[0][1]\n        self.assertEqual(list(args), list(\"abcdefg\"))\n\n    def test_output_domain_picklable(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, Table(\"iris\")[::5])\n        features = [\n            ContinuousDescriptor(\"X1\", \"max(0, sepal_width - 5)\", 2),\n            DiscreteDescriptor(\"D1\", \"HIGH if sepal_width > 5 else LOW\",\n                               (\"HIGH\", \"LOW\"), False),\n            DiscreteDescriptor(\"D2\", \"'HIGH' if sepal_length > 5 else 'LOW'\",\n                               (), False),\n            DateTimeDescriptor(\"T1\", \"0\"),\n            DateTimeDescriptor(\"T2\", \"'1900-01-01'\"),\n        ]\n        for f in features:\n            w.addFeature(f)\n        w.apply()\n        out = self.get_output(w.Outputs.data)\n        domain_a = out.domain\n        domain_b= pickle.loads(pickle.dumps(domain_a))\n        for name in [\"X1\", \"D1\", \"D2\", \"T1\", \"T2\"]:\n            a = domain_a[name]\n            b = domain_b[name]\n            self.assertEqual(a, b)\n            self.assertEqual(hash(a), hash(b))\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\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\nfrom scipy import sparse as sp\n\nfrom orangewidget.settings import Context\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.data.owfeatureconstructor import (\n    DiscreteDescriptor, ContinuousDescriptor, StringDescriptor,\n    construct_variables, OWFeatureConstructor,\n    FeatureEditor, DiscreteFeatureEditor, FeatureConstructorHandler,\n    DateTimeDescriptor, StringFeatureEditor)\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=True)]\n        )\n        data = data.transform(Domain(data.domain.attributes +\n                                     construct_variables(desc, data)[0],\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(data.domain.attributes +\n                                     construct_variables(desc, data)[0],\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(data.domain.attributes +\n                                     construct_variables(featuremodel, data)[0],\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(data.domain.attributes +\n                                     construct_variables(featuremodel, data)[0],\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(f\"2019-07-{int(row['MEDV'] / 3):02}\",\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                                     data.domain.metas +\n                                     construct_variables(desc, data)[1]))\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                                  meta=False,\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    def test_construct_placement(self):\n        domain = Domain([ContinuousVariable(x) for x in \"ab\"])\n        data = Table.from_numpy(domain, np.arange(4).reshape(2, 2))\n        desc = PyListModel(\n            [ContinuousDescriptor(\"x\", \"a + b\", 1, False),\n             ContinuousDescriptor(\"y\", \"a + b\", 1, True),\n             StringDescriptor(\"z\", \"a + b\", True)])\n        attrs, metas = construct_variables(desc, data)\n        self.assertEqual([var.name for var in attrs], [\"x\"])\n        self.assertEqual([var.name for var in metas], [\"y\", \"z\"])\n\n    @staticmethod\n    def test_unicode_normalization():\n        micro = \"\\u00b5\"\n        domain = Domain([ContinuousVariable(micro)])\n        name = 'Micro Variable'\n        expression = micro\n        desc = PyListModel(\n            [ContinuousDescriptor(name=name, expression=expression,\n                                  number_of_decimals=2)]\n        )\n        data = Table.from_numpy(domain, np.arange(5).reshape(5, 1))\n        data = data.transform(Domain(data.domain.attributes,\n                                     [],\n                                     construct_variables(desc, data)[0]))\n        np.testing.assert_equal(data.X, data.metas)\n\n    @staticmethod\n    def test_transform_sparse():\n        domain = Domain([ContinuousVariable(\"A\")])\n        desc = [\n            ContinuousDescriptor(name=\"X\", expression=\"A\", number_of_decimals=2)\n        ]\n        X = sp.csc_matrix(np.arange(5).reshape(5, 1))\n        data = Table.from_numpy(domain, X)\n        data_ = data.transform(Domain(data.domain.attributes,\n                                      [],\n                                      construct_variables(desc, data)[0]))\n        np.testing.assert_equal(data.get_column(0), data_.get_column(0)\n        )\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\", []), [\"a\"])\n        self.assertEqual(freevars_(\"lambda: a\", [\"a\"]), [])\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        self.assertEqual(freevars_(\"lambda a: a + c\", []), [\"c\"])\n        self.assertEqual(freevars_(\"lambda a: a + c\", [\"c\"]), [])\n        self.assertEqual(freevars_(\"lambda a, b=k: a + c\", []), [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"lambda *a, b=k: a + c\", []), [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"lambda a,/, b=k: a + c\", []), [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"lambda a,/, b=k, **kwg: a + c and kwg\", []),\n                         [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"[a for a in b]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[a for a, k in b]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[(a, j) for a in b]\"), [\"j\", \"b\"])\n        self.assertEqual(freevars_(\"[a for k in b for a in k]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[a for k in b if k for a in k if a]\"),\n                         [\"b\"])\n        self.assertEqual(freevars_(\"[a for k in b if kk for a in k if aa]\"),\n                         [\"b\", \"kk\", \"aa\"])\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        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_(\"[i async for i in s]\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"(i async for i 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, False, 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(list(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_time_str(self):\n        data = Table.from_numpy(Domain([TimeVariable(\"T\", have_date=True)]), [[0], [0]])\n        f = FeatureFunc(\"str(T)\", [(\"T\", data.domain[0])])\n        c = f(data)\n        self.assertEqual(list(c), [\"1970-01-01\", \"1970-01-01\"])\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        with iris.unlocked():\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    def test_hash_eq(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"1 / petal_length\",\n                        [(\"petal_length\", iris.domain[\"petal length\"])])\n        g = copy.deepcopy(f)\n        self.assertEqual(f, g)\n        self.assertEqual(hash(f), hash(g))\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_transform_error(self):\n        data = Table(\"iris\")[::5]\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.addFeature(ContinuousDescriptor(\"X\", \"1/0\", 3))\n        self.widget.apply()\n        self.wait_until_finished(self.widget)\n        self.assertTrue(self.widget.Error.transform_error.is_shown())\n        self.widget.removeFeature(0)\n        self.widget.addFeature(ContinuousDescriptor(\"X\", \"1\", 3))\n        self.widget.apply()\n        self.wait_until_finished(self.widget)\n        self.assertFalse(self.widget.Error.transform_error.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(\"1\")\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.wait_until_finished(self.widget)\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_missing_strings(self):\n        domain = Domain([], metas=[StringVariable(\"S1\")])\n        data = Table.from_list(domain, [[\"A\"], [\"B\"], [None]])\n        self.widget.setData(data)\n\n        editor = StringFeatureEditor()\n        editor.nameedit.setText(\"S2\")\n        editor.expressionedit.setText(\"S1 + S1\")\n        self.widget.addFeature(editor.editorData())\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        np.testing.assert_equal(output.metas,\n                                [[\"A\", \"AA\"], [\"B\", \"BB\"], [\"\", \"\"]])\n\n    @patch(\"Orange.widgets.data.owfeatureconstructor.QMessageBox\")\n    def test_fix_values(self, msgbox):\n        w = self.widget\n\n        msgbox.ApplyRole, msgbox.RejectRole = object(), object()\n        msgbox.return_value = Mock()\n        dlgexec = msgbox.return_value.exec = Mock()\n\n        v = [DiscreteVariable(name, values=tuple(\"abc\"))\n             for name in (\"ana\", \"berta\", \"cilka\")]\n        domain = Domain(v, [])\n        self.send_signal(w.Inputs.data, Table.from_numpy(domain, [[0, 1, 2]]))\n\n        w.descriptors = [StringDescriptor(\n            \"y\", \"ana.value + berta.value + cilka.value\")]\n\n        # Reject fixing - no changes\n        dlgexec.return_value=msgbox.RejectRole\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"ana.value + berta.value + cilka.value\")\n\n        dlgexec.return_value = Mock(return_value=msgbox.AcceptRole)\n\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression, \"ana + berta + cilka\")\n\n        w.descriptors = [StringDescriptor(\n            \"y\", \"ana.value + dani.value + cilka.value\")]\n        with patch.object(w, \"apply\"):  # dani doesn't exist and will fail\n            w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"ana + dani.value + cilka\")\n\n        w.descriptors = [ContinuousDescriptor(\"y\", \"sqrt(berta)\", 1)]\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"sqrt({'a': 0, 'b': 1, 'c': 2}[berta])\")\n\n    def test_migration_discrete_strings(self):\n        v = [DiscreteVariable(\"Ana\", values=tuple(\"012\")),\n             ContinuousVariable(\"Cilka\")]\n        domain = Domain(v)\n        data = Table.from_numpy(domain, [[1, 3.14]])\n\n        settings_w_discrete = {\n            \"context_settings\":\n            [Context(\n                attributes=dict(Ana=1, Cilka=2), metas={},\n                values=dict(\n                    descriptors=[\n                        ContinuousDescriptor(\"y\", \"Ana + int(Cilka)\", 1),\n                        StringDescriptor(\"u\", \"Ana.value + 'X'\")\n                    ],\n                    currentIndex=0)\n             )]\n        }\n        widget = self.create_widget(OWFeatureConstructor, settings_w_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.expressions_with_values)\n        self.assertFalse(widget.fix_button.isHidden())\n        out = self.get_output(widget.Outputs.data)\n        np.testing.assert_almost_equal(out.X, [[1, 3.14, 4]])\n        np.testing.assert_equal(out.metas, [[\"1X\"]])\n\n        settings_no_discrete = {\n            \"context_settings\":\n            [Context(\n                attributes=dict(Ana=1, Cilka=2), metas={},\n                values=dict(\n                    descriptors=[\n                        ContinuousDescriptor(\"y\", \"int(Cilka)\", 1),\n                    ],\n                    currentIndex=0)\n             )]\n        }\n        widget = self.create_widget(OWFeatureConstructor, settings_no_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertFalse(widget.expressions_with_values)\n        self.assertTrue(widget.fix_button.isHidden())\n        out = self.get_output(widget.Outputs.data)\n        np.testing.assert_almost_equal(out.X, [[1, 3.14, 3]])\n\n        widget = self.create_widget(OWFeatureConstructor, settings_w_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.expressions_with_values)\n        self.assertFalse(widget.fix_button.isHidden())\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.expressions_with_values)\n        self.assertTrue(widget.fix_button.isHidden())\n\n    def test_report(self):\n        settings = {\n            \"context_settings\":\n                [Context(\n                    attributes=dict(x=2, y=2, z=2), metas={},\n                    values=dict(\n                        descriptors=[\n                            ContinuousDescriptor(\"a\", \"x + 2\", 1),\n                            DiscreteDescriptor(\"b\", \"x < 3\", (), False),\n                            DiscreteDescriptor(\"c\", \"x > 15\", (), True),\n                            DiscreteDescriptor(\"d\", \"y > x\", (\"foo\", \"bar\"), False),\n                            DiscreteDescriptor(\"e\", \"x ** 2 + y == 5\", (\"foo\", \"bar\"), True),\n                            StringDescriptor(\"f\", \"str(x)\"),\n                            DateTimeDescriptor(\"g\", \"z\")\n                        ],\n                        currentIndex=0)\n                )]\n        }\n\n        w = self.create_widget(OWFeatureConstructor, settings)\n        v = [ContinuousVariable(name) for name in \"xyz\"]\n        domain = Domain(v, [])\n        self.send_signal(w.Inputs.data, Table.from_numpy(domain, [[0, 1, 2]]))\n        w.report_items = Mock()\n        w.send_report()\n        args = w.report_items.call_args[0][1]\n        self.assertEqual(list(args), list(\"abcdefg\"))\n\n    def test_output_domain_picklable(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, Table(\"iris\")[::5])\n        features = [\n            ContinuousDescriptor(\"X1\", \"max(0, sepal_width - 5)\", 2),\n            DiscreteDescriptor(\"D1\", \"HIGH if sepal_width > 5 else LOW\",\n                               (\"HIGH\", \"LOW\"), False),\n            DiscreteDescriptor(\"D2\", \"'HIGH' if sepal_length > 5 else 'LOW'\",\n                               (), False),\n            DateTimeDescriptor(\"T1\", \"0\"),\n            DateTimeDescriptor(\"T2\", \"'1900-01-01'\"),\n        ]\n        for f in features:\n            w.addFeature(f)\n        w.apply()\n        out = self.get_output(w.Outputs.data)\n        domain_a = out.domain\n        domain_b= pickle.loads(pickle.dumps(domain_a))\n        for name in [\"X1\", \"D1\", \"D2\", \"T1\", \"T2\"]:\n            a = domain_a[name]\n            b = domain_b[name]\n            self.assertEqual(a, b)\n            self.assertEqual(hash(a), hash(b))\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": "1b16f6f70",
                "new_commit_hash": "11aaf0f1b",
                "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": 226,
                            "length": 20
                        },
                        "new_range": {
                            "start": 226,
                            "length": 34
                        },
                        "section": "class TestTools(unittest.TestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertEqual(freevars_(\"{a, b}\"), [\"a\", \"b\"])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"0 if abs(a) < 0.1 else b\", [\"abs\"]),",
                                "type": "context"
                            },
                            {
                                "content": "                         [\"a\", \"b\"])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda: a\", []), [\"a\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda: a\", [\"a\"]), [])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a: b + 1\"), [\"b\"])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a: b + 1\", [\"b\"]), [])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a: a + 1\"), [])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"(lambda a: a + 1)(a)\"), [\"a\"])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a, *arg: arg + (a,)\"), [])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a, *arg, **kwargs: arg + (a,)\"), [])",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a: a + c\", []), [\"c\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a: a + c\", [\"c\"]), [])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a, b=k: a + c\", []), [\"k\", \"c\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda *a, b=k: a + c\", []), [\"k\", \"c\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a,/, b=k: a + c\", []), [\"k\", \"c\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"lambda a,/, b=k, **kwg: a + c and kwg\", []),",
                                "type": "added"
                            },
                            {
                                "content": "                         [\"k\", \"c\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"[a for a in b]\"), [\"b\"])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"[a for a, k in b]\"), [\"b\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"[(a, j) for a in b]\"), [\"j\", \"b\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"[a for k in b for a in k]\"), [\"b\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"[a for k in b if k for a in k if a]\"),",
                                "type": "added"
                            },
                            {
                                "content": "                         [\"b\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"[a for k in b if kk for a in k if aa]\"),",
                                "type": "added"
                            },
                            {
                                "content": "                         [\"b\", \"kk\", \"aa\"])",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"[1 + a for c in b if c]\"), [\"a\", \"b\"])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"{a for _ in [] if b}\"), [\"a\", \"b\"])",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(freevars_(\"{a for _ in [] if b}\", [\"a\", \"b\"]), [])",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_validate_exp(self):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        stmt = ast.parse(\"1\", mode=\"single\")",
                                "type": "context"
                            },
                            {
                                "content": "        with self.assertRaises(ValueError):",
                                "type": "context"
                            },
                            {
                                "content": "            validate_exp(stmt)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                            "type": "method",
                            "name": "TestTools.test_validate_exp",
                            "content": "class 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}\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 241,
                            "end_lineno": 284,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                                "type": "class",
                                "name": "TestTools",
                                "content": "class 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}\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 179,
                                "end_lineno": 284,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                            "type": "class",
                            "name": "TestTools",
                            "content": "class 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}\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 179,
                            "end_lineno": 284,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                            "type": "method",
                            "name": "TestTools.test_free_vars",
                            "content": "class 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}\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 180,
                            "end_lineno": 239,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                                "type": "class",
                                "name": "TestTools",
                                "content": "class 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}\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 179,
                                "end_lineno": 284,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 272,
                            "length": 16
                        },
                        "new_range": {
                            "start": 286,
                            "length": 10
                        },
                        "section": "class TestTools(unittest.TestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertTrue(validate_(\"[]\"))",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        with self.assertRaises(ValueError):",
                                "type": "context"
                            },
                            {
                                "content": "            validate_(\"[a for a in s]\")",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        with self.assertRaises(ValueError):",
                                "type": "deleted"
                            },
                            {
                                "content": "            validate_(\"(a for a in s)\")",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "        with self.assertRaises(ValueError):",
                                "type": "deleted"
                            },
                            {
                                "content": "            validate_(\"{a for a in s}\")",
                                "type": "deleted"
                            },
                            {
                                "content": "            validate_(\"[i async for i in s]\")",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        with self.assertRaises(ValueError):",
                                "type": "context"
                            },
                            {
                                "content": "            validate_(\"{a:1 for a in s}\")",
                                "type": "deleted"
                            },
                            {
                                "content": "            validate_(\"(i async for i in s)\")",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "class FeatureFuncTest(unittest.TestCase):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                            "type": "method",
                            "name": "TestTools.test_validate_exp",
                            "content": "class 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}\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 241,
                            "end_lineno": 284,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                                "type": "class",
                                "name": "TestTools",
                                "content": "class 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}\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 179,
                                "end_lineno": 284,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owfeatureconstructor.py",
                            "type": "class",
                            "name": "TestTools",
                            "content": "class 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}\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 179,
                            "end_lineno": 284,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "90e994605f5d81ef13103c2a7095bbd62c655da0^",
    "new_commit_hash": "90e994605f5d81ef13103c2a7095bbd62c655da0",
    "commit_message": "Merge pull request #6272 from ales-erjavec/owfeatureconstructor-name-bindings-expressions\n\n[ENH] Feature Constructor: Enable comprehensions and lambdas in expressions",
    "commit_date": "2023-01-09T14:33:39+01:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "90e994605f5d81ef13103c2a7095bbd62c655da0",
    "test_file_codes": [
        "# pylint: disable=unsubscriptable-object\nimport unittest\nimport ast\nimport sys\nimport math\nimport pickle\nimport copy\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\nfrom scipy import sparse as sp\n\nfrom orangewidget.settings import Context\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.data.owfeatureconstructor import (\n    DiscreteDescriptor, ContinuousDescriptor, StringDescriptor,\n    construct_variables, OWFeatureConstructor,\n    FeatureEditor, DiscreteFeatureEditor, FeatureConstructorHandler,\n    DateTimeDescriptor, StringFeatureEditor)\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=True)]\n        )\n        data = data.transform(Domain(data.domain.attributes +\n                                     construct_variables(desc, data)[0],\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(data.domain.attributes +\n                                     construct_variables(desc, data)[0],\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(data.domain.attributes +\n                                     construct_variables(featuremodel, data)[0],\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(data.domain.attributes +\n                                     construct_variables(featuremodel, data)[0],\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(f\"2019-07-{int(row['MEDV'] / 3):02}\",\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                                     data.domain.metas +\n                                     construct_variables(desc, data)[1]))\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                                  meta=False,\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    def test_construct_placement(self):\n        domain = Domain([ContinuousVariable(x) for x in \"ab\"])\n        data = Table.from_numpy(domain, np.arange(4).reshape(2, 2))\n        desc = PyListModel(\n            [ContinuousDescriptor(\"x\", \"a + b\", 1, False),\n             ContinuousDescriptor(\"y\", \"a + b\", 1, True),\n             StringDescriptor(\"z\", \"a + b\", True)])\n        attrs, metas = construct_variables(desc, data)\n        self.assertEqual([var.name for var in attrs], [\"x\"])\n        self.assertEqual([var.name for var in metas], [\"y\", \"z\"])\n\n    @staticmethod\n    def test_unicode_normalization():\n        micro = \"\\u00b5\"\n        domain = Domain([ContinuousVariable(micro)])\n        name = 'Micro Variable'\n        expression = micro\n        desc = PyListModel(\n            [ContinuousDescriptor(name=name, expression=expression,\n                                  number_of_decimals=2)]\n        )\n        data = Table.from_numpy(domain, np.arange(5).reshape(5, 1))\n        data = data.transform(Domain(data.domain.attributes,\n                                     [],\n                                     construct_variables(desc, data)[0]))\n        np.testing.assert_equal(data.X, data.metas)\n\n    @staticmethod\n    def test_transform_sparse():\n        domain = Domain([ContinuousVariable(\"A\")])\n        desc = [\n            ContinuousDescriptor(name=\"X\", expression=\"A\", number_of_decimals=2)\n        ]\n        X = sp.csc_matrix(np.arange(5).reshape(5, 1))\n        data = Table.from_numpy(domain, X)\n        data_ = data.transform(Domain(data.domain.attributes,\n                                      [],\n                                      construct_variables(desc, data)[0]))\n        np.testing.assert_equal(data.get_column(0), data_.get_column(0)\n        )\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\", []), [\"a\"])\n        self.assertEqual(freevars_(\"lambda: a\", [\"a\"]), [])\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        self.assertEqual(freevars_(\"lambda a: a + c\", []), [\"c\"])\n        self.assertEqual(freevars_(\"lambda a: a + c\", [\"c\"]), [])\n        self.assertEqual(freevars_(\"lambda a, b=k: a + c\", []), [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"lambda *a, b=k: a + c\", []), [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"lambda a,/, b=k: a + c\", []), [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"lambda a,/, b=k, **kwg: a + c and kwg\", []),\n                         [\"k\", \"c\"])\n        self.assertEqual(freevars_(\"[a for a in b]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[a for a, k in b]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[(a, j) for a in b]\"), [\"j\", \"b\"])\n        self.assertEqual(freevars_(\"[a for k in b for a in k]\"), [\"b\"])\n        self.assertEqual(freevars_(\"[a for k in b if k for a in k if a]\"),\n                         [\"b\"])\n        self.assertEqual(freevars_(\"[a for k in b if kk for a in k if aa]\"),\n                         [\"b\", \"kk\", \"aa\"])\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        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_(\"[i async for i in s]\")\n\n        with self.assertRaises(ValueError):\n            validate_(\"(i async for i 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, False, 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(list(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_time_str(self):\n        data = Table.from_numpy(Domain([TimeVariable(\"T\", have_date=True)]), [[0], [0]])\n        f = FeatureFunc(\"str(T)\", [(\"T\", data.domain[0])])\n        c = f(data)\n        self.assertEqual(list(c), [\"1970-01-01\", \"1970-01-01\"])\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        with iris.unlocked():\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    def test_hash_eq(self):\n        iris = Table(\"iris\")\n        f = FeatureFunc(\"1 / petal_length\",\n                        [(\"petal_length\", iris.domain[\"petal length\"])])\n        g = copy.deepcopy(f)\n        self.assertEqual(f, g)\n        self.assertEqual(hash(f), hash(g))\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_transform_error(self):\n        data = Table(\"iris\")[::5]\n        self.send_signal(self.widget.Inputs.data, data)\n        self.widget.addFeature(ContinuousDescriptor(\"X\", \"1/0\", 3))\n        self.widget.apply()\n        self.wait_until_finished(self.widget)\n        self.assertTrue(self.widget.Error.transform_error.is_shown())\n        self.widget.removeFeature(0)\n        self.widget.addFeature(ContinuousDescriptor(\"X\", \"1\", 3))\n        self.widget.apply()\n        self.wait_until_finished(self.widget)\n        self.assertFalse(self.widget.Error.transform_error.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(\"1\")\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.wait_until_finished(self.widget)\n        self.assertTrue(self.widget.Error.more_values_needed.is_shown())\n\n    def test_missing_strings(self):\n        domain = Domain([], metas=[StringVariable(\"S1\")])\n        data = Table.from_list(domain, [[\"A\"], [\"B\"], [None]])\n        self.widget.setData(data)\n\n        editor = StringFeatureEditor()\n        editor.nameedit.setText(\"S2\")\n        editor.expressionedit.setText(\"S1 + S1\")\n        self.widget.addFeature(editor.editorData())\n        self.widget.apply()\n        output = self.get_output(self.widget.Outputs.data)\n        np.testing.assert_equal(output.metas,\n                                [[\"A\", \"AA\"], [\"B\", \"BB\"], [\"\", \"\"]])\n\n    @patch(\"Orange.widgets.data.owfeatureconstructor.QMessageBox\")\n    def test_fix_values(self, msgbox):\n        w = self.widget\n\n        msgbox.ApplyRole, msgbox.RejectRole = object(), object()\n        msgbox.return_value = Mock()\n        dlgexec = msgbox.return_value.exec = Mock()\n\n        v = [DiscreteVariable(name, values=tuple(\"abc\"))\n             for name in (\"ana\", \"berta\", \"cilka\")]\n        domain = Domain(v, [])\n        self.send_signal(w.Inputs.data, Table.from_numpy(domain, [[0, 1, 2]]))\n\n        w.descriptors = [StringDescriptor(\n            \"y\", \"ana.value + berta.value + cilka.value\")]\n\n        # Reject fixing - no changes\n        dlgexec.return_value=msgbox.RejectRole\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"ana.value + berta.value + cilka.value\")\n\n        dlgexec.return_value = Mock(return_value=msgbox.AcceptRole)\n\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression, \"ana + berta + cilka\")\n\n        w.descriptors = [StringDescriptor(\n            \"y\", \"ana.value + dani.value + cilka.value\")]\n        with patch.object(w, \"apply\"):  # dani doesn't exist and will fail\n            w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"ana + dani.value + cilka\")\n\n        w.descriptors = [ContinuousDescriptor(\"y\", \"sqrt(berta)\", 1)]\n        w.fix_expressions()\n        self.assertEqual(w.descriptors[0].expression,\n                         \"sqrt({'a': 0, 'b': 1, 'c': 2}[berta])\")\n\n    def test_migration_discrete_strings(self):\n        v = [DiscreteVariable(\"Ana\", values=tuple(\"012\")),\n             ContinuousVariable(\"Cilka\")]\n        domain = Domain(v)\n        data = Table.from_numpy(domain, [[1, 3.14]])\n\n        settings_w_discrete = {\n            \"context_settings\":\n            [Context(\n                attributes=dict(Ana=1, Cilka=2), metas={},\n                values=dict(\n                    descriptors=[\n                        ContinuousDescriptor(\"y\", \"Ana + int(Cilka)\", 1),\n                        StringDescriptor(\"u\", \"Ana.value + 'X'\")\n                    ],\n                    currentIndex=0)\n             )]\n        }\n        widget = self.create_widget(OWFeatureConstructor, settings_w_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.expressions_with_values)\n        self.assertFalse(widget.fix_button.isHidden())\n        out = self.get_output(widget.Outputs.data)\n        np.testing.assert_almost_equal(out.X, [[1, 3.14, 4]])\n        np.testing.assert_equal(out.metas, [[\"1X\"]])\n\n        settings_no_discrete = {\n            \"context_settings\":\n            [Context(\n                attributes=dict(Ana=1, Cilka=2), metas={},\n                values=dict(\n                    descriptors=[\n                        ContinuousDescriptor(\"y\", \"int(Cilka)\", 1),\n                    ],\n                    currentIndex=0)\n             )]\n        }\n        widget = self.create_widget(OWFeatureConstructor, settings_no_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertFalse(widget.expressions_with_values)\n        self.assertTrue(widget.fix_button.isHidden())\n        out = self.get_output(widget.Outputs.data)\n        np.testing.assert_almost_equal(out.X, [[1, 3.14, 3]])\n\n        widget = self.create_widget(OWFeatureConstructor, settings_w_discrete)\n        self.send_signal(widget.Inputs.data, data)\n        self.assertTrue(widget.expressions_with_values)\n        self.assertFalse(widget.fix_button.isHidden())\n        self.send_signal(widget.Inputs.data, None)\n        self.assertFalse(widget.expressions_with_values)\n        self.assertTrue(widget.fix_button.isHidden())\n\n    def test_report(self):\n        settings = {\n            \"context_settings\":\n                [Context(\n                    attributes=dict(x=2, y=2, z=2), metas={},\n                    values=dict(\n                        descriptors=[\n                            ContinuousDescriptor(\"a\", \"x + 2\", 1),\n                            DiscreteDescriptor(\"b\", \"x < 3\", (), False),\n                            DiscreteDescriptor(\"c\", \"x > 15\", (), True),\n                            DiscreteDescriptor(\"d\", \"y > x\", (\"foo\", \"bar\"), False),\n                            DiscreteDescriptor(\"e\", \"x ** 2 + y == 5\", (\"foo\", \"bar\"), True),\n                            StringDescriptor(\"f\", \"str(x)\"),\n                            DateTimeDescriptor(\"g\", \"z\")\n                        ],\n                        currentIndex=0)\n                )]\n        }\n\n        w = self.create_widget(OWFeatureConstructor, settings)\n        v = [ContinuousVariable(name) for name in \"xyz\"]\n        domain = Domain(v, [])\n        self.send_signal(w.Inputs.data, Table.from_numpy(domain, [[0, 1, 2]]))\n        w.report_items = Mock()\n        w.send_report()\n        args = w.report_items.call_args[0][1]\n        self.assertEqual(list(args), list(\"abcdefg\"))\n\n    def test_output_domain_picklable(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, Table(\"iris\")[::5])\n        features = [\n            ContinuousDescriptor(\"X1\", \"max(0, sepal_width - 5)\", 2),\n            DiscreteDescriptor(\"D1\", \"HIGH if sepal_width > 5 else LOW\",\n                               (\"HIGH\", \"LOW\"), False),\n            DiscreteDescriptor(\"D2\", \"'HIGH' if sepal_length > 5 else 'LOW'\",\n                               (), False),\n            DateTimeDescriptor(\"T1\", \"0\"),\n            DateTimeDescriptor(\"T2\", \"'1900-01-01'\"),\n        ]\n        for f in features:\n            w.addFeature(f)\n        w.apply()\n        out = self.get_output(w.Outputs.data)\n        domain_a = out.domain\n        domain_b= pickle.loads(pickle.dumps(domain_a))\n        for name in [\"X1\", \"D1\", \"D2\", \"T1\", \"T2\"]:\n            a = domain_a[name]\n            b = domain_b[name]\n            self.assertEqual(a, b)\n            self.assertEqual(hash(a), hash(b))\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\nCompiling Orange/classification/_tree_scorers.pyx because it changed.\nCompiling Orange/data/_contingency.pyx because it changed.\nCompiling Orange/data/_io.pyx because it changed.\nCompiling Orange/data/_valuecount.pyx because it changed.\nCompiling Orange/data/_variable.pyx because it changed.\nCompiling Orange/distance/_distance.pyx because it changed.\nCompiling Orange/preprocess/_discretize.pyx because it changed.\nCompiling Orange/preprocess/_relieff.pyx because it changed.\nCompiling Orange/projection/_som.pyx because it changed.\n[1/9] Cythonizing Orange/classification/_tree_scorers.pyx\n[2/9] Cythonizing Orange/data/_contingency.pyx\n[3/9] Cythonizing Orange/data/_io.pyx\n[4/9] Cythonizing Orange/data/_valuecount.pyx\n[5/9] Cythonizing Orange/data/_variable.pyx\n[6/9] Cythonizing Orange/distance/_distance.pyx\n[7/9] Cythonizing Orange/preprocess/_discretize.pyx\n[8/9] Cythonizing Orange/preprocess/_relieff.pyx\n[9/9] Cythonizing Orange/projection/_som.pyx\nbuilding 'Orange.classification._tree_scorers' extension\ncreating build\ncreating build/temp.linux-x86_64-3.7\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_tree_scorers.c -o build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o\ncreating build/lib.linux-x86_64-3.7\ncreating build/lib.linux-x86_64-3.7/Orange\ncreating build/lib.linux-x86_64-3.7/Orange/classification\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 build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_contingency.c -o build/temp.linux-x86_64-3.7/Orange/data/_contingency.o\ncreating build/lib.linux-x86_64-3.7/Orange/data\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 build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_io.c -o build/temp.linux-x86_64-3.7/Orange/data/_io.o\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 build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_valuecount.c -o build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o\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 build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_variable.c -o build/temp.linux-x86_64-3.7/Orange/data/_variable.o\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 build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/distance/_distance.c -o build/temp.linux-x86_64-3.7/Orange/distance/_distance.o\ncreating build/lib.linux-x86_64-3.7/Orange/distance\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 build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_discretize.c -o build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o\ncreating build/lib.linux-x86_64-3.7/Orange/preprocess\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 build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_relieff.cpp -o build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o\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 build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/projection/_som.c -o build/temp.linux-x86_64-3.7/Orange/projection/_som.o\ncreating build/lib.linux-x86_64-3.7/Orange/projection\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 build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._simple_tree' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_simple_tree.c -o build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o\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 build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/widgets/utils/_grid_density.cpp -o build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o\ncreating build/lib.linux-x86_64-3.7/Orange/widgets\ncreating build/lib.linux-x86_64-3.7/Orange/widgets/utils\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 build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nrunning develop\nrunning egg_info\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'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.35.0.dev0+90e9946 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0\nProcessing dependencies for Orange3==3.35.0.dev0+90e9946\nSearching for xlsxwriter\nReading https://pypi.org/simple/xlsxwriter/\nDownloading https://files.pythonhosted.org/packages/a7/ea/53d1fe468e63e092cf16e2c18d16f50c29851242f9dd12d6a66e0d7f0d02/XlsxWriter-3.2.0-py3-none-any.whl#sha256=ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e\nBest match: XlsxWriter 3.2.0\nProcessing XlsxWriter-3.2.0-py3-none-any.whl\nInstalling XlsxWriter-3.2.0-py3-none-any.whl to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages\nAdding XlsxWriter 3.2.0 to easy-install.pth file\nInstalling vba_extract.py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/XlsxWriter-3.2.0-py3.7.egg\nSearching for xlrd>=1.2.0\nReading https://pypi.org/simple/xlrd/\nDownloading https://files.pythonhosted.org/packages/a6/0c/c2a72d51fe56e08a08acc85d13013558a2d793028ae7385448a6ccdfae64/xlrd-2.0.1-py2.py3-none-any.whl#sha256=6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd\nBest match: xlrd 2.0.1\nProcessing xlrd-2.0.1-py2.py3-none-any.whl\nInstalling xlrd-2.0.1-py2.py3-none-any.whl to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages\nAdding xlrd 2.0.1 to easy-install.pth file\nInstalling runxlrd.py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/xlrd-2.0.1-py3.7.egg\nSearching for serverfiles\nReading https://pypi.org/simple/serverfiles/\nDownloading https://files.pythonhosted.org/packages/33/36/794fe1c4ed29b0f56a26d72e956baf0285d8fe9e29faf36797a28a8ad45b/serverfiles-0.3.1.tar.gz#sha256=5e10fc32e758791e3735bc08bce2ed4702a83b1e45ab96c5d59cc8aeecfbebe1\nBest match: serverfiles 0.3.1\nProcessing serverfiles-0.3.1.tar.gz\nWriting /tmp/easy_install-urq2ezex/serverfiles-0.3.1/setup.cfg\nRunning serverfiles-0.3.1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-urq2ezex/serverfiles-0.3.1/egg-dist-tmp-540wmhcu\ncreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/serverfiles-0.3.1-py3.7.egg\nExtracting serverfiles-0.3.1-py3.7.egg to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/serverfiles-0.3.1-py3.7.egg\nSearching for scipy>=1.9\nReading https://pypi.org/simple/scipy/\nDownloading https://files.pythonhosted.org/packages/d9/7b/2b8ac283cf32465ed08bc20a83d559fe7b174a484781702ba8accea001d6/scipy-1.15.0.tar.gz#sha256=300742e2cc94e36a2880ebe464a1c8b4352a7b0f3e36ec3d2ac006cdbe0219ac\nBest match: scipy 1.15.0\nProcessing scipy-1.15.0.tar.gz\nVerifying Orange installation...\n❌ Orange verification failed\nPython 3.7 installation failed, trying Python 3.8...\nAttempting installation with Python 3.8...\nrunning build_ext\nbuilding 'Orange.classification._tree_scorers' extension\ncreating build/temp.linux-x86_64-cpython-38/Orange/classification\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/classification/_tree_scorers.c -o build/temp.linux-x86_64-cpython-38/Orange/classification/_tree_scorers.o\ncreating build/lib.linux-x86_64-cpython-38/Orange/classification\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/classification/_tree_scorers.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncreating build/temp.linux-x86_64-cpython-38/Orange/data\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/data/_contingency.c -o build/temp.linux-x86_64-cpython-38/Orange/data/_contingency.o\ncreating build/lib.linux-x86_64-cpython-38/Orange/data\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/data/_contingency.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/data/_io.c -o build/temp.linux-x86_64-cpython-38/Orange/data/_io.o\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/data/_io.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/data/_valuecount.c -o build/temp.linux-x86_64-cpython-38/Orange/data/_valuecount.o\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/data/_valuecount.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/data/_variable.c -o build/temp.linux-x86_64-cpython-38/Orange/data/_variable.o\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/data/_variable.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncreating build/temp.linux-x86_64-cpython-38/Orange/distance\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/distance/_distance.c -o build/temp.linux-x86_64-cpython-38/Orange/distance/_distance.o\ncreating build/lib.linux-x86_64-cpython-38/Orange/distance\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/distance/_distance.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncreating build/temp.linux-x86_64-cpython-38/Orange/preprocess\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/preprocess/_discretize.c -o build/temp.linux-x86_64-cpython-38/Orange/preprocess/_discretize.o\ncreating build/lib.linux-x86_64-cpython-38/Orange/preprocess\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/preprocess/_discretize.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\nc++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/preprocess/_relieff.cpp -o build/temp.linux-x86_64-cpython-38/Orange/preprocess/_relieff.o\nc++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -pthread -shared build/temp.linux-x86_64-cpython-38/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/preprocess/_relieff.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncreating build/temp.linux-x86_64-cpython-38/Orange/projection\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/projection/_som.c -o build/temp.linux-x86_64-cpython-38/Orange/projection/_som.o\ncreating build/lib.linux-x86_64-cpython-38/Orange/projection\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/projection/_som.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.classification._simple_tree' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/classification/_simple_tree.c -o build/temp.linux-x86_64-cpython-38/Orange/classification/_simple_tree.o\ncc -pthread -shared -Wl,--exclude-libs,ALL build/temp.linux-x86_64-cpython-38/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/classification/_simple_tree.cpython-38-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncreating build/temp.linux-x86_64-cpython-38/Orange/widgets/utils\nc++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/include/python3.8 -c Orange/widgets/utils/_grid_density.cpp -o build/temp.linux-x86_64-cpython-38/Orange/widgets/utils/_grid_density.o\ncreating build/lib.linux-x86_64-cpython-38/Orange/widgets/utils\nc++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -pthread -shared build/temp.linux-x86_64-cpython-38/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib -lm -o build/lib.linux-x86_64-cpython-38/Orange/widgets/utils/_grid_density.cpython-38-x86_64-linux-gnu.so\ncopying build/lib.linux-x86_64-cpython-38/Orange/classification/_tree_scorers.cpython-38-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_contingency.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_io.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_valuecount.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_variable.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/distance/_distance.cpython-38-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-cpython-38/Orange/preprocess/_discretize.cpython-38-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-cpython-38/Orange/preprocess/_relieff.cpython-38-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-cpython-38/Orange/projection/_som.cpython-38-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-cpython-38/Orange/classification/_simple_tree.cpython-38-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-cpython-38/Orange/widgets/utils/_grid_density.cpython-38-x86_64-linux-gnu.so -> Orange/widgets/utils\nrunning develop\nrunning 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\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayscalars.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/npy_math.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ufuncobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayscalars.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/npy_math.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ufuncobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayscalars.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/npy_math.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ufuncobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayscalars.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/npy_math.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ufuncobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayscalars.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ufuncobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayscalars.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/npy_math.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ufuncobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayscalars.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/npy_math.h won't be automatically included in the manifest: the path must be relative\ndependency /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ufuncobject.h won't be automatically included in the manifest: the path must be relative\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncopying build/lib.linux-x86_64-cpython-38/Orange/classification/_tree_scorers.cpython-38-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_contingency.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_io.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_valuecount.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/data/_variable.cpython-38-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-cpython-38/Orange/distance/_distance.cpython-38-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-cpython-38/Orange/preprocess/_discretize.cpython-38-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-cpython-38/Orange/preprocess/_relieff.cpython-38-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-cpython-38/Orange/projection/_som.cpython-38-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-cpython-38/Orange/classification/_simple_tree.cpython-38-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-cpython-38/Orange/widgets/utils/_grid_density.cpython-38-x86_64-linux-gnu.so -> Orange/widgets/utils\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.35.0.dev0+90e9946 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0\nProcessing dependencies for Orange3==3.35.0.dev0+90e9946\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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for setuptools==75.3.0\nBest match: setuptools 75.3.0\nAdding setuptools 75.3.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for scipy==1.10.1\nBest match: scipy 1.10.1\nAdding scipy 1.10.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for scikit-learn==1.1.3\nBest match: scikit-learn 1.1.3\nAdding scikit-learn 1.1.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for requests==2.32.3\nBest match: requests 2.32.3\nAdding requests 2.32.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for qtconsole==5.6.1\nBest match: qtconsole 5.6.1\nAdding qtconsole 5.6.1 to easy-install.pth file\nInstalling jupyter-qtconsole script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for PyYAML==6.0.2\nBest match: PyYAML 6.0.2\nAdding PyYAML 6.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pyqtgraph==0.13.3\nBest match: pyqtgraph 0.13.3\nAdding pyqtgraph 0.13.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pygments==2.19.1\nBest match: pygments 2.19.1\nAdding pygments 2.19.1 to easy-install.pth file\nInstalling pygmentize script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pip==24.3.1\nBest match: pip 24.3.1\nAdding pip 24.3.1 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pandas==2.0.3\nBest match: pandas 2.0.3\nAdding pandas 2.0.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for openpyxl==3.1.5\nBest match: openpyxl 3.1.5\nAdding openpyxl 3.1.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for numpy==1.24.4\nBest match: numpy 1.24.4\nAdding numpy 1.24.4 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling f2py3.8 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for networkx==3.1\nBest match: networkx 3.1\nAdding networkx 3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for matplotlib==3.7.5\nBest match: matplotlib 3.7.5\nAdding matplotlib 3.7.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for keyrings.alt==5.0.2\nBest match: keyrings.alt 5.0.2\nAdding keyrings.alt 5.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for keyring==25.5.0\nBest match: keyring 25.5.0\nAdding keyring 25.5.0 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for joblib==1.4.2\nBest match: joblib 1.4.2\nAdding joblib 1.4.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for httpx==0.28.1\nBest match: httpx 0.28.1\nAdding httpx 0.28.1 to easy-install.pth file\nInstalling httpx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for threadpoolctl==3.5.0\nBest match: threadpoolctl 3.5.0\nAdding threadpoolctl 3.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for urllib3==2.2.3\nBest match: urllib3 2.2.3\nAdding urllib3 2.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for packaging==24.2\nBest match: packaging 24.2\nAdding packaging 24.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for QtPy==2.4.2\nBest match: QtPy 2.4.2\nAdding QtPy 2.4.2 to easy-install.pth file\nInstalling qtpy script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for ipykernel==6.29.5\nBest match: ipykernel 6.29.5\nAdding ipykernel 6.29.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for jupyter-client==8.6.3\nBest match: jupyter-client 8.6.3\nAdding jupyter-client 8.6.3 to easy-install.pth file\nInstalling jupyter-kernel script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling jupyter-kernelspec script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling jupyter-run script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for jupyter-core==5.7.2\nBest match: jupyter-core 5.7.2\nAdding jupyter-core 5.7.2 to easy-install.pth file\nInstalling jupyter script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling jupyter-migrate script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling jupyter-troubleshoot script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for traitlets==5.14.3\nBest match: traitlets 5.14.3\nAdding traitlets 5.14.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for tzdata==2024.2\nBest match: tzdata 2024.2\nAdding tzdata 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for typing-extensions==4.12.2\nBest match: typing-extensions 4.12.2\nAdding typing-extensions 4.12.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/setuptools/_vendor\nSearching for qasync==0.27.1\nBest match: qasync 0.27.1\nAdding qasync 0.27.1 to easy-install.pth file\ndetected new path './setuptools/_vendor'\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for requests-cache==1.2.1\nBest match: requests-cache 1.2.1\nAdding requests-cache 1.2.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for et-xmlfile==2.0.0\nBest match: et-xmlfile 2.0.0\nAdding et-xmlfile 2.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for importlib-resources==6.4.5\nBest match: importlib-resources 6.4.5\nAdding importlib-resources 6.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pillow==10.4.0\nBest match: pillow 10.4.0\nAdding pillow 10.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for kiwisolver==1.4.7\nBest match: kiwisolver 1.4.7\nAdding kiwisolver 1.4.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for fonttools==4.55.3\nBest match: fonttools 4.55.3\nAdding fonttools 4.55.3 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for cycler==0.12.1\nBest match: cycler 0.12.1\nAdding cycler 0.12.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for contourpy==1.1.1\nBest match: contourpy 1.1.1\nAdding contourpy 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for jaraco.context==6.0.1\nBest match: jaraco.context 6.0.1\nAdding jaraco.context 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for jaraco.classes==3.4.0\nBest match: jaraco.classes 3.4.0\nAdding jaraco.classes 3.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for importlib-metadata==8.5.0\nBest match: importlib-metadata 8.5.0\nAdding importlib-metadata 8.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for jaraco.functools==4.1.0\nBest match: jaraco.functools 4.1.0\nAdding jaraco.functools 4.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for httpcore==1.0.7\nBest match: httpcore 1.0.7\nAdding httpcore 1.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for anyio==4.5.2\nBest match: anyio 4.5.2\nAdding anyio 4.5.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for tornado==6.4.2\nBest match: tornado 6.4.2\nAdding tornado 6.4.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pyzmq==26.2.0\nBest match: pyzmq 26.2.0\nAdding pyzmq 26.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for psutil==6.1.1\nBest match: psutil 6.1.1\nAdding psutil 6.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for nest-asyncio==1.6.0\nBest match: nest-asyncio 1.6.0\nAdding nest-asyncio 1.6.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for matplotlib-inline==0.1.7\nBest match: matplotlib-inline 0.1.7\nAdding matplotlib-inline 0.1.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for ipython==8.12.3\nBest match: ipython 8.12.3\nAdding ipython 8.12.3 to easy-install.pth file\nInstalling ipython script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\nInstalling ipython3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for debugpy==1.8.11\nBest match: debugpy 1.8.11\nAdding debugpy 1.8.11 to easy-install.pth file\nInstalling debugpy script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for comm==0.2.2\nBest match: comm 0.2.2\nAdding comm 0.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for platformdirs==4.3.6\nBest match: platformdirs 4.3.6\nAdding platformdirs 4.3.6 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for cattrs==24.1.2\nBest match: cattrs 24.1.2\nAdding cattrs 24.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for attrs==24.3.0\nBest match: attrs 24.3.0\nAdding attrs 24.3.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for zipp==3.20.2\nBest match: zipp 3.20.2\nAdding zipp 3.20.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for backports.tarfile==1.2.0\nBest match: backports.tarfile 1.2.0\nbackports.tarfile 1.2.0 is already the active version in easy-install.pth\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/setuptools/_vendor\nSearching for more-itertools==10.5.0\nBest match: more-itertools 10.5.0\nAdding more-itertools 10.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pexpect==4.9.0\nBest match: pexpect 4.9.0\nAdding pexpect 4.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for stack-data==0.6.3\nBest match: stack-data 0.6.3\nAdding stack-data 0.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for prompt-toolkit==3.0.48\nBest match: prompt-toolkit 3.0.48\nAdding prompt-toolkit 3.0.48 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pickleshare==0.7.5\nBest match: pickleshare 0.7.5\nAdding pickleshare 0.7.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for jedi==0.19.2\nBest match: jedi 0.19.2\nAdding jedi 0.19.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for decorator==5.1.1\nBest match: decorator 5.1.1\nAdding decorator 5.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for backcall==0.2.0\nBest match: backcall 0.2.0\nAdding backcall 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for cffi==1.17.1\nBest match: cffi 1.17.1\nAdding cffi 1.17.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for ptyprocess==0.7.0\nBest match: ptyprocess 0.7.0\nAdding ptyprocess 0.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pure-eval==0.2.3\nBest match: pure-eval 0.2.3\nAdding pure-eval 0.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for asttokens==3.0.0\nBest match: asttokens 3.0.0\nAdding asttokens 3.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for executing==2.1.0\nBest match: executing 2.1.0\nAdding executing 2.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for wcwidth==0.2.13\nBest match: wcwidth 0.2.13\nAdding wcwidth 0.2.13 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for parso==0.8.4\nBest match: parso 0.8.4\nAdding parso 0.8.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nSearching for pycparser==2.22\nBest match: pycparser 2.22\nAdding pycparser 2.22 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages\nFinished processing dependencies for Orange3==3.35.0.dev0+90e9946\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.8\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 48ms\nPrepared 18 packages in 20ms\nInstalled 18 packages in 163ms\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\n  × No solution found when resolving dependencies:\n  ╰─▶ Because the current Python version (3.7.9) does not satisfy Python>=3.8 and all of:\n          scipy>=1.9.0,<=1.9.1\n          scipy>=1.10.0,<=1.10.1\n      depend on Python>=3.8,<3.12, we can conclude that all of:\n          scipy>=1.9.0,<=1.9.1\n          scipy>=1.10.0,<=1.10.1\n       cannot be used.\n      And because only the following versions of scipy are available:\n          scipy<=1.9.0\n          scipy==1.9.1\n          scipy==1.9.2\n          scipy==1.9.3\n          scipy==1.10.0\n          scipy==1.10.1\n          scipy==1.11.0\n          scipy==1.11.1\n          scipy==1.11.2\n          scipy==1.11.3\n          scipy==1.11.4\n          scipy==1.12.0\n          scipy==1.13.0\n          scipy==1.13.1\n          scipy==1.14.0\n          scipy==1.14.1\n          scipy==1.15.0\n      we can conclude that all of:\n          scipy>=1.9.0,<1.9.2\n          scipy>1.9.3,<1.11.0\n       cannot be used. (1)\n\n      Because the current Python version (3.7.9) does not satisfy Python>=3.8 and scipy>=1.9.2,<=1.9.3 depends on Python>=3.8,\n      we can conclude that scipy>=1.9.2,<=1.9.3 cannot be used.\n      And because we know from (1) that all of:\n          scipy>=1.9.0,<1.9.2\n          scipy>1.9.3,<1.11.0\n       cannot be used, we can conclude that scipy>=1.9.0,<1.11.0 cannot be used.\n      And because scipy==1.11.0 was yanked (reason: License Violation), we can conclude that scipy>=1.9.0,<1.11.1 cannot be\n      used. (2)\n\n      Because the current Python version (3.7.9) does not satisfy Python>=3.8 and scipy>=1.11.1,<=1.11.3 depends on\n      Python>=3.9,<3.13, we can conclude that scipy>=1.11.1,<=1.11.3 cannot be used.\n      And because we know from (2) that scipy>=1.9.0,<1.11.1 cannot be used, we can conclude that scipy>=1.9.0,<1.11.4 cannot\n      be used. (3)\n\n      Because the current Python version (3.7.9) does not satisfy Python>=3.8 and scipy>=1.11.4,<=1.13.1 depends on\n      Python>=3.9, we can conclude that scipy>=1.11.4,<=1.13.1 cannot be used.\n      And because we know from (3) that scipy>=1.9.0,<1.11.4 cannot be used, we can conclude that scipy>=1.9.0,<1.14.0 cannot\n      be used. (4)\n\n      Because the current Python version (3.7.9) does not satisfy Python>=3.8 and scipy>=1.14.0 depends on Python>=3.10, we can\n      conclude that scipy>=1.14.0 cannot be used.\n      And because we know from (4) that scipy>=1.9.0,<1.14.0 cannot be used, we can conclude that scipy>=1.9.0 cannot be used.\n      And because you require scipy>=1.9, we can conclude that your requirements are unsatisfiable.\n\n      hint: Pre-releases are available for `scipy` in the requested range (e.g., 1.15.0rc2), but pre-releases weren't enabled\n      (try: `--prerelease=allow`)\nResolved 59 packages in 690ms\nInstalled 52 packages in 566ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + backcall==0.2.0\n + cattrs==23.1.2\n + certifi==2024.12.14\n + charset-normalizer==3.4.1\n + commonmark==0.9.1\n + cycler==0.11.0\n + debugpy==1.7.0\n + decorator==5.1.1\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + entrypoints==0.4\n + fonttools==4.38.0\n + idna==3.10\n + ipykernel==6.16.2\n + ipython==7.34.0\n + ipython-genutils==0.2.0\n + jedi==0.19.2\n + jupyter-client==7.4.9\n + jupyter-core==4.12.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + matplotlib-inline==0.1.6\n + nest-asyncio==1.6.0\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + parso==0.8.4\n + pexpect==4.9.0\n + pickleshare==0.7.5\n + pillow==9.5.0\n + pip==24.0\n + platformdirs==4.0.0\n + prompt-toolkit==3.0.48\n + psutil==6.1.1\n + ptyprocess==0.7.0\n + pygments==2.17.2\n + pyparsing==3.1.4\n + pyqtgraph==0.12.3\n + python-dateutil==2.9.0.post0\n + pyzmq==26.2.0\n + qasync==0.25.0\n + qtconsole==5.4.4\n + qtpy==2.4.2\n + requests==2.31.0\n + requests-cache==1.1.1\n + six==1.17.0\n + tornado==6.2\n + traitlets==5.9.0\n + url-normalize==1.4.3\n + urllib3==2.0.7\n + wcwidth==0.2.13\nResolved 2 packages in 3ms\nInstalled 2 packages in 19ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nResolved 19 packages in 108ms\nInstalled 8 packages in 1.36s\n + catboost==1.2.7\n + graphviz==0.20.1\n + pandas==1.3.5\n + plotly==5.18.0\n + pytz==2024.2\n + scipy==1.7.3\n + tenacity==8.2.3\n + xgboost==1.6.2\nResolved 5 packages in 9ms\nInstalled 3 packages in 21ms\n + joblib==1.3.2\n + scikit-learn==1.0.2\n + threadpoolctl==3.1.0\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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:3427:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3427 |   __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:4883:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4883 |   __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:5629:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5629 |           for (__pyx_t_19 = 0; __pyx_t_19 < __pyx_t_18; __pyx_t_19+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5750:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5750 |           __pyx_t_20 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5756:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5756 |           __pyx_t_20 = (((__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:7217:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7217 |         for (__pyx_t_12 = 0; __pyx_t_12 < __pyx_t_11; __pyx_t_12+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7874:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7874 |         for (__pyx_t_12 = 0; __pyx_t_12 < __pyx_t_11; __pyx_t_12+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7934:150: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7934 |               __pyx_t_16 = (((*((__pyx_t_5numpy_int32_t const  *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_15 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                                      ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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:4120:43: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4120 |           __pyx_v_value = ((__pyx_v_value * __pyx_v_decs) + (__pyx_v_c & 0xf));\n      |                            ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/data/_io.c:4318:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4318 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 223, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:683:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  683 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1590:7: note: ‘__pyx_v_attr_index’ was declared here\n 1590 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:3183:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3183 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:774:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:790:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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:3863:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3863 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3862:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3862 |   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:4934:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4934 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4933:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4933 |   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:4732:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4732 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4307:7: note: ‘__pyx_v_win_y’ was declared here\n 4307 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4721:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4721 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4306:7: note: ‘__pyx_v_win_x’ was declared here\n 4306 |   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:5682:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5682 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5681:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5681 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:3184:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3184 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2887:7: note: ‘__pyx_v_win_y’ was declared here\n 2887 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3173:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3173 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2886:7: note: ‘__pyx_v_win_x’ was declared here\n 2886 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3357:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3357 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\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      |         ^~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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_90e994605f5d81ef13103c2a7095bbd62c655da0/.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,\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 '*.svg' under directory 'Orange/canvas'\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 '*.svg' under directory 'distribute'\nwarning: no files found matching 'COPYING'\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/pkg_resources/__init__.py:119: PkgResourcesDeprecationWarning:  is an invalid version and will not be supported in a future release\n  PkgResourcesDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.7/site-packages/pkg_resources/__init__.py:119: PkgResourcesDeprecationWarning:  is an invalid version and will not be supported in a future release\n  PkgResourcesDeprecationWarning,\nerror: Couldn't find a setup script in /tmp/easy_install-jsrf_zgt/scipy-1.15.0.tar.gz\nUsing CPython 3.8.20\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 15 packages in 83ms\nPrepared 15 packages in 1ms\nInstalled 15 packages in 78ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + iniconfig==2.0.0\n + numpy==1.24.4\n + packaging==24.2\n + pluggy==1.5.0\n + pyqt5==5.15.11\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.15.0\n + pyqtwebengine==5.15.7\n + pyqtwebengine-qt5==5.15.16\n + pytest==8.3.4\n + setuptools==75.3.0\n + tomli==2.2.1\n + wheel==0.45.1\nResolved 59 packages in 77ms\nInstalled 55 packages in 98ms\n + anyio==4.5.2\n + backports-tarfile==1.2.0\n + baycomp==1.0.3\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.17.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + contourpy==1.1.1\n + cryptography==44.0.0\n + cycler==0.12.1\n + et-xmlfile==2.0.0\n + fonttools==4.55.3\n + h11==0.14.0\n + httpcore==1.0.7\n + httpx==0.28.1\n + idna==3.10\n + importlib-metadata==8.5.0\n + importlib-resources==6.4.5\n + jaraco-classes==3.4.0\n + jaraco-context==6.0.1\n + jaraco-functools==4.1.0\n + jeepney==0.8.0\n + joblib==1.4.2\n + keyring==25.5.0\n + keyrings-alt==5.0.2\n + kiwisolver==1.4.7\n + matplotlib==3.7.5\n + more-itertools==10.5.0\n + networkx==3.1\n + openpyxl==3.1.5\n + opentsne==1.0.1\n + pandas==2.0.3\n + pillow==10.4.0\n + pip==24.3.1\n + pycparser==2.22\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.2\n + requests==2.32.3\n + scikit-learn==1.1.3\n + scipy==1.10.1\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + sniffio==1.3.1\n + threadpoolctl==3.5.0\n + typing-extensions==4.12.2\n + tzdata==2024.2\n + urllib3==2.2.3\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\n + zipp==3.20.2\nResolved 64 packages in 70ms\nInstalled 41 packages in 94ms\n + anyqt==0.2.0\n + asttokens==3.0.0\n + attrs==24.3.0\n + backcall==0.2.0\n + cattrs==24.1.2\n + comm==0.2.2\n + commonmark==0.9.1\n + debugpy==1.8.11\n + decorator==5.1.1\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + executing==2.1.0\n + ipykernel==6.29.5\n + ipython==8.12.3\n + jedi==0.19.2\n + jupyter-client==8.6.3\n + jupyter-core==5.7.2\n + matplotlib-inline==0.1.7\n + nest-asyncio==1.6.0\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + parso==0.8.4\n + pexpect==4.9.0\n + pickleshare==0.7.5\n + platformdirs==4.3.6\n + prompt-toolkit==3.0.48\n + psutil==6.1.1\n + ptyprocess==0.7.0\n + pure-eval==0.2.3\n + pygments==2.19.1\n + pyqtgraph==0.13.3\n + pyzmq==26.2.0\n + qasync==0.27.1\n + qtconsole==5.6.1\n + qtpy==2.4.2\n + requests-cache==1.2.1\n + stack-data==0.6.3\n + tornado==6.4.2\n + traitlets==5.14.3\n + url-normalize==1.4.3\n + wcwidth==0.2.13\nResolved 2 packages in 2ms\nInstalled 2 packages in 4ms\n + psycopg2==2.9.10\n + pymssql==2.3.2\nResolved 23 packages in 14ms\nInstalled 6 packages in 429ms\n + catboost==1.2.7\n + graphviz==0.20.3\n + nvidia-nccl-cu12==2.24.3\n + plotly==5.24.1\n + tenacity==9.0.0\n + xgboost==2.1.3\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n                 from Orange/classification/_tree_scorers.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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:3427:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3427 |   __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:4883:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4883 |   __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:5629:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5629 |           for (__pyx_t_19 = 0; __pyx_t_19 < __pyx_t_18; __pyx_t_19+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5750:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5750 |           __pyx_t_20 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5756:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5756 |           __pyx_t_20 = (((__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:7217:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7217 |         for (__pyx_t_12 = 0; __pyx_t_12 < __pyx_t_11; __pyx_t_12+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7874:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7874 |         for (__pyx_t_12 = 0; __pyx_t_12 < __pyx_t_11; __pyx_t_12+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7934:150: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7934 |               __pyx_t_16 = (((*((__pyx_t_5numpy_int32_t const  *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_15 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                                      ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n                 from Orange/data/_contingency.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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:4120:43: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4120 |           __pyx_v_value = ((__pyx_v_value * __pyx_v_decs) + (__pyx_v_c & 0xf));\n      |                            ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/data/_io.c:4318:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4318 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 223, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:683:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  683 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1590:7: note: ‘__pyx_v_attr_index’ was declared here\n 1590 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:3183:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3183 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n                 from Orange/data/_valuecount.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n                 from Orange/distance/_distance.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n                 from Orange/preprocess/_discretize.c:774:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n                 from Orange/preprocess/_relieff.cpp:790:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n                 from Orange/projection/_som.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/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:3863:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3863 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3862:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3862 |   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:4934:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4934 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4933:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4933 |   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:5682:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5682 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5681:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5681 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:3184:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3184 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2887:7: note: ‘__pyx_v_win_y’ was declared here\n 2887 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3173:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3173 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2886:7: note: ‘__pyx_v_win_x’ was declared here\n 2886 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3357:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3357 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4732:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4732 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4307:7: note: ‘__pyx_v_win_y’ was declared here\n 4307 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4721:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4721 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4306:7: note: ‘__pyx_v_win_x’ was declared here\n 4306 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\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      |         ^~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'test_suite'\n  warnings.warn(msg)\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/setuptools/_distutils/dist.py:261: UserWarning: Unknown distribution option: 'test_suite'\n  warnings.warn(msg)\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/setuptools/command/develop.py:41: EasyInstallDeprecationWarning: easy_install command is deprecated.\n!!\n\n        ********************************************************************************\n        Please avoid running ``setup.py`` and ``easy_install``.\n        Instead, use pypa/build, pypa/installer or other\n        standards-based tools.\n\n        See https://github.com/pypa/setuptools/issues/917 for details.\n        ********************************************************************************\n\n!!\n  easy_install.initialize_options(self)\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/.venv/lib/python3.8/site-packages/setuptools/_distutils/cmd.py:66: SetuptoolsDeprecationWarning: setup.py install is deprecated.\n!!\n\n        ********************************************************************************\n        Please avoid running ``setup.py`` directly.\n        Instead, use pypa/build, pypa/installer or other\n        standards-based tools.\n\n        See https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html for details.\n        ********************************************************************************\n\n!!\n  self.initialize_options()\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 '*.svg' under directory 'Orange/canvas'\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 '*.svg' under directory 'distribute'\nwarning: no files found matching 'COPYING'\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.8.20, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0\nconfigfile: pyproject.toml\nplugins: anyio-4.5.2, typeguard-4.3.0\ncollected 38 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.8/site-packages/orangewidget/tests/base.py:242: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n.venv/lib/python3.8/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________________ OWFeatureConstructorTests.test_transform_error ________________\n------------------------------ Captured log call -------------------------------\nERROR    Orange.widgets.data.owfeatureconstructor:owfeatureconstructor.py:830 \nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib/python3.8/concurrent/futures/thread.py\", line 57, in run\n    result = self.fn(*self.args, **self.kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/widgets/data/owfeatureconstructor.py\", line 939, in run\n    data = data.transform(new_domain)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 839, in transform\n    return type(self).from_table(domain, self)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 774, in from_table\n    out = array_conv.get_columns(source, row_indices, n_rows,\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 314, in get_columns\n    col_array = match_density(_compute_column(col, sourceri))\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 210, in _compute_column\n    col = func(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/widgets/data/owfeatureconstructor.py\", line 1264, in __call__\n    return self.__call_table(table)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/widgets/data/owfeatureconstructor.py\", line 1285, in __call_table\n    y = list(starmap(f, args))\n  File \"<lambda>\", line 1, in <lambda>\nZeroDivisionError: division by zero\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_placement\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::FeatureConstructorTest::test_transform_sparse\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_unicode_normalization\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_hash_eq\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::FeatureFuncTest::test_time_str\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_fix_values\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_image_export\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_migration_discrete_strings\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_missing_strings\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_msg_base_class\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_output_domain_picklable\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_renaming_duplicate_vars\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_report\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_transform_error\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.8/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.8/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.8/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, 34 passed, 3 skipped in 4.27s ====================\nWidgets left: 0    Max widgets: 2448 \n\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.8.20, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0\nconfigfile: pyproject.toml\nplugins: anyio-4.5.2, typeguard-4.3.0\ncollected 38 items\n\nr2e_tests/test_1.py sss.........F...............F.........               [100%]\n\n=================================== FAILURES ===================================\n___________________________ TestTools.test_free_vars ___________________________\n\nself = <r2e_tests.test_1.TestTools testMethod=test_free_vars>\n\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\", []), [\"a\"])\n        self.assertEqual(freevars_(\"lambda: a\", [\"a\"]), [])\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        self.assertEqual(freevars_(\"lambda a: a + c\", []), [\"c\"])\n        self.assertEqual(freevars_(\"lambda a: a + c\", [\"c\"]), [])\n>       self.assertEqual(freevars_(\"lambda a, b=k: a + c\", []), [\"k\", \"c\"])\nE       AssertionError: Lists differ: ['c'] != ['k', 'c']\nE       \nE       First differing element 0:\nE       'c'\nE       'k'\nE       \nE       Second list contains 1 additional elements.\nE       First extra element 1:\nE       'c'\nE       \nE       - ['c']\nE       + ['k', 'c']\n\nr2e_tests/test_1.py:239: AssertionError\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.8/site-packages/orangewidget/tests/base.py:242: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n.venv/lib/python3.8/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________________ OWFeatureConstructorTests.test_transform_error ________________\n------------------------------ Captured log call -------------------------------\nERROR    Orange.widgets.data.owfeatureconstructor:owfeatureconstructor.py:827 \nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.8.20-linux-x86_64-gnu/lib/python3.8/concurrent/futures/thread.py\", line 57, in run\n    result = self.fn(*self.args, **self.kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/widgets/data/owfeatureconstructor.py\", line 936, in run\n    data = data.transform(new_domain)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 839, in transform\n    return type(self).from_table(domain, self)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 774, in from_table\n    out = array_conv.get_columns(source, row_indices, n_rows,\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 314, in get_columns\n    col_array = match_density(_compute_column(col, sourceri))\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/data/table.py\", line 210, in _compute_column\n    col = func(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/widgets/data/owfeatureconstructor.py\", line 1258, in __call__\n    return self.__call_table(table)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_90e994605f5d81ef13103c2a7095bbd62c655da0/Orange/widgets/data/owfeatureconstructor.py\", line 1279, in __call_table\n    y = list(starmap(f, args))\n  File \"<lambda>\", line 1, in <lambda>\nZeroDivisionError: division by zero\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_placement\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::FeatureConstructorTest::test_transform_sparse\nPASSED r2e_tests/test_1.py::FeatureConstructorTest::test_unicode_normalization\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_hash_eq\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::FeatureFuncTest::test_time_str\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_fix_values\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_image_export\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_migration_discrete_strings\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_missing_strings\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_msg_base_class\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_output_domain_picklable\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_renaming_duplicate_vars\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_report\nPASSED r2e_tests/test_1.py::OWFeatureConstructorTests::test_transform_error\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.8/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.8/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.8/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::TestTools::test_free_vars - AssertionError: Lists...\nFAILED r2e_tests/test_1.py::OWFeatureConstructorTests::test_minimum_size - As...\n=================== 2 failed, 33 passed, 3 skipped in 2.96s ====================\nWidgets left: 0    Max widgets: 2448 \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:** Missing Free Variables in Lambda Expressions with Default Parameters

**Description:**
When analyzing lambda expressions that include default parameters, some free variables are not correctly identified. Specifically, variables used in default arguments are not being recognized as free variables, which can lead to unexpected behavior in expression evaluations.

**Example Buggy Code:**
```python
def freevars(exp, env):
    # Existing implementation...
    pass  # Placeholder for the actual implementation

# Example usage
expression = "lambda a, b=k: a + c"
free_variables = freevars(ast.parse(expression, '', 'eval'), [])
print(free_variables)  # Output: ['c']
```

In this example, the lambda function `lambda a, b=k: a + c` uses `k` as a default value for parameter `b` and `c` in the expression `a + c`. However, the `freevars` function only identifies `c` as a free variable, neglecting `k`.

**Expected Behavior:**
The `freevars` function should identify both `k` and `c` as free variables in the lambda expression. Therefore, the output should be:
```
['k', 'c']
```

**Actual Behavior:**
Only `c` is identified as a free variable, resulting in the output:
```
['c']
```

This discrepancy causes the test `test_free_vars` to fail, as it expects both `k` and `c` to be recognized as free variables.

**Impact:**
Failing to identify all free variables can lead to incorrect evaluations of expressions that rely on these variables, potentially causing bugs in features that utilize lambda expressions with default parameters.

[/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
import unicodedata

from concurrent.futures import CancelledError
from dataclasses import dataclass
from traceback import format_exception_only
from collections import namedtuple, OrderedDict
from itertools import chain, count, starmap
from typing import List, Dict, Any, Mapping, Optional

import numpy as np

from AnyQt.QtWidgets import (
    QSizePolicy, QAbstractItemView, QComboBox, QLineEdit,
    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,
    QPushButton, QMenu, QListView, QFrame, QLabel, QMessageBox,
    QGridLayout, QWidget, QCheckBox)
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.preprocess.transformation import MappingTransform
from Orange.util import frompyfunc
from Orange.data import Variable, Table, Value, Instance
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, ftry, unique_everseen as unique
)
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets import report
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import OWWidget, Msg, Input, Output
from Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState


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

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

StringDescriptor = \
    namedtuple("StringDescriptor",
               ["name", "expression", "meta"],
               defaults=[True])


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,
            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):
    ExpressionTooltip = """
Use variable names as values in expression.
Categorical features are passed as strings
(note the change in behaviour from Orange 3.30).

""".lstrip()

    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 = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.nameedit = QLineEdit(
            placeholderText="Name...",
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.Fixed)
        )

        self.metaattributecb = QCheckBox("Meta attribute")

        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(
            chain(["Select Function"], sorted_funcs),
            chain([''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])
        )
        self.funcs_model.setParent(self)

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

        layout.addWidget(self.nameedit, 0, 0)
        layout.addWidget(self.metaattributecb, 1, 0)
        layout.addWidget(self.expressionedit, 0, 1, 1, 2)
        layout.addWidget(self.attributescb, 1, 1)
        layout.addWidget(self.functionscb, 1, 2)
        layout.addWidget(QWidget(), 2, 0)

        self.setLayout(layout)

        self.nameedit.editingFinished.connect(self._invalidate)
        self.metaattributecb.clicked.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.metaattributecb.setChecked(data.meta)
        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(),
                                 meta=self.metaattributecb.isChecked())

    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\n\n" \
                        + FeatureEditor.ExpressionTooltip

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


class DateTimeFeatureEditor(FeatureEditor):
    ExpressionTooltip = 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(),
            meta=self.metaattributecb.isChecked(),
        )


class DiscreteFeatureEditor(FeatureEditor):
    ExpressionTooltip = 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.addWidget(label, 2, 0)
        layout.addWidget(self.valuesedit, 2, 1, 1, 2)

    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(),
            meta=self.metaattributecb.isChecked(),
            values=values,
            ordered=False,
            expression=self.expressionedit.text()
        )


class StringFeatureEditor(FeatureEditor):
    ExpressionTooltip = "A string expression\n\n" \
                        + FeatureEditor.ExpressionTooltip

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.metaattributecb.setChecked(True)
        self.metaattributecb.setDisabled(True)

    def editorData(self):
        return StringDescriptor(
            name=self.nameedit.text(),
            meta=True,
            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)


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)


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, ConcurrentWidgetMixin):
    name = "Feature Constructor"
    description = "Construct new features (data columns) from a set of " \
                  "existing features in the input dataset."
    category = "Transform"
    icon = "icons/FeatureConstructor.svg"
    keywords = ['function', 'lambda']
    priority = 2240

    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)
    expressions_with_values = ContextSetting(False)
    settings_version = 3

    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: {}.")
        transform_error = Msg("{}")

    class Warning(OWWidget.Warning):
        renamed_var = Msg("Recently added variable has been renamed, "
                          "to avoid duplicates.\n")

    def __init__(self):
        super().__init__()
        ConcurrentWidgetMixin.__init__(self)
        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 generate_newname(fmt):
            return unique_name(fmt, self.reserved_names())

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

        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)
        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
        )

        layout.addWidget(self.featureview)

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

        self.fix_button = gui.button(
            self.buttonsArea, self, "Upgrade Expressions",
            callback=self.fix_expressions)
        self.fix_button.setHidden(True)
        gui.button(self.buttonsArea, self, "Send", callback=self.apply, default=True)

    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:
            self.Warning.clear()
            editor = self.editorstack.currentWidget()
            proposed = editor.editorData().name
            uniq = get_unique_names(self.reserved_names(self.currentIndex),
                                    proposed)

            feature = editor.editorData()
            if editor.editorData().name != uniq:
                self.Warning.renamed_var()
                feature = feature.__class__(uniq, *feature[1:])

            self.featuremodel[self.currentIndex] = feature
            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)

    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):
        """Set the input dataset."""
        self.closeContext()

        self.data = data
        self.expressions_with_values = False

        self.descriptors = []
        self.currentIndex = -1
        if self.data is not None:
            self.openContext(data)

        # 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.fix_button.setHidden(not self.expressions_with_values)
        self.editorstack.setEnabled(self.currentIndex >= 0)

    def handleNewSignals(self):
        if self.data is not None:
            self.apply()
        else:
            self.cancel()
            self.Outputs.data.send(None)
            self.fix_button.setHidden(True)

    def onDeleteWidget(self):
        self.shutdown()
        super().onDeleteWidget()

    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 var in attr:
            col = data.get_column(var)
            mask = ~np.isnan(col)
            grater_or_equal = np.greater_equal(
                col, len(var.values), out=mask, where=mask
            )
            if grater_or_equal.any():
                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):
        self.cancel()
        self.Error.clear()
        if self.data is None:
            return

        desc = list(self.featuremodel)
        desc = self._validate_descriptors(desc)
        self.start(run, self.data, desc, self.expressions_with_values)

    def on_done(self, result: "Result") -> None:
        data, attrs = result.data, result.attributes
        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.Outputs.data.send(data)

    def on_exception(self, ex: Exception):
        log = logging.getLogger(__name__)
        log.error("", exc_info=ex)
        self.Error.transform_error(
            "".join(format_exception_only(type(ex), ex)).rstrip(),
            exc_info=ex
        )

    def on_partial_result(self, _):
        pass

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

    def fix_expressions(self):
        dlg = QMessageBox(
            QMessageBox.Question,
            "Fix Expressions",
            "This widget's behaviour has changed. Values of categorical "
            "variables are now inserted as their textual representations "
            "(strings); previously they appeared as integer numbers, with an "
            "attribute '.value' that contained the text.\n\n"
            "The widget currently runs in compatibility mode. After "
            "expressions are updated, manually check for their correctness.")
        dlg.addButton("Update", QMessageBox.ApplyRole)
        dlg.addButton("Cancel", QMessageBox.RejectRole)
        if dlg.exec() == QMessageBox.RejectRole:
            return

        def fixer(mo):
            var = domain[mo.group(2)]
            if mo.group(3) == ".value":  # uses string; remove `.value`
                return "".join(mo.group(1, 2, 4))
            # Uses ints: get them by indexing
            return mo.group(1) + "{" + \
                   ", ".join(f"'{val}': {i}"
                             for i, val in enumerate(var.values)) + \
                   f"}}[{var.name}]" + mo.group(4)

        domain = self.data.domain
        disc_vars = "|".join(f"{var.name}"
                             for var in chain(domain.variables, domain.metas)
                             if var.is_discrete)
        expr = re.compile(r"(^|\W)(" + disc_vars + r")(\.value)?(\W|$)")
        self.descriptors[:] = [
            descriptor._replace(
                expression=expr.sub(fixer, descriptor.expression))
            for descriptor in self.descriptors]

        self.expressions_with_values = False
        self.fix_button.hide()
        index = self.currentIndex
        self.featuremodel[:] = list(self.descriptors)
        self.setCurrentIndex(index)
        self.apply()

    @classmethod
    def migrate_context(cls, context, version):
        if version is None or version < 2:
            used_vars = set(chain(*(
                freevars(ast.parse(descriptor.expression, mode="eval"), [])
                for descriptor in context.values["descriptors"]
                if descriptor.expression)))
            disc_vars = {name
                         for (name, vtype) in chain(context.attributes.items(),
                                                    context.metas.items())
                         if vtype == 1}
            if used_vars & disc_vars:
                context.values["expressions_with_values"] = True


@dataclass
class Result:
    data: Table
    attributes: List[Variable]
    metas: List[Variable]


def run(data: Table, desc, use_values, task: TaskState) -> Result:
    if task.is_interruption_requested():
        raise CancelledError  # pragma: no cover
    new_variables, new_metas = construct_variables(desc, data, use_values)
    # Explicit cancellation point after `construct_variables` which can
    # already run `compute_value`.
    if task.is_interruption_requested():
        raise CancelledError  # pragma: no cover
    new_domain = Orange.data.Domain(
        data.domain.attributes + new_variables,
        data.domain.class_vars,
        metas=data.domain.metas + new_metas
    )
    try:
        for variable in new_variables:
            variable.compute_value.mask_exceptions = False
        data = data.transform(new_domain)
    finally:
        for variable in new_variables:
            variable.compute_value.mask_exceptions = True
    return Result(data, new_variables, new_metas)


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, use_values=False):
    # subs
    variables = []
    metas = []
    source_vars = data.domain.variables + data.domain.metas
    for desc in descriptions:
        desc, func = bind_variable(desc, source_vars, data, use_values)
        var = make_variable(desc, func)
        [variables, metas][desc.meta].append(var)
    return tuple(variables), tuple(metas)


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


def bind_variable(descriptor, env, data, use_values):
    """
    (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 = {unicodedata.normalize("NFKC", 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
    dtype = object if isinstance(descriptor, StringDescriptor) else float

    if isinstance(descriptor, DiscreteDescriptor):
        if not descriptor.values:
            str_func = FeatureFunc(descriptor.expression, source_vars,
                                   use_values=use_values)
            values = sorted({str(x) for x in str_func(data)})
            values = {name: i for i, name in enumerate(values)}
            descriptor = descriptor._replace(values=values)
            cast = MappingTransformCast(values)
        else:
            values = [sanitized_name(v) for v in descriptor.values]
            values = {name: i for i, name in enumerate(values)}

    if isinstance(descriptor, DateTimeDescriptor):
        cast = DateTimeCast()

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


_parse_datetime = Orange.data.TimeVariable("_").parse
_cast_datetime_num_types = (int, float)


def cast_datetime(e):
    if isinstance(e, _cast_datetime_num_types):
        return e
    if e == "" or e is None:
        return np.nan
    return _parse_datetime(e)


_cast_datetime = frompyfunc(cast_datetime, 1, 1, dtype=float)


class DateTimeCast:
    def __call__(self, values):
        return _cast_datetime(values)

    def __eq__(self, other):
        return isinstance(other, DateTimeCast)

    def __hash__(self):
        return hash(cast_datetime)


class MappingTransformCast:
    def __init__(self, mapping: Mapping):
        self.t = MappingTransform(None, mapping)

    def __reduce_ex__(self, protocol):
        return type(self), (self.t.mapping, )

    def __call__(self, values):
        return self.t.transform(values)

    def __eq__(self, other):
        return isinstance(other, MappingTransformCast) and self.t == other.t

    def __hash__(self):
        return hash(self.t)


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)
    """
    dtype: Optional['DType'] = None

    def __init__(self, expression, args, extra_env=None, cast=None, use_values=False,
                 dtype=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
        self.use_values = use_values
        self.dtype = dtype

    def __call__(self, table, *_):
        if isinstance(table, Table):
            return self.__call_table(table)
        else:
            return self.__call_instance(table)

    def __call_table(self, table):
        try:
            cols = [self.extract_column(table, var) for _, var in self.args]
        except ValueError:
            if self.mask_exceptions:
                return np.full(len(table), np.nan)
            else:
                raise

        if not cols:
            args = [()] * len(table)
        else:
            args = zip(*cols)
        f = self.func
        if self.mask_exceptions:
            y = list(starmap(ftry(f, Exception, np.nan), args))
        else:
            y = list(starmap(f, args))
        if self.cast is not None:
            y = self.cast(y)
        return np.asarray(y, dtype=self.dtype)

    def __call_instance(self, instance: Instance):
        table = Table.from_numpy(
            instance.domain,
            np.array([instance.x]),
            np.array([instance.y]),
            np.array([instance.metas]),
        )
        return self.__call_table(table)[0]

    def extract_column(self, table: Table, var: Variable):
        data = table.get_column(var)
        if var.is_string:
            return data
        elif var.is_discrete and not self.use_values:
            values = np.array([*var.values, None], dtype=object)
            idx = data.astype(int)
            idx[~np.isfinite(data)] = len(values) - 1
            return values[idx].tolist()
        elif var.is_time:  # time always needs Values due to str(val) formatting
            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access
        elif not self.use_values:
            return data.tolist()
        else:
            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access

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

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

    def __hash__(self):
        return hash((self.expression, tuple(self.args),
                     tuple(sorted(self.extra_env.items())), self.cast))

    def __eq__(self, other):
        return type(self) is type(other) \
            and self.expression == other.expression and self.args == other.args \
            and self.extra_env == other.extra_env and self.cast == other.cast


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:** Missing Free Variables in Lambda Expressions with Default Parameters

**Description:**
When analyzing lambda expressions that include default parameters, some free variables are not correctly identified. Specifically, variables used in default arguments are not being recognized as free variables, which can lead to unexpected behavior in expression evaluations.

**Example Buggy Code:**
```python
def freevars(exp, env):
    # Existing implementation...
    pass  # Placeholder for the actual implementation

# Example usage
expression = "lambda a, b=k: a + c"
free_variables = freevars(ast.parse(expression, '', 'eval'), [])
print(free_variables)  # Output: ['c']
```

In this example, the lambda function `lambda a, b=k: a + c` uses `k` as a default value for parameter `b` and `c` in the expression `a + c`. However, the `freevars` function only identifies `c` as a free variable, neglecting `k`.

**Expected Behavior:**
The `freevars` function should identify both `k` and `c` as free variables in the lambda expression. Therefore, the output should be:
```
['k', 'c']
```

**Actual Behavior:**
Only `c` is identified as a free variable, resulting in the output:
```
['c']
```

This discrepancy causes the test `test_free_vars` to fail, as it expects both `k` and `c` to be recognized as free variables.

**Impact:**
Failing to identify all free variables can lead to incorrect evaluations of expressions that rely on these variables, potentially causing bugs in features that utilize lambda expressions with default parameters.

[/ISSUE]
{
    "FeatureConstructorTest.test_construct_numeric_names": "PASSED",
    "FeatureConstructorTest.test_construct_placement": "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",
    "FeatureConstructorTest.test_transform_sparse": "PASSED",
    "FeatureConstructorTest.test_unicode_normalization": "PASSED",
    "TestTools.test_free_vars": "PASSED",
    "TestTools.test_validate_exp": "PASSED",
    "FeatureFuncTest.test_call": "PASSED",
    "FeatureFuncTest.test_hash_eq": "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",
    "FeatureFuncTest.test_time_str": "PASSED",
    "OWFeatureConstructorTests.test_create_variable_with_no_data": "PASSED",
    "OWFeatureConstructorTests.test_discrete_no_values": "PASSED",
    "OWFeatureConstructorTests.test_error_invalid_expression": "PASSED",
    "OWFeatureConstructorTests.test_fix_values": "PASSED",
    "OWFeatureConstructorTests.test_image_export": "PASSED",
    "OWFeatureConstructorTests.test_migration_discrete_strings": "PASSED",
    "OWFeatureConstructorTests.test_missing_strings": "PASSED",
    "OWFeatureConstructorTests.test_msg_base_class": "PASSED",
    "OWFeatureConstructorTests.test_output_domain_picklable": "PASSED",
    "OWFeatureConstructorTests.test_renaming_duplicate_vars": "PASSED",
    "OWFeatureConstructorTests.test_report": "PASSED",
    "OWFeatureConstructorTests.test_transform_error": "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 4b85d06ba..143861424 100644
--- a/Orange/widgets/data/owfeatureconstructor.py
+++ b/Orange/widgets/data/owfeatureconstructor.py
@@ -369,7 +369,7 @@ class DescriptorModel(itemmodels.PyListModel):
             return super().data(index, role)
 
 
-def freevars(exp, env):
+def freevars(exp: ast.AST, env: List[str]):
     """
     Return names of all free variables in a parsed (expression) AST.
 
@@ -402,11 +402,14 @@ def freevars(exp, 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)
+        arg_names = [a.arg for a in chain(args.posonlyargs, args.args)]
+        arg_names += [args.vararg.arg] if args.vararg else []
+        arg_names += [a.arg for a in args.kwonlyargs] if args.kwonlyargs else []
+        arg_names += [args.kwarg.arg] if args.kwarg else []
+        vars_ = chain.from_iterable(
+            freevars(e, env) for e in chain(args.defaults, args.kw_defaults)
+        )
+        return list(vars_) + freevars(exp.body, env + arg_names)
     elif etype == ast.IfExp:
         return (freevars(exp.test, env) + freevars(exp.body, env) +
                 freevars(exp.orelse, env))
@@ -420,7 +423,7 @@ def freevars(exp, env):
         vars_ = []
         for gen in exp.generators:
             target_names = freevars(gen.target, [])  # assigned names
-            vars_iter = freevars(gen.iter, env)
+            vars_iter = freevars(gen.iter, env + env_ext)
             env_ext += target_names
             vars_ifs = list(chain(*(freevars(ifexp, env + target_names)
                                     for ifexp in gen.ifs or [])))
@@ -500,7 +503,7 @@ class FeatureConstructorHandler(DomainContextHandler):
         for var in metas:
             available[sanitized_name(var)] = None
 
-        if freevars(exp_ast, available):
+        if freevars(exp_ast, list(available)):
             return False
         return True
 
@@ -944,16 +947,12 @@ 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
+    # pylint: disable=too-many-branches,too-many-return-statements
     if not isinstance(exp, ast.AST):
         raise TypeError("exp is not a 'ast.AST' instance")
 
@@ -966,12 +965,21 @@ def validate_exp(exp):
         return all(map(validate_exp, [exp.left, exp.right]))
     elif etype == ast.UnaryOp:
         return validate_exp(exp.operand)
+    elif etype == ast.Lambda:
+        return all(validate_exp(e) for e in exp.args.defaults) and \
+               all(validate_exp(e) for e in exp.args.kw_defaults) and \
+               validate_exp(exp.body)
     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 in (ast.SetComp, ast.ListComp, ast.GeneratorExp):
+        return validate_exp(exp.elt) and all(map(validate_exp, exp.generators))
+    elif etype == ast.DictComp:
+        return validate_exp(exp.key) and validate_exp(exp.value) and \
+               all(map(validate_exp, exp.generators))
     elif etype == ast.Compare:
         return all(map(validate_exp, [exp.left] + exp.comparators))
     elif etype == ast.Call:
@@ -979,7 +987,6 @@ def validate_exp(exp):
                        [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
@@ -990,7 +997,6 @@ def validate_exp(exp):
     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
@@ -1003,6 +1009,9 @@ def validate_exp(exp):
         return validate_exp(exp.value)
     elif etype == ast.keyword:
         return validate_exp(exp.value)
+    elif etype == ast.comprehension and not exp.is_async:
+        return validate_exp(exp.target) and validate_exp(exp.iter) and \
+               all(map(validate_exp, exp.ifs))
     else:
         raise ValueError(exp)
 
@@ -1173,9 +1182,9 @@ __ALLOWED = [
     "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",
+    "list", "map", "max", "memoryview", "min", "next", "object",
     "oct", "ord", "pow", "range", "repr", "reversed", "round",
-    "set", "slice", "sorted", "str", "tuple", "type",
+    "set", "slice", "sorted", "str", "sum", "tuple", "type",
     "zip"
 ]
 
@@ -1209,9 +1218,6 @@ __GLOBALS.update({
     "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),

{"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\nimport unicodedata\n\nfrom concurrent.futures import CancelledError\nfrom dataclasses import dataclass\nfrom traceback import format_exception_only\nfrom collections import namedtuple, OrderedDict\nfrom itertools import chain, count, starmap\nfrom typing import List, Dict, Any, Mapping, Optional\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import (\n    QSizePolicy, QAbstractItemView, QComboBox, QLineEdit,\n    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,\n    QPushButton, QMenu, QListView, QFrame, QLabel, QMessageBox,\n    QGridLayout, QWidget, QCheckBox)\nfrom AnyQt.QtGui import QKeySequence\nfrom AnyQt.QtCore import Qt, pyqtSignal as Signal, pyqtProperty as Property\n\nfrom orangewidget.utils.combobox import ComboBoxSearch\n\nimport Orange\nfrom Orange.preprocess.transformation import MappingTransform\nfrom Orange.util import frompyfunc\nfrom Orange.data import Variable, Table, Value, Instance\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 (\n    itemmodels, vartype, ftry, unique_everseen as unique\n)\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets import report\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\nfrom Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState\n\n\nFeatureDescriptor = \\\n    namedtuple(\"FeatureDescriptor\", [\"name\", \"expression\", \"meta\"],\n               defaults=[False])\n\nContinuousDescriptor = \\\n    namedtuple(\"ContinuousDescriptor\",\n               [\"name\", \"expression\", \"number_of_decimals\", \"meta\"],\n               defaults=[False])\nDateTimeDescriptor = \\\n    namedtuple(\"DateTimeDescriptor\",\n               [\"name\", \"expression\", \"meta\"],\n               defaults=[False])\nDiscreteDescriptor = \\\n    namedtuple(\"DiscreteDescriptor\",\n               [\"name\", \"expression\", \"values\", \"ordered\", \"meta\"],\n               defaults=[False])\n\nStringDescriptor = \\\n    namedtuple(\"StringDescriptor\",\n               [\"name\", \"expression\", \"meta\"],\n               defaults=[True])\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            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    ExpressionTooltip = \"\"\"\nUse variable names as values in expression.\nCategorical features are passed as strings\n(note the change in behaviour from Orange 3.30).\n\n\"\"\".lstrip()\n\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 = QGridLayout()\n        layout.setContentsMargins(0, 0, 0, 0)\n        self.nameedit = QLineEdit(\n            placeholderText=\"Name...\",\n            sizePolicy=QSizePolicy(QSizePolicy.Minimum,\n                                   QSizePolicy.Fixed)\n        )\n\n        self.metaattributecb = QCheckBox(\"Meta attribute\")\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            chain([\"Select Function\"], sorted_funcs),\n            chain([''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])\n        )\n        self.funcs_model.setParent(self)\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        layout.addWidget(self.nameedit, 0, 0)\n        layout.addWidget(self.metaattributecb, 1, 0)\n        layout.addWidget(self.expressionedit, 0, 1, 1, 2)\n        layout.addWidget(self.attributescb, 1, 1)\n        layout.addWidget(self.functionscb, 1, 2)\n        layout.addWidget(QWidget(), 2, 0)\n\n        self.setLayout(layout)\n\n        self.nameedit.editingFinished.connect(self._invalidate)\n        self.metaattributecb.clicked.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.metaattributecb.setChecked(data.meta)\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                                 meta=self.metaattributecb.isChecked())\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\" \\\n                        + FeatureEditor.ExpressionTooltip\n\n    def editorData(self):\n        return ContinuousDescriptor(\n            name=self.nameedit.text(),\n            expression=self.expressionedit.text(),\n            meta=self.metaattributecb.isChecked(),\n            number_of_decimals=None,\n        )\n\n\nclass DateTimeFeatureEditor(FeatureEditor):\n    ExpressionTooltip = FeatureEditor.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            meta=self.metaattributecb.isChecked(),\n        )\n\n\nclass DiscreteFeatureEditor(FeatureEditor):\n    ExpressionTooltip = FeatureEditor.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.addWidget(label, 2, 0)\n        layout.addWidget(self.valuesedit, 2, 1, 1, 2)\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            meta=self.metaattributecb.isChecked(),\n            values=values,\n            ordered=False,\n            expression=self.expressionedit.text()\n        )\n\n\nclass StringFeatureEditor(FeatureEditor):\n    ExpressionTooltip = \"A string expression\\n\\n\" \\\n                        + FeatureEditor.ExpressionTooltip\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.metaattributecb.setChecked(True)\n        self.metaattributecb.setDisabled(True)\n\n    def editorData(self):\n        return StringDescriptor(\n            name=self.nameedit.text(),\n            meta=True,\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\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\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, ConcurrentWidgetMixin):\n    name = \"Feature Constructor\"\n    description = \"Construct new features (data columns) from a set of \" \\\n                  \"existing features in the input dataset.\"\n    category = \"Transform\"\n    icon = \"icons/FeatureConstructor.svg\"\n    keywords = ['function', 'lambda']\n    priority = 2240\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    expressions_with_values = ContextSetting(False)\n    settings_version = 3\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        transform_error = Msg(\"{}\")\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        ConcurrentWidgetMixin.__init__(self)\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, meta=False))\n        )\n        disc = menu.addAction(\"Categorical\")\n        disc.triggered.connect(\n            lambda: self.addFeature(\n                DiscreteDescriptor(generate_newname(\"D{}\"), \"\", (), False, meta=False))\n        )\n        string = menu.addAction(\"Text\")\n        string.triggered.connect(\n            lambda: self.addFeature(\n                StringDescriptor(generate_newname(\"S{}\"), \"\", meta=True))\n        )\n        datetime = menu.addAction(\"Date/Time\")\n        datetime.triggered.connect(\n            lambda: self.addFeature(\n                DateTimeDescriptor(generate_newname(\"T{}\"), \"\", meta=False))\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)\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        layout.addWidget(self.featureview)\n\n        box.layout().addLayout(layout, 1)\n\n        self.fix_button = gui.button(\n            self.buttonsArea, self, \"Upgrade Expressions\",\n            callback=self.fix_expressions)\n        self.fix_button.setHidden(True)\n        gui.button(self.buttonsArea, self, \"Send\", callback=self.apply, default=True)\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            uniq = get_unique_names(self.reserved_names(self.currentIndex),\n                                    proposed)\n\n            feature = editor.editorData()\n            if editor.editorData().name != uniq:\n                self.Warning.renamed_var()\n                feature = feature.__class__(uniq, *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        self.expressions_with_values = False\n\n        self.descriptors = []\n        self.currentIndex = -1\n        if self.data is not None:\n            self.openContext(data)\n\n        # disconnect from the selection model while reseting the model\n        selmodel = self.featureview.selectionModel()\n        selmodel.selectionChanged.disconnect(self._on_selectedVariableChanged)\n\n        self.featuremodel[:] = list(self.descriptors)\n        self.setCurrentIndex(self.currentIndex)\n\n        selmodel.selectionChanged.connect(self._on_selectedVariableChanged)\n        self.fix_button.setHidden(not self.expressions_with_values)\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.cancel()\n            self.Outputs.data.send(None)\n            self.fix_button.setHidden(True)\n\n    def onDeleteWidget(self):\n        self.shutdown()\n        super().onDeleteWidget()\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 var in attr:\n            col = data.get_column(var)\n            mask = ~np.isnan(col)\n            grater_or_equal = np.greater_equal(\n                col, len(var.values), out=mask, where=mask\n            )\n            if grater_or_equal.any():\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        self.cancel()\n        self.Error.clear()\n        if self.data is None:\n            return\n\n        desc = list(self.featuremodel)\n        desc = self._validate_descriptors(desc)\n        self.start(run, self.data, desc, self.expressions_with_values)\n\n    def on_done(self, result: \"Result\") -> None:\n        data, attrs = result.data, result.attributes\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.Outputs.data.send(data)\n\n    def on_exception(self, ex: Exception):\n        log = logging.getLogger(__name__)\n        log.error(\"\", exc_info=ex)\n        self.Error.transform_error(\n            \"\".join(format_exception_only(type(ex), ex)).rstrip(),\n            exc_info=ex\n        )\n\n    def on_partial_result(self, _):\n        pass\n\n    def send_report(self):\n        items = OrderedDict()\n        for feature in self.featuremodel:\n            if isinstance(feature, DiscreteDescriptor):\n                desc = \"categorical\"\n                if feature.values:\n                    desc += \" with values \" \\\n                            + \", \".join(f\"'{val}'\" for val in feature.values)\n                if feature.ordered:\n                    desc += \"; ordered\"\n            elif isinstance(feature, ContinuousDescriptor):\n                desc = \"numeric\"\n            elif isinstance(feature, DateTimeDescriptor):\n                desc = \"date/time\"\n            else:\n                desc = \"text\"\n            items[feature.name] = f\"{feature.expression} ({desc})\"\n        self.report_items(\n            report.plural(\"Constructed feature{s}\", len(items)), items)\n\n    def fix_expressions(self):\n        dlg = QMessageBox(\n            QMessageBox.Question,\n            \"Fix Expressions\",\n            \"This widget's behaviour has changed. Values of categorical \"\n            \"variables are now inserted as their textual representations \"\n            \"(strings); previously they appeared as integer numbers, with an \"\n            \"attribute '.value' that contained the text.\\n\\n\"\n            \"The widget currently runs in compatibility mode. After \"\n            \"expressions are updated, manually check for their correctness.\")\n        dlg.addButton(\"Update\", QMessageBox.ApplyRole)\n        dlg.addButton(\"Cancel\", QMessageBox.RejectRole)\n        if dlg.exec() == QMessageBox.RejectRole:\n            return\n\n        def fixer(mo):\n            var = domain[mo.group(2)]\n            if mo.group(3) == \".value\":  # uses string; remove `.value`\n                return \"\".join(mo.group(1, 2, 4))\n            # Uses ints: get them by indexing\n            return mo.group(1) + \"{\" + \\\n                   \", \".join(f\"'{val}': {i}\"\n                             for i, val in enumerate(var.values)) + \\\n                   f\"}}[{var.name}]\" + mo.group(4)\n\n        domain = self.data.domain\n        disc_vars = \"|\".join(f\"{var.name}\"\n                             for var in chain(domain.variables, domain.metas)\n                             if var.is_discrete)\n        expr = re.compile(r\"(^|\\W)(\" + disc_vars + r\")(\\.value)?(\\W|$)\")\n        self.descriptors[:] = [\n            descriptor._replace(\n                expression=expr.sub(fixer, descriptor.expression))\n            for descriptor in self.descriptors]\n\n        self.expressions_with_values = False\n        self.fix_button.hide()\n        index = self.currentIndex\n        self.featuremodel[:] = list(self.descriptors)\n        self.setCurrentIndex(index)\n        self.apply()\n\n    @classmethod\n    def migrate_context(cls, context, version):\n        if version is None or version < 2:\n            used_vars = set(chain(*(\n                freevars(ast.parse(descriptor.expression, mode=\"eval\"), [])\n                for descriptor in context.values[\"descriptors\"]\n                if descriptor.expression)))\n            disc_vars = {name\n                         for (name, vtype) in chain(context.attributes.items(),\n                                                    context.metas.items())\n                         if vtype == 1}\n            if used_vars & disc_vars:\n                context.values[\"expressions_with_values\"] = True\n\n\n@dataclass\nclass Result:\n    data: Table\n    attributes: List[Variable]\n    metas: List[Variable]\n\n\ndef run(data: Table, desc, use_values, task: TaskState) -> Result:\n    if task.is_interruption_requested():\n        raise CancelledError  # pragma: no cover\n    new_variables, new_metas = construct_variables(desc, data, use_values)\n    # Explicit cancellation point after `construct_variables` which can\n    # already run `compute_value`.\n    if task.is_interruption_requested():\n        raise CancelledError  # pragma: no cover\n    new_domain = Orange.data.Domain(\n        data.domain.attributes + new_variables,\n        data.domain.class_vars,\n        metas=data.domain.metas + new_metas\n    )\n    try:\n        for variable in new_variables:\n            variable.compute_value.mask_exceptions = False\n        data = data.transform(new_domain)\n    finally:\n        for variable in new_variables:\n            variable.compute_value.mask_exceptions = True\n    return Result(data, new_variables, new_metas)\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, use_values=False):\n    # subs\n    variables = []\n    metas = []\n    source_vars = data.domain.variables + data.domain.metas\n    for desc in descriptions:\n        desc, func = bind_variable(desc, source_vars, data, use_values)\n        var = make_variable(desc, func)\n        [variables, metas][desc.meta].append(var)\n    return tuple(variables), tuple(metas)\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, use_values):\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 = {unicodedata.normalize(\"NFKC\", sanitized_name(v.name)): v\n                 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    dtype = object if isinstance(descriptor, StringDescriptor) else float\n\n    if isinstance(descriptor, DiscreteDescriptor):\n        if not descriptor.values:\n            str_func = FeatureFunc(descriptor.expression, source_vars,\n                                   use_values=use_values)\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            cast = MappingTransformCast(values)\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        cast = DateTimeCast()\n\n    func = FeatureFunc(descriptor.expression, source_vars, values, cast,\n                       use_values=use_values, dtype=dtype)\n    return descriptor, func\n\n\n_parse_datetime = Orange.data.TimeVariable(\"_\").parse\n_cast_datetime_num_types = (int, float)\n\n\ndef cast_datetime(e):\n    if isinstance(e, _cast_datetime_num_types):\n        return e\n    if e == \"\" or e is None:\n        return np.nan\n    return _parse_datetime(e)\n\n\n_cast_datetime = frompyfunc(cast_datetime, 1, 1, dtype=float)\n\n\nclass DateTimeCast:\n    def __call__(self, values):\n        return _cast_datetime(values)\n\n    def __eq__(self, other):\n        return isinstance(other, DateTimeCast)\n\n    def __hash__(self):\n        return hash(cast_datetime)\n\n\nclass MappingTransformCast:\n    def __init__(self, mapping: Mapping):\n        self.t = MappingTransform(None, mapping)\n\n    def __reduce_ex__(self, protocol):\n        return type(self), (self.t.mapping, )\n\n    def __call__(self, values):\n        return self.t.transform(values)\n\n    def __eq__(self, other):\n        return isinstance(other, MappingTransformCast) and self.t == other.t\n\n    def __hash__(self):\n        return hash(self.t)\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    dtype: Optional['DType'] = None\n\n    def __init__(self, expression, args, extra_env=None, cast=None, use_values=False,\n                 dtype=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        self.use_values = use_values\n        self.dtype = dtype\n\n    def __call__(self, table, *_):\n        if isinstance(table, Table):\n            return self.__call_table(table)\n        else:\n            return self.__call_instance(table)\n\n    def __call_table(self, table):\n        try:\n            cols = [self.extract_column(table, var) for _, var in self.args]\n        except ValueError:\n            if self.mask_exceptions:\n                return np.full(len(table), np.nan)\n            else:\n                raise\n\n        if not cols:\n            args = [()] * len(table)\n        else:\n            args = zip(*cols)\n        f = self.func\n        if self.mask_exceptions:\n            y = list(starmap(ftry(f, Exception, np.nan), args))\n        else:\n            y = list(starmap(f, args))\n        if self.cast is not None:\n            y = self.cast(y)\n        return np.asarray(y, dtype=self.dtype)\n\n    def __call_instance(self, instance: Instance):\n        table = Table.from_numpy(\n            instance.domain,\n            np.array([instance.x]),\n            np.array([instance.y]),\n            np.array([instance.metas]),\n        )\n        return self.__call_table(table)[0]\n\n    def extract_column(self, table: Table, var: Variable):\n        data = table.get_column(var)\n        if var.is_string:\n            return data\n        elif var.is_discrete and not self.use_values:\n            values = np.array([*var.values, None], dtype=object)\n            idx = data.astype(int)\n            idx[~np.isfinite(data)] = len(values) - 1\n            return values[idx].tolist()\n        elif var.is_time:  # time always needs Values due to str(val) formatting\n            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access\n        elif not self.use_values:\n            return data.tolist()\n        else:\n            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access\n\n    def __reduce__(self):\n        return type(self), (self.expression, self.args,\n                            self.extra_env, self.cast, self.use_values,\n                            self.dtype)\n\n    def __repr__(self):\n        return \"{0.__name__}{1!r}\".format(*self.__reduce__())\n\n    def __hash__(self):\n        return hash((self.expression, tuple(self.args),\n                     tuple(sorted(self.extra_env.items())), self.cast))\n\n    def __eq__(self, other):\n        return type(self) is type(other) \\\n            and self.expression == other.expression and self.args == other.args \\\n            and self.extra_env == other.extra_env and self.cast == other.cast\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:** Missing Free Variables in Lambda Expressions with Default Parameters

**Description:**
When analyzing lambda expressions that include default parameters, some free variables are not correctly identified. Specifically, variables used in default arguments are not being recognized as free variables, which can lead to unexpected behavior in expression evaluations.

**Example Buggy Code:**
```python
def freevars(exp, env):
    # Existing implementation...
    pass  # Placeholder for the actual implementation

# Example usage
expression = "lambda a, b=k: a + c"
free_variables = freevars(ast.parse(expression, '', 'eval'), [])
print(free_variables)  # Output: ['c']
```

In this example, the lambda function `lambda a, b=k: a + c` uses `k` as a default value for parameter `b` and `c` in the expression `a + c`. However, the `freevars` function only identifies `c` as a free variable, neglecting `k`.

**Expected Behavior:**
The `freevars` function should identify both `k` and `c` as free variables in the lambda expression. Therefore, the output should be:
```
['k', 'c']
```

**Actual Behavior:**
Only `c` is identified as a free variable, resulting in the output:
```
['c']
```

This discrepancy causes the test `test_free_vars` to fail, as it expects both `k` and `c` to be recognized as free variables.

**Impact:**
Failing to identify all free variables can lead to incorrect evaluations of expressions that rely on these variables, potentially causing bugs in features that utilize lambda expressions with default parameters.

[/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
import unicodedata

from concurrent.futures import CancelledError
from dataclasses import dataclass
from traceback import format_exception_only
from collections import namedtuple, OrderedDict
from itertools import chain, count, starmap
from typing import List, Dict, Any, Mapping, Optional

import numpy as np

from AnyQt.QtWidgets import (
    QSizePolicy, QAbstractItemView, QComboBox, QLineEdit,
    QHBoxLayout, QVBoxLayout, QStackedWidget, QStyledItemDelegate,
    QPushButton, QMenu, QListView, QFrame, QLabel, QMessageBox,
    QGridLayout, QWidget, QCheckBox)
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.preprocess.transformation import MappingTransform
from Orange.util import frompyfunc
from Orange.data import Variable, Table, Value, Instance
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, ftry, unique_everseen as unique
)
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets import report
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import OWWidget, Msg, Input, Output
from Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState


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

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

StringDescriptor = \
    namedtuple("StringDescriptor",
               ["name", "expression", "meta"],
               defaults=[True])


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,
            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):
    ExpressionTooltip = """
Use variable names as values in expression.
Categorical features are passed as strings
(note the change in behaviour from Orange 3.30).

""".lstrip()

    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 = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.nameedit = QLineEdit(
            placeholderText="Name...",
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.Fixed)
        )

        self.metaattributecb = QCheckBox("Meta attribute")

        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(
            chain(["Select Function"], sorted_funcs),
            chain([''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs])
        )
        self.funcs_model.setParent(self)

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

        layout.addWidget(self.nameedit, 0, 0)
        layout.addWidget(self.metaattributecb, 1, 0)
        layout.addWidget(self.expressionedit, 0, 1, 1, 2)
        layout.addWidget(self.attributescb, 1, 1)
        layout.addWidget(self.functionscb, 1, 2)
        layout.addWidget(QWidget(), 2, 0)

        self.setLayout(layout)

        self.nameedit.editingFinished.connect(self._invalidate)
        self.metaattributecb.clicked.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.metaattributecb.setChecked(data.meta)
        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(),
                                 meta=self.metaattributecb.isChecked())

    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\n\n" \
                        + FeatureEditor.ExpressionTooltip

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


class DateTimeFeatureEditor(FeatureEditor):
    ExpressionTooltip = 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(),
            meta=self.metaattributecb.isChecked(),
        )


class DiscreteFeatureEditor(FeatureEditor):
    ExpressionTooltip = 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.addWidget(label, 2, 0)
        layout.addWidget(self.valuesedit, 2, 1, 1, 2)

    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(),
            meta=self.metaattributecb.isChecked(),
            values=values,
            ordered=False,
            expression=self.expressionedit.text()
        )


class StringFeatureEditor(FeatureEditor):
    ExpressionTooltip = "A string expression\n\n" \
                        + FeatureEditor.ExpressionTooltip

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.metaattributecb.setChecked(True)
        self.metaattributecb.setDisabled(True)

    def editorData(self):
        return StringDescriptor(
            name=self.nameedit.text(),
            meta=True,
            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)


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)


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, ConcurrentWidgetMixin):
    name = "Feature Constructor"
    description = "Construct new features (data columns) from a set of " \
                  "existing features in the input dataset."
    category = "Transform"
    icon = "icons/FeatureConstructor.svg"
    keywords = ['function', 'lambda']
    priority = 2240

    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)
    expressions_with_values = ContextSetting(False)
    settings_version = 3

    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: {}.")
        transform_error = Msg("{}")

    class Warning(OWWidget.Warning):
        renamed_var = Msg("Recently added variable has been renamed, "
                          "to avoid duplicates.\n")

    def __init__(self):
        super().__init__()
        ConcurrentWidgetMixin.__init__(self)
        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 generate_newname(fmt):
            return unique_name(fmt, self.reserved_names())

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

        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)
        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
        )

        layout.addWidget(self.featureview)

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

        self.fix_button = gui.button(
            self.buttonsArea, self, "Upgrade Expressions",
            callback=self.fix_expressions)
        self.fix_button.setHidden(True)
        gui.button(self.buttonsArea, self, "Send", callback=self.apply, default=True)

    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:
            self.Warning.clear()
            editor = self.editorstack.currentWidget()
            proposed = editor.editorData().name
            uniq = get_unique_names(self.reserved_names(self.currentIndex),
                                    proposed)

            feature = editor.editorData()
            if editor.editorData().name != uniq:
                self.Warning.renamed_var()
                feature = feature.__class__(uniq, *feature[1:])

            self.featuremodel[self.currentIndex] = feature
            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)

    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):
        """Set the input dataset."""
        self.closeContext()

        self.data = data
        self.expressions_with_values = False

        self.descriptors = []
        self.currentIndex = -1
        if self.data is not None:
            self.openContext(data)

        # 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.fix_button.setHidden(not self.expressions_with_values)
        self.editorstack.setEnabled(self.currentIndex >= 0)

    def handleNewSignals(self):
        if self.data is not None:
            self.apply()
        else:
            self.cancel()
            self.Outputs.data.send(None)
            self.fix_button.setHidden(True)

    def onDeleteWidget(self):
        self.shutdown()
        super().onDeleteWidget()

    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 var in attr:
            col = data.get_column(var)
            mask = ~np.isnan(col)
            grater_or_equal = np.greater_equal(
                col, len(var.values), out=mask, where=mask
            )
            if grater_or_equal.any():
                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):
        self.cancel()
        self.Error.clear()
        if self.data is None:
            return

        desc = list(self.featuremodel)
        desc = self._validate_descriptors(desc)
        self.start(run, self.data, desc, self.expressions_with_values)

    def on_done(self, result: "Result") -> None:
        data, attrs = result.data, result.attributes
        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.Outputs.data.send(data)

    def on_exception(self, ex: Exception):
        log = logging.getLogger(__name__)
        log.error("", exc_info=ex)
        self.Error.transform_error(
            "".join(format_exception_only(type(ex), ex)).rstrip(),
            exc_info=ex
        )

    def on_partial_result(self, _):
        pass

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

    def fix_expressions(self):
        dlg = QMessageBox(
            QMessageBox.Question,
            "Fix Expressions",
            "This widget's behaviour has changed. Values of categorical "
            "variables are now inserted as their textual representations "
            "(strings); previously they appeared as integer numbers, with an "
            "attribute '.value' that contained the text.\n\n"
            "The widget currently runs in compatibility mode. After "
            "expressions are updated, manually check for their correctness.")
        dlg.addButton("Update", QMessageBox.ApplyRole)
        dlg.addButton("Cancel", QMessageBox.RejectRole)
        if dlg.exec() == QMessageBox.RejectRole:
            return

        def fixer(mo):
            var = domain[mo.group(2)]
            if mo.group(3) == ".value":  # uses string; remove `.value`
                return "".join(mo.group(1, 2, 4))
            # Uses ints: get them by indexing
            return mo.group(1) + "{" + \
                   ", ".join(f"'{val}': {i}"
                             for i, val in enumerate(var.values)) + \
                   f"}}[{var.name}]" + mo.group(4)

        domain = self.data.domain
        disc_vars = "|".join(f"{var.name}"
                             for var in chain(domain.variables, domain.metas)
                             if var.is_discrete)
        expr = re.compile(r"(^|\W)(" + disc_vars + r")(\.value)?(\W|$)")
        self.descriptors[:] = [
            descriptor._replace(
                expression=expr.sub(fixer, descriptor.expression))
            for descriptor in self.descriptors]

        self.expressions_with_values = False
        self.fix_button.hide()
        index = self.currentIndex
        self.featuremodel[:] = list(self.descriptors)
        self.setCurrentIndex(index)
        self.apply()

    @classmethod
    def migrate_context(cls, context, version):
        if version is None or version < 2:
            used_vars = set(chain(*(
                freevars(ast.parse(descriptor.expression, mode="eval"), [])
                for descriptor in context.values["descriptors"]
                if descriptor.expression)))
            disc_vars = {name
                         for (name, vtype) in chain(context.attributes.items(),
                                                    context.metas.items())
                         if vtype == 1}
            if used_vars & disc_vars:
                context.values["expressions_with_values"] = True


@dataclass
class Result:
    data: Table
    attributes: List[Variable]
    metas: List[Variable]


def run(data: Table, desc, use_values, task: TaskState) -> Result:
    if task.is_interruption_requested():
        raise CancelledError  # pragma: no cover
    new_variables, new_metas = construct_variables(desc, data, use_values)
    # Explicit cancellation point after `construct_variables` which can
    # already run `compute_value`.
    if task.is_interruption_requested():
        raise CancelledError  # pragma: no cover
    new_domain = Orange.data.Domain(
        data.domain.attributes + new_variables,
        data.domain.class_vars,
        metas=data.domain.metas + new_metas
    )
    try:
        for variable in new_variables:
            variable.compute_value.mask_exceptions = False
        data = data.transform(new_domain)
    finally:
        for variable in new_variables:
            variable.compute_value.mask_exceptions = True
    return Result(data, new_variables, new_metas)


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, use_values=False):
    # subs
    variables = []
    metas = []
    source_vars = data.domain.variables + data.domain.metas
    for desc in descriptions:
        desc, func = bind_variable(desc, source_vars, data, use_values)
        var = make_variable(desc, func)
        [variables, metas][desc.meta].append(var)
    return tuple(variables), tuple(metas)


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


def bind_variable(descriptor, env, data, use_values):
    """
    (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 = {unicodedata.normalize("NFKC", 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
    dtype = object if isinstance(descriptor, StringDescriptor) else float

    if isinstance(descriptor, DiscreteDescriptor):
        if not descriptor.values:
            str_func = FeatureFunc(descriptor.expression, source_vars,
                                   use_values=use_values)
            values = sorted({str(x) for x in str_func(data)})
            values = {name: i for i, name in enumerate(values)}
            descriptor = descriptor._replace(values=values)
            cast = MappingTransformCast(values)
        else:
            values = [sanitized_name(v) for v in descriptor.values]
            values = {name: i for i, name in enumerate(values)}

    if isinstance(descriptor, DateTimeDescriptor):
        cast = DateTimeCast()

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


_parse_datetime = Orange.data.TimeVariable("_").parse
_cast_datetime_num_types = (int, float)


def cast_datetime(e):
    if isinstance(e, _cast_datetime_num_types):
        return e
    if e == "" or e is None:
        return np.nan
    return _parse_datetime(e)


_cast_datetime = frompyfunc(cast_datetime, 1, 1, dtype=float)


class DateTimeCast:
    def __call__(self, values):
        return _cast_datetime(values)

    def __eq__(self, other):
        return isinstance(other, DateTimeCast)

    def __hash__(self):
        return hash(cast_datetime)


class MappingTransformCast:
    def __init__(self, mapping: Mapping):
        self.t = MappingTransform(None, mapping)

    def __reduce_ex__(self, protocol):
        return type(self), (self.t.mapping, )

    def __call__(self, values):
        return self.t.transform(values)

    def __eq__(self, other):
        return isinstance(other, MappingTransformCast) and self.t == other.t

    def __hash__(self):
        return hash(self.t)


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)
    """
    dtype: Optional['DType'] = None

    def __init__(self, expression, args, extra_env=None, cast=None, use_values=False,
                 dtype=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
        self.use_values = use_values
        self.dtype = dtype

    def __call__(self, table, *_):
        if isinstance(table, Table):
            return self.__call_table(table)
        else:
            return self.__call_instance(table)

    def __call_table(self, table):
        try:
            cols = [self.extract_column(table, var) for _, var in self.args]
        except ValueError:
            if self.mask_exceptions:
                return np.full(len(table), np.nan)
            else:
                raise

        if not cols:
            args = [()] * len(table)
        else:
            args = zip(*cols)
        f = self.func
        if self.mask_exceptions:
            y = list(starmap(ftry(f, Exception, np.nan), args))
        else:
            y = list(starmap(f, args))
        if self.cast is not None:
            y = self.cast(y)
        return np.asarray(y, dtype=self.dtype)

    def __call_instance(self, instance: Instance):
        table = Table.from_numpy(
            instance.domain,
            np.array([instance.x]),
            np.array([instance.y]),
            np.array([instance.metas]),
        )
        return self.__call_table(table)[0]

    def extract_column(self, table: Table, var: Variable):
        data = table.get_column(var)
        if var.is_string:
            return data
        elif var.is_discrete and not self.use_values:
            values = np.array([*var.values, None], dtype=object)
            idx = data.astype(int)
            idx[~np.isfinite(data)] = len(values) - 1
            return values[idx].tolist()
        elif var.is_time:  # time always needs Values due to str(val) formatting
            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access
        elif not self.use_values:
            return data.tolist()
        else:
            return Value._as_values(var, data.tolist())  # pylint: disable=protected-access

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

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

    def __hash__(self):
        return hash((self.expression, tuple(self.args),
                     tuple(sorted(self.extra_env.items())), self.cast))

    def __eq__(self, other):
        return type(self) is type(other) \
            and self.expression == other.expression and self.args == other.args \
            and self.extra_env == other.extra_env and self.cast == other.cast


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