{
    "file_diffs": [
        {
            "old_file_content": "# -*- coding: utf-8 -*-\n\"\"\"\nCSV File Import Widget\n----------------------\n\n\"\"\"\nimport sys\nimport os\nimport csv\nimport enum\nimport io\nimport traceback\nimport warnings\nimport logging\nimport weakref\nimport json\n\nimport gzip\nimport lzma\nimport bz2\nimport zipfile\n\nfrom xml.sax.saxutils import escape\nfrom functools import singledispatch\nfrom contextlib import ExitStack\n\nimport typing\nfrom typing import (\n    List, Tuple, Dict, Optional, Any, Callable, Iterable, Hashable,\n    Union, AnyStr, BinaryIO\n)\n\nfrom PyQt5.QtCore import (\n    Qt, QFileInfo, QTimer, QSettings, QObject, QSize, QMimeDatabase, QMimeType\n)\nfrom PyQt5.QtGui import QStandardItem, QStandardItemModel, QPalette\nfrom PyQt5.QtWidgets import (\n    QLabel, QComboBox, QPushButton, QDialog, QDialogButtonBox, QGridLayout,\n    QVBoxLayout, QSizePolicy, QStyle, QFileIconProvider, QFileDialog,\n    QApplication, QMessageBox, QTextBrowser\n)\nfrom PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal\n\nimport numpy as np\nimport pandas.errors\nimport pandas as pd\n\nfrom pandas.api import types as pdtypes\n\nimport Orange.data\n\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils.concurrent import PyOwned\nfrom Orange.widgets.utils import textimport, concurrent as qconcurrent\nfrom Orange.widgets.utils.overlay import OverlayWidget\nfrom Orange.widgets.utils.settings import (\n    QSettings_readArray, QSettings_writeArray\n)\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nif typing.TYPE_CHECKING:\n    # pylint: disable=invalid-name\n    T = typing.TypeVar(\"T\")\n    K = typing.TypeVar(\"K\")\n\n__all__ = [\"OWCSVFileImport\"]\n\n_log = logging.getLogger(__name__)\n\nColumnType = textimport.ColumnType\nRowSpec = textimport.RowSpec\n\n\ndef enum_lookup(enumtype, name):\n    # type: (typing.Type[T], str) -> Optional[T]\n    \"\"\"\n    Return an value from `enumtype` by its symbolic name or None if not found.\n    \"\"\"\n    try:\n        return enumtype[name]\n    except LookupError:\n        return None\n\n\ndef dialect_eq(lhs, rhs):\n    # type: (csv.Dialect, csv.Dialect) -> bool\n    \"\"\"Compare 2 `csv.Dialect` instances for equality.\"\"\"\n    return (lhs.delimiter == rhs.delimiter and\n            lhs.quotechar == rhs.quotechar and\n            lhs.doublequote == rhs.doublequote and\n            lhs.escapechar == rhs.escapechar and\n            lhs.quoting == rhs.quoting and\n            lhs.skipinitialspace == rhs.skipinitialspace)\n\n\nclass Options:\n    \"\"\"\n    Stored options for loading CSV-like file.\n\n    Arguments\n    ---------\n    encoding : str\n        A encoding to use for reading.\n    dialect : csv.Dialect\n        A csv.Dialect instance.\n    columntypes: Iterable[Tuple[range, ColumnType]]\n        A list of column type ranges specifying the types for columns.\n        Need not list all columns. Columns not listed are assumed to have auto\n        type inference.\n    rowspec : Iterable[Tuple[range, RowSpec]]\n         A list of row spec ranges.\n    decimal_separator : str\n        Decimal separator - a single character string; default: `\".\"`\n    group_separator : str\n        Thousands group separator - empty or a single character string;\n        default: empty string\n    \"\"\"\n    RowSpec = RowSpec\n    ColumnType = ColumnType\n\n    def __init__(self, encoding='utf-8', dialect=csv.excel(), columntypes=[],\n                 rowspec=((range(0, 1), RowSpec.Header),),\n                 decimal_separator=\".\", group_separator=\"\"):\n        # type: (str, csv.Dialect, List[Tuple[range, ColumnType]], ...) -> None\n        self.encoding = encoding\n        self.dialect = dialect\n        self.columntypes = list(columntypes)\n        self.rowspec = list(rowspec)  # type: List[Tuple[range, Options.RowSpec]]\n        self.decimal_separator = decimal_separator\n        self.group_separator = group_separator\n\n    def __eq__(self, other):\n        # type: (Options) -> bool\n        \"\"\"\n        Compare this instance to `other` for equality.\n        \"\"\"\n        if isinstance(other, Options):\n            return (dialect_eq(self.dialect, other.dialect) and\n                    self.encoding == other.encoding and\n                    self.columntypes == other.columntypes and\n                    self.rowspec == other.rowspec and\n                    self.group_separator == other.group_separator and\n                    self.decimal_separator == other.decimal_separator)\n        else:\n            return NotImplemented\n\n    def __repr__(self):\n        class_, args = self.__reduce__()\n        return \"{}{!r}\".format(class_.__name__, args)\n    __str__ = __repr__\n\n    def __reduce__(self):\n        return type(self), (self.encoding, self.dialect,\n                            self.columntypes, self.rowspec)\n\n    def as_dict(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Return return Option parameters as plain types suitable for\n        serialization (e.g JSON serializable).\n        \"\"\"\n        return {\n            \"encoding\": self.encoding,\n            \"delimiter\": self.dialect.delimiter,\n            \"quotechar\": self.dialect.quotechar,\n            \"doublequote\": self.dialect.doublequote,\n            \"skipinitialspace\": self.dialect.skipinitialspace,\n            \"quoting\": self.dialect.quoting,\n            \"columntypes\": Options.spec_as_encodable(self.columntypes),\n            \"rowspec\": Options.spec_as_encodable(self.rowspec),\n            \"decimal_separator\": self.decimal_separator,\n            \"group_separator\": self.group_separator,\n        }\n\n    @staticmethod\n    def from_dict(mapping):\n        # type: (Dict[str, Any]) -> Options\n        \"\"\"\n        Reconstruct a `Options` from a plain dictionary (see :func:`as_dict`).\n        \"\"\"\n        encoding = mapping[\"encoding\"]\n        delimiter = mapping[\"delimiter\"]\n        quotechar = mapping[\"quotechar\"]\n        doublequote = mapping[\"doublequote\"]\n        quoting = mapping[\"quoting\"]\n        skipinitialspace = mapping[\"skipinitialspace\"]\n\n        dialect = textimport.Dialect(\n            delimiter, quotechar, None, doublequote, skipinitialspace,\n            quoting=quoting)\n\n        colspec = mapping[\"columntypes\"]\n        rowspec = mapping[\"rowspec\"]\n        colspec = Options.spec_from_encodable(colspec, ColumnType)\n        rowspec = Options.spec_from_encodable(rowspec, RowSpec)\n        decimal = mapping.get(\"decimal_separator\", \".\")\n        group = mapping.get(\"group_separator\", \"\")\n\n        return Options(encoding, dialect, colspec, rowspec,\n                       decimal_separator=decimal,\n                       group_separator=group)\n\n    @staticmethod\n    def spec_as_encodable(spec):\n        # type: (List[Tuple[range, enum.Enum]]) -> List[Dict[str, Any]]\n        return [{\"start\": r.start, \"stop\": r.stop, \"value\": value.name}\n                for r, value in spec]\n\n    @staticmethod\n    def spec_from_encodable(spec, enumtype):\n        # type: (List[Dict[str, Any]], typing.Type[T]) -> List[Tuple[range, T]]\n        r = []\n        for v in spec:\n            try:\n                start, stop, name = v[\"start\"], v[\"stop\"], v[\"value\"]\n            except (KeyError, ValueError):\n                pass\n            else:\n                r.append((range(start, stop), enum_lookup(enumtype, name)))\n        return r\n\n\nclass CSVImportDialog(QDialog):\n    \"\"\"\n    A dialog for selecting CSV file import options.\n    \"\"\"\n    def __init__(self, parent=None, flags=Qt.Dialog, **kwargs):\n        super().__init__(parent, flags, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self._options = None\n        self._path = None\n        # Finalizer for opened file handle (in _update_preview)\n        self.__finalizer = None  # type: Optional[Callable[[], None]]\n        self._optionswidget = textimport.CSVImportWidget()\n        self._optionswidget.previewReadErrorOccurred.connect(\n            self.__on_preview_error\n        )\n        self._optionswidget.previewModelReset.connect(\n            self.__on_preview_reset\n        )\n        self._buttons = buttons = QDialogButtonBox(\n            orientation=Qt.Horizontal,\n            standardButtons=(QDialogButtonBox.Ok | QDialogButtonBox.Cancel |\n                             QDialogButtonBox.Reset |\n                             QDialogButtonBox.RestoreDefaults),\n            objectName=\"dialog-button-box\",\n        )\n        # TODO: Help button\n        buttons.accepted.connect(self.accept)\n        buttons.rejected.connect(self.reject)\n\n        b = buttons.button(QDialogButtonBox.Reset)\n        b.clicked.connect(self.reset)\n        b = buttons.button(QDialogButtonBox.RestoreDefaults)\n        b.clicked.connect(self.restoreDefaults)\n        self.layout().addWidget(self._optionswidget)\n        self.layout().addWidget(buttons)\n        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)\n\n        self._overlay = OverlayWidget(self)\n        self._overlay.setWidget(self._optionswidget.dataview)\n        self._overlay.setLayout(QVBoxLayout())\n        self._overlay.layout().addWidget(QLabel(wordWrap=True))\n        self._overlay.hide()\n\n    def setOptions(self, options):\n        # type: (Options) -> None\n        self._options = options\n        self._optionswidget.setEncoding(options.encoding)\n        self._optionswidget.setDialect(options.dialect)\n        self._optionswidget.setNumbersFormat(\n            options.group_separator, options.decimal_separator)\n        self._optionswidget.setColumnTypeRanges(options.columntypes)\n        self._optionswidget.setRowStates(\n            {i: v for r, v in options.rowspec for i in r}\n        )\n\n    def options(self):\n        # type: () -> Options\n        rowspec_ = self._optionswidget.rowStates()\n        rowspec = [(range(i, i + 1), v) for i, v in rowspec_.items()]\n        numformat = self._optionswidget.numbersFormat()\n        return Options(\n            encoding=self._optionswidget.encoding(),\n            dialect=self._optionswidget.dialect(),\n            columntypes=self._optionswidget.columnTypeRanges(),\n            rowspec=rowspec,\n            decimal_separator=numformat[\"decimal\"],\n            group_separator=numformat[\"group\"],\n        )\n\n    def setPath(self, path):\n        \"\"\"\n        Set the preview path.\n        \"\"\"\n        if self._path != path:\n            self._path = path\n            self.__update_preview()\n\n    def path(self):\n        \"\"\"Return the preview path\"\"\"\n        return self._path\n\n    def reset(self):\n        \"\"\"\n        Reset the options to the state previously set with `setOptions`\n        effectively undoing any user modifications since then.\n        \"\"\"\n        self.setOptions(self._options)\n\n    def restoreDefaults(self):\n        \"\"\"\n        Restore the options to default state.\n        \"\"\"\n        # preserve `_options` if set by clients (for `reset`).\n        opts = self._options\n        self.setOptions(Options(\"utf-8\", csv.excel()))\n        self._options = opts\n\n    def __update_preview(self):\n        if not self._path:\n            return\n        try:\n            f = _open(self._path, \"rb\")\n        except OSError as err:\n            traceback.print_exc(file=sys.stderr)\n            fmt = \"\".join(traceback.format_exception_only(type(err), err))\n            self.__set_error(fmt)\n        else:\n            self.__clear_error()\n            self._optionswidget.setSampleContents(f)\n            closeexisting = self.__finalizer\n            if closeexisting is not None:\n                self.destroyed.disconnect(closeexisting)\n                closeexisting()\n            self.__finalizer = weakref.finalize(self, f.close)\n            self.destroyed.connect(self.__finalizer)\n\n    def __set_error(self, text, format=Qt.PlainText):\n        self._optionswidget.setEnabled(False)\n        label = self._overlay.findChild(QLabel)  # type: QLabel\n        label.setText(text)\n        label.setTextFormat(format)\n        self._overlay.show()\n        self._overlay.raise_()\n        dialog_button_box_set_enabled(self._buttons, False)\n\n    def __clear_error(self):\n        if self._overlay.isVisibleTo(self):\n            self._overlay.hide()\n            self._optionswidget.setEnabled(True)\n\n    # Enable/disable the accept buttons on the most egregious errors.\n    def __on_preview_error(self):\n        b = self._buttons.button(QDialogButtonBox.Ok)\n        b.setEnabled(False)\n\n    def __on_preview_reset(self):\n        b = self._buttons.button(QDialogButtonBox.Ok)\n        b.setEnabled(True)\n\n\ndef dialog_button_box_set_enabled(buttonbox, enabled):\n    # type: (QDialogButtonBox, bool) -> None\n    \"\"\"\n    Disable/enable buttons in a QDialogButtonBox based on their role.\n\n    All buttons except the ones with RejectRole or HelpRole are disabled.\n    \"\"\"\n    stashname = \"__p_dialog_button_box_set_enabled\"\n    for b in buttonbox.buttons():\n        role = buttonbox.buttonRole(b)\n        if not enabled:\n            if b.property(stashname) is None:\n                b.setProperty(stashname, b.isEnabledTo(buttonbox))\n            b.setEnabled(\n                role == QDialogButtonBox.RejectRole or\n                role == QDialogButtonBox.HelpRole\n            )\n        else:\n            stashed_state = b.property(stashname)\n            if isinstance(stashed_state, bool):\n                state = stashed_state\n                b.setProperty(stashname, None)\n            else:\n                state = True\n            b.setEnabled(state)\n\n\nclass ImportItem(QStandardItem):\n    \"\"\"\n    An item representing a file path and associated load options\n    \"\"\"\n    PathRole = Qt.UserRole + 12\n    OptionsRole = Qt.UserRole + 14\n\n    def path(self):\n        # type: () -> str\n        path = self.data(ImportItem.PathRole)\n        return path if isinstance(path, str) else \"\"\n\n    def setPath(self, path):\n        # type: (str) -> None\n        self.setData(path, ImportItem.PathRole)\n\n    def options(self):\n        # type: () -> Optional[Options]\n        options = self.data(ImportItem.OptionsRole)\n        return options if isinstance(options, Options) else None\n\n    def setOptions(self, options):\n        # type: (Options) -> None\n        self.setData(options, ImportItem.OptionsRole)\n\n    @classmethod\n    def fromPath(cls, path):\n        # type: (str) -> ImportItem\n        \"\"\"\n        Create a `ImportItem` from a local file system path.\n        \"\"\"\n        iconprovider = QFileIconProvider()\n        basename = os.path.basename(path)\n        item = cls()\n        item.setText(basename)\n        item.setToolTip(path)\n        finfo = QFileInfo(path)\n        if finfo.exists():\n            item.setIcon(iconprovider.icon(finfo))\n        else:\n            item.setIcon(iconprovider.icon(QFileIconProvider.File))\n\n        item.setData(path, ImportItem.PathRole)\n        if not os.path.isfile(path):\n            item.setEnabled(False)\n            item.setToolTip(item.toolTip() + \" (missing from filesystem)\")\n        return item\n\n\ndef qname(type_):\n    # type: (type) -> str\n    \"\"\"\n    Return the fully qualified name for a `type_`.\n    \"\"\"\n\n    return \"{0.__module__}.{0.__qualname__}\".format(type_)\n\n\nclass OWCSVFileImport(widget.OWWidget):\n    name = \"CSV File Import\"\n    description = \"Import a data table from a CSV formatted file.\"\n    icon = \"icons/CSVFile.svg\"\n    priority = 11\n    category = \"Data\"\n    keywords = [\"file\", \"load\", \"read\", \"open\", \"csv\"]\n\n    outputs = [\n        widget.OutputSignal(\n            name=\"Data\",\n            type=Orange.data.Table,\n            doc=\"Loaded data set.\"),\n        widget.OutputSignal(\n            name=\"Data Frame\",\n            type=pd.DataFrame,\n            doc=\"\"\n        )\n    ]\n\n    class Error(widget.OWWidget.Error):\n        error = widget.Msg(\n            \"Unexpected error\"\n        )\n        encoding_error = widget.Msg(\n            \"Encoding error\\n\"\n            \"The file might be encoded in an unsupported encoding or it \"\n            \"might be binary\"\n        )\n\n    #: Paths and options of files accessed in a 'session'\n    _session_items = settings.Setting(\n        [], schema_only=True)  # type: List[Tuple[str, dict]]\n\n    #: Saved dialog state (last directory and selected filter)\n    dialog_state = settings.Setting({\n        \"directory\": \"\",\n        \"filter\": \"\"\n    })  # type: Dict[str, str]\n    MaxHistorySize = 50\n\n    want_main_area = False\n    buttons_area_orientation = None\n    resizing_enabled = False\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(self, *args, **kwargs)\n\n        self.__committimer = QTimer(self, singleShot=True)\n        self.__committimer.timeout.connect(self.commit)\n\n        self.__executor = qconcurrent.ThreadExecutor()\n        self.__watcher = None  # type: Optional[qconcurrent.FutureWatcher]\n\n        self.controlArea.layout().setSpacing(-1)  # reset spacing\n        grid = QGridLayout()\n        grid.addWidget(QLabel(\"File:\", self), 0, 0, 1, 1)\n\n        self.import_items_model = QStandardItemModel(self)\n        self.recent_combo = QComboBox(\n            self, objectName=\"recent-combo\", toolTip=\"Recent files.\",\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            minimumContentsLength=16,\n        )\n        self.recent_combo.setModel(self.import_items_model)\n        self.recent_combo.activated.connect(self.activate_recent)\n        self.recent_combo.setSizePolicy(\n            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n        self.browse_button = QPushButton(\n            \"…\", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),\n            toolTip=\"Browse filesystem\", autoDefault=False,\n        )\n        self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)\n        self.browse_button.clicked.connect(self.browse)\n        grid.addWidget(self.recent_combo, 0, 1, 1, 1)\n        grid.addWidget(self.browse_button, 0, 2, 1, 1)\n        self.controlArea.layout().addLayout(grid)\n\n        ###########\n        # Info text\n        ###########\n        box = gui.widgetBox(self.controlArea, \"Info\", addSpace=False)\n        self.summary_text = QTextBrowser(\n            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,\n            readOnly=True,\n        )\n        self.summary_text.viewport().setBackgroundRole(QPalette.NoRole)\n        self.summary_text.setFrameStyle(QTextBrowser.NoFrame)\n        self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4)\n        self.summary_text.viewport().setAutoFillBackground(False)\n        box.layout().addWidget(self.summary_text)\n\n        self.info.set_output_summary(self.info.NoOutput)\n\n        button_box = QDialogButtonBox(\n            orientation=Qt.Horizontal,\n            standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry\n        )\n        self.load_button = b = button_box.button(QDialogButtonBox.Retry)\n        b.setText(\"Load\")\n        b.clicked.connect(self.__committimer.start)\n        b.setEnabled(False)\n        b.setDefault(True)\n\n        self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel)\n        b.clicked.connect(self.cancel)\n        b.setEnabled(False)\n        b.setAutoDefault(False)\n\n        self.import_options_button = QPushButton(\n            \"Import Options…\", enabled=False, autoDefault=False,\n            clicked=self._activate_import_dialog\n        )\n\n        def update_buttons(cbindex):\n            self.import_options_button.setEnabled(cbindex != -1)\n            self.load_button.setEnabled(cbindex != -1)\n        self.recent_combo.currentIndexChanged.connect(update_buttons)\n\n        button_box.addButton(\n            self.import_options_button, QDialogButtonBox.ActionRole\n        )\n        button_box.setStyleSheet(\n            \"button-layout: {:d};\".format(QDialogButtonBox.MacLayout)\n        )\n        self.controlArea.layout().addWidget(button_box)\n        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)\n\n        self._restoreState()\n        item = self.current_item()\n        if item is not None:\n            self.set_selected_file(item.path(), item.options())\n\n    @Slot(int)\n    def activate_recent(self, index):\n        \"\"\"\n        Activate an item from the recent list.\n        \"\"\"\n        if 0 <= index < self.import_items_model.rowCount():\n            item = self.import_items_model.item(index)\n            assert item is not None\n            path = item.data(ImportItem.PathRole)\n            opts = item.data(ImportItem.OptionsRole)\n            if not isinstance(opts, Options):\n                opts = None\n            self.set_selected_file(path, opts)\n        else:\n            self.recent_combo.setCurrentIndex(-1)\n\n    @Slot()\n    def browse(self):\n        \"\"\"\n        Open a file dialog and select a user specified file.\n        \"\"\"\n        formats = [\n            \"Text - comma separated (*.csv, *)\",\n            \"Text - tab separated (*.tsv, *)\",\n            \"Text - all files (*)\"\n        ]\n\n        dlg = QFileDialog(\n            self, windowTitle=\"Open Data File\",\n            acceptMode=QFileDialog.AcceptOpen,\n            fileMode=QFileDialog.ExistingFile\n        )\n        dlg.setNameFilters(formats)\n        state = self.dialog_state\n        lastdir = state.get(\"directory\", \"\")\n        lastfilter = state.get(\"filter\", \"\")\n\n        if lastdir and os.path.isdir(lastdir):\n            dlg.setDirectory(lastdir)\n        if lastfilter:\n            dlg.selectNameFilter(lastfilter)\n\n        status = dlg.exec_()\n        dlg.deleteLater()\n        if status == QFileDialog.Accepted:\n            self.dialog_state[\"directory\"] = dlg.directory().absolutePath()\n            self.dialog_state[\"filter\"] = dlg.selectedNameFilter()\n\n            selected_filter = dlg.selectedNameFilter()\n            path = dlg.selectedFiles()[0]\n            # pre-flight check; try to determine the nature of the file\n            mtype = _mime_type_for_path(path)\n            if not mtype.inherits(\"text/plain\"):\n                mb = QMessageBox(\n                    parent=self,\n                    windowTitle=\"\",\n                    icon=QMessageBox.Question,\n                    text=\"The '{basename}' may be a binary file.\\n\"\n                         \"Are you sure you want to continue?\".format(\n                             basename=os.path.basename(path)),\n                    standardButtons=QMessageBox.Cancel | QMessageBox.Yes\n                )\n                mb.setWindowModality(Qt.WindowModal)\n                if mb.exec() == QMessageBox.Cancel:\n                    return\n\n            # initialize dialect based on selected extension\n            if selected_filter in formats[:-1]:\n                filter_idx = formats.index(selected_filter)\n                if filter_idx == 0:\n                    dialect = csv.excel()\n                elif filter_idx == 1:\n                    dialect = csv.excel_tab()\n                else:\n                    dialect = csv.excel_tab()\n                header = True\n            else:\n                try:\n                    dialect, header = sniff_csv_with_path(path)\n                except Exception:  # pylint: disable=broad-except\n                    dialect, header = csv.excel(), True\n\n            options = None\n            # Search for path in history.\n            # If found use the stored params to initialize the import dialog\n            items = self.itemsFromSettings()\n            idx = index_where(items, lambda t: samepath(t[0], path))\n            if idx is not None:\n                _, options_ = items[idx]\n                if options_ is not None:\n                    options = options_\n\n            if options is None:\n                if not header:\n                    rowspec = []\n                else:\n                    rowspec = [(range(0, 1), RowSpec.Header)]\n                options = Options(\n                    encoding=\"utf-8\", dialect=dialect, rowspec=rowspec)\n\n            dlg = CSVImportDialog(\n                self, windowTitle=\"Import Options\", sizeGripEnabled=True)\n            dlg.setWindowModality(Qt.WindowModal)\n            dlg.setPath(path)\n            dlg.setOptions(options)\n            status = dlg.exec_()\n            dlg.deleteLater()\n            if status == QDialog.Accepted:\n                self.set_selected_file(path, dlg.options())\n\n    def current_item(self):\n        # type: () -> Optional[ImportItem]\n        \"\"\"\n        Return the current selected item (file) or None if there is no\n        current item.\n        \"\"\"\n        idx = self.recent_combo.currentIndex()\n        if idx == -1:\n            return None\n\n        item = self.recent_combo.model().item(idx)  # type: QStandardItem\n        if isinstance(item, ImportItem):\n            return item\n        else:\n            return None\n\n    def _activate_import_dialog(self):\n        \"\"\"Activate the Import Options dialog for the  current item.\"\"\"\n        item = self.current_item()\n        assert item is not None\n        dlg = CSVImportDialog(\n            self, windowTitle=\"Import Options\", sizeGripEnabled=True,\n        )\n        dlg.setWindowModality(Qt.WindowModal)\n        dlg.setAttribute(Qt.WA_DeleteOnClose)\n        settings = QSettings()\n        qualname = qname(type(self))\n        settings.beginGroup(qualname)\n        size = settings.value(\"size\", QSize(), type=QSize)  # type: QSize\n        if size.isValid():\n            dlg.resize(size)\n\n        path = item.data(ImportItem.PathRole)\n        options = item.data(ImportItem.OptionsRole)\n        dlg.setPath(path)  # Set path before options so column types can\n        if isinstance(options, Options):\n            dlg.setOptions(options)\n\n        def update():\n            newoptions = dlg.options()\n            item.setData(newoptions, ImportItem.OptionsRole)\n            # update the stored item\n            self._add_recent(path, newoptions)\n            if newoptions != options:\n                self._invalidate()\n        dlg.accepted.connect(update)\n\n        def store_size():\n            settings.setValue(\"size\", dlg.size())\n        dlg.finished.connect(store_size)\n        dlg.show()\n\n    def set_selected_file(self, filename, options=None):\n        \"\"\"\n        Set the current selected filename path.\n        \"\"\"\n        self._add_recent(filename, options)\n        self._invalidate()\n\n    #: Saved options for a filename\n    SCHEMA = {\n        \"path\": str,  # Local filesystem path\n        \"options\": str,  # json encoded 'Options'\n    }\n\n    @classmethod\n    def _local_settings(cls):\n        # type: () -> QSettings\n        \"\"\"Return a QSettings instance with local persistent settings.\"\"\"\n        filename = \"{}.ini\".format(qname(cls))\n        fname = os.path.join(settings.widget_settings_dir(), filename)\n        return QSettings(fname, QSettings.IniFormat)\n\n    def _add_recent(self, filename, options=None):\n        # type: (str, Optional[Options]) -> None\n        \"\"\"\n        Add filename to the list of recent files.\n        \"\"\"\n        model = self.import_items_model\n        index = index_where(\n            (model.index(i, 0).data(ImportItem.PathRole)\n             for i in range(model.rowCount())),\n            lambda path: isinstance(path, str) and samepath(path, filename)\n        )\n        if index is not None:\n            item, *_ = model.takeRow(index)\n        else:\n            item = ImportItem.fromPath(filename)\n\n        model.insertRow(0, item)\n\n        if options is not None:\n            item.setOptions(options)\n\n        self.recent_combo.setCurrentIndex(0)\n        # store items to local persistent settings\n        s = self._local_settings()\n        arr = QSettings_readArray(s, \"recent\", OWCSVFileImport.SCHEMA)\n        item = {\"path\": filename}\n        if options is not None:\n            item[\"options\"] = json.dumps(options.as_dict())\n\n        arr = [item for item in arr if item.get(\"path\") != filename]\n        arr.append(item)\n        QSettings_writeArray(s, \"recent\", arr)\n\n        # update workflow session items\n        items = self._session_items[:]\n        idx = index_where(items, lambda t: samepath(t[0], filename))\n        if idx is not None:\n            del items[idx]\n        items.insert(0, (filename, options.as_dict()))\n        self._session_items = items[:OWCSVFileImport.MaxHistorySize]\n\n    def _invalidate(self):\n        # Invalidate the current output and schedule a new commit call.\n        # (NOTE: The widget enters a blocking state)\n        self.__committimer.start()\n        if self.__watcher is not None:\n            self.__cancel_task()\n        self.setBlocking(True)\n\n    def commit(self):\n        \"\"\"\n        Commit the current state and submit the load task for execution.\n\n        Note\n        ----\n        Any existing pending task is canceled.\n        \"\"\"\n        self.__committimer.stop()\n        if self.__watcher is not None:\n            self.__cancel_task()\n        self.error()\n\n        item = self.current_item()\n        if item is None:\n            return\n        path = item.data(ImportItem.PathRole)\n        opts = item.data(ImportItem.OptionsRole)\n        if not isinstance(opts, Options):\n            return\n\n        task = state = TaskState()\n        state.future = ...\n        state.watcher = qconcurrent.FutureWatcher()\n        state.progressChanged.connect(\n            self.__set_read_progress, Qt.DirectConnection)\n\n        def progress_(i, j):\n            task.emitProgressChangedOrCancel(i, j)\n\n        task.future = self.__executor.submit(\n            clear_stack_on_cancel(load_csv),\n            path, opts, progress_,\n        )\n        task.watcher.setFuture(task.future)\n        w = task.watcher\n        w.done.connect(self.__handle_result)\n        w.progress = state\n        self.__watcher = w\n        self.__set_running_state()\n\n    @Slot('qint64', 'qint64')\n    def __set_read_progress(self, read, count):\n        if count > 0:\n            self.progressBarSet(100 * read / count)\n\n    def __cancel_task(self):\n        # Cancel and dispose of the current task\n        assert self.__watcher is not None\n        w = self.__watcher\n        w.future().cancel()\n        w.progress.cancel = True\n        w.done.disconnect(self.__handle_result)\n        w.progress.progressChanged.disconnect(self.__set_read_progress)\n        self.__watcher = None\n\n    def cancel(self):\n        \"\"\"\n        Cancel current pending or executing task.\n        \"\"\"\n        if self.__watcher is not None:\n            self.__cancel_task()\n            self.__clear_running_state()\n            self.setStatusMessage(\"Cancelled\")\n            self.summary_text.setText(\n                \"<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>\"\n            )\n\n    def __set_running_state(self):\n        self.progressBarInit()\n        self.setBlocking(True)\n        self.setStatusMessage(\"Running\")\n        self.cancel_button.setEnabled(True)\n        self.load_button.setText(\"Restart\")\n        path = self.current_item().path()\n        self.Error.clear()\n        self.summary_text.setText(\n            \"<div>Loading: <i>{}</i><br/>\".format(prettyfypath(path))\n        )\n\n    def __clear_running_state(self, ):\n        self.progressBarFinished()\n        self.setStatusMessage(\"\")\n        self.setBlocking(False)\n        self.cancel_button.setEnabled(False)\n        self.load_button.setText(\"Reload\")\n\n    def __set_error_state(self, err):\n        self.Error.clear()\n        if isinstance(err, UnicodeDecodeError):\n            self.Error.encoding_error(exc_info=err)\n        else:\n            self.Error.error(exc_info=err)\n\n        path = self.current_item().path()\n        basename = os.path.basename(path)\n        if isinstance(err, UnicodeDecodeError):\n            text = (\n                \"<div><i>{basename}</i> was not loaded due to a text encoding \"\n                \"error. The file might be saved in an unknown or invalid \"\n                \"encoding, or it might be a binary file.</div>\"\n            ).format(\n                basename=escape(basename)\n            )\n        else:\n            text = (\n                \"<div><i>{basename}</i> was not loaded due to an error:\"\n                \"<p style='white-space: pre;'>{err}</p>\"\n            ).format(\n                basename=escape(basename),\n                err=\"\".join(traceback.format_exception_only(type(err), err))\n            )\n        self.summary_text.setText(text)\n\n    def __clear_error_state(self):\n        self.Error.error.clear()\n        self.summary_text.setText(\"\")\n\n    def onDeleteWidget(self):\n        \"\"\"Reimplemented.\"\"\"\n        if self.__watcher is not None:\n            self.__cancel_task()\n            self.__executor.shutdown()\n        super().onDeleteWidget()\n\n    @Slot(object)\n    def __handle_result(self, f):\n        # type: (qconcurrent.Future[pd.DataFrame]) -> None\n        assert f.done()\n        assert f is self.__watcher.future()\n        self.__watcher = None\n        self.__clear_running_state()\n\n        try:\n            df = f.result()\n            assert isinstance(df, pd.DataFrame)\n        except pandas.errors.EmptyDataError:\n            df = pd.DataFrame({})\n        except Exception as e:  # pylint: disable=broad-except\n            self.__set_error_state(e)\n            df = None\n        else:\n            self.__clear_error_state()\n\n        if df is not None:\n            table = pandas_to_table(df)\n            filename = self.current_item().path()\n            table.name = os.path.splitext(os.path.split(filename)[-1])[0]\n        else:\n            table = None\n        self.send(\"Data Frame\", df)\n        self.send('Data', table)\n        self._update_status_messages(table)\n\n    def _update_status_messages(self, data):\n        summary = len(data) if data else self.info.NoOutput\n        details = format_summary_details(data) if data else \"\"\n        self.info.set_output_summary(summary, details)\n        if data is None:\n            return\n\n        def pluralize(seq):\n            return \"s\" if len(seq) != 1 else \"\"\n\n        summary = (\"{n_instances} row{plural_1}, \"\n                   \"{n_features} feature{plural_2}, \"\n                   \"{n_meta} meta{plural_3}\").format(\n                       n_instances=len(data), plural_1=pluralize(data),\n                       n_features=len(data.domain.attributes),\n                       plural_2=pluralize(data.domain.attributes),\n                       n_meta=len(data.domain.metas),\n                       plural_3=pluralize(data.domain.metas))\n        self.summary_text.setText(summary)\n\n    def itemsFromSettings(self):\n        # type: () -> List[Tuple[str, Options]]\n        \"\"\"\n        Return items from local history.\n        \"\"\"\n        s = self._local_settings()\n        items_ = QSettings_readArray(s, \"recent\", OWCSVFileImport.SCHEMA)\n        items = []  # type: List[Tuple[str, Options]]\n        for item in items_:\n            path = item.get(\"path\", \"\")\n            if not path:\n                continue\n            opts_json = item.get(\"options\", \"\")\n            try:\n                opts = Options.from_dict(json.loads(opts_json))\n            except (csv.Error, LookupError, TypeError, json.JSONDecodeError):\n                _log.error(\"Could not reconstruct options for '%s'\", path,\n                           exc_info=True)\n            else:\n                items.append((path, opts))\n        return items[::-1]\n\n    def _restoreState(self):\n        # Restore the state. Merge session (workflow) items with the\n        # local history.\n        model = self.import_items_model\n        # local history\n        items = self.itemsFromSettings()\n        # stored session items\n        sitems = []\n        for p, m in self._session_items:\n            try:\n                item_ = (p, Options.from_dict(m))\n            except (csv.Error, LookupError):\n                # Is it better to fail then to lose a item slot?\n                _log.error(\"Failed to restore '%s'\", p, exc_info=True)\n            else:\n                sitems.append(item_)\n\n        items = sitems + items\n        items = unique(items, key=lambda t: pathnormalize(t[0]))\n\n        curr = self.recent_combo.currentIndex()\n        if curr != -1:\n            currentpath = self.recent_combo.currentData(ImportItem.PathRole)\n        else:\n            currentpath = None\n        for path, options in items:\n            item = ImportItem.fromPath(path)\n            item.setOptions(options)\n            model.appendRow(item)\n\n        if currentpath is not None:\n            idx = self.recent_combo.findData(currentpath, ImportItem.PathRole)\n            if idx != -1:\n                self.recent_combo.setCurrentIndex(idx)\n\n\n@singledispatch\ndef sniff_csv(file, samplesize=2 ** 20):\n    sniffer = csv.Sniffer()\n    sample = file.read(samplesize)\n    dialect = sniffer.sniff(sample)\n    dialect = textimport.Dialect(\n        dialect.delimiter, dialect.quotechar,\n        dialect.escapechar, dialect.doublequote,\n        dialect.skipinitialspace, dialect.quoting\n    )\n    has_header = sniffer.has_header(sample)\n    return dialect, has_header\n\n\n@sniff_csv.register(str)\n@sniff_csv.register(bytes)\ndef sniff_csv_with_path(path, encoding=\"utf-8\", samplesize=2 ** 20):\n    with _open(path, \"rt\", encoding=encoding) as f:\n        return sniff_csv(f, samplesize)\n\n\ndef _open(path, mode, encoding=None):\n    # type: (str, str, Optional[str]) -> typing.IO[Any]\n    \"\"\"\n    Open a local file `path` for reading. The file may be gzip, bz2 or zip\n    compressed.\n\n    If a zip archive then a single archive member is expected.\n\n    Parameters\n    ----------\n    path : str\n        File system path\n    mode : str\n        'r', 'rb' or 'rt'\n    encoding : Optional[str]\n        Optional text encoding, for opening in text mode.\n\n    Returns\n    -------\n    stream: io.BaseIO\n        A stream opened for reading.\n    \"\"\"\n    if mode not in {'r', 'rb', 'rt'}:\n        raise ValueError('r')\n    _, ext = os.path.splitext(path)\n    ext = ext.lower()\n    if ext == \".gz\":\n        return gzip.open(path, mode, encoding=encoding)\n    elif ext == \".bz2\":\n        return bz2.open(path, mode, encoding=encoding)\n    elif ext == \".xz\":\n        return lzma.open(path, mode, encoding=encoding)\n    elif ext == \".zip\":\n        arh = zipfile.ZipFile(path, 'r')\n        filelist = arh.infolist()\n        if len(filelist) == 1:\n            filename = filelist[0]\n            zinfo = arh.getinfo(filename)\n            f = arh.open(zinfo.filename, 'r')\n            if 't' in mode:\n                f = io.TextIOWrapper(f, encoding=encoding)\n            return f\n        else:\n            raise ValueError(\"Expected a single file in the archive.\")\n    else:\n        return open(path, mode, encoding=encoding)\n\n\ncompression_types = [\n    \"application/gzip\", \"application/zip\",\n    \"application/x-xz\", \"application/x-bzip\",\n    # application/x-lz4\n]\n\n\ndef _mime_type_for_path(path):\n    # type: (str) -> QMimeType\n    \"\"\"\n    Return the mime type of the file on a local filesystem.\n\n    In case the path is a compressed file return the mime type of its contents\n\n    Parameters\n    ----------\n    path : str\n        Local filesystem path\n\n    Returns\n    -------\n    mimetype: QMimeType\n    \"\"\"\n    db = QMimeDatabase()\n    mtype = db.mimeTypeForFile(path, QMimeDatabase.MatchDefault)\n    if any(mtype.inherits(t) for t in compression_types):\n        # peek contents\n        try:\n            with _open(path, \"rb\") as f:\n                sample = f.read(4096)\n        except Exception:  # pylint: disable=broad-except\n            sample = b''\n        mtype = db.mimeTypeForData(sample)\n    return mtype\n\n\nNA_DEFAULT = [\"\", \"?\", \".\", \"~\", \"nan\", \"NAN\", \"NaN\", \"N/A\", \"n/a\", \"NA\"]\n\nNA_VALUES = {\n    ColumnType.Numeric: NA_DEFAULT,\n    ColumnType.Categorical: NA_DEFAULT,\n    ColumnType.Time: NA_DEFAULT + [\"NaT\", \"NAT\"],\n    ColumnType.Text: [],\n    ColumnType.Auto: NA_DEFAULT,\n}\n\n\ndef load_csv(path, opts, progress_callback=None):\n    # type: (Union[AnyStr, BinaryIO], Options, ...) -> pd.DataFrame\n    def dtype(coltype):\n        # type: (ColumnType) -> Optional[str]\n        if coltype == ColumnType.Numeric:\n            return \"float\"\n        elif coltype == ColumnType.Categorical:\n            return \"category\"\n        elif coltype == ColumnType.Time:\n            return \"object\"\n        elif coltype == ColumnType.Text:\n            return \"object\"\n        elif coltype == ColumnType.Skip:\n            return None\n        elif coltype == ColumnType.Auto:\n            return None\n        else:\n            raise TypeError\n\n    def expand(ranges):\n        # type: (Iterable[Tuple[range, T]]) -> Iterable[Tuple[int, T]]\n        return ((i, x) for r, x in ranges for i in r)\n\n    dtypes = {i: dtype(c) for i, c in expand(opts.columntypes)}\n    dtypes = {i: dtp for i, dtp in dtypes.items()\n              if dtp is not None and dtp != ColumnType.Auto}\n\n    columns_ignored = {i for i, c in expand(opts.columntypes)\n                       if c == ColumnType.Skip}\n    dtcols = {i for i, c in expand(opts.columntypes)\n              if c == ColumnType.Time}\n    parse_dates = sorted(dtcols)\n    na_values = {i: NA_VALUES.get(c, NA_DEFAULT)\n                 for i, c in expand(opts.columntypes)}\n    if not parse_dates:\n        parse_dates = False\n\n    # fixup header indices to account for skipped rows (header row indices\n    # pick rows after skiprows)\n\n    hspec = sorted(opts.rowspec, key=lambda t: t[0].start)\n    header_ranges = []\n    nskiped = 0\n    for range_, state in hspec:\n        if state == RowSpec.Skipped:\n            nskiped += len(range_)\n        elif state == RowSpec.Header:\n            header_ranges.append(\n                range(range_.start - nskiped, range_.stop - nskiped)\n            )\n    headers = [i for r in header_ranges for i in r]\n    skiprows = [row for r, st in hspec if st == RowSpec.Skipped for row in r]\n\n    if not headers:\n        header = None\n        prefix = \"X.\"\n\n    elif len(headers) == 1:\n        header = headers[0]\n        prefix = None\n    else:\n        header = headers\n        prefix = None\n\n    if not skiprows:\n        skiprows = None\n\n    numbers_format_kwds = {}\n\n    if opts.decimal_separator != \".\":\n        numbers_format_kwds[\"decimal\"] = opts.decimal_separator\n\n    if opts.group_separator != \"\":\n        numbers_format_kwds[\"thousands\"] = opts.group_separator\n\n    if numbers_format_kwds:\n        # float_precision = \"round_trip\" cannot handle non c-locale decimal and\n        # thousands sep (https://github.com/pandas-dev/pandas/issues/35365).\n        # Fallback to 'high'.\n        numbers_format_kwds[\"float_precision\"] = \"high\"\n    else:\n        numbers_format_kwds[\"float_precision\"] = \"round_trip\"\n\n    with ExitStack() as stack:\n        if isinstance(path, (str, bytes)):\n            f = stack.enter_context(_open(path, 'rb'))\n        elif isinstance(path, (io.RawIOBase, io.BufferedIOBase)) or \\\n                hasattr(path, \"read\"):\n            f = path\n        else:\n            raise TypeError()\n        file = TextReadWrapper(\n            f, encoding=opts.encoding,\n            progress_callback=progress_callback)\n        stack.callback(file.detach)\n        df = pd.read_csv(\n            file, sep=opts.dialect.delimiter, dialect=opts.dialect,\n            skipinitialspace=opts.dialect.skipinitialspace,\n            header=header, skiprows=skiprows,\n            dtype=dtypes, parse_dates=parse_dates, prefix=prefix,\n            na_values=na_values, keep_default_na=False,\n            **numbers_format_kwds\n        )\n        df = guess_types(df, dtypes, columns_ignored)\n\n        if columns_ignored:\n            # TODO: use 'usecols' parameter in `read_csv` call to\n            # avoid loading/parsing the columns\n            df.drop(\n                columns=[df.columns[i] for i in columns_ignored\n                         if i < len(df.columns)],\n                inplace=True\n            )\n        return df\n\n\ndef guess_types(\n        df: pd.DataFrame, dtypes: Dict[int, str], columns_ignored: List[int]\n) -> pd.DataFrame:\n    \"\"\"\n    Guess data type for variables according to values.\n\n    Parameters\n    ----------\n    df\n        Data frame\n    dtypes\n        The dictionary with data types set by user. We will guess values only\n        for columns that does not have data type defined.\n    columns_ignored\n        List with indices of ignored columns. Ignored columns are skipped.\n\n    Returns\n    -------\n    A data frame with changed dtypes according to the strategy.\n    \"\"\"\n    for i, col in enumerate(df):\n        # only when automatic is set in widget dialog\n        if dtypes.get(i, None) is None and i not in columns_ignored:\n            df[col] = guess_data_type(df[col])\n    return df\n\n\ndef guess_data_type(col: pd.Series) -> pd.Series:\n    \"\"\"\n    Guess column types. Logic is same than in guess_data_type from io_utils\n    module. This function only change the dtype of the column such that later\n    correct Orange.data.variable is used.\n    Logic:\n    - if can converted to date-time (ISO) -> TimeVariable\n    - if numeric (only numbers)\n        - only values {0, 1} or {1, 2} -> DiscreteVariable\n        - else -> ContinuousVariable\n    - if not numbers:\n        - num_unique_values < len(data) ** 0.7 and < 100 -> DiscreteVariable\n        - else -> StringVariable\n\n    Parameters\n    ----------\n    col\n        Data column\n\n    Returns\n    -------\n    Data column with correct dtype\n    \"\"\"\n    def parse_dates(s):\n        \"\"\"\n        This is an extremely fast approach to datetime parsing.\n        For large data, the same dates are often repeated. Rather than\n        re-parse these, we store all unique dates, parse them, and\n        use a lookup to convert all dates.\n        \"\"\"\n        try:\n            dates = {date: pd.to_datetime(date) for date in s.unique()}\n        except ValueError:\n            return None\n        return s.map(dates)\n\n    if pdtypes.is_numeric_dtype(col):\n        unique_values = col.unique()\n        if len(unique_values) <= 2 and (\n                len(np.setdiff1d(unique_values, [0, 1])) == 0\n                or len(np.setdiff1d(unique_values, [1, 2])) == 0):\n            return col.astype(\"category\")\n    else:  # object\n        # try parse as date - if None not a date\n        parsed_col = parse_dates(col)\n        if parsed_col is not None:\n            return parsed_col\n        unique_values = col.unique()\n        if len(unique_values) < 100 and len(unique_values) < len(col)**0.7:\n            return col.astype(\"category\")\n    return col\n\n\ndef clear_stack_on_cancel(f):\n    \"\"\"\n    A decorator that catches the TaskState.UserCancelException exception\n    and clears the exception's traceback to remove local references.\n\n    Parameters\n    ----------\n    f : callable\n\n    Returns\n    -------\n    wrapped : callable\n    \"\"\"\n    def wrapper(*args, **kwargs):\n        try:\n            return f(*args, **kwargs)\n        except TaskState.UserCancelException as e:\n            # TODO: Is this enough to allow immediate gc of the stack?\n            # How does it chain across cython code?\n            # Maybe just return None.\n            e = e.with_traceback(None)\n            e.__context__ = None\n            e.__cause__ = None\n            raise e\n        except BaseException as e:\n            traceback.clear_frames(e.__traceback__)\n            raise\n\n    return wrapper\n\n\nclass TaskState(QObject, PyOwned):\n    class UserCancelException(BaseException):\n        \"\"\"User interrupt exception.\"\"\"\n\n    #: Signal emitted with the current read progress. First value is the current\n    #: progress state, second value is the total progress to complete\n    #: (-1 if unknown)\n    progressChanged = Signal('qint64', 'qint64')\n    __progressChanged = Signal('qint64', 'qint64')\n    #: Was cancel requested.\n    cancel = False\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        # route the signal via this object's queue\n        self.__progressChanged.connect(\n            self.progressChanged, Qt.QueuedConnection)\n\n    def emitProgressChangedOrCancel(self, current, total):\n        # type: (int, int) -> None\n        \"\"\"\n        Emit the progressChanged signal with `current` and `total`.\n        \"\"\"\n        if self.cancel:\n            raise TaskState.UserCancelException()\n        else:\n            self.__progressChanged.emit(current, total)\n\n\nclass TextReadWrapper(io.TextIOWrapper):\n    \"\"\"\n    TextIOWrapper reporting the read progress.\n\n    Assuming a single forward read pass.\n    \"\"\"\n\n    #: A `Callable[[int, int], []]` called when the file position is\n    #: advanced by read; called with current byte position and total\n    #: file size.\n    progress_callback = ...  # type: Callable[[int, int], None]\n\n    def __init__(self, buffer, *args, progress_callback=None, **kwargs):\n        super().__init__(buffer, *args, **kwargs)\n        if progress_callback is None:\n            def progress_callback(i, j):  # pylint: disable=unused-argument\n                pass\n        self.progress_callback = progress_callback\n        try:\n            self.__size = os.fstat(buffer.fileno()).st_size\n        except OSError:\n            self.__size = -1\n\n    def read(self, size=-1):\n        s = super().read(size)\n        # try to go around any gzip/bz2/lzma wrappers to the base\n        # raw file (does not work for zipfile.ZipExtFile; should\n        # dispatch on buffer type)\n        try:\n            fd = self.buffer.fileno()\n        except (AttributeError, io.UnsupportedOperation):\n            pos = -1\n        else:\n            try:\n                pos = os.lseek(fd, 0, os.SEEK_CUR)\n            except OSError:\n                pos = -1\n\n        self.progress_callback(pos, self.__size)\n        return s\n\n\ndef index_where(iterable, pred):\n    # type: (Iterable[T], Callable[[T], bool]) -> Optional[int]\n    \"\"\"\n    Return the (first) index of el in `iterable` where `pred(el)` returns True.\n\n    If no element matches return `None`.\n    \"\"\"\n    for i, el in enumerate(iterable):\n        if pred(el):\n            return i\n    return None\n\n\ndef unique(iterable, key=None):\n    # type: (Iterable[T], Optional[Callable[[T], Hashable]]) -> Iterable[T]\n    \"\"\"\n    Return an iterator over unique elements of `iterable`.\n\n    If `key` is supplied it is used as a substitute for determining\n    'uniqueness' of elements.\n\n    Parameters\n    ----------\n    iterable : Iterable[T]\n    key : Callable[[T], Hashable]\n\n    Returns\n    -------\n    unique : Iterable[T]\n    \"\"\"\n    seen = set()\n    if key is None:\n        key = lambda t: t\n    for el in iterable:\n        el_k = key(el)\n        if el_k not in seen:\n            seen.add(el_k)\n            yield el\n\n\ndef samepath(p1, p2):\n    # type: (str, str) -> bool\n    \"\"\"\n    Return True if the paths `p1` and `p2` match after case and path\n    normalization.\n    \"\"\"\n    return pathnormalize(p1) == pathnormalize(p2)\n\n\ndef pathnormalize(p):\n    \"\"\"\n    Normalize a path (apply both path and case normalization.\n    \"\"\"\n    return os.path.normcase(os.path.normpath(p))\n\n\ndef prettyfypath(path):\n    \"\"\"\n    Return the path with the $HOME prefix shortened to '~/' if applicable.\n\n    Example\n    -------\n    >>> prettyfypath(\"/home/user/file.dat\")\n    '~/file.dat'\n    \"\"\"\n    home = os.path.expanduser(\"~/\")\n    home_n = pathnormalize(home)\n    path_n = pathnormalize(path)\n    if path_n.startswith(home_n):\n        path = os.path.join(\"~\", os.path.relpath(path, home))\n    return path\n\n\ndef pandas_to_table(df):\n    # type: (pd.DataFrame) -> Orange.data.Table\n    \"\"\"\n    Convert a pandas.DataFrame to a Orange.data.Table instance.\n    \"\"\"\n    index = df.index\n    if not isinstance(index, pd.RangeIndex):\n        df = df.reset_index()\n\n    columns = []  # type: List[Tuple[Orange.data.Variable, np.ndarray]]\n\n    for header, series in df.items():  # type: (Any, pd.Series)\n        if pdtypes.is_categorical(series):\n            coldata = series.values  # type: pd.Categorical\n            categories = [str(c) for c in coldata.categories]\n            var = Orange.data.DiscreteVariable.make(\n                str(header), values=categories\n            )\n            # Remap the coldata into the var.values order/set\n            coldata = pd.Categorical(\n                coldata.astype(\"str\"), categories=var.values\n            )\n            codes = coldata.codes\n            assert np.issubdtype(codes.dtype, np.integer)\n            orangecol = np.array(codes, dtype=np.float)\n            orangecol[codes < 0] = np.nan\n        elif pdtypes.is_datetime64_any_dtype(series):\n            # Check that this converts tz local to UTC\n            series = series.astype(np.dtype(\"M8[ns]\"))\n            coldata = series.values  # type: np.ndarray\n            assert coldata.dtype == \"M8[ns]\"\n            mask = np.isnat(coldata)\n            orangecol = coldata.astype(np.int64) / 10 ** 9\n            orangecol[mask] = np.nan\n            var = Orange.data.TimeVariable.make(str(header))\n            var.have_date = var.have_time = 1\n        elif pdtypes.is_object_dtype(series):\n            coldata = series.fillna('').values\n            assert isinstance(coldata, np.ndarray)\n            orangecol = coldata\n            var = Orange.data.StringVariable.make(str(header))\n        elif pdtypes.is_integer_dtype(series):\n            coldata = series.values\n            var = Orange.data.ContinuousVariable.make(str(header))\n            var.number_of_decimals = 0\n            orangecol = coldata.astype(np.float64)\n        elif pdtypes.is_numeric_dtype(series):\n            orangecol = series.values.astype(np.float64)\n            var = Orange.data.ContinuousVariable.make(str(header))\n        else:\n            warnings.warn(\n                \"Column '{}' with dtype: {} skipped.\"\n                .format(header, series.dtype),\n                UserWarning\n            )\n            continue\n        columns.append((var, orangecol))\n\n    cols_x = [(var, col) for var, col in columns if var.is_primitive()]\n    cols_m = [(var, col) for var, col in columns if not var.is_primitive()]\n\n    variables = [v for v, _ in cols_x]\n    if cols_x:\n        X = np.column_stack([a for _, a in cols_x])\n    else:\n        X = np.empty((df.shape[0], 0), dtype=np.float)\n    metas = [v for v, _ in cols_m]\n    if cols_m:\n        M = np.column_stack([a for _, a in cols_m])\n    else:\n        M = None\n\n    domain = Orange.data.Domain(variables, metas=metas)\n    return Orange.data.Table.from_numpy(domain, X, None, M)\n\n\ndef main(argv=None):  # pragma: no cover\n    app = QApplication(argv or [])\n    w = OWCSVFileImport()\n    w.show()\n    w.raise_()\n    app.exec_()\n    w.saveSettings()\n    w.onDeleteWidget()\n    return 0\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    sys.exit(main(sys.argv))\n",
            "new_file_content": "# -*- coding: utf-8 -*-\n\"\"\"\nCSV File Import Widget\n----------------------\n\n\"\"\"\nimport sys\nimport os\nimport csv\nimport enum\nimport io\nimport traceback\nimport warnings\nimport logging\nimport weakref\nimport json\n\nimport gzip\nimport lzma\nimport bz2\nimport zipfile\n\nfrom xml.sax.saxutils import escape\nfrom functools import singledispatch\nfrom contextlib import ExitStack\n\nimport typing\nfrom typing import (\n    List, Tuple, Dict, Optional, Any, Callable, Iterable, Hashable,\n    Union, AnyStr, BinaryIO\n)\n\nfrom PyQt5.QtCore import (\n    Qt, QFileInfo, QTimer, QSettings, QObject, QSize, QMimeDatabase, QMimeType\n)\nfrom PyQt5.QtGui import QStandardItem, QStandardItemModel, QPalette\nfrom PyQt5.QtWidgets import (\n    QLabel, QComboBox, QPushButton, QDialog, QDialogButtonBox, QGridLayout,\n    QVBoxLayout, QSizePolicy, QStyle, QFileIconProvider, QFileDialog,\n    QApplication, QMessageBox, QTextBrowser\n)\nfrom PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal\n\nimport numpy as np\nimport pandas.errors\nimport pandas as pd\n\nfrom pandas.api import types as pdtypes\n\nimport Orange.data\n\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils.concurrent import PyOwned\nfrom Orange.widgets.utils import textimport, concurrent as qconcurrent\nfrom Orange.widgets.utils.overlay import OverlayWidget\nfrom Orange.widgets.utils.settings import (\n    QSettings_readArray, QSettings_writeArray\n)\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nif typing.TYPE_CHECKING:\n    # pylint: disable=invalid-name\n    T = typing.TypeVar(\"T\")\n    K = typing.TypeVar(\"K\")\n\n__all__ = [\"OWCSVFileImport\"]\n\n_log = logging.getLogger(__name__)\n\nColumnType = textimport.ColumnType\nRowSpec = textimport.RowSpec\n\n\ndef enum_lookup(enumtype, name):\n    # type: (typing.Type[T], str) -> Optional[T]\n    \"\"\"\n    Return an value from `enumtype` by its symbolic name or None if not found.\n    \"\"\"\n    try:\n        return enumtype[name]\n    except LookupError:\n        return None\n\n\ndef dialect_eq(lhs, rhs):\n    # type: (csv.Dialect, csv.Dialect) -> bool\n    \"\"\"Compare 2 `csv.Dialect` instances for equality.\"\"\"\n    return (lhs.delimiter == rhs.delimiter and\n            lhs.quotechar == rhs.quotechar and\n            lhs.doublequote == rhs.doublequote and\n            lhs.escapechar == rhs.escapechar and\n            lhs.quoting == rhs.quoting and\n            lhs.skipinitialspace == rhs.skipinitialspace)\n\n\nclass Options:\n    \"\"\"\n    Stored options for loading CSV-like file.\n\n    Arguments\n    ---------\n    encoding : str\n        A encoding to use for reading.\n    dialect : csv.Dialect\n        A csv.Dialect instance.\n    columntypes: Iterable[Tuple[range, ColumnType]]\n        A list of column type ranges specifying the types for columns.\n        Need not list all columns. Columns not listed are assumed to have auto\n        type inference.\n    rowspec : Iterable[Tuple[range, RowSpec]]\n         A list of row spec ranges.\n    decimal_separator : str\n        Decimal separator - a single character string; default: `\".\"`\n    group_separator : str\n        Thousands group separator - empty or a single character string;\n        default: empty string\n    \"\"\"\n    RowSpec = RowSpec\n    ColumnType = ColumnType\n\n    def __init__(self, encoding='utf-8', dialect=csv.excel(), columntypes=[],\n                 rowspec=((range(0, 1), RowSpec.Header),),\n                 decimal_separator=\".\", group_separator=\"\"):\n        # type: (str, csv.Dialect, List[Tuple[range, ColumnType]], ...) -> None\n        self.encoding = encoding\n        self.dialect = dialect\n        self.columntypes = list(columntypes)\n        self.rowspec = list(rowspec)  # type: List[Tuple[range, Options.RowSpec]]\n        self.decimal_separator = decimal_separator\n        self.group_separator = group_separator\n\n    def __eq__(self, other):\n        # type: (Options) -> bool\n        \"\"\"\n        Compare this instance to `other` for equality.\n        \"\"\"\n        if isinstance(other, Options):\n            return (dialect_eq(self.dialect, other.dialect) and\n                    self.encoding == other.encoding and\n                    self.columntypes == other.columntypes and\n                    self.rowspec == other.rowspec and\n                    self.group_separator == other.group_separator and\n                    self.decimal_separator == other.decimal_separator)\n        else:\n            return NotImplemented\n\n    def __repr__(self):\n        class_, args = self.__reduce__()\n        return \"{}{!r}\".format(class_.__name__, args)\n    __str__ = __repr__\n\n    def __reduce__(self):\n        return type(self), (self.encoding, self.dialect,\n                            self.columntypes, self.rowspec)\n\n    def as_dict(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Return return Option parameters as plain types suitable for\n        serialization (e.g JSON serializable).\n        \"\"\"\n        return {\n            \"encoding\": self.encoding,\n            \"delimiter\": self.dialect.delimiter,\n            \"quotechar\": self.dialect.quotechar,\n            \"doublequote\": self.dialect.doublequote,\n            \"skipinitialspace\": self.dialect.skipinitialspace,\n            \"quoting\": self.dialect.quoting,\n            \"columntypes\": Options.spec_as_encodable(self.columntypes),\n            \"rowspec\": Options.spec_as_encodable(self.rowspec),\n            \"decimal_separator\": self.decimal_separator,\n            \"group_separator\": self.group_separator,\n        }\n\n    @staticmethod\n    def from_dict(mapping):\n        # type: (Dict[str, Any]) -> Options\n        \"\"\"\n        Reconstruct a `Options` from a plain dictionary (see :func:`as_dict`).\n        \"\"\"\n        encoding = mapping[\"encoding\"]\n        delimiter = mapping[\"delimiter\"]\n        quotechar = mapping[\"quotechar\"]\n        doublequote = mapping[\"doublequote\"]\n        quoting = mapping[\"quoting\"]\n        skipinitialspace = mapping[\"skipinitialspace\"]\n\n        dialect = textimport.Dialect(\n            delimiter, quotechar, None, doublequote, skipinitialspace,\n            quoting=quoting)\n\n        colspec = mapping[\"columntypes\"]\n        rowspec = mapping[\"rowspec\"]\n        colspec = Options.spec_from_encodable(colspec, ColumnType)\n        rowspec = Options.spec_from_encodable(rowspec, RowSpec)\n        decimal = mapping.get(\"decimal_separator\", \".\")\n        group = mapping.get(\"group_separator\", \"\")\n\n        return Options(encoding, dialect, colspec, rowspec,\n                       decimal_separator=decimal,\n                       group_separator=group)\n\n    @staticmethod\n    def spec_as_encodable(spec):\n        # type: (List[Tuple[range, enum.Enum]]) -> List[Dict[str, Any]]\n        return [{\"start\": r.start, \"stop\": r.stop, \"value\": value.name}\n                for r, value in spec]\n\n    @staticmethod\n    def spec_from_encodable(spec, enumtype):\n        # type: (List[Dict[str, Any]], typing.Type[T]) -> List[Tuple[range, T]]\n        r = []\n        for v in spec:\n            try:\n                start, stop, name = v[\"start\"], v[\"stop\"], v[\"value\"]\n            except (KeyError, ValueError):\n                pass\n            else:\n                r.append((range(start, stop), enum_lookup(enumtype, name)))\n        return r\n\n\nclass CSVImportDialog(QDialog):\n    \"\"\"\n    A dialog for selecting CSV file import options.\n    \"\"\"\n    def __init__(self, parent=None, flags=Qt.Dialog, **kwargs):\n        super().__init__(parent, flags, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self._options = None\n        self._path = None\n        # Finalizer for opened file handle (in _update_preview)\n        self.__finalizer = None  # type: Optional[Callable[[], None]]\n        self._optionswidget = textimport.CSVImportWidget()\n        self._optionswidget.previewReadErrorOccurred.connect(\n            self.__on_preview_error\n        )\n        self._optionswidget.previewModelReset.connect(\n            self.__on_preview_reset\n        )\n        self._buttons = buttons = QDialogButtonBox(\n            orientation=Qt.Horizontal,\n            standardButtons=(QDialogButtonBox.Ok | QDialogButtonBox.Cancel |\n                             QDialogButtonBox.Reset |\n                             QDialogButtonBox.RestoreDefaults),\n            objectName=\"dialog-button-box\",\n        )\n        # TODO: Help button\n        buttons.accepted.connect(self.accept)\n        buttons.rejected.connect(self.reject)\n\n        b = buttons.button(QDialogButtonBox.Reset)\n        b.clicked.connect(self.reset)\n        b = buttons.button(QDialogButtonBox.RestoreDefaults)\n        b.clicked.connect(self.restoreDefaults)\n        self.layout().addWidget(self._optionswidget)\n        self.layout().addWidget(buttons)\n        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)\n\n        self._overlay = OverlayWidget(self)\n        self._overlay.setWidget(self._optionswidget.dataview)\n        self._overlay.setLayout(QVBoxLayout())\n        self._overlay.layout().addWidget(QLabel(wordWrap=True))\n        self._overlay.hide()\n\n    def setOptions(self, options):\n        # type: (Options) -> None\n        self._options = options\n        self._optionswidget.setEncoding(options.encoding)\n        self._optionswidget.setDialect(options.dialect)\n        self._optionswidget.setNumbersFormat(\n            options.group_separator, options.decimal_separator)\n        self._optionswidget.setColumnTypeRanges(options.columntypes)\n        self._optionswidget.setRowStates(\n            {i: v for r, v in options.rowspec for i in r}\n        )\n\n    def options(self):\n        # type: () -> Options\n        rowspec_ = self._optionswidget.rowStates()\n        rowspec = [(range(i, i + 1), v) for i, v in rowspec_.items()]\n        numformat = self._optionswidget.numbersFormat()\n        return Options(\n            encoding=self._optionswidget.encoding(),\n            dialect=self._optionswidget.dialect(),\n            columntypes=self._optionswidget.columnTypeRanges(),\n            rowspec=rowspec,\n            decimal_separator=numformat[\"decimal\"],\n            group_separator=numformat[\"group\"],\n        )\n\n    def setPath(self, path):\n        \"\"\"\n        Set the preview path.\n        \"\"\"\n        if self._path != path:\n            self._path = path\n            self.__update_preview()\n\n    def path(self):\n        \"\"\"Return the preview path\"\"\"\n        return self._path\n\n    def reset(self):\n        \"\"\"\n        Reset the options to the state previously set with `setOptions`\n        effectively undoing any user modifications since then.\n        \"\"\"\n        self.setOptions(self._options)\n\n    def restoreDefaults(self):\n        \"\"\"\n        Restore the options to default state.\n        \"\"\"\n        # preserve `_options` if set by clients (for `reset`).\n        opts = self._options\n        self.setOptions(Options(\"utf-8\", csv.excel()))\n        self._options = opts\n\n    def __update_preview(self):\n        if not self._path:\n            return\n        try:\n            f = _open(self._path, \"rb\")\n        except OSError as err:\n            traceback.print_exc(file=sys.stderr)\n            fmt = \"\".join(traceback.format_exception_only(type(err), err))\n            self.__set_error(fmt)\n        else:\n            self.__clear_error()\n            self._optionswidget.setSampleContents(f)\n            closeexisting = self.__finalizer\n            if closeexisting is not None:\n                self.destroyed.disconnect(closeexisting)\n                closeexisting()\n            self.__finalizer = weakref.finalize(self, f.close)\n            self.destroyed.connect(self.__finalizer)\n\n    def __set_error(self, text, format=Qt.PlainText):\n        self._optionswidget.setEnabled(False)\n        label = self._overlay.findChild(QLabel)  # type: QLabel\n        label.setText(text)\n        label.setTextFormat(format)\n        self._overlay.show()\n        self._overlay.raise_()\n        dialog_button_box_set_enabled(self._buttons, False)\n\n    def __clear_error(self):\n        if self._overlay.isVisibleTo(self):\n            self._overlay.hide()\n            self._optionswidget.setEnabled(True)\n\n    # Enable/disable the accept buttons on the most egregious errors.\n    def __on_preview_error(self):\n        b = self._buttons.button(QDialogButtonBox.Ok)\n        b.setEnabled(False)\n\n    def __on_preview_reset(self):\n        b = self._buttons.button(QDialogButtonBox.Ok)\n        b.setEnabled(True)\n\n\ndef dialog_button_box_set_enabled(buttonbox, enabled):\n    # type: (QDialogButtonBox, bool) -> None\n    \"\"\"\n    Disable/enable buttons in a QDialogButtonBox based on their role.\n\n    All buttons except the ones with RejectRole or HelpRole are disabled.\n    \"\"\"\n    stashname = \"__p_dialog_button_box_set_enabled\"\n    for b in buttonbox.buttons():\n        role = buttonbox.buttonRole(b)\n        if not enabled:\n            if b.property(stashname) is None:\n                b.setProperty(stashname, b.isEnabledTo(buttonbox))\n            b.setEnabled(\n                role == QDialogButtonBox.RejectRole or\n                role == QDialogButtonBox.HelpRole\n            )\n        else:\n            stashed_state = b.property(stashname)\n            if isinstance(stashed_state, bool):\n                state = stashed_state\n                b.setProperty(stashname, None)\n            else:\n                state = True\n            b.setEnabled(state)\n\n\nclass ImportItem(QStandardItem):\n    \"\"\"\n    An item representing a file path and associated load options\n    \"\"\"\n    PathRole = Qt.UserRole + 12\n    OptionsRole = Qt.UserRole + 14\n\n    def path(self):\n        # type: () -> str\n        path = self.data(ImportItem.PathRole)\n        return path if isinstance(path, str) else \"\"\n\n    def setPath(self, path):\n        # type: (str) -> None\n        self.setData(path, ImportItem.PathRole)\n\n    def options(self):\n        # type: () -> Optional[Options]\n        options = self.data(ImportItem.OptionsRole)\n        return options if isinstance(options, Options) else None\n\n    def setOptions(self, options):\n        # type: (Options) -> None\n        self.setData(options, ImportItem.OptionsRole)\n\n    @classmethod\n    def fromPath(cls, path):\n        # type: (str) -> ImportItem\n        \"\"\"\n        Create a `ImportItem` from a local file system path.\n        \"\"\"\n        iconprovider = QFileIconProvider()\n        basename = os.path.basename(path)\n        item = cls()\n        item.setText(basename)\n        item.setToolTip(path)\n        finfo = QFileInfo(path)\n        if finfo.exists():\n            item.setIcon(iconprovider.icon(finfo))\n        else:\n            item.setIcon(iconprovider.icon(QFileIconProvider.File))\n\n        item.setData(path, ImportItem.PathRole)\n        if not os.path.isfile(path):\n            item.setEnabled(False)\n            item.setToolTip(item.toolTip() + \" (missing from filesystem)\")\n        return item\n\n\ndef qname(type_):\n    # type: (type) -> str\n    \"\"\"\n    Return the fully qualified name for a `type_`.\n    \"\"\"\n\n    return \"{0.__module__}.{0.__qualname__}\".format(type_)\n\n\nclass OWCSVFileImport(widget.OWWidget):\n    name = \"CSV File Import\"\n    description = \"Import a data table from a CSV formatted file.\"\n    icon = \"icons/CSVFile.svg\"\n    priority = 11\n    category = \"Data\"\n    keywords = [\"file\", \"load\", \"read\", \"open\", \"csv\"]\n\n    outputs = [\n        widget.OutputSignal(\n            name=\"Data\",\n            type=Orange.data.Table,\n            doc=\"Loaded data set.\"),\n        widget.OutputSignal(\n            name=\"Data Frame\",\n            type=pd.DataFrame,\n            doc=\"\"\n        )\n    ]\n\n    class Error(widget.OWWidget.Error):\n        error = widget.Msg(\n            \"Unexpected error\"\n        )\n        encoding_error = widget.Msg(\n            \"Encoding error\\n\"\n            \"The file might be encoded in an unsupported encoding or it \"\n            \"might be binary\"\n        )\n\n    #: Paths and options of files accessed in a 'session'\n    _session_items = settings.Setting(\n        [], schema_only=True)  # type: List[Tuple[str, dict]]\n\n    #: Saved dialog state (last directory and selected filter)\n    dialog_state = settings.Setting({\n        \"directory\": \"\",\n        \"filter\": \"\"\n    })  # type: Dict[str, str]\n    MaxHistorySize = 50\n\n    want_main_area = False\n    buttons_area_orientation = None\n    resizing_enabled = False\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(self, *args, **kwargs)\n\n        self.__committimer = QTimer(self, singleShot=True)\n        self.__committimer.timeout.connect(self.commit)\n\n        self.__executor = qconcurrent.ThreadExecutor()\n        self.__watcher = None  # type: Optional[qconcurrent.FutureWatcher]\n\n        self.controlArea.layout().setSpacing(-1)  # reset spacing\n        grid = QGridLayout()\n        grid.addWidget(QLabel(\"File:\", self), 0, 0, 1, 1)\n\n        self.import_items_model = QStandardItemModel(self)\n        self.recent_combo = QComboBox(\n            self, objectName=\"recent-combo\", toolTip=\"Recent files.\",\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            minimumContentsLength=16,\n        )\n        self.recent_combo.setModel(self.import_items_model)\n        self.recent_combo.activated.connect(self.activate_recent)\n        self.recent_combo.setSizePolicy(\n            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n        self.browse_button = QPushButton(\n            \"…\", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),\n            toolTip=\"Browse filesystem\", autoDefault=False,\n        )\n        self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)\n        self.browse_button.clicked.connect(self.browse)\n        grid.addWidget(self.recent_combo, 0, 1, 1, 1)\n        grid.addWidget(self.browse_button, 0, 2, 1, 1)\n        self.controlArea.layout().addLayout(grid)\n\n        ###########\n        # Info text\n        ###########\n        box = gui.widgetBox(self.controlArea, \"Info\", addSpace=False)\n        self.summary_text = QTextBrowser(\n            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,\n            readOnly=True,\n        )\n        self.summary_text.viewport().setBackgroundRole(QPalette.NoRole)\n        self.summary_text.setFrameStyle(QTextBrowser.NoFrame)\n        self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4)\n        self.summary_text.viewport().setAutoFillBackground(False)\n        box.layout().addWidget(self.summary_text)\n\n        self.info.set_output_summary(self.info.NoOutput)\n\n        button_box = QDialogButtonBox(\n            orientation=Qt.Horizontal,\n            standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry\n        )\n        self.load_button = b = button_box.button(QDialogButtonBox.Retry)\n        b.setText(\"Load\")\n        b.clicked.connect(self.__committimer.start)\n        b.setEnabled(False)\n        b.setDefault(True)\n\n        self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel)\n        b.clicked.connect(self.cancel)\n        b.setEnabled(False)\n        b.setAutoDefault(False)\n\n        self.import_options_button = QPushButton(\n            \"Import Options…\", enabled=False, autoDefault=False,\n            clicked=self._activate_import_dialog\n        )\n\n        def update_buttons(cbindex):\n            self.import_options_button.setEnabled(cbindex != -1)\n            self.load_button.setEnabled(cbindex != -1)\n        self.recent_combo.currentIndexChanged.connect(update_buttons)\n\n        button_box.addButton(\n            self.import_options_button, QDialogButtonBox.ActionRole\n        )\n        button_box.setStyleSheet(\n            \"button-layout: {:d};\".format(QDialogButtonBox.MacLayout)\n        )\n        self.controlArea.layout().addWidget(button_box)\n        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)\n\n        self._restoreState()\n        item = self.current_item()\n        if item is not None:\n            self.set_selected_file(item.path(), item.options())\n\n    @Slot(int)\n    def activate_recent(self, index):\n        \"\"\"\n        Activate an item from the recent list.\n        \"\"\"\n        if 0 <= index < self.import_items_model.rowCount():\n            item = self.import_items_model.item(index)\n            assert item is not None\n            path = item.data(ImportItem.PathRole)\n            opts = item.data(ImportItem.OptionsRole)\n            if not isinstance(opts, Options):\n                opts = None\n            self.set_selected_file(path, opts)\n        else:\n            self.recent_combo.setCurrentIndex(-1)\n\n    @Slot()\n    def browse(self):\n        \"\"\"\n        Open a file dialog and select a user specified file.\n        \"\"\"\n        formats = [\n            \"Text - comma separated (*.csv, *)\",\n            \"Text - tab separated (*.tsv, *)\",\n            \"Text - all files (*)\"\n        ]\n\n        dlg = QFileDialog(\n            self, windowTitle=\"Open Data File\",\n            acceptMode=QFileDialog.AcceptOpen,\n            fileMode=QFileDialog.ExistingFile\n        )\n        dlg.setNameFilters(formats)\n        state = self.dialog_state\n        lastdir = state.get(\"directory\", \"\")\n        lastfilter = state.get(\"filter\", \"\")\n\n        if lastdir and os.path.isdir(lastdir):\n            dlg.setDirectory(lastdir)\n        if lastfilter:\n            dlg.selectNameFilter(lastfilter)\n\n        status = dlg.exec_()\n        dlg.deleteLater()\n        if status == QFileDialog.Accepted:\n            self.dialog_state[\"directory\"] = dlg.directory().absolutePath()\n            self.dialog_state[\"filter\"] = dlg.selectedNameFilter()\n\n            selected_filter = dlg.selectedNameFilter()\n            path = dlg.selectedFiles()[0]\n            # pre-flight check; try to determine the nature of the file\n            mtype = _mime_type_for_path(path)\n            if not mtype.inherits(\"text/plain\"):\n                mb = QMessageBox(\n                    parent=self,\n                    windowTitle=\"\",\n                    icon=QMessageBox.Question,\n                    text=\"The '{basename}' may be a binary file.\\n\"\n                         \"Are you sure you want to continue?\".format(\n                             basename=os.path.basename(path)),\n                    standardButtons=QMessageBox.Cancel | QMessageBox.Yes\n                )\n                mb.setWindowModality(Qt.WindowModal)\n                if mb.exec() == QMessageBox.Cancel:\n                    return\n\n            # initialize dialect based on selected extension\n            if selected_filter in formats[:-1]:\n                filter_idx = formats.index(selected_filter)\n                if filter_idx == 0:\n                    dialect = csv.excel()\n                elif filter_idx == 1:\n                    dialect = csv.excel_tab()\n                else:\n                    dialect = csv.excel_tab()\n                header = True\n            else:\n                try:\n                    dialect, header = sniff_csv_with_path(path)\n                except Exception:  # pylint: disable=broad-except\n                    dialect, header = csv.excel(), True\n\n            options = None\n            # Search for path in history.\n            # If found use the stored params to initialize the import dialog\n            items = self.itemsFromSettings()\n            idx = index_where(items, lambda t: samepath(t[0], path))\n            if idx is not None:\n                _, options_ = items[idx]\n                if options_ is not None:\n                    options = options_\n\n            if options is None:\n                if not header:\n                    rowspec = []\n                else:\n                    rowspec = [(range(0, 1), RowSpec.Header)]\n                options = Options(\n                    encoding=\"utf-8\", dialect=dialect, rowspec=rowspec)\n\n            dlg = CSVImportDialog(\n                self, windowTitle=\"Import Options\", sizeGripEnabled=True)\n            dlg.setWindowModality(Qt.WindowModal)\n            dlg.setPath(path)\n            dlg.setOptions(options)\n            status = dlg.exec_()\n            dlg.deleteLater()\n            if status == QDialog.Accepted:\n                self.set_selected_file(path, dlg.options())\n\n    def current_item(self):\n        # type: () -> Optional[ImportItem]\n        \"\"\"\n        Return the current selected item (file) or None if there is no\n        current item.\n        \"\"\"\n        idx = self.recent_combo.currentIndex()\n        if idx == -1:\n            return None\n\n        item = self.recent_combo.model().item(idx)  # type: QStandardItem\n        if isinstance(item, ImportItem):\n            return item\n        else:\n            return None\n\n    def _activate_import_dialog(self):\n        \"\"\"Activate the Import Options dialog for the  current item.\"\"\"\n        item = self.current_item()\n        assert item is not None\n        dlg = CSVImportDialog(\n            self, windowTitle=\"Import Options\", sizeGripEnabled=True,\n        )\n        dlg.setWindowModality(Qt.WindowModal)\n        dlg.setAttribute(Qt.WA_DeleteOnClose)\n        settings = QSettings()\n        qualname = qname(type(self))\n        settings.beginGroup(qualname)\n        size = settings.value(\"size\", QSize(), type=QSize)  # type: QSize\n        if size.isValid():\n            dlg.resize(size)\n\n        path = item.data(ImportItem.PathRole)\n        options = item.data(ImportItem.OptionsRole)\n        dlg.setPath(path)  # Set path before options so column types can\n        if isinstance(options, Options):\n            dlg.setOptions(options)\n\n        def update():\n            newoptions = dlg.options()\n            item.setData(newoptions, ImportItem.OptionsRole)\n            # update the stored item\n            self._add_recent(path, newoptions)\n            if newoptions != options:\n                self._invalidate()\n        dlg.accepted.connect(update)\n\n        def store_size():\n            settings.setValue(\"size\", dlg.size())\n        dlg.finished.connect(store_size)\n        dlg.show()\n\n    def set_selected_file(self, filename, options=None):\n        \"\"\"\n        Set the current selected filename path.\n        \"\"\"\n        self._add_recent(filename, options)\n        self._invalidate()\n\n    #: Saved options for a filename\n    SCHEMA = {\n        \"path\": str,  # Local filesystem path\n        \"options\": str,  # json encoded 'Options'\n    }\n\n    @classmethod\n    def _local_settings(cls):\n        # type: () -> QSettings\n        \"\"\"Return a QSettings instance with local persistent settings.\"\"\"\n        filename = \"{}.ini\".format(qname(cls))\n        fname = os.path.join(settings.widget_settings_dir(), filename)\n        return QSettings(fname, QSettings.IniFormat)\n\n    def _add_recent(self, filename, options=None):\n        # type: (str, Optional[Options]) -> None\n        \"\"\"\n        Add filename to the list of recent files.\n        \"\"\"\n        model = self.import_items_model\n        index = index_where(\n            (model.index(i, 0).data(ImportItem.PathRole)\n             for i in range(model.rowCount())),\n            lambda path: isinstance(path, str) and samepath(path, filename)\n        )\n        if index is not None:\n            item, *_ = model.takeRow(index)\n        else:\n            item = ImportItem.fromPath(filename)\n\n        model.insertRow(0, item)\n\n        if options is not None:\n            item.setOptions(options)\n\n        self.recent_combo.setCurrentIndex(0)\n        # store items to local persistent settings\n        s = self._local_settings()\n        arr = QSettings_readArray(s, \"recent\", OWCSVFileImport.SCHEMA)\n        item = {\"path\": filename}\n        if options is not None:\n            item[\"options\"] = json.dumps(options.as_dict())\n\n        arr = [item for item in arr if item.get(\"path\") != filename]\n        arr.append(item)\n        QSettings_writeArray(s, \"recent\", arr)\n\n        # update workflow session items\n        items = self._session_items[:]\n        idx = index_where(items, lambda t: samepath(t[0], filename))\n        if idx is not None:\n            del items[idx]\n        items.insert(0, (filename, options.as_dict()))\n        self._session_items = items[:OWCSVFileImport.MaxHistorySize]\n\n    def _invalidate(self):\n        # Invalidate the current output and schedule a new commit call.\n        # (NOTE: The widget enters a blocking state)\n        self.__committimer.start()\n        if self.__watcher is not None:\n            self.__cancel_task()\n        self.setBlocking(True)\n\n    def commit(self):\n        \"\"\"\n        Commit the current state and submit the load task for execution.\n\n        Note\n        ----\n        Any existing pending task is canceled.\n        \"\"\"\n        self.__committimer.stop()\n        if self.__watcher is not None:\n            self.__cancel_task()\n        self.error()\n\n        item = self.current_item()\n        if item is None:\n            return\n        path = item.data(ImportItem.PathRole)\n        opts = item.data(ImportItem.OptionsRole)\n        if not isinstance(opts, Options):\n            return\n\n        task = state = TaskState()\n        state.future = ...\n        state.watcher = qconcurrent.FutureWatcher()\n        state.progressChanged.connect(\n            self.__set_read_progress, Qt.DirectConnection)\n\n        def progress_(i, j):\n            task.emitProgressChangedOrCancel(i, j)\n\n        task.future = self.__executor.submit(\n            clear_stack_on_cancel(load_csv),\n            path, opts, progress_,\n        )\n        task.watcher.setFuture(task.future)\n        w = task.watcher\n        w.done.connect(self.__handle_result)\n        w.progress = state\n        self.__watcher = w\n        self.__set_running_state()\n\n    @Slot('qint64', 'qint64')\n    def __set_read_progress(self, read, count):\n        if count > 0:\n            self.progressBarSet(100 * read / count)\n\n    def __cancel_task(self):\n        # Cancel and dispose of the current task\n        assert self.__watcher is not None\n        w = self.__watcher\n        w.future().cancel()\n        w.progress.cancel = True\n        w.done.disconnect(self.__handle_result)\n        w.progress.progressChanged.disconnect(self.__set_read_progress)\n        self.__watcher = None\n\n    def cancel(self):\n        \"\"\"\n        Cancel current pending or executing task.\n        \"\"\"\n        if self.__watcher is not None:\n            self.__cancel_task()\n            self.__clear_running_state()\n            self.setStatusMessage(\"Cancelled\")\n            self.summary_text.setText(\n                \"<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>\"\n            )\n\n    def __set_running_state(self):\n        self.progressBarInit()\n        self.setBlocking(True)\n        self.setStatusMessage(\"Running\")\n        self.cancel_button.setEnabled(True)\n        self.load_button.setText(\"Restart\")\n        path = self.current_item().path()\n        self.Error.clear()\n        self.summary_text.setText(\n            \"<div>Loading: <i>{}</i><br/>\".format(prettyfypath(path))\n        )\n\n    def __clear_running_state(self, ):\n        self.progressBarFinished()\n        self.setStatusMessage(\"\")\n        self.setBlocking(False)\n        self.cancel_button.setEnabled(False)\n        self.load_button.setText(\"Reload\")\n\n    def __set_error_state(self, err):\n        self.Error.clear()\n        if isinstance(err, UnicodeDecodeError):\n            self.Error.encoding_error(exc_info=err)\n        else:\n            self.Error.error(exc_info=err)\n\n        path = self.current_item().path()\n        basename = os.path.basename(path)\n        if isinstance(err, UnicodeDecodeError):\n            text = (\n                \"<div><i>{basename}</i> was not loaded due to a text encoding \"\n                \"error. The file might be saved in an unknown or invalid \"\n                \"encoding, or it might be a binary file.</div>\"\n            ).format(\n                basename=escape(basename)\n            )\n        else:\n            text = (\n                \"<div><i>{basename}</i> was not loaded due to an error:\"\n                \"<p style='white-space: pre;'>{err}</p>\"\n            ).format(\n                basename=escape(basename),\n                err=\"\".join(traceback.format_exception_only(type(err), err))\n            )\n        self.summary_text.setText(text)\n\n    def __clear_error_state(self):\n        self.Error.error.clear()\n        self.summary_text.setText(\"\")\n\n    def onDeleteWidget(self):\n        \"\"\"Reimplemented.\"\"\"\n        if self.__watcher is not None:\n            self.__cancel_task()\n            self.__executor.shutdown()\n        super().onDeleteWidget()\n\n    @Slot(object)\n    def __handle_result(self, f):\n        # type: (qconcurrent.Future[pd.DataFrame]) -> None\n        assert f.done()\n        assert f is self.__watcher.future()\n        self.__watcher = None\n        self.__clear_running_state()\n\n        try:\n            df = f.result()\n            assert isinstance(df, pd.DataFrame)\n        except pandas.errors.EmptyDataError:\n            df = pd.DataFrame({})\n        except Exception as e:  # pylint: disable=broad-except\n            self.__set_error_state(e)\n            df = None\n        else:\n            self.__clear_error_state()\n\n        if df is not None:\n            table = pandas_to_table(df)\n            filename = self.current_item().path()\n            table.name = os.path.splitext(os.path.split(filename)[-1])[0]\n        else:\n            table = None\n        self.send(\"Data Frame\", df)\n        self.send('Data', table)\n        self._update_status_messages(table)\n\n    def _update_status_messages(self, data):\n        summary = len(data) if data else self.info.NoOutput\n        details = format_summary_details(data) if data else \"\"\n        self.info.set_output_summary(summary, details)\n        if data is None:\n            return\n\n        def pluralize(seq):\n            return \"s\" if len(seq) != 1 else \"\"\n\n        summary = (\"{n_instances} row{plural_1}, \"\n                   \"{n_features} feature{plural_2}, \"\n                   \"{n_meta} meta{plural_3}\").format(\n                       n_instances=len(data), plural_1=pluralize(data),\n                       n_features=len(data.domain.attributes),\n                       plural_2=pluralize(data.domain.attributes),\n                       n_meta=len(data.domain.metas),\n                       plural_3=pluralize(data.domain.metas))\n        self.summary_text.setText(summary)\n\n    def itemsFromSettings(self):\n        # type: () -> List[Tuple[str, Options]]\n        \"\"\"\n        Return items from local history.\n        \"\"\"\n        s = self._local_settings()\n        items_ = QSettings_readArray(s, \"recent\", OWCSVFileImport.SCHEMA)\n        items = []  # type: List[Tuple[str, Options]]\n        for item in items_:\n            path = item.get(\"path\", \"\")\n            if not path:\n                continue\n            opts_json = item.get(\"options\", \"\")\n            try:\n                opts = Options.from_dict(json.loads(opts_json))\n            except (csv.Error, LookupError, TypeError, json.JSONDecodeError):\n                _log.error(\"Could not reconstruct options for '%s'\", path,\n                           exc_info=True)\n            else:\n                items.append((path, opts))\n        return items[::-1]\n\n    def _restoreState(self):\n        # Restore the state. Merge session (workflow) items with the\n        # local history.\n        model = self.import_items_model\n        # local history\n        items = self.itemsFromSettings()\n        # stored session items\n        sitems = []\n        for p, m in self._session_items:\n            try:\n                item_ = (p, Options.from_dict(m))\n            except (csv.Error, LookupError):\n                # Is it better to fail then to lose a item slot?\n                _log.error(\"Failed to restore '%s'\", p, exc_info=True)\n            else:\n                sitems.append(item_)\n\n        items = sitems + items\n        items = unique(items, key=lambda t: pathnormalize(t[0]))\n\n        curr = self.recent_combo.currentIndex()\n        if curr != -1:\n            currentpath = self.recent_combo.currentData(ImportItem.PathRole)\n        else:\n            currentpath = None\n        for path, options in items:\n            item = ImportItem.fromPath(path)\n            item.setOptions(options)\n            model.appendRow(item)\n\n        if currentpath is not None:\n            idx = self.recent_combo.findData(currentpath, ImportItem.PathRole)\n            if idx != -1:\n                self.recent_combo.setCurrentIndex(idx)\n\n\n@singledispatch\ndef sniff_csv(file, samplesize=2 ** 20):\n    sniffer = csv.Sniffer()\n    sample = file.read(samplesize)\n    dialect = sniffer.sniff(sample)\n    dialect = textimport.Dialect(\n        dialect.delimiter, dialect.quotechar,\n        dialect.escapechar, dialect.doublequote,\n        dialect.skipinitialspace, dialect.quoting\n    )\n    has_header = sniffer.has_header(sample)\n    return dialect, has_header\n\n\n@sniff_csv.register(str)\n@sniff_csv.register(bytes)\ndef sniff_csv_with_path(path, encoding=\"utf-8\", samplesize=2 ** 20):\n    with _open(path, \"rt\", encoding=encoding) as f:\n        return sniff_csv(f, samplesize)\n\n\ndef _open(path, mode, encoding=None):\n    # type: (str, str, Optional[str]) -> typing.IO[Any]\n    \"\"\"\n    Open a local file `path` for reading. The file may be gzip, bz2 or zip\n    compressed.\n\n    If a zip archive then a single archive member is expected.\n\n    Parameters\n    ----------\n    path : str\n        File system path\n    mode : str\n        'r', 'rb' or 'rt'\n    encoding : Optional[str]\n        Optional text encoding, for opening in text mode.\n\n    Returns\n    -------\n    stream: io.BaseIO\n        A stream opened for reading.\n    \"\"\"\n    if mode not in {'r', 'rb', 'rt'}:\n        raise ValueError('r')\n    _, ext = os.path.splitext(path)\n    ext = ext.lower()\n    if ext == \".gz\":\n        return gzip.open(path, mode, encoding=encoding)\n    elif ext == \".bz2\":\n        return bz2.open(path, mode, encoding=encoding)\n    elif ext == \".xz\":\n        return lzma.open(path, mode, encoding=encoding)\n    elif ext == \".zip\":\n        arh = zipfile.ZipFile(path, 'r')\n        filelist = arh.infolist()\n        if len(filelist) == 1:\n            f = arh.open(filelist[0], 'r')\n            # patch the f.close to also close the main archive file\n            f_close = f.close\n\n            def close_():\n                f_close()\n                arh.close()\n            f.close = close_\n            if 't' in mode:\n                f = io.TextIOWrapper(f, encoding=encoding)\n            return f\n        else:\n            raise ValueError(\"Expected a single file in the archive.\")\n    else:\n        return open(path, mode, encoding=encoding)\n\n\ncompression_types = [\n    \"application/gzip\", \"application/zip\",\n    \"application/x-xz\", \"application/x-bzip\",\n    # application/x-lz4\n]\n\n\ndef _mime_type_for_path(path):\n    # type: (str) -> QMimeType\n    \"\"\"\n    Return the mime type of the file on a local filesystem.\n\n    In case the path is a compressed file return the mime type of its contents\n\n    Parameters\n    ----------\n    path : str\n        Local filesystem path\n\n    Returns\n    -------\n    mimetype: QMimeType\n    \"\"\"\n    db = QMimeDatabase()\n    mtype = db.mimeTypeForFile(path, QMimeDatabase.MatchDefault)\n    if any(mtype.inherits(t) for t in compression_types):\n        # peek contents\n        try:\n            with _open(path, \"rb\") as f:\n                sample = f.read(4096)\n        except Exception:  # pylint: disable=broad-except\n            sample = b''\n        mtype = db.mimeTypeForData(sample)\n    return mtype\n\n\nNA_DEFAULT = [\"\", \"?\", \".\", \"~\", \"nan\", \"NAN\", \"NaN\", \"N/A\", \"n/a\", \"NA\"]\n\nNA_VALUES = {\n    ColumnType.Numeric: NA_DEFAULT,\n    ColumnType.Categorical: NA_DEFAULT,\n    ColumnType.Time: NA_DEFAULT + [\"NaT\", \"NAT\"],\n    ColumnType.Text: [],\n    ColumnType.Auto: NA_DEFAULT,\n}\n\n\ndef load_csv(path, opts, progress_callback=None):\n    # type: (Union[AnyStr, BinaryIO], Options, ...) -> pd.DataFrame\n    def dtype(coltype):\n        # type: (ColumnType) -> Optional[str]\n        if coltype == ColumnType.Numeric:\n            return \"float\"\n        elif coltype == ColumnType.Categorical:\n            return \"category\"\n        elif coltype == ColumnType.Time:\n            return \"object\"\n        elif coltype == ColumnType.Text:\n            return \"object\"\n        elif coltype == ColumnType.Skip:\n            return None\n        elif coltype == ColumnType.Auto:\n            return None\n        else:\n            raise TypeError\n\n    def expand(ranges):\n        # type: (Iterable[Tuple[range, T]]) -> Iterable[Tuple[int, T]]\n        return ((i, x) for r, x in ranges for i in r)\n\n    dtypes = {i: dtype(c) for i, c in expand(opts.columntypes)}\n    dtypes = {i: dtp for i, dtp in dtypes.items()\n              if dtp is not None and dtp != ColumnType.Auto}\n\n    columns_ignored = {i for i, c in expand(opts.columntypes)\n                       if c == ColumnType.Skip}\n    dtcols = {i for i, c in expand(opts.columntypes)\n              if c == ColumnType.Time}\n    parse_dates = sorted(dtcols)\n    na_values = {i: NA_VALUES.get(c, NA_DEFAULT)\n                 for i, c in expand(opts.columntypes)}\n    if not parse_dates:\n        parse_dates = False\n\n    # fixup header indices to account for skipped rows (header row indices\n    # pick rows after skiprows)\n\n    hspec = sorted(opts.rowspec, key=lambda t: t[0].start)\n    header_ranges = []\n    nskiped = 0\n    for range_, state in hspec:\n        if state == RowSpec.Skipped:\n            nskiped += len(range_)\n        elif state == RowSpec.Header:\n            header_ranges.append(\n                range(range_.start - nskiped, range_.stop - nskiped)\n            )\n    headers = [i for r in header_ranges for i in r]\n    skiprows = [row for r, st in hspec if st == RowSpec.Skipped for row in r]\n\n    if not headers:\n        header = None\n        prefix = \"X.\"\n\n    elif len(headers) == 1:\n        header = headers[0]\n        prefix = None\n    else:\n        header = headers\n        prefix = None\n\n    if not skiprows:\n        skiprows = None\n\n    numbers_format_kwds = {}\n\n    if opts.decimal_separator != \".\":\n        numbers_format_kwds[\"decimal\"] = opts.decimal_separator\n\n    if opts.group_separator != \"\":\n        numbers_format_kwds[\"thousands\"] = opts.group_separator\n\n    if numbers_format_kwds:\n        # float_precision = \"round_trip\" cannot handle non c-locale decimal and\n        # thousands sep (https://github.com/pandas-dev/pandas/issues/35365).\n        # Fallback to 'high'.\n        numbers_format_kwds[\"float_precision\"] = \"high\"\n    else:\n        numbers_format_kwds[\"float_precision\"] = \"round_trip\"\n\n    with ExitStack() as stack:\n        if isinstance(path, (str, bytes)):\n            f = stack.enter_context(_open(path, 'rb'))\n        elif isinstance(path, (io.RawIOBase, io.BufferedIOBase)) or \\\n                hasattr(path, \"read\"):\n            f = path\n        else:\n            raise TypeError()\n        file = TextReadWrapper(\n            f, encoding=opts.encoding,\n            progress_callback=progress_callback)\n        stack.callback(file.detach)\n        df = pd.read_csv(\n            file, sep=opts.dialect.delimiter, dialect=opts.dialect,\n            skipinitialspace=opts.dialect.skipinitialspace,\n            header=header, skiprows=skiprows,\n            dtype=dtypes, parse_dates=parse_dates, prefix=prefix,\n            na_values=na_values, keep_default_na=False,\n            **numbers_format_kwds\n        )\n        df = guess_types(df, dtypes, columns_ignored)\n\n        if columns_ignored:\n            # TODO: use 'usecols' parameter in `read_csv` call to\n            # avoid loading/parsing the columns\n            df.drop(\n                columns=[df.columns[i] for i in columns_ignored\n                         if i < len(df.columns)],\n                inplace=True\n            )\n        return df\n\n\ndef guess_types(\n        df: pd.DataFrame, dtypes: Dict[int, str], columns_ignored: List[int]\n) -> pd.DataFrame:\n    \"\"\"\n    Guess data type for variables according to values.\n\n    Parameters\n    ----------\n    df\n        Data frame\n    dtypes\n        The dictionary with data types set by user. We will guess values only\n        for columns that does not have data type defined.\n    columns_ignored\n        List with indices of ignored columns. Ignored columns are skipped.\n\n    Returns\n    -------\n    A data frame with changed dtypes according to the strategy.\n    \"\"\"\n    for i, col in enumerate(df):\n        # only when automatic is set in widget dialog\n        if dtypes.get(i, None) is None and i not in columns_ignored:\n            df[col] = guess_data_type(df[col])\n    return df\n\n\ndef guess_data_type(col: pd.Series) -> pd.Series:\n    \"\"\"\n    Guess column types. Logic is same than in guess_data_type from io_utils\n    module. This function only change the dtype of the column such that later\n    correct Orange.data.variable is used.\n    Logic:\n    - if can converted to date-time (ISO) -> TimeVariable\n    - if numeric (only numbers)\n        - only values {0, 1} or {1, 2} -> DiscreteVariable\n        - else -> ContinuousVariable\n    - if not numbers:\n        - num_unique_values < len(data) ** 0.7 and < 100 -> DiscreteVariable\n        - else -> StringVariable\n\n    Parameters\n    ----------\n    col\n        Data column\n\n    Returns\n    -------\n    Data column with correct dtype\n    \"\"\"\n    def parse_dates(s):\n        \"\"\"\n        This is an extremely fast approach to datetime parsing.\n        For large data, the same dates are often repeated. Rather than\n        re-parse these, we store all unique dates, parse them, and\n        use a lookup to convert all dates.\n        \"\"\"\n        try:\n            dates = {date: pd.to_datetime(date) for date in s.unique()}\n        except ValueError:\n            return None\n        return s.map(dates)\n\n    if pdtypes.is_numeric_dtype(col):\n        unique_values = col.unique()\n        if len(unique_values) <= 2 and (\n                len(np.setdiff1d(unique_values, [0, 1])) == 0\n                or len(np.setdiff1d(unique_values, [1, 2])) == 0):\n            return col.astype(\"category\")\n    else:  # object\n        # try parse as date - if None not a date\n        parsed_col = parse_dates(col)\n        if parsed_col is not None:\n            return parsed_col\n        unique_values = col.unique()\n        if len(unique_values) < 100 and len(unique_values) < len(col)**0.7:\n            return col.astype(\"category\")\n    return col\n\n\ndef clear_stack_on_cancel(f):\n    \"\"\"\n    A decorator that catches the TaskState.UserCancelException exception\n    and clears the exception's traceback to remove local references.\n\n    Parameters\n    ----------\n    f : callable\n\n    Returns\n    -------\n    wrapped : callable\n    \"\"\"\n    def wrapper(*args, **kwargs):\n        try:\n            return f(*args, **kwargs)\n        except TaskState.UserCancelException as e:\n            # TODO: Is this enough to allow immediate gc of the stack?\n            # How does it chain across cython code?\n            # Maybe just return None.\n            e = e.with_traceback(None)\n            e.__context__ = None\n            e.__cause__ = None\n            raise e\n        except BaseException as e:\n            traceback.clear_frames(e.__traceback__)\n            raise\n\n    return wrapper\n\n\nclass TaskState(QObject, PyOwned):\n    class UserCancelException(BaseException):\n        \"\"\"User interrupt exception.\"\"\"\n\n    #: Signal emitted with the current read progress. First value is the current\n    #: progress state, second value is the total progress to complete\n    #: (-1 if unknown)\n    progressChanged = Signal('qint64', 'qint64')\n    __progressChanged = Signal('qint64', 'qint64')\n    #: Was cancel requested.\n    cancel = False\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        # route the signal via this object's queue\n        self.__progressChanged.connect(\n            self.progressChanged, Qt.QueuedConnection)\n\n    def emitProgressChangedOrCancel(self, current, total):\n        # type: (int, int) -> None\n        \"\"\"\n        Emit the progressChanged signal with `current` and `total`.\n        \"\"\"\n        if self.cancel:\n            raise TaskState.UserCancelException()\n        else:\n            self.__progressChanged.emit(current, total)\n\n\nclass TextReadWrapper(io.TextIOWrapper):\n    \"\"\"\n    TextIOWrapper reporting the read progress.\n\n    Assuming a single forward read pass.\n    \"\"\"\n\n    #: A `Callable[[int, int], []]` called when the file position is\n    #: advanced by read; called with current byte position and total\n    #: file size.\n    progress_callback = ...  # type: Callable[[int, int], None]\n\n    def __init__(self, buffer, *args, progress_callback=None, **kwargs):\n        super().__init__(buffer, *args, **kwargs)\n        if progress_callback is None:\n            def progress_callback(i, j):  # pylint: disable=unused-argument\n                pass\n        self.progress_callback = progress_callback\n        try:\n            self.__size = os.fstat(buffer.fileno()).st_size\n        except OSError:\n            self.__size = -1\n\n    def read(self, size=-1):\n        s = super().read(size)\n        # try to go around any gzip/bz2/lzma wrappers to the base\n        # raw file (does not work for zipfile.ZipExtFile; should\n        # dispatch on buffer type)\n        try:\n            fd = self.buffer.fileno()\n        except (AttributeError, io.UnsupportedOperation):\n            pos = -1\n        else:\n            try:\n                pos = os.lseek(fd, 0, os.SEEK_CUR)\n            except OSError:\n                pos = -1\n\n        self.progress_callback(pos, self.__size)\n        return s\n\n\ndef index_where(iterable, pred):\n    # type: (Iterable[T], Callable[[T], bool]) -> Optional[int]\n    \"\"\"\n    Return the (first) index of el in `iterable` where `pred(el)` returns True.\n\n    If no element matches return `None`.\n    \"\"\"\n    for i, el in enumerate(iterable):\n        if pred(el):\n            return i\n    return None\n\n\ndef unique(iterable, key=None):\n    # type: (Iterable[T], Optional[Callable[[T], Hashable]]) -> Iterable[T]\n    \"\"\"\n    Return an iterator over unique elements of `iterable`.\n\n    If `key` is supplied it is used as a substitute for determining\n    'uniqueness' of elements.\n\n    Parameters\n    ----------\n    iterable : Iterable[T]\n    key : Callable[[T], Hashable]\n\n    Returns\n    -------\n    unique : Iterable[T]\n    \"\"\"\n    seen = set()\n    if key is None:\n        key = lambda t: t\n    for el in iterable:\n        el_k = key(el)\n        if el_k not in seen:\n            seen.add(el_k)\n            yield el\n\n\ndef samepath(p1, p2):\n    # type: (str, str) -> bool\n    \"\"\"\n    Return True if the paths `p1` and `p2` match after case and path\n    normalization.\n    \"\"\"\n    return pathnormalize(p1) == pathnormalize(p2)\n\n\ndef pathnormalize(p):\n    \"\"\"\n    Normalize a path (apply both path and case normalization.\n    \"\"\"\n    return os.path.normcase(os.path.normpath(p))\n\n\ndef prettyfypath(path):\n    \"\"\"\n    Return the path with the $HOME prefix shortened to '~/' if applicable.\n\n    Example\n    -------\n    >>> prettyfypath(\"/home/user/file.dat\")\n    '~/file.dat'\n    \"\"\"\n    home = os.path.expanduser(\"~/\")\n    home_n = pathnormalize(home)\n    path_n = pathnormalize(path)\n    if path_n.startswith(home_n):\n        path = os.path.join(\"~\", os.path.relpath(path, home))\n    return path\n\n\ndef pandas_to_table(df):\n    # type: (pd.DataFrame) -> Orange.data.Table\n    \"\"\"\n    Convert a pandas.DataFrame to a Orange.data.Table instance.\n    \"\"\"\n    index = df.index\n    if not isinstance(index, pd.RangeIndex):\n        df = df.reset_index()\n\n    columns = []  # type: List[Tuple[Orange.data.Variable, np.ndarray]]\n\n    for header, series in df.items():  # type: (Any, pd.Series)\n        if pdtypes.is_categorical(series):\n            coldata = series.values  # type: pd.Categorical\n            categories = [str(c) for c in coldata.categories]\n            var = Orange.data.DiscreteVariable.make(\n                str(header), values=categories\n            )\n            # Remap the coldata into the var.values order/set\n            coldata = pd.Categorical(\n                coldata.astype(\"str\"), categories=var.values\n            )\n            codes = coldata.codes\n            assert np.issubdtype(codes.dtype, np.integer)\n            orangecol = np.array(codes, dtype=np.float)\n            orangecol[codes < 0] = np.nan\n        elif pdtypes.is_datetime64_any_dtype(series):\n            # Check that this converts tz local to UTC\n            series = series.astype(np.dtype(\"M8[ns]\"))\n            coldata = series.values  # type: np.ndarray\n            assert coldata.dtype == \"M8[ns]\"\n            mask = np.isnat(coldata)\n            orangecol = coldata.astype(np.int64) / 10 ** 9\n            orangecol[mask] = np.nan\n            var = Orange.data.TimeVariable.make(str(header))\n            var.have_date = var.have_time = 1\n        elif pdtypes.is_object_dtype(series):\n            coldata = series.fillna('').values\n            assert isinstance(coldata, np.ndarray)\n            orangecol = coldata\n            var = Orange.data.StringVariable.make(str(header))\n        elif pdtypes.is_integer_dtype(series):\n            coldata = series.values\n            var = Orange.data.ContinuousVariable.make(str(header))\n            var.number_of_decimals = 0\n            orangecol = coldata.astype(np.float64)\n        elif pdtypes.is_numeric_dtype(series):\n            orangecol = series.values.astype(np.float64)\n            var = Orange.data.ContinuousVariable.make(str(header))\n        else:\n            warnings.warn(\n                \"Column '{}' with dtype: {} skipped.\"\n                .format(header, series.dtype),\n                UserWarning\n            )\n            continue\n        columns.append((var, orangecol))\n\n    cols_x = [(var, col) for var, col in columns if var.is_primitive()]\n    cols_m = [(var, col) for var, col in columns if not var.is_primitive()]\n\n    variables = [v for v, _ in cols_x]\n    if cols_x:\n        X = np.column_stack([a for _, a in cols_x])\n    else:\n        X = np.empty((df.shape[0], 0), dtype=np.float)\n    metas = [v for v, _ in cols_m]\n    if cols_m:\n        M = np.column_stack([a for _, a in cols_m])\n    else:\n        M = None\n\n    domain = Orange.data.Domain(variables, metas=metas)\n    return Orange.data.Table.from_numpy(domain, X, None, M)\n\n\ndef main(argv=None):  # pragma: no cover\n    app = QApplication(argv or [])\n    w = OWCSVFileImport()\n    w.show()\n    w.raise_()\n    app.exec_()\n    w.saveSettings()\n    w.onDeleteWidget()\n    return 0\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    sys.exit(main(sys.argv))\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/owcsvimport.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "078adca1c",
                "new_commit_hash": "805f1ec32",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/owcsvimport.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/owcsvimport.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1101,
                            "length": 9
                        },
                        "new_range": {
                            "start": 1101,
                            "length": 14
                        },
                        "section": "def _open(path, mode, encoding=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        arh = zipfile.ZipFile(path, 'r')",
                                "type": "context"
                            },
                            {
                                "content": "        filelist = arh.infolist()",
                                "type": "context"
                            },
                            {
                                "content": "        if len(filelist) == 1:",
                                "type": "context"
                            },
                            {
                                "content": "            filename = filelist[0]",
                                "type": "deleted"
                            },
                            {
                                "content": "            zinfo = arh.getinfo(filename)",
                                "type": "deleted"
                            },
                            {
                                "content": "            f = arh.open(zinfo.filename, 'r')",
                                "type": "deleted"
                            },
                            {
                                "content": "            f = arh.open(filelist[0], 'r')",
                                "type": "added"
                            },
                            {
                                "content": "            # patch the f.close to also close the main archive file",
                                "type": "added"
                            },
                            {
                                "content": "            f_close = f.close",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "            def close_():",
                                "type": "added"
                            },
                            {
                                "content": "                f_close()",
                                "type": "added"
                            },
                            {
                                "content": "                arh.close()",
                                "type": "added"
                            },
                            {
                                "content": "            f.close = close_",
                                "type": "added"
                            },
                            {
                                "content": "            if 't' in mode:",
                                "type": "context"
                            },
                            {
                                "content": "                f = io.TextIOWrapper(f, encoding=encoding)",
                                "type": "context"
                            },
                            {
                                "content": "            return f",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owcsvimport.py",
                            "type": "function",
                            "name": "_open",
                            "content": "def _open(path, mode, encoding=None):\n    # type: (str, str, Optional[str]) -> typing.IO[Any]\n    \"\"\"\n    Open a local file `path` for reading. The file may be gzip, bz2 or zip\n    compressed.\n\n    If a zip archive then a single archive member is expected.\n\n    Parameters\n    ----------\n    path : str\n        File system path\n    mode : str\n        'r', 'rb' or 'rt'\n    encoding : Optional[str]\n        Optional text encoding, for opening in text mode.\n\n    Returns\n    -------\n    stream: io.BaseIO\n        A stream opened for reading.\n    \"\"\"\n    if mode not in {'r', 'rb', 'rt'}:\n        raise ValueError('r')\n    _, ext = os.path.splitext(path)\n    ext = ext.lower()\n    if ext == \".gz\":\n        return gzip.open(path, mode, encoding=encoding)\n    elif ext == \".bz2\":\n        return bz2.open(path, mode, encoding=encoding)\n    elif ext == \".xz\":\n        return lzma.open(path, mode, encoding=encoding)\n    elif ext == \".zip\":\n        arh = zipfile.ZipFile(path, 'r')\n        filelist = arh.infolist()\n        if len(filelist) == 1:\n            filename = filelist[0]\n            zinfo = arh.getinfo(filename)\n            f = arh.open(zinfo.filename, 'r')\n            if 't' in mode:\n                f = io.TextIOWrapper(f, encoding=encoding)\n            return f\n        else:\n            raise ValueError(\"Expected a single file in the archive.\")\n    else:\n        return open(path, mode, encoding=encoding)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 1068,
                            "end_lineno": 1113,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# pylint: disable=no-self-use\nimport unittest\nfrom unittest import mock\nfrom contextlib import ExitStack\n\nimport os\nimport io\nimport csv\nimport json\n\nimport numpy as np\nfrom numpy.testing import assert_array_equal\n\nfrom AnyQt.QtCore import QSettings\n\nfrom Orange.data import DiscreteVariable, TimeVariable, ContinuousVariable, \\\n    StringVariable\nfrom Orange.tests import named_file\nfrom Orange.widgets.tests.base import WidgetTest, GuiTest\nfrom Orange.widgets.data import owcsvimport\nfrom Orange.widgets.data.owcsvimport import (\n    pandas_to_table, ColumnType, RowSpec\n)\nfrom Orange.widgets.utils.settings import QSettings_writeArray\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nclass TestOWCSVFileImport(WidgetTest):\n    def setUp(self):\n        self._stack = ExitStack().__enter__()\n        # patch `_local_settings` to avoid side effects, across tests\n        fname = self._stack.enter_context(named_file(\"\"))\n        s = QSettings(fname, QSettings.IniFormat)\n        self._stack.enter_context(mock.patch.object(\n            owcsvimport.OWCSVFileImport, \"_local_settings\", lambda *a: s\n        ))\n        self.widget = self.create_widget(owcsvimport.OWCSVFileImport)\n\n    def tearDown(self):\n        self.widgets.remove(self.widget)\n        self.widget.onDeleteWidget()\n        self.widget = None\n        self._stack.close()\n\n    def test_basic(self):\n        w = self.widget\n        w.activate_recent(0)\n        w.cancel()\n\n    data_regions_options = owcsvimport.Options(\n        encoding=\"ascii\", dialect=csv.excel_tab(),\n        columntypes=[\n            (range(0, 1), ColumnType.Categorical),\n            (range(1, 2), ColumnType.Text),\n            (range(2, 3), ColumnType.Categorical),\n        ], rowspec=[\n            (range(0, 1), RowSpec.Header),\n            (range(1, 3), RowSpec.Skipped),\n        ],\n    )\n\n    def _check_data_regions(self, table):\n        self.assertEqual(len(table), 3)\n        self.assertEqual(len(table), 3)\n        self.assertTrue(table.domain[\"id\"].is_discrete)\n        self.assertTrue(table.domain[\"continent\"].is_discrete)\n        self.assertTrue(table.domain[\"state\"].is_string)\n        assert_array_equal(table.X, [[0, 1], [1, 1], [2, 0]])\n        assert_array_equal(table.metas,\n                           np.array([[\"UK\"], [\"Russia\"], [\"Mexico\"]], object))\n\n    def test_restore(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        out = self.get_output(\"Data\", w)\n        self._check_data_regions(out)\n        self.assertEqual(out.name, \"data-regions\")\n\n    def test_restore_from_local(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        s = owcsvimport.OWCSVFileImport._local_settings()\n        s.clear()\n        QSettings_writeArray(\n            s, \"recent\", [\n                {\"path\": path,\n                 \"options\": json.dumps(self.data_regions_options.as_dict())}]\n        )\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        self.assertEqual(\n            w._session_items, [(path, self.data_regions_options.as_dict())],\n            \"local settings item must be recorded in _session_items when \"\n            \"activated in __init__\",\n        )\n        self._check_data_regions(self.get_output(\"Data\", w))\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        output_sum = widget.info.set_output_summary = mock.Mock()\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n    data_csv_types_options = owcsvimport.Options(\n        encoding=\"ascii\", dialect=csv.excel_tab(),\n        columntypes=[\n            (range(0, 5), ColumnType.Auto),\n        ]\n    )\n\n    def test_type_guessing(self):\n        \"\"\" Check if correct column type is guessed when column type auto \"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-csv-types.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_csv_types_options.as_dict())\n                ]\n            }\n        )\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n        domain = output.domain\n\n        self.assertIsInstance(domain[\"time\"], TimeVariable)\n        self.assertIsInstance(domain[\"discrete1\"], DiscreteVariable)\n        self.assertIsInstance(domain[\"discrete2\"], DiscreteVariable)\n        self.assertIsInstance(domain[\"numeric1\"], ContinuousVariable)\n        self.assertIsInstance(domain[\"numeric2\"], ContinuousVariable)\n        self.assertIsInstance(domain[\"string\"], StringVariable)\n\n\nclass TestImportDialog(GuiTest):\n    def test_dialog(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"grep_file.txt\")\n        d = owcsvimport.CSVImportDialog()\n        d.setPath(path)\n        opts = owcsvimport.Options(\n            encoding=\"utf-8\",\n            dialect=owcsvimport.textimport.Dialect(\n                \" \", \"\\\"\", \"\\\\\", True, True\n            ),\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Categorical)\n            ],\n            rowspec=[\n                (range(0, 4), RowSpec.Skipped),\n                (range(4, 5), RowSpec.Header),\n                (range(8, 13), RowSpec.Skipped),\n            ]\n        )\n        d.setOptions(opts)\n        d.restoreDefaults()\n        opts1 = d.options()\n        d.reset()\n        opts1 = d.options()\n\n\nclass TestUtils(unittest.TestCase):\n    def test_load_csv(self):\n        contents = (\n            b'1/1/1990,1.0,[,one,\\n'\n            b'1/1/1990,2.0,],two,\\n'\n            b'1/1/1990,3.0,{,three,'\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Time),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Text),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Auto),\n            ],\n            rowspec=[]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (3, 5))\n        self.assertSequenceEqual(\n            list(df.dtypes),\n            [np.dtype(\"M8[ns]\"), np.dtype(float), np.dtype(object),\n             \"category\", np.dtype(float)],\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Skip),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Skip),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Skip),\n            ],\n            rowspec=[\n                (range(1, 2), RowSpec.Skipped)\n            ]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (2, 2))\n        self.assertSequenceEqual(\n            list(df.dtypes), [np.dtype(float), \"category\"]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 0]), [1.0, 3.0]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 1]), [\"one\", \"three\"]\n        )\n\n    def test_convert(self):\n        contents = (\n            b'I, J,  K\\n'\n            b' , A,   \\n'\n            b'B,  ,  1\\n'\n            b'?, ., NA'\n        )\n\n        class dialect(csv.excel):\n            skipinitialspace = True\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Text),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Text),\n\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.metas[:, 0], [\"\", \"B\", \"?\"])\n        assert_array_equal(tb.metas[:, 1], [\"\", \"1\", \"NA\"])\n        assert_array_equal(tb.X[:, 0], [0.0, np.nan, np.nan])\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Categorical),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Numeric),\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.X[:, 0], [np.nan, 0, np.nan])\n        assert_array_equal(tb.X[:, 1], [0, np.nan, np.nan])\n        assert_array_equal(tb.X[:, 2], [np.nan, 1, np.nan])\n\n    def test_decimal_format(self):\n        class Dialect(csv.excel):\n            delimiter = \";\"\n\n        contents = b'3,21;3,37\\n4,13;1.000,142'\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=Dialect(),\n            decimal_separator=\",\",\n            group_separator=\".\",\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n            ],\n            rowspec=[],\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        assert_array_equal(df.values, np.array([[3.21, 3.37], [4.13, 1000.142]]))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "new_file_content": "# pylint: disable=no-self-use,protected-access\nimport unittest\nfrom unittest import mock\nfrom contextlib import ExitStack\n\nimport os\nimport io\nimport csv\nimport json\n\nimport numpy as np\nfrom numpy.testing import assert_array_equal\n\nfrom AnyQt.QtCore import QSettings\n\nfrom Orange.data import DiscreteVariable, TimeVariable, ContinuousVariable, \\\n    StringVariable\nfrom Orange.tests import named_file\nfrom Orange.widgets.tests.base import WidgetTest, GuiTest\nfrom Orange.widgets.data import owcsvimport\nfrom Orange.widgets.data.owcsvimport import (\n    pandas_to_table, ColumnType, RowSpec\n)\nfrom Orange.widgets.utils.settings import QSettings_writeArray\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nclass TestOWCSVFileImport(WidgetTest):\n    def setUp(self):\n        self._stack = ExitStack().__enter__()\n        # patch `_local_settings` to avoid side effects, across tests\n        fname = self._stack.enter_context(named_file(\"\"))\n        s = QSettings(fname, QSettings.IniFormat)\n        self._stack.enter_context(mock.patch.object(\n            owcsvimport.OWCSVFileImport, \"_local_settings\", lambda *a: s\n        ))\n        self.widget = self.create_widget(owcsvimport.OWCSVFileImport)\n\n    def tearDown(self):\n        self.widgets.remove(self.widget)\n        self.widget.onDeleteWidget()\n        self.widget = None\n        self._stack.close()\n\n    def test_basic(self):\n        w = self.widget\n        w.activate_recent(0)\n        w.cancel()\n\n    data_regions_options = owcsvimport.Options(\n        encoding=\"ascii\", dialect=csv.excel_tab(),\n        columntypes=[\n            (range(0, 1), ColumnType.Categorical),\n            (range(1, 2), ColumnType.Text),\n            (range(2, 3), ColumnType.Categorical),\n        ], rowspec=[\n            (range(0, 1), RowSpec.Header),\n            (range(1, 3), RowSpec.Skipped),\n        ],\n    )\n\n    def _check_data_regions(self, table):\n        self.assertEqual(len(table), 3)\n        self.assertEqual(len(table), 3)\n        self.assertTrue(table.domain[\"id\"].is_discrete)\n        self.assertTrue(table.domain[\"continent\"].is_discrete)\n        self.assertTrue(table.domain[\"state\"].is_string)\n        assert_array_equal(table.X, [[0, 1], [1, 1], [2, 0]])\n        assert_array_equal(table.metas,\n                           np.array([[\"UK\"], [\"Russia\"], [\"Mexico\"]], object))\n\n    def test_restore(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        out = self.get_output(\"Data\", w)\n        self._check_data_regions(out)\n        self.assertEqual(out.name, \"data-regions\")\n\n    def test_restore_from_local(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        s = owcsvimport.OWCSVFileImport._local_settings()\n        s.clear()\n        QSettings_writeArray(\n            s, \"recent\", [\n                {\"path\": path,\n                 \"options\": json.dumps(self.data_regions_options.as_dict())}]\n        )\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        self.assertEqual(\n            w._session_items, [(path, self.data_regions_options.as_dict())],\n            \"local settings item must be recorded in _session_items when \"\n            \"activated in __init__\",\n        )\n        self._check_data_regions(self.get_output(\"Data\", w))\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        output_sum = widget.info.set_output_summary = mock.Mock()\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n    data_csv_types_options = owcsvimport.Options(\n        encoding=\"ascii\", dialect=csv.excel_tab(),\n        columntypes=[\n            (range(0, 5), ColumnType.Auto),\n        ]\n    )\n\n    def test_type_guessing(self):\n        \"\"\" Check if correct column type is guessed when column type auto \"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-csv-types.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_csv_types_options.as_dict())\n                ]\n            }\n        )\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n        domain = output.domain\n\n        self.assertIsInstance(domain[\"time\"], TimeVariable)\n        self.assertIsInstance(domain[\"discrete1\"], DiscreteVariable)\n        self.assertIsInstance(domain[\"discrete2\"], DiscreteVariable)\n        self.assertIsInstance(domain[\"numeric1\"], ContinuousVariable)\n        self.assertIsInstance(domain[\"numeric2\"], ContinuousVariable)\n        self.assertIsInstance(domain[\"string\"], StringVariable)\n\n\nclass TestImportDialog(GuiTest):\n    def test_dialog(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"grep_file.txt\")\n        d = owcsvimport.CSVImportDialog()\n        d.setPath(path)\n        opts = owcsvimport.Options(\n            encoding=\"utf-8\",\n            dialect=owcsvimport.textimport.Dialect(\n                \" \", \"\\\"\", \"\\\\\", True, True\n            ),\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Categorical)\n            ],\n            rowspec=[\n                (range(0, 4), RowSpec.Skipped),\n                (range(4, 5), RowSpec.Header),\n                (range(8, 13), RowSpec.Skipped),\n            ]\n        )\n        d.setOptions(opts)\n        d.restoreDefaults()\n        opts1 = d.options()\n        d.reset()\n        opts1 = d.options()\n\n\nclass TestUtils(unittest.TestCase):\n    def test_load_csv(self):\n        contents = (\n            b'1/1/1990,1.0,[,one,\\n'\n            b'1/1/1990,2.0,],two,\\n'\n            b'1/1/1990,3.0,{,three,'\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Time),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Text),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Auto),\n            ],\n            rowspec=[]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (3, 5))\n        self.assertSequenceEqual(\n            list(df.dtypes),\n            [np.dtype(\"M8[ns]\"), np.dtype(float), np.dtype(object),\n             \"category\", np.dtype(float)],\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Skip),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Skip),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Skip),\n            ],\n            rowspec=[\n                (range(1, 2), RowSpec.Skipped)\n            ]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (2, 2))\n        self.assertSequenceEqual(\n            list(df.dtypes), [np.dtype(float), \"category\"]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 0]), [1.0, 3.0]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 1]), [\"one\", \"three\"]\n        )\n\n    def test_convert(self):\n        contents = (\n            b'I, J,  K\\n'\n            b' , A,   \\n'\n            b'B,  ,  1\\n'\n            b'?, ., NA'\n        )\n\n        class dialect(csv.excel):\n            skipinitialspace = True\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Text),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Text),\n\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.metas[:, 0], [\"\", \"B\", \"?\"])\n        assert_array_equal(tb.metas[:, 1], [\"\", \"1\", \"NA\"])\n        assert_array_equal(tb.X[:, 0], [0.0, np.nan, np.nan])\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Categorical),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Numeric),\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.X[:, 0], [np.nan, 0, np.nan])\n        assert_array_equal(tb.X[:, 1], [0, np.nan, np.nan])\n        assert_array_equal(tb.X[:, 2], [np.nan, 1, np.nan])\n\n    def test_decimal_format(self):\n        class Dialect(csv.excel):\n            delimiter = \";\"\n\n        contents = b'3,21;3,37\\n4,13;1.000,142'\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=Dialect(),\n            decimal_separator=\",\",\n            group_separator=\".\",\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n            ],\n            rowspec=[],\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        assert_array_equal(df.values, np.array([[3.21, 3.37], [4.13, 1000.142]]))\n\n    def test_open_compressed(self):\n        content = 'abc'\n        for ext in [\"txt\", \"gz\", \"bz2\", \"xz\", \"zip\"]:\n            with named_file('', suffix=f\".{ext}\") as fname:\n                with _open_write(fname, \"wt\", encoding=\"ascii\") as f:\n                    f.write(content)\n                f.close()\n\n                with owcsvimport._open(fname, \"rt\", encoding=\"ascii\") as f:\n                    self.assertEqual(content, f.read())\n\n\ndef _open_write(path, mode, encoding=None):\n    # pylint: disable=import-outside-toplevel\n    if mode not in {'w', 'wb', 'wt'}:\n        raise ValueError('r')\n    _, ext = os.path.splitext(path)\n    ext = ext.lower()\n    if ext == \".gz\":\n        import gzip\n        return gzip.open(path, mode, encoding=encoding)\n    elif ext == \".bz2\":\n        import bz2\n        return bz2.open(path, mode, encoding=encoding)\n    elif ext == \".xz\":\n        import lzma\n        return lzma.open(path, mode, encoding=encoding)\n    elif ext == \".zip\":\n        import zipfile\n        arh = zipfile.ZipFile(path, 'w')\n        filename, _ = os.path.splitext(os.path.basename(path))\n        f = arh.open(filename, mode=\"w\")\n        f_close = f.close\n        # patch the f.close to also close the main archive file\n\n        def close_():\n            f_close()\n            arh.close()\n        f.close = close_\n        if 't' in mode:\n            f = io.TextIOWrapper(f, encoding=encoding)\n        return f\n    else:\n        return open(path, mode, encoding=encoding)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/tests/test_owcsvimport.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "b64bf7835",
                "new_commit_hash": "6335263b6",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/tests/test_owcsvimport.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/tests/test_owcsvimport.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1,
                            "length": 4
                        },
                        "new_range": {
                            "start": 1,
                            "length": 4
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "# pylint: disable=no-self-use",
                                "type": "deleted"
                            },
                            {
                                "content": "# pylint: disable=no-self-use,protected-access",
                                "type": "added"
                            },
                            {
                                "content": "import unittest",
                                "type": "context"
                            },
                            {
                                "content": "from unittest import mock",
                                "type": "context"
                            },
                            {
                                "content": "from contextlib import ExitStack",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 306,
                            "length": 6
                        },
                        "new_range": {
                            "start": 306,
                            "length": 51
                        },
                        "section": "class TestUtils(unittest.TestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        df = owcsvimport.load_csv(io.BytesIO(contents), opts)",
                                "type": "context"
                            },
                            {
                                "content": "        assert_array_equal(df.values, np.array([[3.21, 3.37], [4.13, 1000.142]]))",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_open_compressed(self):",
                                "type": "added"
                            },
                            {
                                "content": "        content = 'abc'",
                                "type": "added"
                            },
                            {
                                "content": "        for ext in [\"txt\", \"gz\", \"bz2\", \"xz\", \"zip\"]:",
                                "type": "added"
                            },
                            {
                                "content": "            with named_file('', suffix=f\".{ext}\") as fname:",
                                "type": "added"
                            },
                            {
                                "content": "                with _open_write(fname, \"wt\", encoding=\"ascii\") as f:",
                                "type": "added"
                            },
                            {
                                "content": "                    f.write(content)",
                                "type": "added"
                            },
                            {
                                "content": "                f.close()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "                with owcsvimport._open(fname, \"rt\", encoding=\"ascii\") as f:",
                                "type": "added"
                            },
                            {
                                "content": "                    self.assertEqual(content, f.read())",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "def _open_write(path, mode, encoding=None):",
                                "type": "added"
                            },
                            {
                                "content": "    # pylint: disable=import-outside-toplevel",
                                "type": "added"
                            },
                            {
                                "content": "    if mode not in {'w', 'wb', 'wt'}:",
                                "type": "added"
                            },
                            {
                                "content": "        raise ValueError('r')",
                                "type": "added"
                            },
                            {
                                "content": "    _, ext = os.path.splitext(path)",
                                "type": "added"
                            },
                            {
                                "content": "    ext = ext.lower()",
                                "type": "added"
                            },
                            {
                                "content": "    if ext == \".gz\":",
                                "type": "added"
                            },
                            {
                                "content": "        import gzip",
                                "type": "added"
                            },
                            {
                                "content": "        return gzip.open(path, mode, encoding=encoding)",
                                "type": "added"
                            },
                            {
                                "content": "    elif ext == \".bz2\":",
                                "type": "added"
                            },
                            {
                                "content": "        import bz2",
                                "type": "added"
                            },
                            {
                                "content": "        return bz2.open(path, mode, encoding=encoding)",
                                "type": "added"
                            },
                            {
                                "content": "    elif ext == \".xz\":",
                                "type": "added"
                            },
                            {
                                "content": "        import lzma",
                                "type": "added"
                            },
                            {
                                "content": "        return lzma.open(path, mode, encoding=encoding)",
                                "type": "added"
                            },
                            {
                                "content": "    elif ext == \".zip\":",
                                "type": "added"
                            },
                            {
                                "content": "        import zipfile",
                                "type": "added"
                            },
                            {
                                "content": "        arh = zipfile.ZipFile(path, 'w')",
                                "type": "added"
                            },
                            {
                                "content": "        filename, _ = os.path.splitext(os.path.basename(path))",
                                "type": "added"
                            },
                            {
                                "content": "        f = arh.open(filename, mode=\"w\")",
                                "type": "added"
                            },
                            {
                                "content": "        f_close = f.close",
                                "type": "added"
                            },
                            {
                                "content": "        # patch the f.close to also close the main archive file",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        def close_():",
                                "type": "added"
                            },
                            {
                                "content": "            f_close()",
                                "type": "added"
                            },
                            {
                                "content": "            arh.close()",
                                "type": "added"
                            },
                            {
                                "content": "        f.close = close_",
                                "type": "added"
                            },
                            {
                                "content": "        if 't' in mode:",
                                "type": "added"
                            },
                            {
                                "content": "            f = io.TextIOWrapper(f, encoding=encoding)",
                                "type": "added"
                            },
                            {
                                "content": "        return f",
                                "type": "added"
                            },
                            {
                                "content": "    else:",
                                "type": "added"
                            },
                            {
                                "content": "        return open(path, mode, encoding=encoding)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "if __name__ == \"__main__\":",
                                "type": "context"
                            },
                            {
                                "content": "    unittest.main()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owcsvimport.py",
                            "type": "class",
                            "name": "TestUtils",
                            "content": "class TestUtils(unittest.TestCase):\n    def test_load_csv(self):\n        contents = (\n            b'1/1/1990,1.0,[,one,\\n'\n            b'1/1/1990,2.0,],two,\\n'\n            b'1/1/1990,3.0,{,three,'\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Time),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Text),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Auto),\n            ],\n            rowspec=[]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (3, 5))\n        self.assertSequenceEqual(\n            list(df.dtypes),\n            [np.dtype(\"M8[ns]\"), np.dtype(float), np.dtype(object),\n             \"category\", np.dtype(float)],\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Skip),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Skip),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Skip),\n            ],\n            rowspec=[\n                (range(1, 2), RowSpec.Skipped)\n            ]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (2, 2))\n        self.assertSequenceEqual(\n            list(df.dtypes), [np.dtype(float), \"category\"]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 0]), [1.0, 3.0]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 1]), [\"one\", \"three\"]\n        )\n\n    def test_convert(self):\n        contents = (\n            b'I, J,  K\\n'\n            b' , A,   \\n'\n            b'B,  ,  1\\n'\n            b'?, ., NA'\n        )\n\n        class dialect(csv.excel):\n            skipinitialspace = True\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Text),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Text),\n\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.metas[:, 0], [\"\", \"B\", \"?\"])\n        assert_array_equal(tb.metas[:, 1], [\"\", \"1\", \"NA\"])\n        assert_array_equal(tb.X[:, 0], [0.0, np.nan, np.nan])\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Categorical),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Numeric),\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.X[:, 0], [np.nan, 0, np.nan])\n        assert_array_equal(tb.X[:, 1], [0, np.nan, np.nan])\n        assert_array_equal(tb.X[:, 2], [np.nan, 1, np.nan])\n\n    def test_decimal_format(self):\n        class Dialect(csv.excel):\n            delimiter = \";\"\n\n        contents = b'3,21;3,37\\n4,13;1.000,142'\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=Dialect(),\n            decimal_separator=\",\",\n            group_separator=\".\",\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n            ],\n            rowspec=[],\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        assert_array_equal(df.values, np.array([[3.21, 3.37], [4.13, 1000.142]]))\n\n    def test_open_compressed(self):\n        content = 'abc'\n        for ext in [\"txt\", \"gz\", \"bz2\", \"xz\", \"zip\"]:\n            with named_file('', suffix=f\".{ext}\") as fname:\n                with _open_write(fname, \"wt\", encoding=\"ascii\") as f:\n                    f.write(content)\n                f.close()\n\n                with owcsvimport._open(fname, \"rt\", encoding=\"ascii\") as f:\n                    self.assertEqual(content, f.read())",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 193,
                            "end_lineno": 318,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owcsvimport.py",
                            "type": "method",
                            "name": "TestUtils.test_open_compressed",
                            "content": "class TestUtils(unittest.TestCase):\n    def test_load_csv(self):\n        contents = (\n            b'1/1/1990,1.0,[,one,\\n'\n            b'1/1/1990,2.0,],two,\\n'\n            b'1/1/1990,3.0,{,three,'\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Time),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Text),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Auto),\n            ],\n            rowspec=[]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (3, 5))\n        self.assertSequenceEqual(\n            list(df.dtypes),\n            [np.dtype(\"M8[ns]\"), np.dtype(float), np.dtype(object),\n             \"category\", np.dtype(float)],\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Skip),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Skip),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Skip),\n            ],\n            rowspec=[\n                (range(1, 2), RowSpec.Skipped)\n            ]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (2, 2))\n        self.assertSequenceEqual(\n            list(df.dtypes), [np.dtype(float), \"category\"]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 0]), [1.0, 3.0]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 1]), [\"one\", \"three\"]\n        )\n\n    def test_convert(self):\n        contents = (\n            b'I, J,  K\\n'\n            b' , A,   \\n'\n            b'B,  ,  1\\n'\n            b'?, ., NA'\n        )\n\n        class dialect(csv.excel):\n            skipinitialspace = True\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Text),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Text),\n\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.metas[:, 0], [\"\", \"B\", \"?\"])\n        assert_array_equal(tb.metas[:, 1], [\"\", \"1\", \"NA\"])\n        assert_array_equal(tb.X[:, 0], [0.0, np.nan, np.nan])\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Categorical),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Numeric),\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.X[:, 0], [np.nan, 0, np.nan])\n        assert_array_equal(tb.X[:, 1], [0, np.nan, np.nan])\n        assert_array_equal(tb.X[:, 2], [np.nan, 1, np.nan])\n\n    def test_decimal_format(self):\n        class Dialect(csv.excel):\n            delimiter = \";\"\n\n        contents = b'3,21;3,37\\n4,13;1.000,142'\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=Dialect(),\n            decimal_separator=\",\",\n            group_separator=\".\",\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n            ],\n            rowspec=[],\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        assert_array_equal(df.values, np.array([[3.21, 3.37], [4.13, 1000.142]]))\n\n    def test_open_compressed(self):\n        content = 'abc'\n        for ext in [\"txt\", \"gz\", \"bz2\", \"xz\", \"zip\"]:\n            with named_file('', suffix=f\".{ext}\") as fname:\n                with _open_write(fname, \"wt\", encoding=\"ascii\") as f:\n                    f.write(content)\n                f.close()\n\n                with owcsvimport._open(fname, \"rt\", encoding=\"ascii\") as f:\n                    self.assertEqual(content, f.read())",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 309,
                            "end_lineno": 318,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owcsvimport.py",
                                "type": "class",
                                "name": "TestUtils",
                                "content": "class TestUtils(unittest.TestCase):\n    def test_load_csv(self):\n        contents = (\n            b'1/1/1990,1.0,[,one,\\n'\n            b'1/1/1990,2.0,],two,\\n'\n            b'1/1/1990,3.0,{,three,'\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Time),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Text),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Auto),\n            ],\n            rowspec=[]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (3, 5))\n        self.assertSequenceEqual(\n            list(df.dtypes),\n            [np.dtype(\"M8[ns]\"), np.dtype(float), np.dtype(object),\n             \"category\", np.dtype(float)],\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Skip),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Skip),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Skip),\n            ],\n            rowspec=[\n                (range(1, 2), RowSpec.Skipped)\n            ]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (2, 2))\n        self.assertSequenceEqual(\n            list(df.dtypes), [np.dtype(float), \"category\"]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 0]), [1.0, 3.0]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 1]), [\"one\", \"three\"]\n        )\n\n    def test_convert(self):\n        contents = (\n            b'I, J,  K\\n'\n            b' , A,   \\n'\n            b'B,  ,  1\\n'\n            b'?, ., NA'\n        )\n\n        class dialect(csv.excel):\n            skipinitialspace = True\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Text),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Text),\n\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.metas[:, 0], [\"\", \"B\", \"?\"])\n        assert_array_equal(tb.metas[:, 1], [\"\", \"1\", \"NA\"])\n        assert_array_equal(tb.X[:, 0], [0.0, np.nan, np.nan])\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Categorical),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Numeric),\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.X[:, 0], [np.nan, 0, np.nan])\n        assert_array_equal(tb.X[:, 1], [0, np.nan, np.nan])\n        assert_array_equal(tb.X[:, 2], [np.nan, 1, np.nan])\n\n    def test_decimal_format(self):\n        class Dialect(csv.excel):\n            delimiter = \";\"\n\n        contents = b'3,21;3,37\\n4,13;1.000,142'\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=Dialect(),\n            decimal_separator=\",\",\n            group_separator=\".\",\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n            ],\n            rowspec=[],\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        assert_array_equal(df.values, np.array([[3.21, 3.37], [4.13, 1000.142]]))\n\n    def test_open_compressed(self):\n        content = 'abc'\n        for ext in [\"txt\", \"gz\", \"bz2\", \"xz\", \"zip\"]:\n            with named_file('', suffix=f\".{ext}\") as fname:\n                with _open_write(fname, \"wt\", encoding=\"ascii\") as f:\n                    f.write(content)\n                f.close()\n\n                with owcsvimport._open(fname, \"rt\", encoding=\"ascii\") as f:\n                    self.assertEqual(content, f.read())",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 193,
                                "end_lineno": 318,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owcsvimport.py",
                            "type": "function",
                            "name": "_open_write",
                            "content": "def _open_write(path, mode, encoding=None):\n    # pylint: disable=import-outside-toplevel\n    if mode not in {'w', 'wb', 'wt'}:\n        raise ValueError('r')\n    _, ext = os.path.splitext(path)\n    ext = ext.lower()\n    if ext == \".gz\":\n        import gzip\n        return gzip.open(path, mode, encoding=encoding)\n    elif ext == \".bz2\":\n        import bz2\n        return bz2.open(path, mode, encoding=encoding)\n    elif ext == \".xz\":\n        import lzma\n        return lzma.open(path, mode, encoding=encoding)\n    elif ext == \".zip\":\n        import zipfile\n        arh = zipfile.ZipFile(path, 'w')\n        filename, _ = os.path.splitext(os.path.basename(path))\n        f = arh.open(filename, mode=\"w\")\n        f_close = f.close\n        # patch the f.close to also close the main archive file\n\n        def close_():\n            f_close()\n            arh.close()\n        f.close = close_\n        if 't' in mode:\n            f = io.TextIOWrapper(f, encoding=encoding)\n        return f\n    else:\n        return open(path, mode, encoding=encoding)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 321,
                            "end_lineno": 352,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b^",
    "new_commit_hash": "21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b",
    "commit_message": "Merge pull request #4921 from ales-erjavec/fixes/owcsvimport-open-zip\n\n[FIX] owcsvimport: Fix a type error in _open for zip archive",
    "commit_date": "2020-07-30T14:33:18+02:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b",
    "test_file_codes": [
        "# pylint: disable=no-self-use,protected-access\nimport unittest\nfrom unittest import mock\nfrom contextlib import ExitStack\n\nimport os\nimport io\nimport csv\nimport json\n\nimport numpy as np\nfrom numpy.testing import assert_array_equal\n\nfrom AnyQt.QtCore import QSettings\n\nfrom Orange.data import DiscreteVariable, TimeVariable, ContinuousVariable, \\\n    StringVariable\nfrom Orange.tests import named_file\nfrom Orange.widgets.tests.base import WidgetTest, GuiTest\nfrom Orange.widgets.data import owcsvimport\nfrom Orange.widgets.data.owcsvimport import (\n    pandas_to_table, ColumnType, RowSpec\n)\nfrom Orange.widgets.utils.settings import QSettings_writeArray\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nclass TestOWCSVFileImport(WidgetTest):\n    def setUp(self):\n        self._stack = ExitStack().__enter__()\n        # patch `_local_settings` to avoid side effects, across tests\n        fname = self._stack.enter_context(named_file(\"\"))\n        s = QSettings(fname, QSettings.IniFormat)\n        self._stack.enter_context(mock.patch.object(\n            owcsvimport.OWCSVFileImport, \"_local_settings\", lambda *a: s\n        ))\n        self.widget = self.create_widget(owcsvimport.OWCSVFileImport)\n\n    def tearDown(self):\n        self.widgets.remove(self.widget)\n        self.widget.onDeleteWidget()\n        self.widget = None\n        self._stack.close()\n\n    def test_basic(self):\n        w = self.widget\n        w.activate_recent(0)\n        w.cancel()\n\n    data_regions_options = owcsvimport.Options(\n        encoding=\"ascii\", dialect=csv.excel_tab(),\n        columntypes=[\n            (range(0, 1), ColumnType.Categorical),\n            (range(1, 2), ColumnType.Text),\n            (range(2, 3), ColumnType.Categorical),\n        ], rowspec=[\n            (range(0, 1), RowSpec.Header),\n            (range(1, 3), RowSpec.Skipped),\n        ],\n    )\n\n    def _check_data_regions(self, table):\n        self.assertEqual(len(table), 3)\n        self.assertEqual(len(table), 3)\n        self.assertTrue(table.domain[\"id\"].is_discrete)\n        self.assertTrue(table.domain[\"continent\"].is_discrete)\n        self.assertTrue(table.domain[\"state\"].is_string)\n        assert_array_equal(table.X, [[0, 1], [1, 1], [2, 0]])\n        assert_array_equal(table.metas,\n                           np.array([[\"UK\"], [\"Russia\"], [\"Mexico\"]], object))\n\n    def test_restore(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        out = self.get_output(\"Data\", w)\n        self._check_data_regions(out)\n        self.assertEqual(out.name, \"data-regions\")\n\n    def test_restore_from_local(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        s = owcsvimport.OWCSVFileImport._local_settings()\n        s.clear()\n        QSettings_writeArray(\n            s, \"recent\", [\n                {\"path\": path,\n                 \"options\": json.dumps(self.data_regions_options.as_dict())}]\n        )\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        self.assertEqual(\n            w._session_items, [(path, self.data_regions_options.as_dict())],\n            \"local settings item must be recorded in _session_items when \"\n            \"activated in __init__\",\n        )\n        self._check_data_regions(self.get_output(\"Data\", w))\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        output_sum = widget.info.set_output_summary = mock.Mock()\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n        output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\n\n    data_csv_types_options = owcsvimport.Options(\n        encoding=\"ascii\", dialect=csv.excel_tab(),\n        columntypes=[\n            (range(0, 5), ColumnType.Auto),\n        ]\n    )\n\n    def test_type_guessing(self):\n        \"\"\" Check if correct column type is guessed when column type auto \"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-csv-types.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_csv_types_options.as_dict())\n                ]\n            }\n        )\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n        domain = output.domain\n\n        self.assertIsInstance(domain[\"time\"], TimeVariable)\n        self.assertIsInstance(domain[\"discrete1\"], DiscreteVariable)\n        self.assertIsInstance(domain[\"discrete2\"], DiscreteVariable)\n        self.assertIsInstance(domain[\"numeric1\"], ContinuousVariable)\n        self.assertIsInstance(domain[\"numeric2\"], ContinuousVariable)\n        self.assertIsInstance(domain[\"string\"], StringVariable)\n\n\nclass TestImportDialog(GuiTest):\n    def test_dialog(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"grep_file.txt\")\n        d = owcsvimport.CSVImportDialog()\n        d.setPath(path)\n        opts = owcsvimport.Options(\n            encoding=\"utf-8\",\n            dialect=owcsvimport.textimport.Dialect(\n                \" \", \"\\\"\", \"\\\\\", True, True\n            ),\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Categorical)\n            ],\n            rowspec=[\n                (range(0, 4), RowSpec.Skipped),\n                (range(4, 5), RowSpec.Header),\n                (range(8, 13), RowSpec.Skipped),\n            ]\n        )\n        d.setOptions(opts)\n        d.restoreDefaults()\n        opts1 = d.options()\n        d.reset()\n        opts1 = d.options()\n\n\nclass TestUtils(unittest.TestCase):\n    def test_load_csv(self):\n        contents = (\n            b'1/1/1990,1.0,[,one,\\n'\n            b'1/1/1990,2.0,],two,\\n'\n            b'1/1/1990,3.0,{,three,'\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Time),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Text),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Auto),\n            ],\n            rowspec=[]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (3, 5))\n        self.assertSequenceEqual(\n            list(df.dtypes),\n            [np.dtype(\"M8[ns]\"), np.dtype(float), np.dtype(object),\n             \"category\", np.dtype(float)],\n        )\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=csv.excel(),\n            columntypes=[\n                (range(0, 1), ColumnType.Skip),\n                (range(1, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Skip),\n                (range(3, 4), ColumnType.Categorical),\n                (range(4, 5), ColumnType.Skip),\n            ],\n            rowspec=[\n                (range(1, 2), RowSpec.Skipped)\n            ]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        self.assertEqual(df.shape, (2, 2))\n        self.assertSequenceEqual(\n            list(df.dtypes), [np.dtype(float), \"category\"]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 0]), [1.0, 3.0]\n        )\n        self.assertSequenceEqual(\n            list(df.iloc[:, 1]), [\"one\", \"three\"]\n        )\n\n    def test_convert(self):\n        contents = (\n            b'I, J,  K\\n'\n            b' , A,   \\n'\n            b'B,  ,  1\\n'\n            b'?, ., NA'\n        )\n\n        class dialect(csv.excel):\n            skipinitialspace = True\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Text),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Text),\n\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.metas[:, 0], [\"\", \"B\", \"?\"])\n        assert_array_equal(tb.metas[:, 1], [\"\", \"1\", \"NA\"])\n        assert_array_equal(tb.X[:, 0], [0.0, np.nan, np.nan])\n\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=dialect(),\n            columntypes=[\n                (range(0, 1), ColumnType.Categorical),\n                (range(1, 2), ColumnType.Categorical),\n                (range(2, 3), ColumnType.Numeric),\n            ],\n            rowspec=[(range(0, 1), RowSpec.Header)]\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        tb = pandas_to_table(df)\n\n        assert_array_equal(tb.X[:, 0], [np.nan, 0, np.nan])\n        assert_array_equal(tb.X[:, 1], [0, np.nan, np.nan])\n        assert_array_equal(tb.X[:, 2], [np.nan, 1, np.nan])\n\n    def test_decimal_format(self):\n        class Dialect(csv.excel):\n            delimiter = \";\"\n\n        contents = b'3,21;3,37\\n4,13;1.000,142'\n        opts = owcsvimport.Options(\n            encoding=\"ascii\",\n            dialect=Dialect(),\n            decimal_separator=\",\",\n            group_separator=\".\",\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n            ],\n            rowspec=[],\n        )\n        df = owcsvimport.load_csv(io.BytesIO(contents), opts)\n        assert_array_equal(df.values, np.array([[3.21, 3.37], [4.13, 1000.142]]))\n\n    def test_open_compressed(self):\n        content = 'abc'\n        for ext in [\"txt\", \"gz\", \"bz2\", \"xz\", \"zip\"]:\n            with named_file('', suffix=f\".{ext}\") as fname:\n                with _open_write(fname, \"wt\", encoding=\"ascii\") as f:\n                    f.write(content)\n                f.close()\n\n                with owcsvimport._open(fname, \"rt\", encoding=\"ascii\") as f:\n                    self.assertEqual(content, f.read())\n\n\ndef _open_write(path, mode, encoding=None):\n    # pylint: disable=import-outside-toplevel\n    if mode not in {'w', 'wb', 'wt'}:\n        raise ValueError('r')\n    _, ext = os.path.splitext(path)\n    ext = ext.lower()\n    if ext == \".gz\":\n        import gzip\n        return gzip.open(path, mode, encoding=encoding)\n    elif ext == \".bz2\":\n        import bz2\n        return bz2.open(path, mode, encoding=encoding)\n    elif ext == \".xz\":\n        import lzma\n        return lzma.open(path, mode, encoding=encoding)\n    elif ext == \".zip\":\n        import zipfile\n        arh = zipfile.ZipFile(path, 'w')\n        filename, _ = os.path.splitext(os.path.basename(path))\n        f = arh.open(filename, mode=\"w\")\n        f_close = f.close\n        # patch the f.close to also close the main archive file\n\n        def close_():\n            f_close()\n            arh.close()\n        f.close = close_\n        if 't' in mode:\n            f = io.TextIOWrapper(f, encoding=encoding)\n        return f\n    else:\n        return open(path, mode, encoding=encoding)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nrunning build_ext\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmp2welklk6/tmp\ncreating /tmp/tmp2welklk6/tmp/tmp2welklk6\ncompile options: '-MMD -MF /tmp/tmp2welklk6/file.c.d -c'\ncc: /tmp/tmp2welklk6/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmplxjxun25/tmp\ncreating /tmp/tmplxjxun25/tmp/tmplxjxun25\ncompile options: '-MMD -MF /tmp/tmplxjxun25/file.c.d -c'\ncc: /tmp/tmplxjxun25/file.c\ncustomize UnixCCompiler using build_ext\nbuilding 'Orange.classification._simple_tree' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_simple_tree.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._tree_scorers' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_tree_scorers.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_valuecount.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_contingency.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_io.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_variable.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/distance/_distance.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/preprocess/_discretize.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/preprocess/_relieff.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/projection/_som.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/widgets/utils/_grid_density.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\nrunning develop\nrunning build_scripts\nrunning egg_info\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncreating Orange3.egg-info\nwriting Orange3.egg-info/PKG-INFO\nwriting dependency_links to Orange3.egg-info/dependency_links.txt\nwriting entry points to Orange3.egg-info/entry_points.txt\nwriting requirements to Orange3.egg-info/requires.txt\nwriting top-level names to Orange3.egg-info/top_level.txt\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nwarning: no files found matching 'README*' under directory 'Orange'\nwarning: no files found matching 'LICENSE' under directory 'Orange'\nwarning: no files found matching '*.basket' under directory 'Orange/datasets'\nwarning: no files found matching '*.js' under directory 'Orange/widgets'\nwarning: no files found matching '*.css' under directory 'Orange/widgets'\nwarning: no files found matching '*.html' under directory 'Orange/widgets'\nwarning: no files found matching 'COPYING'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmptoi9wx1h/tmp\ncreating /tmp/tmptoi9wx1h/tmp/tmptoi9wx1h\ncompile options: '-MMD -MF /tmp/tmptoi9wx1h/file.c.d -c'\ncc: /tmp/tmptoi9wx1h/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpjwtw84nt/tmp\ncreating /tmp/tmpjwtw84nt/tmp/tmpjwtw84nt\ncompile options: '-MMD -MF /tmp/tmpjwtw84nt/file.c.d -c'\ncc: /tmp/tmpjwtw84nt/file.c\ncustomize UnixCCompiler using build_ext\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.27.0.dev0+21b6b45 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b\nProcessing dependencies for Orange3==3.27.0.dev0+21b6b45\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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for xlrd==2.0.1\nBest match: xlrd 2.0.1\nAdding xlrd 2.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for setuptools==59.8.0\nBest match: setuptools 59.8.0\nAdding setuptools 59.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for serverfiles==0.3.1\nBest match: serverfiles 0.3.1\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for scipy==1.7.3\nBest match: scipy 1.7.3\nAdding scipy 1.7.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for scikit-learn==1.0.2\nBest match: scikit-learn 1.0.2\nAdding scikit-learn 1.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for requests==2.31.0\nBest match: requests 2.31.0\nAdding requests 2.31.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for PyYAML==6.0.1\nBest match: PyYAML 6.0.1\nAdding PyYAML 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for python-louvain==0.16\nBest match: python-louvain 0.16\nAdding python-louvain 0.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for pyqtgraph==0.12.4\nBest match: pyqtgraph 0.12.4\nAdding pyqtgraph 0.12.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for pip==24.0\nBest match: pip 24.0\nAdding pip 24.0 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for pandas==1.3.5\nBest match: pandas 1.3.5\nAdding pandas 1.3.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.22.0\nBest match: orange-widget-base 4.22.0\nAdding orange-widget-base 4.22.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for orange-canvas-core==0.1.35\nBest match: orange-canvas-core 0.1.35\nAdding orange-canvas-core 0.1.35 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for openpyxl==3.1.3\nBest match: openpyxl 3.1.3\nAdding openpyxl 3.1.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for openTSNE==1.0.1\nBest match: openTSNE 1.0.1\nAdding openTSNE 1.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for numpy==1.17.5\nBest match: numpy 1.17.5\nAdding numpy 1.17.5 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for networkx==2.6.3\nBest match: networkx 2.6.3\nAdding networkx 2.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for matplotlib==3.5.3\nBest match: matplotlib 3.5.3\nAdding matplotlib 3.5.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for keyrings.alt==4.2.0\nBest match: keyrings.alt 4.2.0\nAdding keyrings.alt 4.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for keyring==24.1.1\nBest match: keyring 24.1.1\nAdding keyring 24.1.1 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for joblib==1.3.2\nBest match: joblib 1.3.2\nAdding joblib 1.3.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for httpx==0.24.1\nBest match: httpx 0.24.1\nAdding httpx 0.24.1 to easy-install.pth file\nInstalling httpx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for docutils==0.20.1\nBest match: docutils 0.20.1\nAdding docutils 0.20.1 to easy-install.pth file\nInstalling docutils script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for chardet==5.2.0\nBest match: chardet 5.2.0\nAdding chardet 5.2.0 to easy-install.pth file\nInstalling chardetect script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for Bottleneck==1.4.0\nBest match: Bottleneck 1.4.0\nAdding Bottleneck 1.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for baycomp==1.0.3\nBest match: baycomp 1.0.3\nAdding baycomp 1.0.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine==5.15.6\nBest match: PyQtWebEngine 5.15.6\nAdding PyQtWebEngine 5.15.6 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for PyQt5==5.15.10\nBest match: PyQt5 5.15.10\nAdding PyQt5 5.15.10 to easy-install.pth file\nInstalling pylupdate5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling pyrcc5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling pyuic5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for AnyQt==0.2.0\nBest match: AnyQt 0.2.0\nAdding AnyQt 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for threadpoolctl==3.1.0\nBest match: threadpoolctl 3.1.0\nAdding threadpoolctl 3.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for certifi==2024.12.14\nBest match: certifi 2024.12.14\nAdding certifi 2024.12.14 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for charset-normalizer==3.4.1\nBest match: charset-normalizer 3.4.1\nAdding charset-normalizer 3.4.1 to easy-install.pth file\nInstalling normalizer script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for idna==3.10\nBest match: idna 3.10\nAdding idna 3.10 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for urllib3==2.0.7\nBest match: urllib3 2.0.7\nAdding urllib3 2.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for pytz==2024.2\nBest match: pytz 2024.2\nAdding pytz 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for python-dateutil==2.9.0.post0\nBest match: python-dateutil 2.9.0.post0\nAdding python-dateutil 2.9.0.post0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for typing-extensions==4.7.1\nBest match: typing-extensions 4.7.1\nAdding typing-extensions 4.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for packaging==24.0\nBest match: packaging 24.0\nAdding packaging 24.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for qasync==0.25.0\nBest match: qasync 0.25.0\nAdding qasync 0.25.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for requests-cache==1.1.1\nBest match: requests-cache 1.1.1\nAdding requests-cache 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for commonmark==0.9.1\nBest match: commonmark 0.9.1\nAdding commonmark 0.9.1 to easy-install.pth file\nInstalling cmark script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for dictdiffer==0.9.0\nBest match: dictdiffer 0.9.0\nAdding dictdiffer 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for importlib-metadata==6.7.0\nBest match: importlib-metadata 6.7.0\nAdding importlib-metadata 6.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for et-xmlfile==1.1.0\nBest match: et-xmlfile 1.1.0\nAdding et-xmlfile 1.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for kiwisolver==1.4.5\nBest match: kiwisolver 1.4.5\nAdding kiwisolver 1.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for pyparsing==3.1.4\nBest match: pyparsing 3.1.4\nAdding pyparsing 3.1.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for Pillow==9.5.0\nBest match: Pillow 9.5.0\nAdding Pillow 9.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for fonttools==4.38.0\nBest match: fonttools 4.38.0\nAdding fonttools 4.38.0 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for cycler==0.11.0\nBest match: cycler 0.11.0\nAdding cycler 0.11.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for jaraco.classes==3.2.3\nBest match: jaraco.classes 3.2.3\nAdding jaraco.classes 3.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for importlib-resources==5.12.0\nBest match: importlib-resources 5.12.0\nAdding importlib-resources 5.12.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for SecretStorage==3.3.3\nBest match: SecretStorage 3.3.3\nAdding SecretStorage 3.3.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for jeepney==0.8.0\nBest match: jeepney 0.8.0\nAdding jeepney 0.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for sniffio==1.3.1\nBest match: sniffio 1.3.1\nAdding sniffio 1.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for httpcore==0.17.3\nBest match: httpcore 0.17.3\nAdding httpcore 0.17.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for PyQt5-sip==12.13.0\nBest match: PyQt5-sip 12.13.0\nAdding PyQt5-sip 12.13.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine-Qt5==5.15.16\nBest match: PyQtWebEngine-Qt5 5.15.16\nAdding PyQtWebEngine-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for PyQt5-Qt5==5.15.16\nBest match: PyQt5-Qt5 5.15.16\nAdding PyQt5-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for six==1.17.0\nBest match: six 1.17.0\nAdding six 1.17.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for cattrs==23.1.2\nBest match: cattrs 23.1.2\nAdding cattrs 23.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for attrs==24.2.0\nBest match: attrs 24.2.0\nAdding attrs 24.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for platformdirs==4.0.0\nBest match: platformdirs 4.0.0\nAdding platformdirs 4.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for url-normalize==1.4.3\nBest match: url-normalize 1.4.3\nAdding url-normalize 1.4.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for zipp==3.15.0\nBest match: zipp 3.15.0\nAdding zipp 3.15.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for more-itertools==9.1.0\nBest match: more-itertools 9.1.0\nAdding more-itertools 9.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for cryptography==44.0.0\nBest match: cryptography 44.0.0\nAdding cryptography 44.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for anyio==3.7.1\nBest match: anyio 3.7.1\nAdding anyio 3.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for h11==0.14.0\nBest match: h11 0.14.0\nAdding h11 0.14.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for exceptiongroup==1.2.2\nBest match: exceptiongroup 1.2.2\nAdding exceptiongroup 1.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for cffi==1.15.1\nBest match: cffi 1.15.1\nAdding cffi 1.15.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nSearching for pycparser==2.21\nBest match: pycparser 2.21\nAdding pycparser 2.21 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.27.0.dev0+21b6b45\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 231ms\nPrepared 18 packages in 17ms\nInstalled 18 packages in 556ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + numpy==1.17.5\n + packaging==24.0\n + pluggy==1.2.0\n + pyqt5==5.15.10\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.13.0\n + pyqtwebengine==5.15.6\n + pyqtwebengine-qt5==5.15.16\n + pytest==7.4.4\n + setuptools==59.8.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + wheel==0.42.0\n + zipp==3.15.0\nResolved 54 packages in 314ms\nInstalled 47 packages in 786ms\n + anyio==3.7.1\n + baycomp==1.0.3\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + cycler==0.11.0\n + et-xmlfile==1.1.0\n + fonttools==4.38.0\n + h11==0.14.0\n + httpcore==0.17.3\n + httpx==0.24.1\n + idna==3.10\n + importlib-resources==5.12.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + joblib==1.3.2\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + more-itertools==9.1.0\n + networkx==2.6.3\n + openpyxl==3.1.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pillow==9.5.0\n + pip==24.0\n + pycparser==2.21\n + pyparsing==3.1.4\n + python-dateutil==2.9.0.post0\n + python-louvain==0.16\n + pytz==2024.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + scikit-learn==1.0.2\n + scipy==1.7.3\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + sniffio==1.3.1\n + threadpoolctl==3.1.0\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 39 packages in 121ms\nInstalled 13 packages in 82ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + platformdirs==4.0.0\n + pyqtgraph==0.12.4\n + qasync==0.25.0\n + requests-cache==1.1.1\n + url-normalize==1.4.3\nResolved 2 packages in 3ms\nInstalled 2 packages in 24ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nAudited 1 package in 1ms\nAudited 2 packages in 1ms\nOrange/classification/_simple_tree.c: In function ‘gain_ratio_c’:\nOrange/classification/_simple_tree.c:63:9: warning: implicit declaration of function ‘qsort_r’; did you mean ‘qsort’? [-Wimplicit-function-declaration]\n   63 |         qsort_r(base, nel, size, compar, thunk)\n      |         ^~~~~~~\nOrange/classification/_simple_tree.c:147:9: note: in expansion of macro ‘QSORT_R_FUNC’\n  147 |         QSORT_R_FUNC(examples, size, sizeof(struct Example), (void*) &compar_attr, compar_examples);\n      |         ^~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_2find_threshold_entropy’:\nOrange/classification/_tree_scorers.c:3115:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3115 |   __pyx_t_7 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_6find_threshold_MSE’:\nOrange/classification/_tree_scorers.c:4619:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4619 |   __pyx_t_1 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_8find_binarization_MSE’:\nOrange/classification/_tree_scorers.c:5396:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5396 |           for (__pyx_t_22 = 0; __pyx_t_22 < __pyx_t_21; __pyx_t_22+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5517:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5517 |           __pyx_t_27 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5523:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5523 |           __pyx_t_27 = (((__pyx_v_N - __pyx_v_left) >= __pyx_v_min_leaf) != 0);\n      |                                                     ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_14compute_predictions_csr’:\nOrange/classification/_tree_scorers.c:7051:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7051 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7741:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7741 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7801:143: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7801 |               __pyx_t_17 = (((*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_18 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                               ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/data/_io.c: In function ‘__pyx_pf_6Orange_4data_3_io_sparse_read_float.constprop’:\nOrange/data/_io.c:3881:24: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3881 |           __pyx_v_decs = (__pyx_v_decs / 10.0);\n      |           ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:4070:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4070 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 221, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:480:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  480 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1301:7: note: ‘__pyx_v_attr_index’ was declared here\n 1301 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:2935:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2935 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:627:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_5update_hex’:\nOrange/projection/_som.c:3655:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3655 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3654:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3654 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4547:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4547 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_38, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_39, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4114:7: note: ‘__pyx_v_win_y’ was declared here\n 4114 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4536:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4536 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_36, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_37, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4113:7: note: ‘__pyx_v_win_x’ was declared here\n 4113 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_8update_sparse.constprop’:\nOrange/projection/_som.c:4746:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4746 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4745:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4745 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3141:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3141 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:2964:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2964 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_28, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2665:7: note: ‘__pyx_v_win_y’ was declared here\n 2665 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:2953:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2953 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_25, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2664:7: note: ‘__pyx_v_win_x’ was declared here\n 2664 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_10update_sparse_hex.constprop’:\nOrange/projection/_som.c:5515:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5515 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5514:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5514 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.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_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/.venv/lib/python3.7/site-packages/setuptools/command/egg_info.py:628: SetuptoolsDeprecationWarning: Custom 'build_py' does not implement 'get_data_files_without_manifest'.\nPlease extend command classes from setuptools instead of distutils.\n  SetuptoolsDeprecationWarning\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b\nplugins: anyio-3.7.1\ncollected 16 items\n\nr2e_tests/test_1.py sss....FFFFF....                                     [100%]\n\n=================================== FAILURES ===================================\n_______________________ TestOWCSVFileImport.test_restore _______________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore>\n\n    def test_restore(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n    \n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        out = self.get_output(\"Data\", w)\n>       self._check_data_regions(out)\n\nr2e_tests/test_1.py:88: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore>\ntable = None\n\n    def _check_data_regions(self, table):\n>       self.assertEqual(len(table), 3)\nE       TypeError: object of type 'NoneType' has no len()\n\nr2e_tests/test_1.py:63: TypeError\n_________________ TestOWCSVFileImport.test_restore_from_local __________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore_from_local>\n\n    def test_restore_from_local(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        s = owcsvimport.OWCSVFileImport._local_settings()\n        s.clear()\n        QSettings_writeArray(\n            s, \"recent\", [\n                {\"path\": path,\n                 \"options\": json.dumps(self.data_regions_options.as_dict())}]\n        )\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        self.assertEqual(\n            w._session_items, [(path, self.data_regions_options.as_dict())],\n            \"local settings item must be recorded in _session_items when \"\n            \"activated in __init__\",\n        )\n>       self._check_data_regions(self.get_output(\"Data\", w))\n\nr2e_tests/test_1.py:112: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore_from_local>\ntable = None\n\n    def _check_data_regions(self, table):\n>       self.assertEqual(len(table), 3)\nE       TypeError: object of type 'NoneType' has no len()\n\nr2e_tests/test_1.py:63: TypeError\n_______________________ TestOWCSVFileImport.test_summary _______________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_summary>\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        output_sum = widget.info.set_output_summary = mock.Mock()\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n>       output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\nE       TypeError: object of type 'NoneType' has no len()\n\nr2e_tests/test_1.py:130: TypeError\n____________________ TestOWCSVFileImport.test_type_guessing ____________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_type_guessing>\n\n    def test_type_guessing(self):\n        \"\"\" Check if correct column type is guessed when column type auto \"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-csv-types.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_csv_types_options.as_dict())\n                ]\n            }\n        )\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n>       domain = output.domain\nE       AttributeError: 'NoneType' object has no attribute 'domain'\n\nr2e_tests/test_1.py:155: AttributeError\n_________________________ TestImportDialog.test_dialog _________________________\n\nself = <r2e_tests.test_1.TestImportDialog testMethod=test_dialog>\n\n    def test_dialog(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"grep_file.txt\")\n        d = owcsvimport.CSVImportDialog()\n        d.setPath(path)\n        opts = owcsvimport.Options(\n            encoding=\"utf-8\",\n            dialect=owcsvimport.textimport.Dialect(\n                \" \", \"\\\"\", \"\\\\\", True, True\n            ),\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Categorical)\n            ],\n            rowspec=[\n                (range(0, 4), RowSpec.Skipped),\n                (range(4, 5), RowSpec.Header),\n                (range(8, 13), RowSpec.Skipped),\n            ]\n        )\n        d.setOptions(opts)\n        d.restoreDefaults()\n>       opts1 = d.options()\n\nr2e_tests/test_1.py:188: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nOrange/widgets/data/owcsvimport.py:282: in options\n    rowspec_ = self._optionswidget.rowStates()\nOrange/widgets/utils/textimport.py:905: in rowStates\n    return self.__rowStates()\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Orange.widgets.utils.textimport.CSVImportWidget object at 0x7b59519e3a50>\n\n    def __rowStates(self):\n        model = self.__previewmodel\n        items = (\n            (row, model.headerData(row, Qt.Vertical,\n                                   TablePreviewModel.RowStateRole))\n>           for row in range(model.rowCount())\n        )\nE       AttributeError: 'NoneType' object has no attribute 'rowCount'\n\nOrange/widgets/utils/textimport.py:933: AttributeError\n----------------------------- Captured stderr call -----------------------------\nTraceback (most recent call last):\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/Orange/widgets/data/owcsvimport.py\", line 326, in __update_preview\n    f = _open(self._path, \"rb\")\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/Orange/widgets/data/owcsvimport.py\", line 1118, in _open\n    return open(path, mode, encoding=encoding)\nFileNotFoundError: [Errno 2] No such file or directory: '/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/r2e_tests/grep_file.txt'\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_basic\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_image_export\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestUtils::test_convert\nPASSED r2e_tests/test_1.py::TestUtils::test_decimal_format\nPASSED r2e_tests/test_1.py::TestUtils::test_load_csv\nPASSED r2e_tests/test_1.py::TestUtils::test_open_compressed\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_restore - TypeError: ob...\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_restore_from_local - Ty...\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_summary - TypeError: ob...\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_type_guessing - Attribu...\nFAILED r2e_tests/test_1.py::TestImportDialog::test_dialog - AttributeError: '...\n==================== 5 failed, 8 passed, 3 skipped in 6.99s ====================\nWidgets left: 0    Max widgets: 486 \n\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b\nplugins: anyio-3.7.1\ncollected 16 items\n\nr2e_tests/test_1.py sss....FFFFF...F                                     [100%]\n\n=================================== FAILURES ===================================\n_______________________ TestOWCSVFileImport.test_restore _______________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore>\n\n    def test_restore(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n    \n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        out = self.get_output(\"Data\", w)\n>       self._check_data_regions(out)\n\nr2e_tests/test_1.py:88: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore>\ntable = None\n\n    def _check_data_regions(self, table):\n>       self.assertEqual(len(table), 3)\nE       TypeError: object of type 'NoneType' has no len()\n\nr2e_tests/test_1.py:63: TypeError\n_________________ TestOWCSVFileImport.test_restore_from_local __________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore_from_local>\n\n    def test_restore_from_local(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        s = owcsvimport.OWCSVFileImport._local_settings()\n        s.clear()\n        QSettings_writeArray(\n            s, \"recent\", [\n                {\"path\": path,\n                 \"options\": json.dumps(self.data_regions_options.as_dict())}]\n        )\n        w = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n        )\n        item = w.current_item()\n        self.assertEqual(item.path(), path)\n        self.assertEqual(item.options(), self.data_regions_options)\n        self.assertEqual(\n            w._session_items, [(path, self.data_regions_options.as_dict())],\n            \"local settings item must be recorded in _session_items when \"\n            \"activated in __init__\",\n        )\n>       self._check_data_regions(self.get_output(\"Data\", w))\n\nr2e_tests/test_1.py:112: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_restore_from_local>\ntable = None\n\n    def _check_data_regions(self, table):\n>       self.assertEqual(len(table), 3)\nE       TypeError: object of type 'NoneType' has no len()\n\nr2e_tests/test_1.py:63: TypeError\n_______________________ TestOWCSVFileImport.test_summary _______________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_summary>\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-regions.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_regions_options.as_dict())\n                ]\n            }\n        )\n        output_sum = widget.info.set_output_summary = mock.Mock()\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n>       output_sum.assert_called_with(len(output),\n                                      format_summary_details(output))\nE       TypeError: object of type 'NoneType' has no len()\n\nr2e_tests/test_1.py:130: TypeError\n____________________ TestOWCSVFileImport.test_type_guessing ____________________\n\nself = <r2e_tests.test_1.TestOWCSVFileImport testMethod=test_type_guessing>\n\n    def test_type_guessing(self):\n        \"\"\" Check if correct column type is guessed when column type auto \"\"\"\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"data-csv-types.tab\")\n        widget = self.create_widget(\n            owcsvimport.OWCSVFileImport,\n            stored_settings={\n                \"_session_items\": [\n                    (path, self.data_csv_types_options.as_dict())\n                ]\n            }\n        )\n        widget.commit()\n        self.wait_until_finished(widget)\n        output = self.get_output(\"Data\", widget)\n>       domain = output.domain\nE       AttributeError: 'NoneType' object has no attribute 'domain'\n\nr2e_tests/test_1.py:155: AttributeError\n_________________________ TestImportDialog.test_dialog _________________________\n\nself = <r2e_tests.test_1.TestImportDialog testMethod=test_dialog>\n\n    def test_dialog(self):\n        dirname = os.path.dirname(__file__)\n        path = os.path.join(dirname, \"grep_file.txt\")\n        d = owcsvimport.CSVImportDialog()\n        d.setPath(path)\n        opts = owcsvimport.Options(\n            encoding=\"utf-8\",\n            dialect=owcsvimport.textimport.Dialect(\n                \" \", \"\\\"\", \"\\\\\", True, True\n            ),\n            columntypes=[\n                (range(0, 2), ColumnType.Numeric),\n                (range(2, 3), ColumnType.Categorical)\n            ],\n            rowspec=[\n                (range(0, 4), RowSpec.Skipped),\n                (range(4, 5), RowSpec.Header),\n                (range(8, 13), RowSpec.Skipped),\n            ]\n        )\n        d.setOptions(opts)\n        d.restoreDefaults()\n>       opts1 = d.options()\n\nr2e_tests/test_1.py:188: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nOrange/widgets/data/owcsvimport.py:282: in options\n    rowspec_ = self._optionswidget.rowStates()\nOrange/widgets/utils/textimport.py:905: in rowStates\n    return self.__rowStates()\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <Orange.widgets.utils.textimport.CSVImportWidget object at 0x73aa5e8a5e10>\n\n    def __rowStates(self):\n        model = self.__previewmodel\n        items = (\n            (row, model.headerData(row, Qt.Vertical,\n                                   TablePreviewModel.RowStateRole))\n>           for row in range(model.rowCount())\n        )\nE       AttributeError: 'NoneType' object has no attribute 'rowCount'\n\nOrange/widgets/utils/textimport.py:933: AttributeError\n----------------------------- Captured stderr call -----------------------------\nTraceback (most recent call last):\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/Orange/widgets/data/owcsvimport.py\", line 326, in __update_preview\n    f = _open(self._path, \"rb\")\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/Orange/widgets/data/owcsvimport.py\", line 1113, in _open\n    return open(path, mode, encoding=encoding)\nFileNotFoundError: [Errno 2] No such file or directory: '/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_21b6b45f0bfdb5b81810ef0f6e7bc1380e69cf1b/r2e_tests/grep_file.txt'\n________________________ TestUtils.test_open_compressed ________________________\n\nself = <r2e_tests.test_1.TestUtils testMethod=test_open_compressed>\n\n    def test_open_compressed(self):\n        content = 'abc'\n        for ext in [\"txt\", \"gz\", \"bz2\", \"xz\", \"zip\"]:\n            with named_file('', suffix=f\".{ext}\") as fname:\n                with _open_write(fname, \"wt\", encoding=\"ascii\") as f:\n                    f.write(content)\n                f.close()\n    \n>               with owcsvimport._open(fname, \"rt\", encoding=\"ascii\") as f:\n\nr2e_tests/test_1.py:317: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \nOrange/widgets/data/owcsvimport.py:1105: in _open\n    zinfo = arh.getinfo(filename)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <zipfile.ZipFile filename='/tmp/tmpbgtf94xl.zip' mode='r'>\nname = <ZipInfo filename='tmpbgtf94xl' filemode='?rw-------' file_size=3>\n\n    def getinfo(self, name):\n        \"\"\"Return the instance of ZipInfo given 'name'.\"\"\"\n        info = self.NameToInfo.get(name)\n        if info is None:\n            raise KeyError(\n>               'There is no item named %r in the archive' % name)\nE           KeyError: \"There is no item named <ZipInfo filename='tmpbgtf94xl' filemode='?rw-------' file_size=3> in the archive\"\n\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/zipfile.py:1431: KeyError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_basic\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_image_export\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWCSVFileImport::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestUtils::test_convert\nPASSED r2e_tests/test_1.py::TestUtils::test_decimal_format\nPASSED r2e_tests/test_1.py::TestUtils::test_load_csv\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_restore - TypeError: ob...\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_restore_from_local - Ty...\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_summary - TypeError: ob...\nFAILED r2e_tests/test_1.py::TestOWCSVFileImport::test_type_guessing - Attribu...\nFAILED r2e_tests/test_1.py::TestImportDialog::test_dialog - AttributeError: '...\nFAILED r2e_tests/test_1.py::TestUtils::test_open_compressed - KeyError: \"Ther...\n==================== 6 failed, 7 passed, 3 skipped in 3.60s ====================\nWidgets left: 0    Max widgets: 486 \n\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/data/owcsvimport.py Orange/widgets/data/tests/test_owcsvimport.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** KeyError When Opening Single-File Zip Archive in owcsvimport

**Description:**
When using the `_open` function to read a single-file zip archive, a `KeyError` is raised indicating that the specified file does not exist in the archive. This occurs even though the archive contains exactly one file.

**Example Code:**
```python
import zipfile
from owcsvimport import _open

# Create a single-file zip archive
with zipfile.ZipFile('archive.zip', 'w') as zipf:
    zipf.writestr('data.csv', 'col1,col2\n1,2\n3,4')

# Attempt to open and read the file from the zip archive
with _open('archive.zip', 'rt', encoding='ascii') as f:
    content = f.read()
```

**Expected Behavior:**
The `data.csv` file inside `archive.zip` should be opened successfully, and its contents should be read without errors.

**Actual Behavior:**
A `KeyError` is raised with a message similar to: "There is no item named `<ZipInfo filename='archive.zip' ...>` in the archive."
[/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/owcsvimport.py
# -*- coding: utf-8 -*-
"""
CSV File Import Widget
----------------------

"""
import sys
import os
import csv
import enum
import io
import traceback
import warnings
import logging
import weakref
import json

import gzip
import lzma
import bz2
import zipfile

from xml.sax.saxutils import escape
from functools import singledispatch
from contextlib import ExitStack

import typing
from typing import (
    List, Tuple, Dict, Optional, Any, Callable, Iterable, Hashable,
    Union, AnyStr, BinaryIO
)

from PyQt5.QtCore import (
    Qt, QFileInfo, QTimer, QSettings, QObject, QSize, QMimeDatabase, QMimeType
)
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QPalette
from PyQt5.QtWidgets import (
    QLabel, QComboBox, QPushButton, QDialog, QDialogButtonBox, QGridLayout,
    QVBoxLayout, QSizePolicy, QStyle, QFileIconProvider, QFileDialog,
    QApplication, QMessageBox, QTextBrowser
)
from PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal

import numpy as np
import pandas.errors
import pandas as pd

from pandas.api import types as pdtypes

import Orange.data

from Orange.widgets import widget, gui, settings
from Orange.widgets.utils.concurrent import PyOwned
from Orange.widgets.utils import textimport, concurrent as qconcurrent
from Orange.widgets.utils.overlay import OverlayWidget
from Orange.widgets.utils.settings import (
    QSettings_readArray, QSettings_writeArray
)
from Orange.widgets.utils.state_summary import format_summary_details


if typing.TYPE_CHECKING:
    # pylint: disable=invalid-name
    T = typing.TypeVar("T")
    K = typing.TypeVar("K")

__all__ = ["OWCSVFileImport"]

_log = logging.getLogger(__name__)

ColumnType = textimport.ColumnType
RowSpec = textimport.RowSpec


def enum_lookup(enumtype, name):
    # type: (typing.Type[T], str) -> Optional[T]
    """
    Return an value from `enumtype` by its symbolic name or None if not found.
    """
    try:
        return enumtype[name]
    except LookupError:
        return None


def dialect_eq(lhs, rhs):
    # type: (csv.Dialect, csv.Dialect) -> bool
    """Compare 2 `csv.Dialect` instances for equality."""
    return (lhs.delimiter == rhs.delimiter and
            lhs.quotechar == rhs.quotechar and
            lhs.doublequote == rhs.doublequote and
            lhs.escapechar == rhs.escapechar and
            lhs.quoting == rhs.quoting and
            lhs.skipinitialspace == rhs.skipinitialspace)


class Options:
    """
    Stored options for loading CSV-like file.

    Arguments
    ---------
    encoding : str
        A encoding to use for reading.
    dialect : csv.Dialect
        A csv.Dialect instance.
    columntypes: Iterable[Tuple[range, ColumnType]]
        A list of column type ranges specifying the types for columns.
        Need not list all columns. Columns not listed are assumed to have auto
        type inference.
    rowspec : Iterable[Tuple[range, RowSpec]]
         A list of row spec ranges.
    decimal_separator : str
        Decimal separator - a single character string; default: `"."`
    group_separator : str
        Thousands group separator - empty or a single character string;
        default: empty string
    """
    RowSpec = RowSpec
    ColumnType = ColumnType

    def __init__(self, encoding='utf-8', dialect=csv.excel(), columntypes=[],
                 rowspec=((range(0, 1), RowSpec.Header),),
                 decimal_separator=".", group_separator=""):
        # type: (str, csv.Dialect, List[Tuple[range, ColumnType]], ...) -> None
        self.encoding = encoding
        self.dialect = dialect
        self.columntypes = list(columntypes)
        self.rowspec = list(rowspec)  # type: List[Tuple[range, Options.RowSpec]]
        self.decimal_separator = decimal_separator
        self.group_separator = group_separator

    def __eq__(self, other):
        # type: (Options) -> bool
        """
        Compare this instance to `other` for equality.
        """
        if isinstance(other, Options):
            return (dialect_eq(self.dialect, other.dialect) and
                    self.encoding == other.encoding and
                    self.columntypes == other.columntypes and
                    self.rowspec == other.rowspec and
                    self.group_separator == other.group_separator and
                    self.decimal_separator == other.decimal_separator)
        else:
            return NotImplemented

    def __repr__(self):
        class_, args = self.__reduce__()
        return "{}{!r}".format(class_.__name__, args)
    __str__ = __repr__

    def __reduce__(self):
        return type(self), (self.encoding, self.dialect,
                            self.columntypes, self.rowspec)

    def as_dict(self):
        # type: () -> Dict[str, Any]
        """
        Return return Option parameters as plain types suitable for
        serialization (e.g JSON serializable).
        """
        return {
            "encoding": self.encoding,
            "delimiter": self.dialect.delimiter,
            "quotechar": self.dialect.quotechar,
            "doublequote": self.dialect.doublequote,
            "skipinitialspace": self.dialect.skipinitialspace,
            "quoting": self.dialect.quoting,
            "columntypes": Options.spec_as_encodable(self.columntypes),
            "rowspec": Options.spec_as_encodable(self.rowspec),
            "decimal_separator": self.decimal_separator,
            "group_separator": self.group_separator,
        }

    @staticmethod
    def from_dict(mapping):
        # type: (Dict[str, Any]) -> Options
        """
        Reconstruct a `Options` from a plain dictionary (see :func:`as_dict`).
        """
        encoding = mapping["encoding"]
        delimiter = mapping["delimiter"]
        quotechar = mapping["quotechar"]
        doublequote = mapping["doublequote"]
        quoting = mapping["quoting"]
        skipinitialspace = mapping["skipinitialspace"]

        dialect = textimport.Dialect(
            delimiter, quotechar, None, doublequote, skipinitialspace,
            quoting=quoting)

        colspec = mapping["columntypes"]
        rowspec = mapping["rowspec"]
        colspec = Options.spec_from_encodable(colspec, ColumnType)
        rowspec = Options.spec_from_encodable(rowspec, RowSpec)
        decimal = mapping.get("decimal_separator", ".")
        group = mapping.get("group_separator", "")

        return Options(encoding, dialect, colspec, rowspec,
                       decimal_separator=decimal,
                       group_separator=group)

    @staticmethod
    def spec_as_encodable(spec):
        # type: (List[Tuple[range, enum.Enum]]) -> List[Dict[str, Any]]
        return [{"start": r.start, "stop": r.stop, "value": value.name}
                for r, value in spec]

    @staticmethod
    def spec_from_encodable(spec, enumtype):
        # type: (List[Dict[str, Any]], typing.Type[T]) -> List[Tuple[range, T]]
        r = []
        for v in spec:
            try:
                start, stop, name = v["start"], v["stop"], v["value"]
            except (KeyError, ValueError):
                pass
            else:
                r.append((range(start, stop), enum_lookup(enumtype, name)))
        return r


class CSVImportDialog(QDialog):
    """
    A dialog for selecting CSV file import options.
    """
    def __init__(self, parent=None, flags=Qt.Dialog, **kwargs):
        super().__init__(parent, flags, **kwargs)
        self.setLayout(QVBoxLayout())

        self._options = None
        self._path = None
        # Finalizer for opened file handle (in _update_preview)
        self.__finalizer = None  # type: Optional[Callable[[], None]]
        self._optionswidget = textimport.CSVImportWidget()
        self._optionswidget.previewReadErrorOccurred.connect(
            self.__on_preview_error
        )
        self._optionswidget.previewModelReset.connect(
            self.__on_preview_reset
        )
        self._buttons = buttons = QDialogButtonBox(
            orientation=Qt.Horizontal,
            standardButtons=(QDialogButtonBox.Ok | QDialogButtonBox.Cancel |
                             QDialogButtonBox.Reset |
                             QDialogButtonBox.RestoreDefaults),
            objectName="dialog-button-box",
        )
        # TODO: Help button
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)

        b = buttons.button(QDialogButtonBox.Reset)
        b.clicked.connect(self.reset)
        b = buttons.button(QDialogButtonBox.RestoreDefaults)
        b.clicked.connect(self.restoreDefaults)
        self.layout().addWidget(self._optionswidget)
        self.layout().addWidget(buttons)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self._overlay = OverlayWidget(self)
        self._overlay.setWidget(self._optionswidget.dataview)
        self._overlay.setLayout(QVBoxLayout())
        self._overlay.layout().addWidget(QLabel(wordWrap=True))
        self._overlay.hide()

    def setOptions(self, options):
        # type: (Options) -> None
        self._options = options
        self._optionswidget.setEncoding(options.encoding)
        self._optionswidget.setDialect(options.dialect)
        self._optionswidget.setNumbersFormat(
            options.group_separator, options.decimal_separator)
        self._optionswidget.setColumnTypeRanges(options.columntypes)
        self._optionswidget.setRowStates(
            {i: v for r, v in options.rowspec for i in r}
        )

    def options(self):
        # type: () -> Options
        rowspec_ = self._optionswidget.rowStates()
        rowspec = [(range(i, i + 1), v) for i, v in rowspec_.items()]
        numformat = self._optionswidget.numbersFormat()
        return Options(
            encoding=self._optionswidget.encoding(),
            dialect=self._optionswidget.dialect(),
            columntypes=self._optionswidget.columnTypeRanges(),
            rowspec=rowspec,
            decimal_separator=numformat["decimal"],
            group_separator=numformat["group"],
        )

    def setPath(self, path):
        """
        Set the preview path.
        """
        if self._path != path:
            self._path = path
            self.__update_preview()

    def path(self):
        """Return the preview path"""
        return self._path

    def reset(self):
        """
        Reset the options to the state previously set with `setOptions`
        effectively undoing any user modifications since then.
        """
        self.setOptions(self._options)

    def restoreDefaults(self):
        """
        Restore the options to default state.
        """
        # preserve `_options` if set by clients (for `reset`).
        opts = self._options
        self.setOptions(Options("utf-8", csv.excel()))
        self._options = opts

    def __update_preview(self):
        if not self._path:
            return
        try:
            f = _open(self._path, "rb")
        except OSError as err:
            traceback.print_exc(file=sys.stderr)
            fmt = "".join(traceback.format_exception_only(type(err), err))
            self.__set_error(fmt)
        else:
            self.__clear_error()
            self._optionswidget.setSampleContents(f)
            closeexisting = self.__finalizer
            if closeexisting is not None:
                self.destroyed.disconnect(closeexisting)
                closeexisting()
            self.__finalizer = weakref.finalize(self, f.close)
            self.destroyed.connect(self.__finalizer)

    def __set_error(self, text, format=Qt.PlainText):
        self._optionswidget.setEnabled(False)
        label = self._overlay.findChild(QLabel)  # type: QLabel
        label.setText(text)
        label.setTextFormat(format)
        self._overlay.show()
        self._overlay.raise_()
        dialog_button_box_set_enabled(self._buttons, False)

    def __clear_error(self):
        if self._overlay.isVisibleTo(self):
            self._overlay.hide()
            self._optionswidget.setEnabled(True)

    # Enable/disable the accept buttons on the most egregious errors.
    def __on_preview_error(self):
        b = self._buttons.button(QDialogButtonBox.Ok)
        b.setEnabled(False)

    def __on_preview_reset(self):
        b = self._buttons.button(QDialogButtonBox.Ok)
        b.setEnabled(True)


def dialog_button_box_set_enabled(buttonbox, enabled):
    # type: (QDialogButtonBox, bool) -> None
    """
    Disable/enable buttons in a QDialogButtonBox based on their role.

    All buttons except the ones with RejectRole or HelpRole are disabled.
    """
    stashname = "__p_dialog_button_box_set_enabled"
    for b in buttonbox.buttons():
        role = buttonbox.buttonRole(b)
        if not enabled:
            if b.property(stashname) is None:
                b.setProperty(stashname, b.isEnabledTo(buttonbox))
            b.setEnabled(
                role == QDialogButtonBox.RejectRole or
                role == QDialogButtonBox.HelpRole
            )
        else:
            stashed_state = b.property(stashname)
            if isinstance(stashed_state, bool):
                state = stashed_state
                b.setProperty(stashname, None)
            else:
                state = True
            b.setEnabled(state)


class ImportItem(QStandardItem):
    """
    An item representing a file path and associated load options
    """
    PathRole = Qt.UserRole + 12
    OptionsRole = Qt.UserRole + 14

    def path(self):
        # type: () -> str
        path = self.data(ImportItem.PathRole)
        return path if isinstance(path, str) else ""

    def setPath(self, path):
        # type: (str) -> None
        self.setData(path, ImportItem.PathRole)

    def options(self):
        # type: () -> Optional[Options]
        options = self.data(ImportItem.OptionsRole)
        return options if isinstance(options, Options) else None

    def setOptions(self, options):
        # type: (Options) -> None
        self.setData(options, ImportItem.OptionsRole)

    @classmethod
    def fromPath(cls, path):
        # type: (str) -> ImportItem
        """
        Create a `ImportItem` from a local file system path.
        """
        iconprovider = QFileIconProvider()
        basename = os.path.basename(path)
        item = cls()
        item.setText(basename)
        item.setToolTip(path)
        finfo = QFileInfo(path)
        if finfo.exists():
            item.setIcon(iconprovider.icon(finfo))
        else:
            item.setIcon(iconprovider.icon(QFileIconProvider.File))

        item.setData(path, ImportItem.PathRole)
        if not os.path.isfile(path):
            item.setEnabled(False)
            item.setToolTip(item.toolTip() + " (missing from filesystem)")
        return item


def qname(type_):
    # type: (type) -> str
    """
    Return the fully qualified name for a `type_`.
    """

    return "{0.__module__}.{0.__qualname__}".format(type_)


class OWCSVFileImport(widget.OWWidget):
    name = "CSV File Import"
    description = "Import a data table from a CSV formatted file."
    icon = "icons/CSVFile.svg"
    priority = 11
    category = "Data"
    keywords = ["file", "load", "read", "open", "csv"]

    outputs = [
        widget.OutputSignal(
            name="Data",
            type=Orange.data.Table,
            doc="Loaded data set."),
        widget.OutputSignal(
            name="Data Frame",
            type=pd.DataFrame,
            doc=""
        )
    ]

    class Error(widget.OWWidget.Error):
        error = widget.Msg(
            "Unexpected error"
        )
        encoding_error = widget.Msg(
            "Encoding error\n"
            "The file might be encoded in an unsupported encoding or it "
            "might be binary"
        )

    #: Paths and options of files accessed in a 'session'
    _session_items = settings.Setting(
        [], schema_only=True)  # type: List[Tuple[str, dict]]

    #: Saved dialog state (last directory and selected filter)
    dialog_state = settings.Setting({
        "directory": "",
        "filter": ""
    })  # type: Dict[str, str]
    MaxHistorySize = 50

    want_main_area = False
    buttons_area_orientation = None
    resizing_enabled = False

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

        self.__committimer = QTimer(self, singleShot=True)
        self.__committimer.timeout.connect(self.commit)

        self.__executor = qconcurrent.ThreadExecutor()
        self.__watcher = None  # type: Optional[qconcurrent.FutureWatcher]

        self.controlArea.layout().setSpacing(-1)  # reset spacing
        grid = QGridLayout()
        grid.addWidget(QLabel("File:", self), 0, 0, 1, 1)

        self.import_items_model = QStandardItemModel(self)
        self.recent_combo = QComboBox(
            self, objectName="recent-combo", toolTip="Recent files.",
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=16,
        )
        self.recent_combo.setModel(self.import_items_model)
        self.recent_combo.activated.connect(self.activate_recent)
        self.recent_combo.setSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        self.browse_button = QPushButton(
            "…", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),
            toolTip="Browse filesystem", autoDefault=False,
        )
        self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.browse_button.clicked.connect(self.browse)
        grid.addWidget(self.recent_combo, 0, 1, 1, 1)
        grid.addWidget(self.browse_button, 0, 2, 1, 1)
        self.controlArea.layout().addLayout(grid)

        ###########
        # Info text
        ###########
        box = gui.widgetBox(self.controlArea, "Info", addSpace=False)
        self.summary_text = QTextBrowser(
            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,
            readOnly=True,
        )
        self.summary_text.viewport().setBackgroundRole(QPalette.NoRole)
        self.summary_text.setFrameStyle(QTextBrowser.NoFrame)
        self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4)
        self.summary_text.viewport().setAutoFillBackground(False)
        box.layout().addWidget(self.summary_text)

        self.info.set_output_summary(self.info.NoOutput)

        button_box = QDialogButtonBox(
            orientation=Qt.Horizontal,
            standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry
        )
        self.load_button = b = button_box.button(QDialogButtonBox.Retry)
        b.setText("Load")
        b.clicked.connect(self.__committimer.start)
        b.setEnabled(False)
        b.setDefault(True)

        self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel)
        b.clicked.connect(self.cancel)
        b.setEnabled(False)
        b.setAutoDefault(False)

        self.import_options_button = QPushButton(
            "Import Options…", enabled=False, autoDefault=False,
            clicked=self._activate_import_dialog
        )

        def update_buttons(cbindex):
            self.import_options_button.setEnabled(cbindex != -1)
            self.load_button.setEnabled(cbindex != -1)
        self.recent_combo.currentIndexChanged.connect(update_buttons)

        button_box.addButton(
            self.import_options_button, QDialogButtonBox.ActionRole
        )
        button_box.setStyleSheet(
            "button-layout: {:d};".format(QDialogButtonBox.MacLayout)
        )
        self.controlArea.layout().addWidget(button_box)
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)

        self._restoreState()
        item = self.current_item()
        if item is not None:
            self.set_selected_file(item.path(), item.options())

    @Slot(int)
    def activate_recent(self, index):
        """
        Activate an item from the recent list.
        """
        if 0 <= index < self.import_items_model.rowCount():
            item = self.import_items_model.item(index)
            assert item is not None
            path = item.data(ImportItem.PathRole)
            opts = item.data(ImportItem.OptionsRole)
            if not isinstance(opts, Options):
                opts = None
            self.set_selected_file(path, opts)
        else:
            self.recent_combo.setCurrentIndex(-1)

    @Slot()
    def browse(self):
        """
        Open a file dialog and select a user specified file.
        """
        formats = [
            "Text - comma separated (*.csv, *)",
            "Text - tab separated (*.tsv, *)",
            "Text - all files (*)"
        ]

        dlg = QFileDialog(
            self, windowTitle="Open Data File",
            acceptMode=QFileDialog.AcceptOpen,
            fileMode=QFileDialog.ExistingFile
        )
        dlg.setNameFilters(formats)
        state = self.dialog_state
        lastdir = state.get("directory", "")
        lastfilter = state.get("filter", "")

        if lastdir and os.path.isdir(lastdir):
            dlg.setDirectory(lastdir)
        if lastfilter:
            dlg.selectNameFilter(lastfilter)

        status = dlg.exec_()
        dlg.deleteLater()
        if status == QFileDialog.Accepted:
            self.dialog_state["directory"] = dlg.directory().absolutePath()
            self.dialog_state["filter"] = dlg.selectedNameFilter()

            selected_filter = dlg.selectedNameFilter()
            path = dlg.selectedFiles()[0]
            # pre-flight check; try to determine the nature of the file
            mtype = _mime_type_for_path(path)
            if not mtype.inherits("text/plain"):
                mb = QMessageBox(
                    parent=self,
                    windowTitle="",
                    icon=QMessageBox.Question,
                    text="The '{basename}' may be a binary file.\n"
                         "Are you sure you want to continue?".format(
                             basename=os.path.basename(path)),
                    standardButtons=QMessageBox.Cancel | QMessageBox.Yes
                )
                mb.setWindowModality(Qt.WindowModal)
                if mb.exec() == QMessageBox.Cancel:
                    return

            # initialize dialect based on selected extension
            if selected_filter in formats[:-1]:
                filter_idx = formats.index(selected_filter)
                if filter_idx == 0:
                    dialect = csv.excel()
                elif filter_idx == 1:
                    dialect = csv.excel_tab()
                else:
                    dialect = csv.excel_tab()
                header = True
            else:
                try:
                    dialect, header = sniff_csv_with_path(path)
                except Exception:  # pylint: disable=broad-except
                    dialect, header = csv.excel(), True

            options = None
            # Search for path in history.
            # If found use the stored params to initialize the import dialog
            items = self.itemsFromSettings()
            idx = index_where(items, lambda t: samepath(t[0], path))
            if idx is not None:
                _, options_ = items[idx]
                if options_ is not None:
                    options = options_

            if options is None:
                if not header:
                    rowspec = []
                else:
                    rowspec = [(range(0, 1), RowSpec.Header)]
                options = Options(
                    encoding="utf-8", dialect=dialect, rowspec=rowspec)

            dlg = CSVImportDialog(
                self, windowTitle="Import Options", sizeGripEnabled=True)
            dlg.setWindowModality(Qt.WindowModal)
            dlg.setPath(path)
            dlg.setOptions(options)
            status = dlg.exec_()
            dlg.deleteLater()
            if status == QDialog.Accepted:
                self.set_selected_file(path, dlg.options())

    def current_item(self):
        # type: () -> Optional[ImportItem]
        """
        Return the current selected item (file) or None if there is no
        current item.
        """
        idx = self.recent_combo.currentIndex()
        if idx == -1:
            return None

        item = self.recent_combo.model().item(idx)  # type: QStandardItem
        if isinstance(item, ImportItem):
            return item
        else:
            return None

    def _activate_import_dialog(self):
        """Activate the Import Options dialog for the  current item."""
        item = self.current_item()
        assert item is not None
        dlg = CSVImportDialog(
            self, windowTitle="Import Options", sizeGripEnabled=True,
        )
        dlg.setWindowModality(Qt.WindowModal)
        dlg.setAttribute(Qt.WA_DeleteOnClose)
        settings = QSettings()
        qualname = qname(type(self))
        settings.beginGroup(qualname)
        size = settings.value("size", QSize(), type=QSize)  # type: QSize
        if size.isValid():
            dlg.resize(size)

        path = item.data(ImportItem.PathRole)
        options = item.data(ImportItem.OptionsRole)
        dlg.setPath(path)  # Set path before options so column types can
        if isinstance(options, Options):
            dlg.setOptions(options)

        def update():
            newoptions = dlg.options()
            item.setData(newoptions, ImportItem.OptionsRole)
            # update the stored item
            self._add_recent(path, newoptions)
            if newoptions != options:
                self._invalidate()
        dlg.accepted.connect(update)

        def store_size():
            settings.setValue("size", dlg.size())
        dlg.finished.connect(store_size)
        dlg.show()

    def set_selected_file(self, filename, options=None):
        """
        Set the current selected filename path.
        """
        self._add_recent(filename, options)
        self._invalidate()

    #: Saved options for a filename
    SCHEMA = {
        "path": str,  # Local filesystem path
        "options": str,  # json encoded 'Options'
    }

    @classmethod
    def _local_settings(cls):
        # type: () -> QSettings
        """Return a QSettings instance with local persistent settings."""
        filename = "{}.ini".format(qname(cls))
        fname = os.path.join(settings.widget_settings_dir(), filename)
        return QSettings(fname, QSettings.IniFormat)

    def _add_recent(self, filename, options=None):
        # type: (str, Optional[Options]) -> None
        """
        Add filename to the list of recent files.
        """
        model = self.import_items_model
        index = index_where(
            (model.index(i, 0).data(ImportItem.PathRole)
             for i in range(model.rowCount())),
            lambda path: isinstance(path, str) and samepath(path, filename)
        )
        if index is not None:
            item, *_ = model.takeRow(index)
        else:
            item = ImportItem.fromPath(filename)

        model.insertRow(0, item)

        if options is not None:
            item.setOptions(options)

        self.recent_combo.setCurrentIndex(0)
        # store items to local persistent settings
        s = self._local_settings()
        arr = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        item = {"path": filename}
        if options is not None:
            item["options"] = json.dumps(options.as_dict())

        arr = [item for item in arr if item.get("path") != filename]
        arr.append(item)
        QSettings_writeArray(s, "recent", arr)

        # update workflow session items
        items = self._session_items[:]
        idx = index_where(items, lambda t: samepath(t[0], filename))
        if idx is not None:
            del items[idx]
        items.insert(0, (filename, options.as_dict()))
        self._session_items = items[:OWCSVFileImport.MaxHistorySize]

    def _invalidate(self):
        # Invalidate the current output and schedule a new commit call.
        # (NOTE: The widget enters a blocking state)
        self.__committimer.start()
        if self.__watcher is not None:
            self.__cancel_task()
        self.setBlocking(True)

    def commit(self):
        """
        Commit the current state and submit the load task for execution.

        Note
        ----
        Any existing pending task is canceled.
        """
        self.__committimer.stop()
        if self.__watcher is not None:
            self.__cancel_task()
        self.error()

        item = self.current_item()
        if item is None:
            return
        path = item.data(ImportItem.PathRole)
        opts = item.data(ImportItem.OptionsRole)
        if not isinstance(opts, Options):
            return

        task = state = TaskState()
        state.future = ...
        state.watcher = qconcurrent.FutureWatcher()
        state.progressChanged.connect(
            self.__set_read_progress, Qt.DirectConnection)

        def progress_(i, j):
            task.emitProgressChangedOrCancel(i, j)

        task.future = self.__executor.submit(
            clear_stack_on_cancel(load_csv),
            path, opts, progress_,
        )
        task.watcher.setFuture(task.future)
        w = task.watcher
        w.done.connect(self.__handle_result)
        w.progress = state
        self.__watcher = w
        self.__set_running_state()

    @Slot('qint64', 'qint64')
    def __set_read_progress(self, read, count):
        if count > 0:
            self.progressBarSet(100 * read / count)

    def __cancel_task(self):
        # Cancel and dispose of the current task
        assert self.__watcher is not None
        w = self.__watcher
        w.future().cancel()
        w.progress.cancel = True
        w.done.disconnect(self.__handle_result)
        w.progress.progressChanged.disconnect(self.__set_read_progress)
        self.__watcher = None

    def cancel(self):
        """
        Cancel current pending or executing task.
        """
        if self.__watcher is not None:
            self.__cancel_task()
            self.__clear_running_state()
            self.setStatusMessage("Cancelled")
            self.summary_text.setText(
                "<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>"
            )

    def __set_running_state(self):
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Running")
        self.cancel_button.setEnabled(True)
        self.load_button.setText("Restart")
        path = self.current_item().path()
        self.Error.clear()
        self.summary_text.setText(
            "<div>Loading: <i>{}</i><br/>".format(prettyfypath(path))
        )

    def __clear_running_state(self, ):
        self.progressBarFinished()
        self.setStatusMessage("")
        self.setBlocking(False)
        self.cancel_button.setEnabled(False)
        self.load_button.setText("Reload")

    def __set_error_state(self, err):
        self.Error.clear()
        if isinstance(err, UnicodeDecodeError):
            self.Error.encoding_error(exc_info=err)
        else:
            self.Error.error(exc_info=err)

        path = self.current_item().path()
        basename = os.path.basename(path)
        if isinstance(err, UnicodeDecodeError):
            text = (
                "<div><i>{basename}</i> was not loaded due to a text encoding "
                "error. The file might be saved in an unknown or invalid "
                "encoding, or it might be a binary file.</div>"
            ).format(
                basename=escape(basename)
            )
        else:
            text = (
                "<div><i>{basename}</i> was not loaded due to an error:"
                "<p style='white-space: pre;'>{err}</p>"
            ).format(
                basename=escape(basename),
                err="".join(traceback.format_exception_only(type(err), err))
            )
        self.summary_text.setText(text)

    def __clear_error_state(self):
        self.Error.error.clear()
        self.summary_text.setText("")

    def onDeleteWidget(self):
        """Reimplemented."""
        if self.__watcher is not None:
            self.__cancel_task()
            self.__executor.shutdown()
        super().onDeleteWidget()

    @Slot(object)
    def __handle_result(self, f):
        # type: (qconcurrent.Future[pd.DataFrame]) -> None
        assert f.done()
        assert f is self.__watcher.future()
        self.__watcher = None
        self.__clear_running_state()

        try:
            df = f.result()
            assert isinstance(df, pd.DataFrame)
        except pandas.errors.EmptyDataError:
            df = pd.DataFrame({})
        except Exception as e:  # pylint: disable=broad-except
            self.__set_error_state(e)
            df = None
        else:
            self.__clear_error_state()

        if df is not None:
            table = pandas_to_table(df)
            filename = self.current_item().path()
            table.name = os.path.splitext(os.path.split(filename)[-1])[0]
        else:
            table = None
        self.send("Data Frame", df)
        self.send('Data', table)
        self._update_status_messages(table)

    def _update_status_messages(self, data):
        summary = len(data) if data else self.info.NoOutput
        details = format_summary_details(data) if data else ""
        self.info.set_output_summary(summary, details)
        if data is None:
            return

        def pluralize(seq):
            return "s" if len(seq) != 1 else ""

        summary = ("{n_instances} row{plural_1}, "
                   "{n_features} feature{plural_2}, "
                   "{n_meta} meta{plural_3}").format(
                       n_instances=len(data), plural_1=pluralize(data),
                       n_features=len(data.domain.attributes),
                       plural_2=pluralize(data.domain.attributes),
                       n_meta=len(data.domain.metas),
                       plural_3=pluralize(data.domain.metas))
        self.summary_text.setText(summary)

    def itemsFromSettings(self):
        # type: () -> List[Tuple[str, Options]]
        """
        Return items from local history.
        """
        s = self._local_settings()
        items_ = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        items = []  # type: List[Tuple[str, Options]]
        for item in items_:
            path = item.get("path", "")
            if not path:
                continue
            opts_json = item.get("options", "")
            try:
                opts = Options.from_dict(json.loads(opts_json))
            except (csv.Error, LookupError, TypeError, json.JSONDecodeError):
                _log.error("Could not reconstruct options for '%s'", path,
                           exc_info=True)
            else:
                items.append((path, opts))
        return items[::-1]

    def _restoreState(self):
        # Restore the state. Merge session (workflow) items with the
        # local history.
        model = self.import_items_model
        # local history
        items = self.itemsFromSettings()
        # stored session items
        sitems = []
        for p, m in self._session_items:
            try:
                item_ = (p, Options.from_dict(m))
            except (csv.Error, LookupError):
                # Is it better to fail then to lose a item slot?
                _log.error("Failed to restore '%s'", p, exc_info=True)
            else:
                sitems.append(item_)

        items = sitems + items
        items = unique(items, key=lambda t: pathnormalize(t[0]))

        curr = self.recent_combo.currentIndex()
        if curr != -1:
            currentpath = self.recent_combo.currentData(ImportItem.PathRole)
        else:
            currentpath = None
        for path, options in items:
            item = ImportItem.fromPath(path)
            item.setOptions(options)
            model.appendRow(item)

        if currentpath is not None:
            idx = self.recent_combo.findData(currentpath, ImportItem.PathRole)
            if idx != -1:
                self.recent_combo.setCurrentIndex(idx)


@singledispatch
def sniff_csv(file, samplesize=2 ** 20):
    sniffer = csv.Sniffer()
    sample = file.read(samplesize)
    dialect = sniffer.sniff(sample)
    dialect = textimport.Dialect(
        dialect.delimiter, dialect.quotechar,
        dialect.escapechar, dialect.doublequote,
        dialect.skipinitialspace, dialect.quoting
    )
    has_header = sniffer.has_header(sample)
    return dialect, has_header


@sniff_csv.register(str)
@sniff_csv.register(bytes)
def sniff_csv_with_path(path, encoding="utf-8", samplesize=2 ** 20):
    with _open(path, "rt", encoding=encoding) as f:
        return sniff_csv(f, samplesize)


def _open(path, mode, encoding=None):
    # type: (str, str, Optional[str]) -> typing.IO[Any]
    """
    Open a local file `path` for reading. The file may be gzip, bz2 or zip
    compressed.

    If a zip archive then a single archive member is expected.

    Parameters
    ----------
    path : str
        File system path
    mode : str
        'r', 'rb' or 'rt'
    encoding : Optional[str]
        Optional text encoding, for opening in text mode.

    Returns
    -------
    stream: io.BaseIO
        A stream opened for reading.
    """
    if mode not in {'r', 'rb', 'rt'}:
        raise ValueError('r')
    _, ext = os.path.splitext(path)
    ext = ext.lower()
    if ext == ".gz":
        return gzip.open(path, mode, encoding=encoding)
    elif ext == ".bz2":
        return bz2.open(path, mode, encoding=encoding)
    elif ext == ".xz":
        return lzma.open(path, mode, encoding=encoding)
    elif ext == ".zip":
        arh = zipfile.ZipFile(path, 'r')
        filelist = arh.infolist()
        if len(filelist) == 1:
            filename = filelist[0]
            zinfo = arh.getinfo(filename)
            f = arh.open(zinfo.filename, 'r')
            if 't' in mode:
                f = io.TextIOWrapper(f, encoding=encoding)
            return f
        else:
            raise ValueError("Expected a single file in the archive.")
    else:
        return open(path, mode, encoding=encoding)


compression_types = [
    "application/gzip", "application/zip",
    "application/x-xz", "application/x-bzip",
    # application/x-lz4
]


def _mime_type_for_path(path):
    # type: (str) -> QMimeType
    """
    Return the mime type of the file on a local filesystem.

    In case the path is a compressed file return the mime type of its contents

    Parameters
    ----------
    path : str
        Local filesystem path

    Returns
    -------
    mimetype: QMimeType
    """
    db = QMimeDatabase()
    mtype = db.mimeTypeForFile(path, QMimeDatabase.MatchDefault)
    if any(mtype.inherits(t) for t in compression_types):
        # peek contents
        try:
            with _open(path, "rb") as f:
                sample = f.read(4096)
        except Exception:  # pylint: disable=broad-except
            sample = b''
        mtype = db.mimeTypeForData(sample)
    return mtype


NA_DEFAULT = ["", "?", ".", "~", "nan", "NAN", "NaN", "N/A", "n/a", "NA"]

NA_VALUES = {
    ColumnType.Numeric: NA_DEFAULT,
    ColumnType.Categorical: NA_DEFAULT,
    ColumnType.Time: NA_DEFAULT + ["NaT", "NAT"],
    ColumnType.Text: [],
    ColumnType.Auto: NA_DEFAULT,
}


def load_csv(path, opts, progress_callback=None):
    # type: (Union[AnyStr, BinaryIO], Options, ...) -> pd.DataFrame
    def dtype(coltype):
        # type: (ColumnType) -> Optional[str]
        if coltype == ColumnType.Numeric:
            return "float"
        elif coltype == ColumnType.Categorical:
            return "category"
        elif coltype == ColumnType.Time:
            return "object"
        elif coltype == ColumnType.Text:
            return "object"
        elif coltype == ColumnType.Skip:
            return None
        elif coltype == ColumnType.Auto:
            return None
        else:
            raise TypeError

    def expand(ranges):
        # type: (Iterable[Tuple[range, T]]) -> Iterable[Tuple[int, T]]
        return ((i, x) for r, x in ranges for i in r)

    dtypes = {i: dtype(c) for i, c in expand(opts.columntypes)}
    dtypes = {i: dtp for i, dtp in dtypes.items()
              if dtp is not None and dtp != ColumnType.Auto}

    columns_ignored = {i for i, c in expand(opts.columntypes)
                       if c == ColumnType.Skip}
    dtcols = {i for i, c in expand(opts.columntypes)
              if c == ColumnType.Time}
    parse_dates = sorted(dtcols)
    na_values = {i: NA_VALUES.get(c, NA_DEFAULT)
                 for i, c in expand(opts.columntypes)}
    if not parse_dates:
        parse_dates = False

    # fixup header indices to account for skipped rows (header row indices
    # pick rows after skiprows)

    hspec = sorted(opts.rowspec, key=lambda t: t[0].start)
    header_ranges = []
    nskiped = 0
    for range_, state in hspec:
        if state == RowSpec.Skipped:
            nskiped += len(range_)
        elif state == RowSpec.Header:
            header_ranges.append(
                range(range_.start - nskiped, range_.stop - nskiped)
            )
    headers = [i for r in header_ranges for i in r]
    skiprows = [row for r, st in hspec if st == RowSpec.Skipped for row in r]

    if not headers:
        header = None
        prefix = "X."

    elif len(headers) == 1:
        header = headers[0]
        prefix = None
    else:
        header = headers
        prefix = None

    if not skiprows:
        skiprows = None

    numbers_format_kwds = {}

    if opts.decimal_separator != ".":
        numbers_format_kwds["decimal"] = opts.decimal_separator

    if opts.group_separator != "":
        numbers_format_kwds["thousands"] = opts.group_separator

    if numbers_format_kwds:
        # float_precision = "round_trip" cannot handle non c-locale decimal and
        # thousands sep (https://github.com/pandas-dev/pandas/issues/35365).
        # Fallback to 'high'.
        numbers_format_kwds["float_precision"] = "high"
    else:
        numbers_format_kwds["float_precision"] = "round_trip"

    with ExitStack() as stack:
        if isinstance(path, (str, bytes)):
            f = stack.enter_context(_open(path, 'rb'))
        elif isinstance(path, (io.RawIOBase, io.BufferedIOBase)) or \
                hasattr(path, "read"):
            f = path
        else:
            raise TypeError()
        file = TextReadWrapper(
            f, encoding=opts.encoding,
            progress_callback=progress_callback)
        stack.callback(file.detach)
        df = pd.read_csv(
            file, sep=opts.dialect.delimiter, dialect=opts.dialect,
            skipinitialspace=opts.dialect.skipinitialspace,
            header=header, skiprows=skiprows,
            dtype=dtypes, parse_dates=parse_dates, prefix=prefix,
            na_values=na_values, keep_default_na=False,
            **numbers_format_kwds
        )
        df = guess_types(df, dtypes, columns_ignored)

        if columns_ignored:
            # TODO: use 'usecols' parameter in `read_csv` call to
            # avoid loading/parsing the columns
            df.drop(
                columns=[df.columns[i] for i in columns_ignored
                         if i < len(df.columns)],
                inplace=True
            )
        return df


def guess_types(
        df: pd.DataFrame, dtypes: Dict[int, str], columns_ignored: List[int]
) -> pd.DataFrame:
    """
    Guess data type for variables according to values.

    Parameters
    ----------
    df
        Data frame
    dtypes
        The dictionary with data types set by user. We will guess values only
        for columns that does not have data type defined.
    columns_ignored
        List with indices of ignored columns. Ignored columns are skipped.

    Returns
    -------
    A data frame with changed dtypes according to the strategy.
    """
    for i, col in enumerate(df):
        # only when automatic is set in widget dialog
        if dtypes.get(i, None) is None and i not in columns_ignored:
            df[col] = guess_data_type(df[col])
    return df


def guess_data_type(col: pd.Series) -> pd.Series:
    """
    Guess column types. Logic is same than in guess_data_type from io_utils
    module. This function only change the dtype of the column such that later
    correct Orange.data.variable is used.
    Logic:
    - if can converted to date-time (ISO) -> TimeVariable
    - if numeric (only numbers)
        - only values {0, 1} or {1, 2} -> DiscreteVariable
        - else -> ContinuousVariable
    - if not numbers:
        - num_unique_values < len(data) ** 0.7 and < 100 -> DiscreteVariable
        - else -> StringVariable

    Parameters
    ----------
    col
        Data column

    Returns
    -------
    Data column with correct dtype
    """
    def parse_dates(s):
        """
        This is an extremely fast approach to datetime parsing.
        For large data, the same dates are often repeated. Rather than
        re-parse these, we store all unique dates, parse them, and
        use a lookup to convert all dates.
        """
        try:
            dates = {date: pd.to_datetime(date) for date in s.unique()}
        except ValueError:
            return None
        return s.map(dates)

    if pdtypes.is_numeric_dtype(col):
        unique_values = col.unique()
        if len(unique_values) <= 2 and (
                len(np.setdiff1d(unique_values, [0, 1])) == 0
                or len(np.setdiff1d(unique_values, [1, 2])) == 0):
            return col.astype("category")
    else:  # object
        # try parse as date - if None not a date
        parsed_col = parse_dates(col)
        if parsed_col is not None:
            return parsed_col
        unique_values = col.unique()
        if len(unique_values) < 100 and len(unique_values) < len(col)**0.7:
            return col.astype("category")
    return col


def clear_stack_on_cancel(f):
    """
    A decorator that catches the TaskState.UserCancelException exception
    and clears the exception's traceback to remove local references.

    Parameters
    ----------
    f : callable

    Returns
    -------
    wrapped : callable
    """
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except TaskState.UserCancelException as e:
            # TODO: Is this enough to allow immediate gc of the stack?
            # How does it chain across cython code?
            # Maybe just return None.
            e = e.with_traceback(None)
            e.__context__ = None
            e.__cause__ = None
            raise e
        except BaseException as e:
            traceback.clear_frames(e.__traceback__)
            raise

    return wrapper


class TaskState(QObject, PyOwned):
    class UserCancelException(BaseException):
        """User interrupt exception."""

    #: Signal emitted with the current read progress. First value is the current
    #: progress state, second value is the total progress to complete
    #: (-1 if unknown)
    progressChanged = Signal('qint64', 'qint64')
    __progressChanged = Signal('qint64', 'qint64')
    #: Was cancel requested.
    cancel = False

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # route the signal via this object's queue
        self.__progressChanged.connect(
            self.progressChanged, Qt.QueuedConnection)

    def emitProgressChangedOrCancel(self, current, total):
        # type: (int, int) -> None
        """
        Emit the progressChanged signal with `current` and `total`.
        """
        if self.cancel:
            raise TaskState.UserCancelException()
        else:
            self.__progressChanged.emit(current, total)


class TextReadWrapper(io.TextIOWrapper):
    """
    TextIOWrapper reporting the read progress.

    Assuming a single forward read pass.
    """

    #: A `Callable[[int, int], []]` called when the file position is
    #: advanced by read; called with current byte position and total
    #: file size.
    progress_callback = ...  # type: Callable[[int, int], None]

    def __init__(self, buffer, *args, progress_callback=None, **kwargs):
        super().__init__(buffer, *args, **kwargs)
        if progress_callback is None:
            def progress_callback(i, j):  # pylint: disable=unused-argument
                pass
        self.progress_callback = progress_callback
        try:
            self.__size = os.fstat(buffer.fileno()).st_size
        except OSError:
            self.__size = -1

    def read(self, size=-1):
        s = super().read(size)
        # try to go around any gzip/bz2/lzma wrappers to the base
        # raw file (does not work for zipfile.ZipExtFile; should
        # dispatch on buffer type)
        try:
            fd = self.buffer.fileno()
        except (AttributeError, io.UnsupportedOperation):
            pos = -1
        else:
            try:
                pos = os.lseek(fd, 0, os.SEEK_CUR)
            except OSError:
                pos = -1

        self.progress_callback(pos, self.__size)
        return s


def index_where(iterable, pred):
    # type: (Iterable[T], Callable[[T], bool]) -> Optional[int]
    """
    Return the (first) index of el in `iterable` where `pred(el)` returns True.

    If no element matches return `None`.
    """
    for i, el in enumerate(iterable):
        if pred(el):
            return i
    return None


def unique(iterable, key=None):
    # type: (Iterable[T], Optional[Callable[[T], Hashable]]) -> Iterable[T]
    """
    Return an iterator over unique elements of `iterable`.

    If `key` is supplied it is used as a substitute for determining
    'uniqueness' of elements.

    Parameters
    ----------
    iterable : Iterable[T]
    key : Callable[[T], Hashable]

    Returns
    -------
    unique : Iterable[T]
    """
    seen = set()
    if key is None:
        key = lambda t: t
    for el in iterable:
        el_k = key(el)
        if el_k not in seen:
            seen.add(el_k)
            yield el


def samepath(p1, p2):
    # type: (str, str) -> bool
    """
    Return True if the paths `p1` and `p2` match after case and path
    normalization.
    """
    return pathnormalize(p1) == pathnormalize(p2)


def pathnormalize(p):
    """
    Normalize a path (apply both path and case normalization.
    """
    return os.path.normcase(os.path.normpath(p))


def prettyfypath(path):
    """
    Return the path with the $HOME prefix shortened to '~/' if applicable.

    Example
    -------
    >>> prettyfypath("/home/user/file.dat")
    '~/file.dat'
    """
    home = os.path.expanduser("~/")
    home_n = pathnormalize(home)
    path_n = pathnormalize(path)
    if path_n.startswith(home_n):
        path = os.path.join("~", os.path.relpath(path, home))
    return path


def pandas_to_table(df):
    # type: (pd.DataFrame) -> Orange.data.Table
    """
    Convert a pandas.DataFrame to a Orange.data.Table instance.
    """
    index = df.index
    if not isinstance(index, pd.RangeIndex):
        df = df.reset_index()

    columns = []  # type: List[Tuple[Orange.data.Variable, np.ndarray]]

    for header, series in df.items():  # type: (Any, pd.Series)
        if pdtypes.is_categorical(series):
            coldata = series.values  # type: pd.Categorical
            categories = [str(c) for c in coldata.categories]
            var = Orange.data.DiscreteVariable.make(
                str(header), values=categories
            )
            # Remap the coldata into the var.values order/set
            coldata = pd.Categorical(
                coldata.astype("str"), categories=var.values
            )
            codes = coldata.codes
            assert np.issubdtype(codes.dtype, np.integer)
            orangecol = np.array(codes, dtype=np.float)
            orangecol[codes < 0] = np.nan
        elif pdtypes.is_datetime64_any_dtype(series):
            # Check that this converts tz local to UTC
            series = series.astype(np.dtype("M8[ns]"))
            coldata = series.values  # type: np.ndarray
            assert coldata.dtype == "M8[ns]"
            mask = np.isnat(coldata)
            orangecol = coldata.astype(np.int64) / 10 ** 9
            orangecol[mask] = np.nan
            var = Orange.data.TimeVariable.make(str(header))
            var.have_date = var.have_time = 1
        elif pdtypes.is_object_dtype(series):
            coldata = series.fillna('').values
            assert isinstance(coldata, np.ndarray)
            orangecol = coldata
            var = Orange.data.StringVariable.make(str(header))
        elif pdtypes.is_integer_dtype(series):
            coldata = series.values
            var = Orange.data.ContinuousVariable.make(str(header))
            var.number_of_decimals = 0
            orangecol = coldata.astype(np.float64)
        elif pdtypes.is_numeric_dtype(series):
            orangecol = series.values.astype(np.float64)
            var = Orange.data.ContinuousVariable.make(str(header))
        else:
            warnings.warn(
                "Column '{}' with dtype: {} skipped."
                .format(header, series.dtype),
                UserWarning
            )
            continue
        columns.append((var, orangecol))

    cols_x = [(var, col) for var, col in columns if var.is_primitive()]
    cols_m = [(var, col) for var, col in columns if not var.is_primitive()]

    variables = [v for v, _ in cols_x]
    if cols_x:
        X = np.column_stack([a for _, a in cols_x])
    else:
        X = np.empty((df.shape[0], 0), dtype=np.float)
    metas = [v for v, _ in cols_m]
    if cols_m:
        M = np.column_stack([a for _, a in cols_m])
    else:
        M = None

    domain = Orange.data.Domain(variables, metas=metas)
    return Orange.data.Table.from_numpy(domain, X, None, M)


def main(argv=None):  # pragma: no cover
    app = QApplication(argv or [])
    w = OWCSVFileImport()
    w.show()
    w.raise_()
    app.exec_()
    w.saveSettings()
    w.onDeleteWidget()
    return 0


if __name__ == "__main__":  # pragma: no cover
    sys.exit(main(sys.argv))

```
--- 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:** KeyError When Opening Single-File Zip Archive in owcsvimport

**Description:**
When using the `_open` function to read a single-file zip archive, a `KeyError` is raised indicating that the specified file does not exist in the archive. This occurs even though the archive contains exactly one file.

**Example Code:**
```python
import zipfile
from owcsvimport import _open

# Create a single-file zip archive
with zipfile.ZipFile('archive.zip', 'w') as zipf:
    zipf.writestr('data.csv', 'col1,col2\n1,2\n3,4')

# Attempt to open and read the file from the zip archive
with _open('archive.zip', 'rt', encoding='ascii') as f:
    content = f.read()
```

**Expected Behavior:**
The `data.csv` file inside `archive.zip` should be opened successfully, and its contents should be read without errors.

**Actual Behavior:**
A `KeyError` is raised with a message similar to: "There is no item named `<ZipInfo filename='archive.zip' ...>` in the archive."
[/ISSUE]
{
    "TestOWCSVFileImport.test_basic": "PASSED",
    "TestOWCSVFileImport.test_image_export": "PASSED",
    "TestOWCSVFileImport.test_minimum_size": "PASSED",
    "TestOWCSVFileImport.test_msg_base_class": "PASSED",
    "TestUtils.test_convert": "PASSED",
    "TestUtils.test_decimal_format": "PASSED",
    "TestUtils.test_load_csv": "PASSED",
    "TestUtils.test_open_compressed": "PASSED",
    "TestOWCSVFileImport.test_restore": "FAILED",
    "TestOWCSVFileImport.test_restore_from_local": "FAILED",
    "TestOWCSVFileImport.test_summary": "FAILED",
    "TestOWCSVFileImport.test_type_guessing": "FAILED",
    "TestImportDialog.test_dialog": "FAILED"
}
diff --git a/Orange/widgets/data/owcsvimport.py b/Orange/widgets/data/owcsvimport.py
index 078adca1c..805f1ec32 100644
--- a/Orange/widgets/data/owcsvimport.py
+++ b/Orange/widgets/data/owcsvimport.py
@@ -1101,9 +1101,14 @@ def _open(path, mode, encoding=None):
         arh = zipfile.ZipFile(path, 'r')
         filelist = arh.infolist()
         if len(filelist) == 1:
-            filename = filelist[0]
-            zinfo = arh.getinfo(filename)
-            f = arh.open(zinfo.filename, 'r')
+            f = arh.open(filelist[0], 'r')
+            # patch the f.close to also close the main archive file
+            f_close = f.close
+
+            def close_():
+                f_close()
+                arh.close()
+            f.close = close_
             if 't' in mode:
                 f = io.TextIOWrapper(f, encoding=encoding)
             return f

{"Orange/widgets/data/owcsvimport.py": "# -*- coding: utf-8 -*-\n\"\"\"\nCSV File Import Widget\n----------------------\n\n\"\"\"\nimport sys\nimport os\nimport csv\nimport enum\nimport io\nimport traceback\nimport warnings\nimport logging\nimport weakref\nimport json\n\nimport gzip\nimport lzma\nimport bz2\nimport zipfile\n\nfrom xml.sax.saxutils import escape\nfrom functools import singledispatch\nfrom contextlib import ExitStack\n\nimport typing\nfrom typing import (\n    List, Tuple, Dict, Optional, Any, Callable, Iterable, Hashable,\n    Union, AnyStr, BinaryIO\n)\n\nfrom PyQt5.QtCore import (\n    Qt, QFileInfo, QTimer, QSettings, QObject, QSize, QMimeDatabase, QMimeType\n)\nfrom PyQt5.QtGui import QStandardItem, QStandardItemModel, QPalette\nfrom PyQt5.QtWidgets import (\n    QLabel, QComboBox, QPushButton, QDialog, QDialogButtonBox, QGridLayout,\n    QVBoxLayout, QSizePolicy, QStyle, QFileIconProvider, QFileDialog,\n    QApplication, QMessageBox, QTextBrowser\n)\nfrom PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal\n\nimport numpy as np\nimport pandas.errors\nimport pandas as pd\n\nfrom pandas.api import types as pdtypes\n\nimport Orange.data\n\nfrom Orange.widgets import widget, gui, settings\nfrom Orange.widgets.utils.concurrent import PyOwned\nfrom Orange.widgets.utils import textimport, concurrent as qconcurrent\nfrom Orange.widgets.utils.overlay import OverlayWidget\nfrom Orange.widgets.utils.settings import (\n    QSettings_readArray, QSettings_writeArray\n)\nfrom Orange.widgets.utils.state_summary import format_summary_details\n\n\nif typing.TYPE_CHECKING:\n    # pylint: disable=invalid-name\n    T = typing.TypeVar(\"T\")\n    K = typing.TypeVar(\"K\")\n\n__all__ = [\"OWCSVFileImport\"]\n\n_log = logging.getLogger(__name__)\n\nColumnType = textimport.ColumnType\nRowSpec = textimport.RowSpec\n\n\ndef enum_lookup(enumtype, name):\n    # type: (typing.Type[T], str) -> Optional[T]\n    \"\"\"\n    Return an value from `enumtype` by its symbolic name or None if not found.\n    \"\"\"\n    try:\n        return enumtype[name]\n    except LookupError:\n        return None\n\n\ndef dialect_eq(lhs, rhs):\n    # type: (csv.Dialect, csv.Dialect) -> bool\n    \"\"\"Compare 2 `csv.Dialect` instances for equality.\"\"\"\n    return (lhs.delimiter == rhs.delimiter and\n            lhs.quotechar == rhs.quotechar and\n            lhs.doublequote == rhs.doublequote and\n            lhs.escapechar == rhs.escapechar and\n            lhs.quoting == rhs.quoting and\n            lhs.skipinitialspace == rhs.skipinitialspace)\n\n\nclass Options:\n    \"\"\"\n    Stored options for loading CSV-like file.\n\n    Arguments\n    ---------\n    encoding : str\n        A encoding to use for reading.\n    dialect : csv.Dialect\n        A csv.Dialect instance.\n    columntypes: Iterable[Tuple[range, ColumnType]]\n        A list of column type ranges specifying the types for columns.\n        Need not list all columns. Columns not listed are assumed to have auto\n        type inference.\n    rowspec : Iterable[Tuple[range, RowSpec]]\n         A list of row spec ranges.\n    decimal_separator : str\n        Decimal separator - a single character string; default: `\".\"`\n    group_separator : str\n        Thousands group separator - empty or a single character string;\n        default: empty string\n    \"\"\"\n    RowSpec = RowSpec\n    ColumnType = ColumnType\n\n    def __init__(self, encoding='utf-8', dialect=csv.excel(), columntypes=[],\n                 rowspec=((range(0, 1), RowSpec.Header),),\n                 decimal_separator=\".\", group_separator=\"\"):\n        # type: (str, csv.Dialect, List[Tuple[range, ColumnType]], ...) -> None\n        self.encoding = encoding\n        self.dialect = dialect\n        self.columntypes = list(columntypes)\n        self.rowspec = list(rowspec)  # type: List[Tuple[range, Options.RowSpec]]\n        self.decimal_separator = decimal_separator\n        self.group_separator = group_separator\n\n    def __eq__(self, other):\n        # type: (Options) -> bool\n        \"\"\"\n        Compare this instance to `other` for equality.\n        \"\"\"\n        if isinstance(other, Options):\n            return (dialect_eq(self.dialect, other.dialect) and\n                    self.encoding == other.encoding and\n                    self.columntypes == other.columntypes and\n                    self.rowspec == other.rowspec and\n                    self.group_separator == other.group_separator and\n                    self.decimal_separator == other.decimal_separator)\n        else:\n            return NotImplemented\n\n    def __repr__(self):\n        class_, args = self.__reduce__()\n        return \"{}{!r}\".format(class_.__name__, args)\n    __str__ = __repr__\n\n    def __reduce__(self):\n        return type(self), (self.encoding, self.dialect,\n                            self.columntypes, self.rowspec)\n\n    def as_dict(self):\n        # type: () -> Dict[str, Any]\n        \"\"\"\n        Return return Option parameters as plain types suitable for\n        serialization (e.g JSON serializable).\n        \"\"\"\n        return {\n            \"encoding\": self.encoding,\n            \"delimiter\": self.dialect.delimiter,\n            \"quotechar\": self.dialect.quotechar,\n            \"doublequote\": self.dialect.doublequote,\n            \"skipinitialspace\": self.dialect.skipinitialspace,\n            \"quoting\": self.dialect.quoting,\n            \"columntypes\": Options.spec_as_encodable(self.columntypes),\n            \"rowspec\": Options.spec_as_encodable(self.rowspec),\n            \"decimal_separator\": self.decimal_separator,\n            \"group_separator\": self.group_separator,\n        }\n\n    @staticmethod\n    def from_dict(mapping):\n        # type: (Dict[str, Any]) -> Options\n        \"\"\"\n        Reconstruct a `Options` from a plain dictionary (see :func:`as_dict`).\n        \"\"\"\n        encoding = mapping[\"encoding\"]\n        delimiter = mapping[\"delimiter\"]\n        quotechar = mapping[\"quotechar\"]\n        doublequote = mapping[\"doublequote\"]\n        quoting = mapping[\"quoting\"]\n        skipinitialspace = mapping[\"skipinitialspace\"]\n\n        dialect = textimport.Dialect(\n            delimiter, quotechar, None, doublequote, skipinitialspace,\n            quoting=quoting)\n\n        colspec = mapping[\"columntypes\"]\n        rowspec = mapping[\"rowspec\"]\n        colspec = Options.spec_from_encodable(colspec, ColumnType)\n        rowspec = Options.spec_from_encodable(rowspec, RowSpec)\n        decimal = mapping.get(\"decimal_separator\", \".\")\n        group = mapping.get(\"group_separator\", \"\")\n\n        return Options(encoding, dialect, colspec, rowspec,\n                       decimal_separator=decimal,\n                       group_separator=group)\n\n    @staticmethod\n    def spec_as_encodable(spec):\n        # type: (List[Tuple[range, enum.Enum]]) -> List[Dict[str, Any]]\n        return [{\"start\": r.start, \"stop\": r.stop, \"value\": value.name}\n                for r, value in spec]\n\n    @staticmethod\n    def spec_from_encodable(spec, enumtype):\n        # type: (List[Dict[str, Any]], typing.Type[T]) -> List[Tuple[range, T]]\n        r = []\n        for v in spec:\n            try:\n                start, stop, name = v[\"start\"], v[\"stop\"], v[\"value\"]\n            except (KeyError, ValueError):\n                pass\n            else:\n                r.append((range(start, stop), enum_lookup(enumtype, name)))\n        return r\n\n\nclass CSVImportDialog(QDialog):\n    \"\"\"\n    A dialog for selecting CSV file import options.\n    \"\"\"\n    def __init__(self, parent=None, flags=Qt.Dialog, **kwargs):\n        super().__init__(parent, flags, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self._options = None\n        self._path = None\n        # Finalizer for opened file handle (in _update_preview)\n        self.__finalizer = None  # type: Optional[Callable[[], None]]\n        self._optionswidget = textimport.CSVImportWidget()\n        self._optionswidget.previewReadErrorOccurred.connect(\n            self.__on_preview_error\n        )\n        self._optionswidget.previewModelReset.connect(\n            self.__on_preview_reset\n        )\n        self._buttons = buttons = QDialogButtonBox(\n            orientation=Qt.Horizontal,\n            standardButtons=(QDialogButtonBox.Ok | QDialogButtonBox.Cancel |\n                             QDialogButtonBox.Reset |\n                             QDialogButtonBox.RestoreDefaults),\n            objectName=\"dialog-button-box\",\n        )\n        # TODO: Help button\n        buttons.accepted.connect(self.accept)\n        buttons.rejected.connect(self.reject)\n\n        b = buttons.button(QDialogButtonBox.Reset)\n        b.clicked.connect(self.reset)\n        b = buttons.button(QDialogButtonBox.RestoreDefaults)\n        b.clicked.connect(self.restoreDefaults)\n        self.layout().addWidget(self._optionswidget)\n        self.layout().addWidget(buttons)\n        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)\n\n        self._overlay = OverlayWidget(self)\n        self._overlay.setWidget(self._optionswidget.dataview)\n        self._overlay.setLayout(QVBoxLayout())\n        self._overlay.layout().addWidget(QLabel(wordWrap=True))\n        self._overlay.hide()\n\n    def setOptions(self, options):\n        # type: (Options) -> None\n        self._options = options\n        self._optionswidget.setEncoding(options.encoding)\n        self._optionswidget.setDialect(options.dialect)\n        self._optionswidget.setNumbersFormat(\n            options.group_separator, options.decimal_separator)\n        self._optionswidget.setColumnTypeRanges(options.columntypes)\n        self._optionswidget.setRowStates(\n            {i: v for r, v in options.rowspec for i in r}\n        )\n\n    def options(self):\n        # type: () -> Options\n        rowspec_ = self._optionswidget.rowStates()\n        rowspec = [(range(i, i + 1), v) for i, v in rowspec_.items()]\n        numformat = self._optionswidget.numbersFormat()\n        return Options(\n            encoding=self._optionswidget.encoding(),\n            dialect=self._optionswidget.dialect(),\n            columntypes=self._optionswidget.columnTypeRanges(),\n            rowspec=rowspec,\n            decimal_separator=numformat[\"decimal\"],\n            group_separator=numformat[\"group\"],\n        )\n\n    def setPath(self, path):\n        \"\"\"\n        Set the preview path.\n        \"\"\"\n        if self._path != path:\n            self._path = path\n            self.__update_preview()\n\n    def path(self):\n        \"\"\"Return the preview path\"\"\"\n        return self._path\n\n    def reset(self):\n        \"\"\"\n        Reset the options to the state previously set with `setOptions`\n        effectively undoing any user modifications since then.\n        \"\"\"\n        self.setOptions(self._options)\n\n    def restoreDefaults(self):\n        \"\"\"\n        Restore the options to default state.\n        \"\"\"\n        # preserve `_options` if set by clients (for `reset`).\n        opts = self._options\n        self.setOptions(Options(\"utf-8\", csv.excel()))\n        self._options = opts\n\n    def __update_preview(self):\n        if not self._path:\n            return\n        try:\n            f = _open(self._path, \"rb\")\n        except OSError as err:\n            traceback.print_exc(file=sys.stderr)\n            fmt = \"\".join(traceback.format_exception_only(type(err), err))\n            self.__set_error(fmt)\n        else:\n            self.__clear_error()\n            self._optionswidget.setSampleContents(f)\n            closeexisting = self.__finalizer\n            if closeexisting is not None:\n                self.destroyed.disconnect(closeexisting)\n                closeexisting()\n            self.__finalizer = weakref.finalize(self, f.close)\n            self.destroyed.connect(self.__finalizer)\n\n    def __set_error(self, text, format=Qt.PlainText):\n        self._optionswidget.setEnabled(False)\n        label = self._overlay.findChild(QLabel)  # type: QLabel\n        label.setText(text)\n        label.setTextFormat(format)\n        self._overlay.show()\n        self._overlay.raise_()\n        dialog_button_box_set_enabled(self._buttons, False)\n\n    def __clear_error(self):\n        if self._overlay.isVisibleTo(self):\n            self._overlay.hide()\n            self._optionswidget.setEnabled(True)\n\n    # Enable/disable the accept buttons on the most egregious errors.\n    def __on_preview_error(self):\n        b = self._buttons.button(QDialogButtonBox.Ok)\n        b.setEnabled(False)\n\n    def __on_preview_reset(self):\n        b = self._buttons.button(QDialogButtonBox.Ok)\n        b.setEnabled(True)\n\n\ndef dialog_button_box_set_enabled(buttonbox, enabled):\n    # type: (QDialogButtonBox, bool) -> None\n    \"\"\"\n    Disable/enable buttons in a QDialogButtonBox based on their role.\n\n    All buttons except the ones with RejectRole or HelpRole are disabled.\n    \"\"\"\n    stashname = \"__p_dialog_button_box_set_enabled\"\n    for b in buttonbox.buttons():\n        role = buttonbox.buttonRole(b)\n        if not enabled:\n            if b.property(stashname) is None:\n                b.setProperty(stashname, b.isEnabledTo(buttonbox))\n            b.setEnabled(\n                role == QDialogButtonBox.RejectRole or\n                role == QDialogButtonBox.HelpRole\n            )\n        else:\n            stashed_state = b.property(stashname)\n            if isinstance(stashed_state, bool):\n                state = stashed_state\n                b.setProperty(stashname, None)\n            else:\n                state = True\n            b.setEnabled(state)\n\n\nclass ImportItem(QStandardItem):\n    \"\"\"\n    An item representing a file path and associated load options\n    \"\"\"\n    PathRole = Qt.UserRole + 12\n    OptionsRole = Qt.UserRole + 14\n\n    def path(self):\n        # type: () -> str\n        path = self.data(ImportItem.PathRole)\n        return path if isinstance(path, str) else \"\"\n\n    def setPath(self, path):\n        # type: (str) -> None\n        self.setData(path, ImportItem.PathRole)\n\n    def options(self):\n        # type: () -> Optional[Options]\n        options = self.data(ImportItem.OptionsRole)\n        return options if isinstance(options, Options) else None\n\n    def setOptions(self, options):\n        # type: (Options) -> None\n        self.setData(options, ImportItem.OptionsRole)\n\n    @classmethod\n    def fromPath(cls, path):\n        # type: (str) -> ImportItem\n        \"\"\"\n        Create a `ImportItem` from a local file system path.\n        \"\"\"\n        iconprovider = QFileIconProvider()\n        basename = os.path.basename(path)\n        item = cls()\n        item.setText(basename)\n        item.setToolTip(path)\n        finfo = QFileInfo(path)\n        if finfo.exists():\n            item.setIcon(iconprovider.icon(finfo))\n        else:\n            item.setIcon(iconprovider.icon(QFileIconProvider.File))\n\n        item.setData(path, ImportItem.PathRole)\n        if not os.path.isfile(path):\n            item.setEnabled(False)\n            item.setToolTip(item.toolTip() + \" (missing from filesystem)\")\n        return item\n\n\ndef qname(type_):\n    # type: (type) -> str\n    \"\"\"\n    Return the fully qualified name for a `type_`.\n    \"\"\"\n\n    return \"{0.__module__}.{0.__qualname__}\".format(type_)\n\n\nclass OWCSVFileImport(widget.OWWidget):\n    name = \"CSV File Import\"\n    description = \"Import a data table from a CSV formatted file.\"\n    icon = \"icons/CSVFile.svg\"\n    priority = 11\n    category = \"Data\"\n    keywords = [\"file\", \"load\", \"read\", \"open\", \"csv\"]\n\n    outputs = [\n        widget.OutputSignal(\n            name=\"Data\",\n            type=Orange.data.Table,\n            doc=\"Loaded data set.\"),\n        widget.OutputSignal(\n            name=\"Data Frame\",\n            type=pd.DataFrame,\n            doc=\"\"\n        )\n    ]\n\n    class Error(widget.OWWidget.Error):\n        error = widget.Msg(\n            \"Unexpected error\"\n        )\n        encoding_error = widget.Msg(\n            \"Encoding error\\n\"\n            \"The file might be encoded in an unsupported encoding or it \"\n            \"might be binary\"\n        )\n\n    #: Paths and options of files accessed in a 'session'\n    _session_items = settings.Setting(\n        [], schema_only=True)  # type: List[Tuple[str, dict]]\n\n    #: Saved dialog state (last directory and selected filter)\n    dialog_state = settings.Setting({\n        \"directory\": \"\",\n        \"filter\": \"\"\n    })  # type: Dict[str, str]\n    MaxHistorySize = 50\n\n    want_main_area = False\n    buttons_area_orientation = None\n    resizing_enabled = False\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(self, *args, **kwargs)\n\n        self.__committimer = QTimer(self, singleShot=True)\n        self.__committimer.timeout.connect(self.commit)\n\n        self.__executor = qconcurrent.ThreadExecutor()\n        self.__watcher = None  # type: Optional[qconcurrent.FutureWatcher]\n\n        self.controlArea.layout().setSpacing(-1)  # reset spacing\n        grid = QGridLayout()\n        grid.addWidget(QLabel(\"File:\", self), 0, 0, 1, 1)\n\n        self.import_items_model = QStandardItemModel(self)\n        self.recent_combo = QComboBox(\n            self, objectName=\"recent-combo\", toolTip=\"Recent files.\",\n            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,\n            minimumContentsLength=16,\n        )\n        self.recent_combo.setModel(self.import_items_model)\n        self.recent_combo.activated.connect(self.activate_recent)\n        self.recent_combo.setSizePolicy(\n            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n        self.browse_button = QPushButton(\n            \"\u2026\", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),\n            toolTip=\"Browse filesystem\", autoDefault=False,\n        )\n        self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)\n        self.browse_button.clicked.connect(self.browse)\n        grid.addWidget(self.recent_combo, 0, 1, 1, 1)\n        grid.addWidget(self.browse_button, 0, 2, 1, 1)\n        self.controlArea.layout().addLayout(grid)\n\n        ###########\n        # Info text\n        ###########\n        box = gui.widgetBox(self.controlArea, \"Info\", addSpace=False)\n        self.summary_text = QTextBrowser(\n            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,\n            readOnly=True,\n        )\n        self.summary_text.viewport().setBackgroundRole(QPalette.NoRole)\n        self.summary_text.setFrameStyle(QTextBrowser.NoFrame)\n        self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4)\n        self.summary_text.viewport().setAutoFillBackground(False)\n        box.layout().addWidget(self.summary_text)\n\n        self.info.set_output_summary(self.info.NoOutput)\n\n        button_box = QDialogButtonBox(\n            orientation=Qt.Horizontal,\n            standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry\n        )\n        self.load_button = b = button_box.button(QDialogButtonBox.Retry)\n        b.setText(\"Load\")\n        b.clicked.connect(self.__committimer.start)\n        b.setEnabled(False)\n        b.setDefault(True)\n\n        self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel)\n        b.clicked.connect(self.cancel)\n        b.setEnabled(False)\n        b.setAutoDefault(False)\n\n        self.import_options_button = QPushButton(\n            \"Import Options\u2026\", enabled=False, autoDefault=False,\n            clicked=self._activate_import_dialog\n        )\n\n        def update_buttons(cbindex):\n            self.import_options_button.setEnabled(cbindex != -1)\n            self.load_button.setEnabled(cbindex != -1)\n        self.recent_combo.currentIndexChanged.connect(update_buttons)\n\n        button_box.addButton(\n            self.import_options_button, QDialogButtonBox.ActionRole\n        )\n        button_box.setStyleSheet(\n            \"button-layout: {:d};\".format(QDialogButtonBox.MacLayout)\n        )\n        self.controlArea.layout().addWidget(button_box)\n        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)\n\n        self._restoreState()\n        item = self.current_item()\n        if item is not None:\n            self.set_selected_file(item.path(), item.options())\n\n    @Slot(int)\n    def activate_recent(self, index):\n        \"\"\"\n        Activate an item from the recent list.\n        \"\"\"\n        if 0 <= index < self.import_items_model.rowCount():\n            item = self.import_items_model.item(index)\n            assert item is not None\n            path = item.data(ImportItem.PathRole)\n            opts = item.data(ImportItem.OptionsRole)\n            if not isinstance(opts, Options):\n                opts = None\n            self.set_selected_file(path, opts)\n        else:\n            self.recent_combo.setCurrentIndex(-1)\n\n    @Slot()\n    def browse(self):\n        \"\"\"\n        Open a file dialog and select a user specified file.\n        \"\"\"\n        formats = [\n            \"Text - comma separated (*.csv, *)\",\n            \"Text - tab separated (*.tsv, *)\",\n            \"Text - all files (*)\"\n        ]\n\n        dlg = QFileDialog(\n            self, windowTitle=\"Open Data File\",\n            acceptMode=QFileDialog.AcceptOpen,\n            fileMode=QFileDialog.ExistingFile\n        )\n        dlg.setNameFilters(formats)\n        state = self.dialog_state\n        lastdir = state.get(\"directory\", \"\")\n        lastfilter = state.get(\"filter\", \"\")\n\n        if lastdir and os.path.isdir(lastdir):\n            dlg.setDirectory(lastdir)\n        if lastfilter:\n            dlg.selectNameFilter(lastfilter)\n\n        status = dlg.exec_()\n        dlg.deleteLater()\n        if status == QFileDialog.Accepted:\n            self.dialog_state[\"directory\"] = dlg.directory().absolutePath()\n            self.dialog_state[\"filter\"] = dlg.selectedNameFilter()\n\n            selected_filter = dlg.selectedNameFilter()\n            path = dlg.selectedFiles()[0]\n            # pre-flight check; try to determine the nature of the file\n            mtype = _mime_type_for_path(path)\n            if not mtype.inherits(\"text/plain\"):\n                mb = QMessageBox(\n                    parent=self,\n                    windowTitle=\"\",\n                    icon=QMessageBox.Question,\n                    text=\"The '{basename}' may be a binary file.\\n\"\n                         \"Are you sure you want to continue?\".format(\n                             basename=os.path.basename(path)),\n                    standardButtons=QMessageBox.Cancel | QMessageBox.Yes\n                )\n                mb.setWindowModality(Qt.WindowModal)\n                if mb.exec() == QMessageBox.Cancel:\n                    return\n\n            # initialize dialect based on selected extension\n            if selected_filter in formats[:-1]:\n                filter_idx = formats.index(selected_filter)\n                if filter_idx == 0:\n                    dialect = csv.excel()\n                elif filter_idx == 1:\n                    dialect = csv.excel_tab()\n                else:\n                    dialect = csv.excel_tab()\n                header = True\n            else:\n                try:\n                    dialect, header = sniff_csv_with_path(path)\n                except Exception:  # pylint: disable=broad-except\n                    dialect, header = csv.excel(), True\n\n            options = None\n            # Search for path in history.\n            # If found use the stored params to initialize the import dialog\n            items = self.itemsFromSettings()\n            idx = index_where(items, lambda t: samepath(t[0], path))\n            if idx is not None:\n                _, options_ = items[idx]\n                if options_ is not None:\n                    options = options_\n\n            if options is None:\n                if not header:\n                    rowspec = []\n                else:\n                    rowspec = [(range(0, 1), RowSpec.Header)]\n                options = Options(\n                    encoding=\"utf-8\", dialect=dialect, rowspec=rowspec)\n\n            dlg = CSVImportDialog(\n                self, windowTitle=\"Import Options\", sizeGripEnabled=True)\n            dlg.setWindowModality(Qt.WindowModal)\n            dlg.setPath(path)\n            dlg.setOptions(options)\n            status = dlg.exec_()\n            dlg.deleteLater()\n            if status == QDialog.Accepted:\n                self.set_selected_file(path, dlg.options())\n\n    def current_item(self):\n        # type: () -> Optional[ImportItem]\n        \"\"\"\n        Return the current selected item (file) or None if there is no\n        current item.\n        \"\"\"\n        idx = self.recent_combo.currentIndex()\n        if idx == -1:\n            return None\n\n        item = self.recent_combo.model().item(idx)  # type: QStandardItem\n        if isinstance(item, ImportItem):\n            return item\n        else:\n            return None\n\n    def _activate_import_dialog(self):\n        \"\"\"Activate the Import Options dialog for the  current item.\"\"\"\n        item = self.current_item()\n        assert item is not None\n        dlg = CSVImportDialog(\n            self, windowTitle=\"Import Options\", sizeGripEnabled=True,\n        )\n        dlg.setWindowModality(Qt.WindowModal)\n        dlg.setAttribute(Qt.WA_DeleteOnClose)\n        settings = QSettings()\n        qualname = qname(type(self))\n        settings.beginGroup(qualname)\n        size = settings.value(\"size\", QSize(), type=QSize)  # type: QSize\n        if size.isValid():\n            dlg.resize(size)\n\n        path = item.data(ImportItem.PathRole)\n        options = item.data(ImportItem.OptionsRole)\n        dlg.setPath(path)  # Set path before options so column types can\n        if isinstance(options, Options):\n            dlg.setOptions(options)\n\n        def update():\n            newoptions = dlg.options()\n            item.setData(newoptions, ImportItem.OptionsRole)\n            # update the stored item\n            self._add_recent(path, newoptions)\n            if newoptions != options:\n                self._invalidate()\n        dlg.accepted.connect(update)\n\n        def store_size():\n            settings.setValue(\"size\", dlg.size())\n        dlg.finished.connect(store_size)\n        dlg.show()\n\n    def set_selected_file(self, filename, options=None):\n        \"\"\"\n        Set the current selected filename path.\n        \"\"\"\n        self._add_recent(filename, options)\n        self._invalidate()\n\n    #: Saved options for a filename\n    SCHEMA = {\n        \"path\": str,  # Local filesystem path\n        \"options\": str,  # json encoded 'Options'\n    }\n\n    @classmethod\n    def _local_settings(cls):\n        # type: () -> QSettings\n        \"\"\"Return a QSettings instance with local persistent settings.\"\"\"\n        filename = \"{}.ini\".format(qname(cls))\n        fname = os.path.join(settings.widget_settings_dir(), filename)\n        return QSettings(fname, QSettings.IniFormat)\n\n    def _add_recent(self, filename, options=None):\n        # type: (str, Optional[Options]) -> None\n        \"\"\"\n        Add filename to the list of recent files.\n        \"\"\"\n        model = self.import_items_model\n        index = index_where(\n            (model.index(i, 0).data(ImportItem.PathRole)\n             for i in range(model.rowCount())),\n            lambda path: isinstance(path, str) and samepath(path, filename)\n        )\n        if index is not None:\n            item, *_ = model.takeRow(index)\n        else:\n            item = ImportItem.fromPath(filename)\n\n        model.insertRow(0, item)\n\n        if options is not None:\n            item.setOptions(options)\n\n        self.recent_combo.setCurrentIndex(0)\n        # store items to local persistent settings\n        s = self._local_settings()\n        arr = QSettings_readArray(s, \"recent\", OWCSVFileImport.SCHEMA)\n        item = {\"path\": filename}\n        if options is not None:\n            item[\"options\"] = json.dumps(options.as_dict())\n\n        arr = [item for item in arr if item.get(\"path\") != filename]\n        arr.append(item)\n        QSettings_writeArray(s, \"recent\", arr)\n\n        # update workflow session items\n        items = self._session_items[:]\n        idx = index_where(items, lambda t: samepath(t[0], filename))\n        if idx is not None:\n            del items[idx]\n        items.insert(0, (filename, options.as_dict()))\n        self._session_items = items[:OWCSVFileImport.MaxHistorySize]\n\n    def _invalidate(self):\n        # Invalidate the current output and schedule a new commit call.\n        # (NOTE: The widget enters a blocking state)\n        self.__committimer.start()\n        if self.__watcher is not None:\n            self.__cancel_task()\n        self.setBlocking(True)\n\n    def commit(self):\n        \"\"\"\n        Commit the current state and submit the load task for execution.\n\n        Note\n        ----\n        Any existing pending task is canceled.\n        \"\"\"\n        self.__committimer.stop()\n        if self.__watcher is not None:\n            self.__cancel_task()\n        self.error()\n\n        item = self.current_item()\n        if item is None:\n            return\n        path = item.data(ImportItem.PathRole)\n        opts = item.data(ImportItem.OptionsRole)\n        if not isinstance(opts, Options):\n            return\n\n        task = state = TaskState()\n        state.future = ...\n        state.watcher = qconcurrent.FutureWatcher()\n        state.progressChanged.connect(\n            self.__set_read_progress, Qt.DirectConnection)\n\n        def progress_(i, j):\n            task.emitProgressChangedOrCancel(i, j)\n\n        task.future = self.__executor.submit(\n            clear_stack_on_cancel(load_csv),\n            path, opts, progress_,\n        )\n        task.watcher.setFuture(task.future)\n        w = task.watcher\n        w.done.connect(self.__handle_result)\n        w.progress = state\n        self.__watcher = w\n        self.__set_running_state()\n\n    @Slot('qint64', 'qint64')\n    def __set_read_progress(self, read, count):\n        if count > 0:\n            self.progressBarSet(100 * read / count)\n\n    def __cancel_task(self):\n        # Cancel and dispose of the current task\n        assert self.__watcher is not None\n        w = self.__watcher\n        w.future().cancel()\n        w.progress.cancel = True\n        w.done.disconnect(self.__handle_result)\n        w.progress.progressChanged.disconnect(self.__set_read_progress)\n        self.__watcher = None\n\n    def cancel(self):\n        \"\"\"\n        Cancel current pending or executing task.\n        \"\"\"\n        if self.__watcher is not None:\n            self.__cancel_task()\n            self.__clear_running_state()\n            self.setStatusMessage(\"Cancelled\")\n            self.summary_text.setText(\n                \"<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>\"\n            )\n\n    def __set_running_state(self):\n        self.progressBarInit()\n        self.setBlocking(True)\n        self.setStatusMessage(\"Running\")\n        self.cancel_button.setEnabled(True)\n        self.load_button.setText(\"Restart\")\n        path = self.current_item().path()\n        self.Error.clear()\n        self.summary_text.setText(\n            \"<div>Loading: <i>{}</i><br/>\".format(prettyfypath(path))\n        )\n\n    def __clear_running_state(self, ):\n        self.progressBarFinished()\n        self.setStatusMessage(\"\")\n        self.setBlocking(False)\n        self.cancel_button.setEnabled(False)\n        self.load_button.setText(\"Reload\")\n\n    def __set_error_state(self, err):\n        self.Error.clear()\n        if isinstance(err, UnicodeDecodeError):\n            self.Error.encoding_error(exc_info=err)\n        else:\n            self.Error.error(exc_info=err)\n\n        path = self.current_item().path()\n        basename = os.path.basename(path)\n        if isinstance(err, UnicodeDecodeError):\n            text = (\n                \"<div><i>{basename}</i> was not loaded due to a text encoding \"\n                \"error. The file might be saved in an unknown or invalid \"\n                \"encoding, or it might be a binary file.</div>\"\n            ).format(\n                basename=escape(basename)\n            )\n        else:\n            text = (\n                \"<div><i>{basename}</i> was not loaded due to an error:\"\n                \"<p style='white-space: pre;'>{err}</p>\"\n            ).format(\n                basename=escape(basename),\n                err=\"\".join(traceback.format_exception_only(type(err), err))\n            )\n        self.summary_text.setText(text)\n\n    def __clear_error_state(self):\n        self.Error.error.clear()\n        self.summary_text.setText(\"\")\n\n    def onDeleteWidget(self):\n        \"\"\"Reimplemented.\"\"\"\n        if self.__watcher is not None:\n            self.__cancel_task()\n            self.__executor.shutdown()\n        super().onDeleteWidget()\n\n    @Slot(object)\n    def __handle_result(self, f):\n        # type: (qconcurrent.Future[pd.DataFrame]) -> None\n        assert f.done()\n        assert f is self.__watcher.future()\n        self.__watcher = None\n        self.__clear_running_state()\n\n        try:\n            df = f.result()\n            assert isinstance(df, pd.DataFrame)\n        except pandas.errors.EmptyDataError:\n            df = pd.DataFrame({})\n        except Exception as e:  # pylint: disable=broad-except\n            self.__set_error_state(e)\n            df = None\n        else:\n            self.__clear_error_state()\n\n        if df is not None:\n            table = pandas_to_table(df)\n            filename = self.current_item().path()\n            table.name = os.path.splitext(os.path.split(filename)[-1])[0]\n        else:\n            table = None\n        self.send(\"Data Frame\", df)\n        self.send('Data', table)\n        self._update_status_messages(table)\n\n    def _update_status_messages(self, data):\n        summary = len(data) if data else self.info.NoOutput\n        details = format_summary_details(data) if data else \"\"\n        self.info.set_output_summary(summary, details)\n        if data is None:\n            return\n\n        def pluralize(seq):\n            return \"s\" if len(seq) != 1 else \"\"\n\n        summary = (\"{n_instances} row{plural_1}, \"\n                   \"{n_features} feature{plural_2}, \"\n                   \"{n_meta} meta{plural_3}\").format(\n                       n_instances=len(data), plural_1=pluralize(data),\n                       n_features=len(data.domain.attributes),\n                       plural_2=pluralize(data.domain.attributes),\n                       n_meta=len(data.domain.metas),\n                       plural_3=pluralize(data.domain.metas))\n        self.summary_text.setText(summary)\n\n    def itemsFromSettings(self):\n        # type: () -> List[Tuple[str, Options]]\n        \"\"\"\n        Return items from local history.\n        \"\"\"\n        s = self._local_settings()\n        items_ = QSettings_readArray(s, \"recent\", OWCSVFileImport.SCHEMA)\n        items = []  # type: List[Tuple[str, Options]]\n        for item in items_:\n            path = item.get(\"path\", \"\")\n            if not path:\n                continue\n            opts_json = item.get(\"options\", \"\")\n            try:\n                opts = Options.from_dict(json.loads(opts_json))\n            except (csv.Error, LookupError, TypeError, json.JSONDecodeError):\n                _log.error(\"Could not reconstruct options for '%s'\", path,\n                           exc_info=True)\n            else:\n                items.append((path, opts))\n        return items[::-1]\n\n    def _restoreState(self):\n        # Restore the state. Merge session (workflow) items with the\n        # local history.\n        model = self.import_items_model\n        # local history\n        items = self.itemsFromSettings()\n        # stored session items\n        sitems = []\n        for p, m in self._session_items:\n            try:\n                item_ = (p, Options.from_dict(m))\n            except (csv.Error, LookupError):\n                # Is it better to fail then to lose a item slot?\n                _log.error(\"Failed to restore '%s'\", p, exc_info=True)\n            else:\n                sitems.append(item_)\n\n        items = sitems + items\n        items = unique(items, key=lambda t: pathnormalize(t[0]))\n\n        curr = self.recent_combo.currentIndex()\n        if curr != -1:\n            currentpath = self.recent_combo.currentData(ImportItem.PathRole)\n        else:\n            currentpath = None\n        for path, options in items:\n            item = ImportItem.fromPath(path)\n            item.setOptions(options)\n            model.appendRow(item)\n\n        if currentpath is not None:\n            idx = self.recent_combo.findData(currentpath, ImportItem.PathRole)\n            if idx != -1:\n                self.recent_combo.setCurrentIndex(idx)\n\n\n@singledispatch\ndef sniff_csv(file, samplesize=2 ** 20):\n    sniffer = csv.Sniffer()\n    sample = file.read(samplesize)\n    dialect = sniffer.sniff(sample)\n    dialect = textimport.Dialect(\n        dialect.delimiter, dialect.quotechar,\n        dialect.escapechar, dialect.doublequote,\n        dialect.skipinitialspace, dialect.quoting\n    )\n    has_header = sniffer.has_header(sample)\n    return dialect, has_header\n\n\n@sniff_csv.register(str)\n@sniff_csv.register(bytes)\ndef sniff_csv_with_path(path, encoding=\"utf-8\", samplesize=2 ** 20):\n    with _open(path, \"rt\", encoding=encoding) as f:\n        return sniff_csv(f, samplesize)\n\n\ndef _open(path, mode, encoding=None):\n    # type: (str, str, Optional[str]) -> typing.IO[Any]\n    \"\"\"\n    Open a local file `path` for reading. The file may be gzip, bz2 or zip\n    compressed.\n\n    If a zip archive then a single archive member is expected.\n\n    Parameters\n    ----------\n    path : str\n        File system path\n    mode : str\n        'r', 'rb' or 'rt'\n    encoding : Optional[str]\n        Optional text encoding, for opening in text mode.\n\n    Returns\n    -------\n    stream: io.BaseIO\n        A stream opened for reading.\n    \"\"\"\n    if mode not in {'r', 'rb', 'rt'}:\n        raise ValueError('r')\n    _, ext = os.path.splitext(path)\n    ext = ext.lower()\n    if ext == \".gz\":\n        return gzip.open(path, mode, encoding=encoding)\n    elif ext == \".bz2\":\n        return bz2.open(path, mode, encoding=encoding)\n    elif ext == \".xz\":\n        return lzma.open(path, mode, encoding=encoding)\n    elif ext == \".zip\":\n        arh = zipfile.ZipFile(path, 'r')\n        filelist = arh.infolist()\n        if len(filelist) == 1:\n            filename = filelist[0]\n            zinfo = arh.getinfo(filename)\n            f = arh.open(zinfo.filename, 'r')\n            if 't' in mode:\n                f = io.TextIOWrapper(f, encoding=encoding)\n            return f\n        else:\n            raise ValueError(\"Expected a single file in the archive.\")\n    else:\n        return open(path, mode, encoding=encoding)\n\n\ncompression_types = [\n    \"application/gzip\", \"application/zip\",\n    \"application/x-xz\", \"application/x-bzip\",\n    # application/x-lz4\n]\n\n\ndef _mime_type_for_path(path):\n    # type: (str) -> QMimeType\n    \"\"\"\n    Return the mime type of the file on a local filesystem.\n\n    In case the path is a compressed file return the mime type of its contents\n\n    Parameters\n    ----------\n    path : str\n        Local filesystem path\n\n    Returns\n    -------\n    mimetype: QMimeType\n    \"\"\"\n    db = QMimeDatabase()\n    mtype = db.mimeTypeForFile(path, QMimeDatabase.MatchDefault)\n    if any(mtype.inherits(t) for t in compression_types):\n        # peek contents\n        try:\n            with _open(path, \"rb\") as f:\n                sample = f.read(4096)\n        except Exception:  # pylint: disable=broad-except\n            sample = b''\n        mtype = db.mimeTypeForData(sample)\n    return mtype\n\n\nNA_DEFAULT = [\"\", \"?\", \".\", \"~\", \"nan\", \"NAN\", \"NaN\", \"N/A\", \"n/a\", \"NA\"]\n\nNA_VALUES = {\n    ColumnType.Numeric: NA_DEFAULT,\n    ColumnType.Categorical: NA_DEFAULT,\n    ColumnType.Time: NA_DEFAULT + [\"NaT\", \"NAT\"],\n    ColumnType.Text: [],\n    ColumnType.Auto: NA_DEFAULT,\n}\n\n\ndef load_csv(path, opts, progress_callback=None):\n    # type: (Union[AnyStr, BinaryIO], Options, ...) -> pd.DataFrame\n    def dtype(coltype):\n        # type: (ColumnType) -> Optional[str]\n        if coltype == ColumnType.Numeric:\n            return \"float\"\n        elif coltype == ColumnType.Categorical:\n            return \"category\"\n        elif coltype == ColumnType.Time:\n            return \"object\"\n        elif coltype == ColumnType.Text:\n            return \"object\"\n        elif coltype == ColumnType.Skip:\n            return None\n        elif coltype == ColumnType.Auto:\n            return None\n        else:\n            raise TypeError\n\n    def expand(ranges):\n        # type: (Iterable[Tuple[range, T]]) -> Iterable[Tuple[int, T]]\n        return ((i, x) for r, x in ranges for i in r)\n\n    dtypes = {i: dtype(c) for i, c in expand(opts.columntypes)}\n    dtypes = {i: dtp for i, dtp in dtypes.items()\n              if dtp is not None and dtp != ColumnType.Auto}\n\n    columns_ignored = {i for i, c in expand(opts.columntypes)\n                       if c == ColumnType.Skip}\n    dtcols = {i for i, c in expand(opts.columntypes)\n              if c == ColumnType.Time}\n    parse_dates = sorted(dtcols)\n    na_values = {i: NA_VALUES.get(c, NA_DEFAULT)\n                 for i, c in expand(opts.columntypes)}\n    if not parse_dates:\n        parse_dates = False\n\n    # fixup header indices to account for skipped rows (header row indices\n    # pick rows after skiprows)\n\n    hspec = sorted(opts.rowspec, key=lambda t: t[0].start)\n    header_ranges = []\n    nskiped = 0\n    for range_, state in hspec:\n        if state == RowSpec.Skipped:\n            nskiped += len(range_)\n        elif state == RowSpec.Header:\n            header_ranges.append(\n                range(range_.start - nskiped, range_.stop - nskiped)\n            )\n    headers = [i for r in header_ranges for i in r]\n    skiprows = [row for r, st in hspec if st == RowSpec.Skipped for row in r]\n\n    if not headers:\n        header = None\n        prefix = \"X.\"\n\n    elif len(headers) == 1:\n        header = headers[0]\n        prefix = None\n    else:\n        header = headers\n        prefix = None\n\n    if not skiprows:\n        skiprows = None\n\n    numbers_format_kwds = {}\n\n    if opts.decimal_separator != \".\":\n        numbers_format_kwds[\"decimal\"] = opts.decimal_separator\n\n    if opts.group_separator != \"\":\n        numbers_format_kwds[\"thousands\"] = opts.group_separator\n\n    if numbers_format_kwds:\n        # float_precision = \"round_trip\" cannot handle non c-locale decimal and\n        # thousands sep (https://github.com/pandas-dev/pandas/issues/35365).\n        # Fallback to 'high'.\n        numbers_format_kwds[\"float_precision\"] = \"high\"\n    else:\n        numbers_format_kwds[\"float_precision\"] = \"round_trip\"\n\n    with ExitStack() as stack:\n        if isinstance(path, (str, bytes)):\n            f = stack.enter_context(_open(path, 'rb'))\n        elif isinstance(path, (io.RawIOBase, io.BufferedIOBase)) or \\\n                hasattr(path, \"read\"):\n            f = path\n        else:\n            raise TypeError()\n        file = TextReadWrapper(\n            f, encoding=opts.encoding,\n            progress_callback=progress_callback)\n        stack.callback(file.detach)\n        df = pd.read_csv(\n            file, sep=opts.dialect.delimiter, dialect=opts.dialect,\n            skipinitialspace=opts.dialect.skipinitialspace,\n            header=header, skiprows=skiprows,\n            dtype=dtypes, parse_dates=parse_dates, prefix=prefix,\n            na_values=na_values, keep_default_na=False,\n            **numbers_format_kwds\n        )\n        df = guess_types(df, dtypes, columns_ignored)\n\n        if columns_ignored:\n            # TODO: use 'usecols' parameter in `read_csv` call to\n            # avoid loading/parsing the columns\n            df.drop(\n                columns=[df.columns[i] for i in columns_ignored\n                         if i < len(df.columns)],\n                inplace=True\n            )\n        return df\n\n\ndef guess_types(\n        df: pd.DataFrame, dtypes: Dict[int, str], columns_ignored: List[int]\n) -> pd.DataFrame:\n    \"\"\"\n    Guess data type for variables according to values.\n\n    Parameters\n    ----------\n    df\n        Data frame\n    dtypes\n        The dictionary with data types set by user. We will guess values only\n        for columns that does not have data type defined.\n    columns_ignored\n        List with indices of ignored columns. Ignored columns are skipped.\n\n    Returns\n    -------\n    A data frame with changed dtypes according to the strategy.\n    \"\"\"\n    for i, col in enumerate(df):\n        # only when automatic is set in widget dialog\n        if dtypes.get(i, None) is None and i not in columns_ignored:\n            df[col] = guess_data_type(df[col])\n    return df\n\n\ndef guess_data_type(col: pd.Series) -> pd.Series:\n    \"\"\"\n    Guess column types. Logic is same than in guess_data_type from io_utils\n    module. This function only change the dtype of the column such that later\n    correct Orange.data.variable is used.\n    Logic:\n    - if can converted to date-time (ISO) -> TimeVariable\n    - if numeric (only numbers)\n        - only values {0, 1} or {1, 2} -> DiscreteVariable\n        - else -> ContinuousVariable\n    - if not numbers:\n        - num_unique_values < len(data) ** 0.7 and < 100 -> DiscreteVariable\n        - else -> StringVariable\n\n    Parameters\n    ----------\n    col\n        Data column\n\n    Returns\n    -------\n    Data column with correct dtype\n    \"\"\"\n    def parse_dates(s):\n        \"\"\"\n        This is an extremely fast approach to datetime parsing.\n        For large data, the same dates are often repeated. Rather than\n        re-parse these, we store all unique dates, parse them, and\n        use a lookup to convert all dates.\n        \"\"\"\n        try:\n            dates = {date: pd.to_datetime(date) for date in s.unique()}\n        except ValueError:\n            return None\n        return s.map(dates)\n\n    if pdtypes.is_numeric_dtype(col):\n        unique_values = col.unique()\n        if len(unique_values) <= 2 and (\n                len(np.setdiff1d(unique_values, [0, 1])) == 0\n                or len(np.setdiff1d(unique_values, [1, 2])) == 0):\n            return col.astype(\"category\")\n    else:  # object\n        # try parse as date - if None not a date\n        parsed_col = parse_dates(col)\n        if parsed_col is not None:\n            return parsed_col\n        unique_values = col.unique()\n        if len(unique_values) < 100 and len(unique_values) < len(col)**0.7:\n            return col.astype(\"category\")\n    return col\n\n\ndef clear_stack_on_cancel(f):\n    \"\"\"\n    A decorator that catches the TaskState.UserCancelException exception\n    and clears the exception's traceback to remove local references.\n\n    Parameters\n    ----------\n    f : callable\n\n    Returns\n    -------\n    wrapped : callable\n    \"\"\"\n    def wrapper(*args, **kwargs):\n        try:\n            return f(*args, **kwargs)\n        except TaskState.UserCancelException as e:\n            # TODO: Is this enough to allow immediate gc of the stack?\n            # How does it chain across cython code?\n            # Maybe just return None.\n            e = e.with_traceback(None)\n            e.__context__ = None\n            e.__cause__ = None\n            raise e\n        except BaseException as e:\n            traceback.clear_frames(e.__traceback__)\n            raise\n\n    return wrapper\n\n\nclass TaskState(QObject, PyOwned):\n    class UserCancelException(BaseException):\n        \"\"\"User interrupt exception.\"\"\"\n\n    #: Signal emitted with the current read progress. First value is the current\n    #: progress state, second value is the total progress to complete\n    #: (-1 if unknown)\n    progressChanged = Signal('qint64', 'qint64')\n    __progressChanged = Signal('qint64', 'qint64')\n    #: Was cancel requested.\n    cancel = False\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        # route the signal via this object's queue\n        self.__progressChanged.connect(\n            self.progressChanged, Qt.QueuedConnection)\n\n    def emitProgressChangedOrCancel(self, current, total):\n        # type: (int, int) -> None\n        \"\"\"\n        Emit the progressChanged signal with `current` and `total`.\n        \"\"\"\n        if self.cancel:\n            raise TaskState.UserCancelException()\n        else:\n            self.__progressChanged.emit(current, total)\n\n\nclass TextReadWrapper(io.TextIOWrapper):\n    \"\"\"\n    TextIOWrapper reporting the read progress.\n\n    Assuming a single forward read pass.\n    \"\"\"\n\n    #: A `Callable[[int, int], []]` called when the file position is\n    #: advanced by read; called with current byte position and total\n    #: file size.\n    progress_callback = ...  # type: Callable[[int, int], None]\n\n    def __init__(self, buffer, *args, progress_callback=None, **kwargs):\n        super().__init__(buffer, *args, **kwargs)\n        if progress_callback is None:\n            def progress_callback(i, j):  # pylint: disable=unused-argument\n                pass\n        self.progress_callback = progress_callback\n        try:\n            self.__size = os.fstat(buffer.fileno()).st_size\n        except OSError:\n            self.__size = -1\n\n    def read(self, size=-1):\n        s = super().read(size)\n        # try to go around any gzip/bz2/lzma wrappers to the base\n        # raw file (does not work for zipfile.ZipExtFile; should\n        # dispatch on buffer type)\n        try:\n            fd = self.buffer.fileno()\n        except (AttributeError, io.UnsupportedOperation):\n            pos = -1\n        else:\n            try:\n                pos = os.lseek(fd, 0, os.SEEK_CUR)\n            except OSError:\n                pos = -1\n\n        self.progress_callback(pos, self.__size)\n        return s\n\n\ndef index_where(iterable, pred):\n    # type: (Iterable[T], Callable[[T], bool]) -> Optional[int]\n    \"\"\"\n    Return the (first) index of el in `iterable` where `pred(el)` returns True.\n\n    If no element matches return `None`.\n    \"\"\"\n    for i, el in enumerate(iterable):\n        if pred(el):\n            return i\n    return None\n\n\ndef unique(iterable, key=None):\n    # type: (Iterable[T], Optional[Callable[[T], Hashable]]) -> Iterable[T]\n    \"\"\"\n    Return an iterator over unique elements of `iterable`.\n\n    If `key` is supplied it is used as a substitute for determining\n    'uniqueness' of elements.\n\n    Parameters\n    ----------\n    iterable : Iterable[T]\n    key : Callable[[T], Hashable]\n\n    Returns\n    -------\n    unique : Iterable[T]\n    \"\"\"\n    seen = set()\n    if key is None:\n        key = lambda t: t\n    for el in iterable:\n        el_k = key(el)\n        if el_k not in seen:\n            seen.add(el_k)\n            yield el\n\n\ndef samepath(p1, p2):\n    # type: (str, str) -> bool\n    \"\"\"\n    Return True if the paths `p1` and `p2` match after case and path\n    normalization.\n    \"\"\"\n    return pathnormalize(p1) == pathnormalize(p2)\n\n\ndef pathnormalize(p):\n    \"\"\"\n    Normalize a path (apply both path and case normalization.\n    \"\"\"\n    return os.path.normcase(os.path.normpath(p))\n\n\ndef prettyfypath(path):\n    \"\"\"\n    Return the path with the $HOME prefix shortened to '~/' if applicable.\n\n    Example\n    -------\n    >>> prettyfypath(\"/home/user/file.dat\")\n    '~/file.dat'\n    \"\"\"\n    home = os.path.expanduser(\"~/\")\n    home_n = pathnormalize(home)\n    path_n = pathnormalize(path)\n    if path_n.startswith(home_n):\n        path = os.path.join(\"~\", os.path.relpath(path, home))\n    return path\n\n\ndef pandas_to_table(df):\n    # type: (pd.DataFrame) -> Orange.data.Table\n    \"\"\"\n    Convert a pandas.DataFrame to a Orange.data.Table instance.\n    \"\"\"\n    index = df.index\n    if not isinstance(index, pd.RangeIndex):\n        df = df.reset_index()\n\n    columns = []  # type: List[Tuple[Orange.data.Variable, np.ndarray]]\n\n    for header, series in df.items():  # type: (Any, pd.Series)\n        if pdtypes.is_categorical(series):\n            coldata = series.values  # type: pd.Categorical\n            categories = [str(c) for c in coldata.categories]\n            var = Orange.data.DiscreteVariable.make(\n                str(header), values=categories\n            )\n            # Remap the coldata into the var.values order/set\n            coldata = pd.Categorical(\n                coldata.astype(\"str\"), categories=var.values\n            )\n            codes = coldata.codes\n            assert np.issubdtype(codes.dtype, np.integer)\n            orangecol = np.array(codes, dtype=np.float)\n            orangecol[codes < 0] = np.nan\n        elif pdtypes.is_datetime64_any_dtype(series):\n            # Check that this converts tz local to UTC\n            series = series.astype(np.dtype(\"M8[ns]\"))\n            coldata = series.values  # type: np.ndarray\n            assert coldata.dtype == \"M8[ns]\"\n            mask = np.isnat(coldata)\n            orangecol = coldata.astype(np.int64) / 10 ** 9\n            orangecol[mask] = np.nan\n            var = Orange.data.TimeVariable.make(str(header))\n            var.have_date = var.have_time = 1\n        elif pdtypes.is_object_dtype(series):\n            coldata = series.fillna('').values\n            assert isinstance(coldata, np.ndarray)\n            orangecol = coldata\n            var = Orange.data.StringVariable.make(str(header))\n        elif pdtypes.is_integer_dtype(series):\n            coldata = series.values\n            var = Orange.data.ContinuousVariable.make(str(header))\n            var.number_of_decimals = 0\n            orangecol = coldata.astype(np.float64)\n        elif pdtypes.is_numeric_dtype(series):\n            orangecol = series.values.astype(np.float64)\n            var = Orange.data.ContinuousVariable.make(str(header))\n        else:\n            warnings.warn(\n                \"Column '{}' with dtype: {} skipped.\"\n                .format(header, series.dtype),\n                UserWarning\n            )\n            continue\n        columns.append((var, orangecol))\n\n    cols_x = [(var, col) for var, col in columns if var.is_primitive()]\n    cols_m = [(var, col) for var, col in columns if not var.is_primitive()]\n\n    variables = [v for v, _ in cols_x]\n    if cols_x:\n        X = np.column_stack([a for _, a in cols_x])\n    else:\n        X = np.empty((df.shape[0], 0), dtype=np.float)\n    metas = [v for v, _ in cols_m]\n    if cols_m:\n        M = np.column_stack([a for _, a in cols_m])\n    else:\n        M = None\n\n    domain = Orange.data.Domain(variables, metas=metas)\n    return Orange.data.Table.from_numpy(domain, X, None, M)\n\n\ndef main(argv=None):  # pragma: no cover\n    app = QApplication(argv or [])\n    w = OWCSVFileImport()\n    w.show()\n    w.raise_()\n    app.exec_()\n    w.saveSettings()\n    w.onDeleteWidget()\n    return 0\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    sys.exit(main(sys.argv))\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:** KeyError When Opening Single-File Zip Archive in owcsvimport

**Description:**
When using the `_open` function to read a single-file zip archive, a `KeyError` is raised indicating that the specified file does not exist in the archive. This occurs even though the archive contains exactly one file.

**Example Code:**
```python
import zipfile
from owcsvimport import _open

# Create a single-file zip archive
with zipfile.ZipFile('archive.zip', 'w') as zipf:
    zipf.writestr('data.csv', 'col1,col2\n1,2\n3,4')

# Attempt to open and read the file from the zip archive
with _open('archive.zip', 'rt', encoding='ascii') as f:
    content = f.read()
```

**Expected Behavior:**
The `data.csv` file inside `archive.zip` should be opened successfully, and its contents should be read without errors.

**Actual Behavior:**
A `KeyError` is raised with a message similar to: "There is no item named `<ZipInfo filename='archive.zip' ...>` in the archive."
[/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/owcsvimport.py
# -*- coding: utf-8 -*-
"""
CSV File Import Widget
----------------------

"""
import sys
import os
import csv
import enum
import io
import traceback
import warnings
import logging
import weakref
import json

import gzip
import lzma
import bz2
import zipfile

from xml.sax.saxutils import escape
from functools import singledispatch
from contextlib import ExitStack

import typing
from typing import (
    List, Tuple, Dict, Optional, Any, Callable, Iterable, Hashable,
    Union, AnyStr, BinaryIO
)

from PyQt5.QtCore import (
    Qt, QFileInfo, QTimer, QSettings, QObject, QSize, QMimeDatabase, QMimeType
)
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QPalette
from PyQt5.QtWidgets import (
    QLabel, QComboBox, QPushButton, QDialog, QDialogButtonBox, QGridLayout,
    QVBoxLayout, QSizePolicy, QStyle, QFileIconProvider, QFileDialog,
    QApplication, QMessageBox, QTextBrowser
)
from PyQt5.QtCore import pyqtSlot as Slot, pyqtSignal as Signal

import numpy as np
import pandas.errors
import pandas as pd

from pandas.api import types as pdtypes

import Orange.data

from Orange.widgets import widget, gui, settings
from Orange.widgets.utils.concurrent import PyOwned
from Orange.widgets.utils import textimport, concurrent as qconcurrent
from Orange.widgets.utils.overlay import OverlayWidget
from Orange.widgets.utils.settings import (
    QSettings_readArray, QSettings_writeArray
)
from Orange.widgets.utils.state_summary import format_summary_details


if typing.TYPE_CHECKING:
    # pylint: disable=invalid-name
    T = typing.TypeVar("T")
    K = typing.TypeVar("K")

__all__ = ["OWCSVFileImport"]

_log = logging.getLogger(__name__)

ColumnType = textimport.ColumnType
RowSpec = textimport.RowSpec


def enum_lookup(enumtype, name):
    # type: (typing.Type[T], str) -> Optional[T]
    """
    Return an value from `enumtype` by its symbolic name or None if not found.
    """
    try:
        return enumtype[name]
    except LookupError:
        return None


def dialect_eq(lhs, rhs):
    # type: (csv.Dialect, csv.Dialect) -> bool
    """Compare 2 `csv.Dialect` instances for equality."""
    return (lhs.delimiter == rhs.delimiter and
            lhs.quotechar == rhs.quotechar and
            lhs.doublequote == rhs.doublequote and
            lhs.escapechar == rhs.escapechar and
            lhs.quoting == rhs.quoting and
            lhs.skipinitialspace == rhs.skipinitialspace)


class Options:
    """
    Stored options for loading CSV-like file.

    Arguments
    ---------
    encoding : str
        A encoding to use for reading.
    dialect : csv.Dialect
        A csv.Dialect instance.
    columntypes: Iterable[Tuple[range, ColumnType]]
        A list of column type ranges specifying the types for columns.
        Need not list all columns. Columns not listed are assumed to have auto
        type inference.
    rowspec : Iterable[Tuple[range, RowSpec]]
         A list of row spec ranges.
    decimal_separator : str
        Decimal separator - a single character string; default: `"."`
    group_separator : str
        Thousands group separator - empty or a single character string;
        default: empty string
    """
    RowSpec = RowSpec
    ColumnType = ColumnType

    def __init__(self, encoding='utf-8', dialect=csv.excel(), columntypes=[],
                 rowspec=((range(0, 1), RowSpec.Header),),
                 decimal_separator=".", group_separator=""):
        # type: (str, csv.Dialect, List[Tuple[range, ColumnType]], ...) -> None
        self.encoding = encoding
        self.dialect = dialect
        self.columntypes = list(columntypes)
        self.rowspec = list(rowspec)  # type: List[Tuple[range, Options.RowSpec]]
        self.decimal_separator = decimal_separator
        self.group_separator = group_separator

    def __eq__(self, other):
        # type: (Options) -> bool
        """
        Compare this instance to `other` for equality.
        """
        if isinstance(other, Options):
            return (dialect_eq(self.dialect, other.dialect) and
                    self.encoding == other.encoding and
                    self.columntypes == other.columntypes and
                    self.rowspec == other.rowspec and
                    self.group_separator == other.group_separator and
                    self.decimal_separator == other.decimal_separator)
        else:
            return NotImplemented

    def __repr__(self):
        class_, args = self.__reduce__()
        return "{}{!r}".format(class_.__name__, args)
    __str__ = __repr__

    def __reduce__(self):
        return type(self), (self.encoding, self.dialect,
                            self.columntypes, self.rowspec)

    def as_dict(self):
        # type: () -> Dict[str, Any]
        """
        Return return Option parameters as plain types suitable for
        serialization (e.g JSON serializable).
        """
        return {
            "encoding": self.encoding,
            "delimiter": self.dialect.delimiter,
            "quotechar": self.dialect.quotechar,
            "doublequote": self.dialect.doublequote,
            "skipinitialspace": self.dialect.skipinitialspace,
            "quoting": self.dialect.quoting,
            "columntypes": Options.spec_as_encodable(self.columntypes),
            "rowspec": Options.spec_as_encodable(self.rowspec),
            "decimal_separator": self.decimal_separator,
            "group_separator": self.group_separator,
        }

    @staticmethod
    def from_dict(mapping):
        # type: (Dict[str, Any]) -> Options
        """
        Reconstruct a `Options` from a plain dictionary (see :func:`as_dict`).
        """
        encoding = mapping["encoding"]
        delimiter = mapping["delimiter"]
        quotechar = mapping["quotechar"]
        doublequote = mapping["doublequote"]
        quoting = mapping["quoting"]
        skipinitialspace = mapping["skipinitialspace"]

        dialect = textimport.Dialect(
            delimiter, quotechar, None, doublequote, skipinitialspace,
            quoting=quoting)

        colspec = mapping["columntypes"]
        rowspec = mapping["rowspec"]
        colspec = Options.spec_from_encodable(colspec, ColumnType)
        rowspec = Options.spec_from_encodable(rowspec, RowSpec)
        decimal = mapping.get("decimal_separator", ".")
        group = mapping.get("group_separator", "")

        return Options(encoding, dialect, colspec, rowspec,
                       decimal_separator=decimal,
                       group_separator=group)

    @staticmethod
    def spec_as_encodable(spec):
        # type: (List[Tuple[range, enum.Enum]]) -> List[Dict[str, Any]]
        return [{"start": r.start, "stop": r.stop, "value": value.name}
                for r, value in spec]

    @staticmethod
    def spec_from_encodable(spec, enumtype):
        # type: (List[Dict[str, Any]], typing.Type[T]) -> List[Tuple[range, T]]
        r = []
        for v in spec:
            try:
                start, stop, name = v["start"], v["stop"], v["value"]
            except (KeyError, ValueError):
                pass
            else:
                r.append((range(start, stop), enum_lookup(enumtype, name)))
        return r


class CSVImportDialog(QDialog):
    """
    A dialog for selecting CSV file import options.
    """
    def __init__(self, parent=None, flags=Qt.Dialog, **kwargs):
        super().__init__(parent, flags, **kwargs)
        self.setLayout(QVBoxLayout())

        self._options = None
        self._path = None
        # Finalizer for opened file handle (in _update_preview)
        self.__finalizer = None  # type: Optional[Callable[[], None]]
        self._optionswidget = textimport.CSVImportWidget()
        self._optionswidget.previewReadErrorOccurred.connect(
            self.__on_preview_error
        )
        self._optionswidget.previewModelReset.connect(
            self.__on_preview_reset
        )
        self._buttons = buttons = QDialogButtonBox(
            orientation=Qt.Horizontal,
            standardButtons=(QDialogButtonBox.Ok | QDialogButtonBox.Cancel |
                             QDialogButtonBox.Reset |
                             QDialogButtonBox.RestoreDefaults),
            objectName="dialog-button-box",
        )
        # TODO: Help button
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)

        b = buttons.button(QDialogButtonBox.Reset)
        b.clicked.connect(self.reset)
        b = buttons.button(QDialogButtonBox.RestoreDefaults)
        b.clicked.connect(self.restoreDefaults)
        self.layout().addWidget(self._optionswidget)
        self.layout().addWidget(buttons)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self._overlay = OverlayWidget(self)
        self._overlay.setWidget(self._optionswidget.dataview)
        self._overlay.setLayout(QVBoxLayout())
        self._overlay.layout().addWidget(QLabel(wordWrap=True))
        self._overlay.hide()

    def setOptions(self, options):
        # type: (Options) -> None
        self._options = options
        self._optionswidget.setEncoding(options.encoding)
        self._optionswidget.setDialect(options.dialect)
        self._optionswidget.setNumbersFormat(
            options.group_separator, options.decimal_separator)
        self._optionswidget.setColumnTypeRanges(options.columntypes)
        self._optionswidget.setRowStates(
            {i: v for r, v in options.rowspec for i in r}
        )

    def options(self):
        # type: () -> Options
        rowspec_ = self._optionswidget.rowStates()
        rowspec = [(range(i, i + 1), v) for i, v in rowspec_.items()]
        numformat = self._optionswidget.numbersFormat()
        return Options(
            encoding=self._optionswidget.encoding(),
            dialect=self._optionswidget.dialect(),
            columntypes=self._optionswidget.columnTypeRanges(),
            rowspec=rowspec,
            decimal_separator=numformat["decimal"],
            group_separator=numformat["group"],
        )

    def setPath(self, path):
        """
        Set the preview path.
        """
        if self._path != path:
            self._path = path
            self.__update_preview()

    def path(self):
        """Return the preview path"""
        return self._path

    def reset(self):
        """
        Reset the options to the state previously set with `setOptions`
        effectively undoing any user modifications since then.
        """
        self.setOptions(self._options)

    def restoreDefaults(self):
        """
        Restore the options to default state.
        """
        # preserve `_options` if set by clients (for `reset`).
        opts = self._options
        self.setOptions(Options("utf-8", csv.excel()))
        self._options = opts

    def __update_preview(self):
        if not self._path:
            return
        try:
            f = _open(self._path, "rb")
        except OSError as err:
            traceback.print_exc(file=sys.stderr)
            fmt = "".join(traceback.format_exception_only(type(err), err))
            self.__set_error(fmt)
        else:
            self.__clear_error()
            self._optionswidget.setSampleContents(f)
            closeexisting = self.__finalizer
            if closeexisting is not None:
                self.destroyed.disconnect(closeexisting)
                closeexisting()
            self.__finalizer = weakref.finalize(self, f.close)
            self.destroyed.connect(self.__finalizer)

    def __set_error(self, text, format=Qt.PlainText):
        self._optionswidget.setEnabled(False)
        label = self._overlay.findChild(QLabel)  # type: QLabel
        label.setText(text)
        label.setTextFormat(format)
        self._overlay.show()
        self._overlay.raise_()
        dialog_button_box_set_enabled(self._buttons, False)

    def __clear_error(self):
        if self._overlay.isVisibleTo(self):
            self._overlay.hide()
            self._optionswidget.setEnabled(True)

    # Enable/disable the accept buttons on the most egregious errors.
    def __on_preview_error(self):
        b = self._buttons.button(QDialogButtonBox.Ok)
        b.setEnabled(False)

    def __on_preview_reset(self):
        b = self._buttons.button(QDialogButtonBox.Ok)
        b.setEnabled(True)


def dialog_button_box_set_enabled(buttonbox, enabled):
    # type: (QDialogButtonBox, bool) -> None
    """
    Disable/enable buttons in a QDialogButtonBox based on their role.

    All buttons except the ones with RejectRole or HelpRole are disabled.
    """
    stashname = "__p_dialog_button_box_set_enabled"
    for b in buttonbox.buttons():
        role = buttonbox.buttonRole(b)
        if not enabled:
            if b.property(stashname) is None:
                b.setProperty(stashname, b.isEnabledTo(buttonbox))
            b.setEnabled(
                role == QDialogButtonBox.RejectRole or
                role == QDialogButtonBox.HelpRole
            )
        else:
            stashed_state = b.property(stashname)
            if isinstance(stashed_state, bool):
                state = stashed_state
                b.setProperty(stashname, None)
            else:
                state = True
            b.setEnabled(state)


class ImportItem(QStandardItem):
    """
    An item representing a file path and associated load options
    """
    PathRole = Qt.UserRole + 12
    OptionsRole = Qt.UserRole + 14

    def path(self):
        # type: () -> str
        path = self.data(ImportItem.PathRole)
        return path if isinstance(path, str) else ""

    def setPath(self, path):
        # type: (str) -> None
        self.setData(path, ImportItem.PathRole)

    def options(self):
        # type: () -> Optional[Options]
        options = self.data(ImportItem.OptionsRole)
        return options if isinstance(options, Options) else None

    def setOptions(self, options):
        # type: (Options) -> None
        self.setData(options, ImportItem.OptionsRole)

    @classmethod
    def fromPath(cls, path):
        # type: (str) -> ImportItem
        """
        Create a `ImportItem` from a local file system path.
        """
        iconprovider = QFileIconProvider()
        basename = os.path.basename(path)
        item = cls()
        item.setText(basename)
        item.setToolTip(path)
        finfo = QFileInfo(path)
        if finfo.exists():
            item.setIcon(iconprovider.icon(finfo))
        else:
            item.setIcon(iconprovider.icon(QFileIconProvider.File))

        item.setData(path, ImportItem.PathRole)
        if not os.path.isfile(path):
            item.setEnabled(False)
            item.setToolTip(item.toolTip() + " (missing from filesystem)")
        return item


def qname(type_):
    # type: (type) -> str
    """
    Return the fully qualified name for a `type_`.
    """

    return "{0.__module__}.{0.__qualname__}".format(type_)


class OWCSVFileImport(widget.OWWidget):
    name = "CSV File Import"
    description = "Import a data table from a CSV formatted file."
    icon = "icons/CSVFile.svg"
    priority = 11
    category = "Data"
    keywords = ["file", "load", "read", "open", "csv"]

    outputs = [
        widget.OutputSignal(
            name="Data",
            type=Orange.data.Table,
            doc="Loaded data set."),
        widget.OutputSignal(
            name="Data Frame",
            type=pd.DataFrame,
            doc=""
        )
    ]

    class Error(widget.OWWidget.Error):
        error = widget.Msg(
            "Unexpected error"
        )
        encoding_error = widget.Msg(
            "Encoding error\n"
            "The file might be encoded in an unsupported encoding or it "
            "might be binary"
        )

    #: Paths and options of files accessed in a 'session'
    _session_items = settings.Setting(
        [], schema_only=True)  # type: List[Tuple[str, dict]]

    #: Saved dialog state (last directory and selected filter)
    dialog_state = settings.Setting({
        "directory": "",
        "filter": ""
    })  # type: Dict[str, str]
    MaxHistorySize = 50

    want_main_area = False
    buttons_area_orientation = None
    resizing_enabled = False

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

        self.__committimer = QTimer(self, singleShot=True)
        self.__committimer.timeout.connect(self.commit)

        self.__executor = qconcurrent.ThreadExecutor()
        self.__watcher = None  # type: Optional[qconcurrent.FutureWatcher]

        self.controlArea.layout().setSpacing(-1)  # reset spacing
        grid = QGridLayout()
        grid.addWidget(QLabel("File:", self), 0, 0, 1, 1)

        self.import_items_model = QStandardItemModel(self)
        self.recent_combo = QComboBox(
            self, objectName="recent-combo", toolTip="Recent files.",
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=16,
        )
        self.recent_combo.setModel(self.import_items_model)
        self.recent_combo.activated.connect(self.activate_recent)
        self.recent_combo.setSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        self.browse_button = QPushButton(
            "…", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),
            toolTip="Browse filesystem", autoDefault=False,
        )
        self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.browse_button.clicked.connect(self.browse)
        grid.addWidget(self.recent_combo, 0, 1, 1, 1)
        grid.addWidget(self.browse_button, 0, 2, 1, 1)
        self.controlArea.layout().addLayout(grid)

        ###########
        # Info text
        ###########
        box = gui.widgetBox(self.controlArea, "Info", addSpace=False)
        self.summary_text = QTextBrowser(
            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,
            readOnly=True,
        )
        self.summary_text.viewport().setBackgroundRole(QPalette.NoRole)
        self.summary_text.setFrameStyle(QTextBrowser.NoFrame)
        self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4)
        self.summary_text.viewport().setAutoFillBackground(False)
        box.layout().addWidget(self.summary_text)

        self.info.set_output_summary(self.info.NoOutput)

        button_box = QDialogButtonBox(
            orientation=Qt.Horizontal,
            standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry
        )
        self.load_button = b = button_box.button(QDialogButtonBox.Retry)
        b.setText("Load")
        b.clicked.connect(self.__committimer.start)
        b.setEnabled(False)
        b.setDefault(True)

        self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel)
        b.clicked.connect(self.cancel)
        b.setEnabled(False)
        b.setAutoDefault(False)

        self.import_options_button = QPushButton(
            "Import Options…", enabled=False, autoDefault=False,
            clicked=self._activate_import_dialog
        )

        def update_buttons(cbindex):
            self.import_options_button.setEnabled(cbindex != -1)
            self.load_button.setEnabled(cbindex != -1)
        self.recent_combo.currentIndexChanged.connect(update_buttons)

        button_box.addButton(
            self.import_options_button, QDialogButtonBox.ActionRole
        )
        button_box.setStyleSheet(
            "button-layout: {:d};".format(QDialogButtonBox.MacLayout)
        )
        self.controlArea.layout().addWidget(button_box)
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)

        self._restoreState()
        item = self.current_item()
        if item is not None:
            self.set_selected_file(item.path(), item.options())

    @Slot(int)
    def activate_recent(self, index):
        """
        Activate an item from the recent list.
        """
        if 0 <= index < self.import_items_model.rowCount():
            item = self.import_items_model.item(index)
            assert item is not None
            path = item.data(ImportItem.PathRole)
            opts = item.data(ImportItem.OptionsRole)
            if not isinstance(opts, Options):
                opts = None
            self.set_selected_file(path, opts)
        else:
            self.recent_combo.setCurrentIndex(-1)

    @Slot()
    def browse(self):
        """
        Open a file dialog and select a user specified file.
        """
        formats = [
            "Text - comma separated (*.csv, *)",
            "Text - tab separated (*.tsv, *)",
            "Text - all files (*)"
        ]

        dlg = QFileDialog(
            self, windowTitle="Open Data File",
            acceptMode=QFileDialog.AcceptOpen,
            fileMode=QFileDialog.ExistingFile
        )
        dlg.setNameFilters(formats)
        state = self.dialog_state
        lastdir = state.get("directory", "")
        lastfilter = state.get("filter", "")

        if lastdir and os.path.isdir(lastdir):
            dlg.setDirectory(lastdir)
        if lastfilter:
            dlg.selectNameFilter(lastfilter)

        status = dlg.exec_()
        dlg.deleteLater()
        if status == QFileDialog.Accepted:
            self.dialog_state["directory"] = dlg.directory().absolutePath()
            self.dialog_state["filter"] = dlg.selectedNameFilter()

            selected_filter = dlg.selectedNameFilter()
            path = dlg.selectedFiles()[0]
            # pre-flight check; try to determine the nature of the file
            mtype = _mime_type_for_path(path)
            if not mtype.inherits("text/plain"):
                mb = QMessageBox(
                    parent=self,
                    windowTitle="",
                    icon=QMessageBox.Question,
                    text="The '{basename}' may be a binary file.\n"
                         "Are you sure you want to continue?".format(
                             basename=os.path.basename(path)),
                    standardButtons=QMessageBox.Cancel | QMessageBox.Yes
                )
                mb.setWindowModality(Qt.WindowModal)
                if mb.exec() == QMessageBox.Cancel:
                    return

            # initialize dialect based on selected extension
            if selected_filter in formats[:-1]:
                filter_idx = formats.index(selected_filter)
                if filter_idx == 0:
                    dialect = csv.excel()
                elif filter_idx == 1:
                    dialect = csv.excel_tab()
                else:
                    dialect = csv.excel_tab()
                header = True
            else:
                try:
                    dialect, header = sniff_csv_with_path(path)
                except Exception:  # pylint: disable=broad-except
                    dialect, header = csv.excel(), True

            options = None
            # Search for path in history.
            # If found use the stored params to initialize the import dialog
            items = self.itemsFromSettings()
            idx = index_where(items, lambda t: samepath(t[0], path))
            if idx is not None:
                _, options_ = items[idx]
                if options_ is not None:
                    options = options_

            if options is None:
                if not header:
                    rowspec = []
                else:
                    rowspec = [(range(0, 1), RowSpec.Header)]
                options = Options(
                    encoding="utf-8", dialect=dialect, rowspec=rowspec)

            dlg = CSVImportDialog(
                self, windowTitle="Import Options", sizeGripEnabled=True)
            dlg.setWindowModality(Qt.WindowModal)
            dlg.setPath(path)
            dlg.setOptions(options)
            status = dlg.exec_()
            dlg.deleteLater()
            if status == QDialog.Accepted:
                self.set_selected_file(path, dlg.options())

    def current_item(self):
        # type: () -> Optional[ImportItem]
        """
        Return the current selected item (file) or None if there is no
        current item.
        """
        idx = self.recent_combo.currentIndex()
        if idx == -1:
            return None

        item = self.recent_combo.model().item(idx)  # type: QStandardItem
        if isinstance(item, ImportItem):
            return item
        else:
            return None

    def _activate_import_dialog(self):
        """Activate the Import Options dialog for the  current item."""
        item = self.current_item()
        assert item is not None
        dlg = CSVImportDialog(
            self, windowTitle="Import Options", sizeGripEnabled=True,
        )
        dlg.setWindowModality(Qt.WindowModal)
        dlg.setAttribute(Qt.WA_DeleteOnClose)
        settings = QSettings()
        qualname = qname(type(self))
        settings.beginGroup(qualname)
        size = settings.value("size", QSize(), type=QSize)  # type: QSize
        if size.isValid():
            dlg.resize(size)

        path = item.data(ImportItem.PathRole)
        options = item.data(ImportItem.OptionsRole)
        dlg.setPath(path)  # Set path before options so column types can
        if isinstance(options, Options):
            dlg.setOptions(options)

        def update():
            newoptions = dlg.options()
            item.setData(newoptions, ImportItem.OptionsRole)
            # update the stored item
            self._add_recent(path, newoptions)
            if newoptions != options:
                self._invalidate()
        dlg.accepted.connect(update)

        def store_size():
            settings.setValue("size", dlg.size())
        dlg.finished.connect(store_size)
        dlg.show()

    def set_selected_file(self, filename, options=None):
        """
        Set the current selected filename path.
        """
        self._add_recent(filename, options)
        self._invalidate()

    #: Saved options for a filename
    SCHEMA = {
        "path": str,  # Local filesystem path
        "options": str,  # json encoded 'Options'
    }

    @classmethod
    def _local_settings(cls):
        # type: () -> QSettings
        """Return a QSettings instance with local persistent settings."""
        filename = "{}.ini".format(qname(cls))
        fname = os.path.join(settings.widget_settings_dir(), filename)
        return QSettings(fname, QSettings.IniFormat)

    def _add_recent(self, filename, options=None):
        # type: (str, Optional[Options]) -> None
        """
        Add filename to the list of recent files.
        """
        model = self.import_items_model
        index = index_where(
            (model.index(i, 0).data(ImportItem.PathRole)
             for i in range(model.rowCount())),
            lambda path: isinstance(path, str) and samepath(path, filename)
        )
        if index is not None:
            item, *_ = model.takeRow(index)
        else:
            item = ImportItem.fromPath(filename)

        model.insertRow(0, item)

        if options is not None:
            item.setOptions(options)

        self.recent_combo.setCurrentIndex(0)
        # store items to local persistent settings
        s = self._local_settings()
        arr = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        item = {"path": filename}
        if options is not None:
            item["options"] = json.dumps(options.as_dict())

        arr = [item for item in arr if item.get("path") != filename]
        arr.append(item)
        QSettings_writeArray(s, "recent", arr)

        # update workflow session items
        items = self._session_items[:]
        idx = index_where(items, lambda t: samepath(t[0], filename))
        if idx is not None:
            del items[idx]
        items.insert(0, (filename, options.as_dict()))
        self._session_items = items[:OWCSVFileImport.MaxHistorySize]

    def _invalidate(self):
        # Invalidate the current output and schedule a new commit call.
        # (NOTE: The widget enters a blocking state)
        self.__committimer.start()
        if self.__watcher is not None:
            self.__cancel_task()
        self.setBlocking(True)

    def commit(self):
        """
        Commit the current state and submit the load task for execution.

        Note
        ----
        Any existing pending task is canceled.
        """
        self.__committimer.stop()
        if self.__watcher is not None:
            self.__cancel_task()
        self.error()

        item = self.current_item()
        if item is None:
            return
        path = item.data(ImportItem.PathRole)
        opts = item.data(ImportItem.OptionsRole)
        if not isinstance(opts, Options):
            return

        task = state = TaskState()
        state.future = ...
        state.watcher = qconcurrent.FutureWatcher()
        state.progressChanged.connect(
            self.__set_read_progress, Qt.DirectConnection)

        def progress_(i, j):
            task.emitProgressChangedOrCancel(i, j)

        task.future = self.__executor.submit(
            clear_stack_on_cancel(load_csv),
            path, opts, progress_,
        )
        task.watcher.setFuture(task.future)
        w = task.watcher
        w.done.connect(self.__handle_result)
        w.progress = state
        self.__watcher = w
        self.__set_running_state()

    @Slot('qint64', 'qint64')
    def __set_read_progress(self, read, count):
        if count > 0:
            self.progressBarSet(100 * read / count)

    def __cancel_task(self):
        # Cancel and dispose of the current task
        assert self.__watcher is not None
        w = self.__watcher
        w.future().cancel()
        w.progress.cancel = True
        w.done.disconnect(self.__handle_result)
        w.progress.progressChanged.disconnect(self.__set_read_progress)
        self.__watcher = None

    def cancel(self):
        """
        Cancel current pending or executing task.
        """
        if self.__watcher is not None:
            self.__cancel_task()
            self.__clear_running_state()
            self.setStatusMessage("Cancelled")
            self.summary_text.setText(
                "<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>"
            )

    def __set_running_state(self):
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Running")
        self.cancel_button.setEnabled(True)
        self.load_button.setText("Restart")
        path = self.current_item().path()
        self.Error.clear()
        self.summary_text.setText(
            "<div>Loading: <i>{}</i><br/>".format(prettyfypath(path))
        )

    def __clear_running_state(self, ):
        self.progressBarFinished()
        self.setStatusMessage("")
        self.setBlocking(False)
        self.cancel_button.setEnabled(False)
        self.load_button.setText("Reload")

    def __set_error_state(self, err):
        self.Error.clear()
        if isinstance(err, UnicodeDecodeError):
            self.Error.encoding_error(exc_info=err)
        else:
            self.Error.error(exc_info=err)

        path = self.current_item().path()
        basename = os.path.basename(path)
        if isinstance(err, UnicodeDecodeError):
            text = (
                "<div><i>{basename}</i> was not loaded due to a text encoding "
                "error. The file might be saved in an unknown or invalid "
                "encoding, or it might be a binary file.</div>"
            ).format(
                basename=escape(basename)
            )
        else:
            text = (
                "<div><i>{basename}</i> was not loaded due to an error:"
                "<p style='white-space: pre;'>{err}</p>"
            ).format(
                basename=escape(basename),
                err="".join(traceback.format_exception_only(type(err), err))
            )
        self.summary_text.setText(text)

    def __clear_error_state(self):
        self.Error.error.clear()
        self.summary_text.setText("")

    def onDeleteWidget(self):
        """Reimplemented."""
        if self.__watcher is not None:
            self.__cancel_task()
            self.__executor.shutdown()
        super().onDeleteWidget()

    @Slot(object)
    def __handle_result(self, f):
        # type: (qconcurrent.Future[pd.DataFrame]) -> None
        assert f.done()
        assert f is self.__watcher.future()
        self.__watcher = None
        self.__clear_running_state()

        try:
            df = f.result()
            assert isinstance(df, pd.DataFrame)
        except pandas.errors.EmptyDataError:
            df = pd.DataFrame({})
        except Exception as e:  # pylint: disable=broad-except
            self.__set_error_state(e)
            df = None
        else:
            self.__clear_error_state()

        if df is not None:
            table = pandas_to_table(df)
            filename = self.current_item().path()
            table.name = os.path.splitext(os.path.split(filename)[-1])[0]
        else:
            table = None
        self.send("Data Frame", df)
        self.send('Data', table)
        self._update_status_messages(table)

    def _update_status_messages(self, data):
        summary = len(data) if data else self.info.NoOutput
        details = format_summary_details(data) if data else ""
        self.info.set_output_summary(summary, details)
        if data is None:
            return

        def pluralize(seq):
            return "s" if len(seq) != 1 else ""

        summary = ("{n_instances} row{plural_1}, "
                   "{n_features} feature{plural_2}, "
                   "{n_meta} meta{plural_3}").format(
                       n_instances=len(data), plural_1=pluralize(data),
                       n_features=len(data.domain.attributes),
                       plural_2=pluralize(data.domain.attributes),
                       n_meta=len(data.domain.metas),
                       plural_3=pluralize(data.domain.metas))
        self.summary_text.setText(summary)

    def itemsFromSettings(self):
        # type: () -> List[Tuple[str, Options]]
        """
        Return items from local history.
        """
        s = self._local_settings()
        items_ = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        items = []  # type: List[Tuple[str, Options]]
        for item in items_:
            path = item.get("path", "")
            if not path:
                continue
            opts_json = item.get("options", "")
            try:
                opts = Options.from_dict(json.loads(opts_json))
            except (csv.Error, LookupError, TypeError, json.JSONDecodeError):
                _log.error("Could not reconstruct options for '%s'", path,
                           exc_info=True)
            else:
                items.append((path, opts))
        return items[::-1]

    def _restoreState(self):
        # Restore the state. Merge session (workflow) items with the
        # local history.
        model = self.import_items_model
        # local history
        items = self.itemsFromSettings()
        # stored session items
        sitems = []
        for p, m in self._session_items:
            try:
                item_ = (p, Options.from_dict(m))
            except (csv.Error, LookupError):
                # Is it better to fail then to lose a item slot?
                _log.error("Failed to restore '%s'", p, exc_info=True)
            else:
                sitems.append(item_)

        items = sitems + items
        items = unique(items, key=lambda t: pathnormalize(t[0]))

        curr = self.recent_combo.currentIndex()
        if curr != -1:
            currentpath = self.recent_combo.currentData(ImportItem.PathRole)
        else:
            currentpath = None
        for path, options in items:
            item = ImportItem.fromPath(path)
            item.setOptions(options)
            model.appendRow(item)

        if currentpath is not None:
            idx = self.recent_combo.findData(currentpath, ImportItem.PathRole)
            if idx != -1:
                self.recent_combo.setCurrentIndex(idx)


@singledispatch
def sniff_csv(file, samplesize=2 ** 20):
    sniffer = csv.Sniffer()
    sample = file.read(samplesize)
    dialect = sniffer.sniff(sample)
    dialect = textimport.Dialect(
        dialect.delimiter, dialect.quotechar,
        dialect.escapechar, dialect.doublequote,
        dialect.skipinitialspace, dialect.quoting
    )
    has_header = sniffer.has_header(sample)
    return dialect, has_header


@sniff_csv.register(str)
@sniff_csv.register(bytes)
def sniff_csv_with_path(path, encoding="utf-8", samplesize=2 ** 20):
    with _open(path, "rt", encoding=encoding) as f:
        return sniff_csv(f, samplesize)


def _open(path, mode, encoding=None):
    # type: (str, str, Optional[str]) -> typing.IO[Any]
    """
    Open a local file `path` for reading. The file may be gzip, bz2 or zip
    compressed.

    If a zip archive then a single archive member is expected.

    Parameters
    ----------
    path : str
        File system path
    mode : str
        'r', 'rb' or 'rt'
    encoding : Optional[str]
        Optional text encoding, for opening in text mode.

    Returns
    -------
    stream: io.BaseIO
        A stream opened for reading.
    """
    if mode not in {'r', 'rb', 'rt'}:
        raise ValueError('r')
    _, ext = os.path.splitext(path)
    ext = ext.lower()
    if ext == ".gz":
        return gzip.open(path, mode, encoding=encoding)
    elif ext == ".bz2":
        return bz2.open(path, mode, encoding=encoding)
    elif ext == ".xz":
        return lzma.open(path, mode, encoding=encoding)
    elif ext == ".zip":
        arh = zipfile.ZipFile(path, 'r')
        filelist = arh.infolist()
        if len(filelist) == 1:
            filename = filelist[0]
            zinfo = arh.getinfo(filename)
            f = arh.open(zinfo.filename, 'r')
            if 't' in mode:
                f = io.TextIOWrapper(f, encoding=encoding)
            return f
        else:
            raise ValueError("Expected a single file in the archive.")
    else:
        return open(path, mode, encoding=encoding)


compression_types = [
    "application/gzip", "application/zip",
    "application/x-xz", "application/x-bzip",
    # application/x-lz4
]


def _mime_type_for_path(path):
    # type: (str) -> QMimeType
    """
    Return the mime type of the file on a local filesystem.

    In case the path is a compressed file return the mime type of its contents

    Parameters
    ----------
    path : str
        Local filesystem path

    Returns
    -------
    mimetype: QMimeType
    """
    db = QMimeDatabase()
    mtype = db.mimeTypeForFile(path, QMimeDatabase.MatchDefault)
    if any(mtype.inherits(t) for t in compression_types):
        # peek contents
        try:
            with _open(path, "rb") as f:
                sample = f.read(4096)
        except Exception:  # pylint: disable=broad-except
            sample = b''
        mtype = db.mimeTypeForData(sample)
    return mtype


NA_DEFAULT = ["", "?", ".", "~", "nan", "NAN", "NaN", "N/A", "n/a", "NA"]

NA_VALUES = {
    ColumnType.Numeric: NA_DEFAULT,
    ColumnType.Categorical: NA_DEFAULT,
    ColumnType.Time: NA_DEFAULT + ["NaT", "NAT"],
    ColumnType.Text: [],
    ColumnType.Auto: NA_DEFAULT,
}


def load_csv(path, opts, progress_callback=None):
    # type: (Union[AnyStr, BinaryIO], Options, ...) -> pd.DataFrame
    def dtype(coltype):
        # type: (ColumnType) -> Optional[str]
        if coltype == ColumnType.Numeric:
            return "float"
        elif coltype == ColumnType.Categorical:
            return "category"
        elif coltype == ColumnType.Time:
            return "object"
        elif coltype == ColumnType.Text:
            return "object"
        elif coltype == ColumnType.Skip:
            return None
        elif coltype == ColumnType.Auto:
            return None
        else:
            raise TypeError

    def expand(ranges):
        # type: (Iterable[Tuple[range, T]]) -> Iterable[Tuple[int, T]]
        return ((i, x) for r, x in ranges for i in r)

    dtypes = {i: dtype(c) for i, c in expand(opts.columntypes)}
    dtypes = {i: dtp for i, dtp in dtypes.items()
              if dtp is not None and dtp != ColumnType.Auto}

    columns_ignored = {i for i, c in expand(opts.columntypes)
                       if c == ColumnType.Skip}
    dtcols = {i for i, c in expand(opts.columntypes)
              if c == ColumnType.Time}
    parse_dates = sorted(dtcols)
    na_values = {i: NA_VALUES.get(c, NA_DEFAULT)
                 for i, c in expand(opts.columntypes)}
    if not parse_dates:
        parse_dates = False

    # fixup header indices to account for skipped rows (header row indices
    # pick rows after skiprows)

    hspec = sorted(opts.rowspec, key=lambda t: t[0].start)
    header_ranges = []
    nskiped = 0
    for range_, state in hspec:
        if state == RowSpec.Skipped:
            nskiped += len(range_)
        elif state == RowSpec.Header:
            header_ranges.append(
                range(range_.start - nskiped, range_.stop - nskiped)
            )
    headers = [i for r in header_ranges for i in r]
    skiprows = [row for r, st in hspec if st == RowSpec.Skipped for row in r]

    if not headers:
        header = None
        prefix = "X."

    elif len(headers) == 1:
        header = headers[0]
        prefix = None
    else:
        header = headers
        prefix = None

    if not skiprows:
        skiprows = None

    numbers_format_kwds = {}

    if opts.decimal_separator != ".":
        numbers_format_kwds["decimal"] = opts.decimal_separator

    if opts.group_separator != "":
        numbers_format_kwds["thousands"] = opts.group_separator

    if numbers_format_kwds:
        # float_precision = "round_trip" cannot handle non c-locale decimal and
        # thousands sep (https://github.com/pandas-dev/pandas/issues/35365).
        # Fallback to 'high'.
        numbers_format_kwds["float_precision"] = "high"
    else:
        numbers_format_kwds["float_precision"] = "round_trip"

    with ExitStack() as stack:
        if isinstance(path, (str, bytes)):
            f = stack.enter_context(_open(path, 'rb'))
        elif isinstance(path, (io.RawIOBase, io.BufferedIOBase)) or \
                hasattr(path, "read"):
            f = path
        else:
            raise TypeError()
        file = TextReadWrapper(
            f, encoding=opts.encoding,
            progress_callback=progress_callback)
        stack.callback(file.detach)
        df = pd.read_csv(
            file, sep=opts.dialect.delimiter, dialect=opts.dialect,
            skipinitialspace=opts.dialect.skipinitialspace,
            header=header, skiprows=skiprows,
            dtype=dtypes, parse_dates=parse_dates, prefix=prefix,
            na_values=na_values, keep_default_na=False,
            **numbers_format_kwds
        )
        df = guess_types(df, dtypes, columns_ignored)

        if columns_ignored:
            # TODO: use 'usecols' parameter in `read_csv` call to
            # avoid loading/parsing the columns
            df.drop(
                columns=[df.columns[i] for i in columns_ignored
                         if i < len(df.columns)],
                inplace=True
            )
        return df


def guess_types(
        df: pd.DataFrame, dtypes: Dict[int, str], columns_ignored: List[int]
) -> pd.DataFrame:
    """
    Guess data type for variables according to values.

    Parameters
    ----------
    df
        Data frame
    dtypes
        The dictionary with data types set by user. We will guess values only
        for columns that does not have data type defined.
    columns_ignored
        List with indices of ignored columns. Ignored columns are skipped.

    Returns
    -------
    A data frame with changed dtypes according to the strategy.
    """
    for i, col in enumerate(df):
        # only when automatic is set in widget dialog
        if dtypes.get(i, None) is None and i not in columns_ignored:
            df[col] = guess_data_type(df[col])
    return df


def guess_data_type(col: pd.Series) -> pd.Series:
    """
    Guess column types. Logic is same than in guess_data_type from io_utils
    module. This function only change the dtype of the column such that later
    correct Orange.data.variable is used.
    Logic:
    - if can converted to date-time (ISO) -> TimeVariable
    - if numeric (only numbers)
        - only values {0, 1} or {1, 2} -> DiscreteVariable
        - else -> ContinuousVariable
    - if not numbers:
        - num_unique_values < len(data) ** 0.7 and < 100 -> DiscreteVariable
        - else -> StringVariable

    Parameters
    ----------
    col
        Data column

    Returns
    -------
    Data column with correct dtype
    """
    def parse_dates(s):
        """
        This is an extremely fast approach to datetime parsing.
        For large data, the same dates are often repeated. Rather than
        re-parse these, we store all unique dates, parse them, and
        use a lookup to convert all dates.
        """
        try:
            dates = {date: pd.to_datetime(date) for date in s.unique()}
        except ValueError:
            return None
        return s.map(dates)

    if pdtypes.is_numeric_dtype(col):
        unique_values = col.unique()
        if len(unique_values) <= 2 and (
                len(np.setdiff1d(unique_values, [0, 1])) == 0
                or len(np.setdiff1d(unique_values, [1, 2])) == 0):
            return col.astype("category")
    else:  # object
        # try parse as date - if None not a date
        parsed_col = parse_dates(col)
        if parsed_col is not None:
            return parsed_col
        unique_values = col.unique()
        if len(unique_values) < 100 and len(unique_values) < len(col)**0.7:
            return col.astype("category")
    return col


def clear_stack_on_cancel(f):
    """
    A decorator that catches the TaskState.UserCancelException exception
    and clears the exception's traceback to remove local references.

    Parameters
    ----------
    f : callable

    Returns
    -------
    wrapped : callable
    """
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except TaskState.UserCancelException as e:
            # TODO: Is this enough to allow immediate gc of the stack?
            # How does it chain across cython code?
            # Maybe just return None.
            e = e.with_traceback(None)
            e.__context__ = None
            e.__cause__ = None
            raise e
        except BaseException as e:
            traceback.clear_frames(e.__traceback__)
            raise

    return wrapper


class TaskState(QObject, PyOwned):
    class UserCancelException(BaseException):
        """User interrupt exception."""

    #: Signal emitted with the current read progress. First value is the current
    #: progress state, second value is the total progress to complete
    #: (-1 if unknown)
    progressChanged = Signal('qint64', 'qint64')
    __progressChanged = Signal('qint64', 'qint64')
    #: Was cancel requested.
    cancel = False

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # route the signal via this object's queue
        self.__progressChanged.connect(
            self.progressChanged, Qt.QueuedConnection)

    def emitProgressChangedOrCancel(self, current, total):
        # type: (int, int) -> None
        """
        Emit the progressChanged signal with `current` and `total`.
        """
        if self.cancel:
            raise TaskState.UserCancelException()
        else:
            self.__progressChanged.emit(current, total)


class TextReadWrapper(io.TextIOWrapper):
    """
    TextIOWrapper reporting the read progress.

    Assuming a single forward read pass.
    """

    #: A `Callable[[int, int], []]` called when the file position is
    #: advanced by read; called with current byte position and total
    #: file size.
    progress_callback = ...  # type: Callable[[int, int], None]

    def __init__(self, buffer, *args, progress_callback=None, **kwargs):
        super().__init__(buffer, *args, **kwargs)
        if progress_callback is None:
            def progress_callback(i, j):  # pylint: disable=unused-argument
                pass
        self.progress_callback = progress_callback
        try:
            self.__size = os.fstat(buffer.fileno()).st_size
        except OSError:
            self.__size = -1

    def read(self, size=-1):
        s = super().read(size)
        # try to go around any gzip/bz2/lzma wrappers to the base
        # raw file (does not work for zipfile.ZipExtFile; should
        # dispatch on buffer type)
        try:
            fd = self.buffer.fileno()
        except (AttributeError, io.UnsupportedOperation):
            pos = -1
        else:
            try:
                pos = os.lseek(fd, 0, os.SEEK_CUR)
            except OSError:
                pos = -1

        self.progress_callback(pos, self.__size)
        return s


def index_where(iterable, pred):
    # type: (Iterable[T], Callable[[T], bool]) -> Optional[int]
    """
    Return the (first) index of el in `iterable` where `pred(el)` returns True.

    If no element matches return `None`.
    """
    for i, el in enumerate(iterable):
        if pred(el):
            return i
    return None


def unique(iterable, key=None):
    # type: (Iterable[T], Optional[Callable[[T], Hashable]]) -> Iterable[T]
    """
    Return an iterator over unique elements of `iterable`.

    If `key` is supplied it is used as a substitute for determining
    'uniqueness' of elements.

    Parameters
    ----------
    iterable : Iterable[T]
    key : Callable[[T], Hashable]

    Returns
    -------
    unique : Iterable[T]
    """
    seen = set()
    if key is None:
        key = lambda t: t
    for el in iterable:
        el_k = key(el)
        if el_k not in seen:
            seen.add(el_k)
            yield el


def samepath(p1, p2):
    # type: (str, str) -> bool
    """
    Return True if the paths `p1` and `p2` match after case and path
    normalization.
    """
    return pathnormalize(p1) == pathnormalize(p2)


def pathnormalize(p):
    """
    Normalize a path (apply both path and case normalization.
    """
    return os.path.normcase(os.path.normpath(p))


def prettyfypath(path):
    """
    Return the path with the $HOME prefix shortened to '~/' if applicable.

    Example
    -------
    >>> prettyfypath("/home/user/file.dat")
    '~/file.dat'
    """
    home = os.path.expanduser("~/")
    home_n = pathnormalize(home)
    path_n = pathnormalize(path)
    if path_n.startswith(home_n):
        path = os.path.join("~", os.path.relpath(path, home))
    return path


def pandas_to_table(df):
    # type: (pd.DataFrame) -> Orange.data.Table
    """
    Convert a pandas.DataFrame to a Orange.data.Table instance.
    """
    index = df.index
    if not isinstance(index, pd.RangeIndex):
        df = df.reset_index()

    columns = []  # type: List[Tuple[Orange.data.Variable, np.ndarray]]

    for header, series in df.items():  # type: (Any, pd.Series)
        if pdtypes.is_categorical(series):
            coldata = series.values  # type: pd.Categorical
            categories = [str(c) for c in coldata.categories]
            var = Orange.data.DiscreteVariable.make(
                str(header), values=categories
            )
            # Remap the coldata into the var.values order/set
            coldata = pd.Categorical(
                coldata.astype("str"), categories=var.values
            )
            codes = coldata.codes
            assert np.issubdtype(codes.dtype, np.integer)
            orangecol = np.array(codes, dtype=np.float)
            orangecol[codes < 0] = np.nan
        elif pdtypes.is_datetime64_any_dtype(series):
            # Check that this converts tz local to UTC
            series = series.astype(np.dtype("M8[ns]"))
            coldata = series.values  # type: np.ndarray
            assert coldata.dtype == "M8[ns]"
            mask = np.isnat(coldata)
            orangecol = coldata.astype(np.int64) / 10 ** 9
            orangecol[mask] = np.nan
            var = Orange.data.TimeVariable.make(str(header))
            var.have_date = var.have_time = 1
        elif pdtypes.is_object_dtype(series):
            coldata = series.fillna('').values
            assert isinstance(coldata, np.ndarray)
            orangecol = coldata
            var = Orange.data.StringVariable.make(str(header))
        elif pdtypes.is_integer_dtype(series):
            coldata = series.values
            var = Orange.data.ContinuousVariable.make(str(header))
            var.number_of_decimals = 0
            orangecol = coldata.astype(np.float64)
        elif pdtypes.is_numeric_dtype(series):
            orangecol = series.values.astype(np.float64)
            var = Orange.data.ContinuousVariable.make(str(header))
        else:
            warnings.warn(
                "Column '{}' with dtype: {} skipped."
                .format(header, series.dtype),
                UserWarning
            )
            continue
        columns.append((var, orangecol))

    cols_x = [(var, col) for var, col in columns if var.is_primitive()]
    cols_m = [(var, col) for var, col in columns if not var.is_primitive()]

    variables = [v for v, _ in cols_x]
    if cols_x:
        X = np.column_stack([a for _, a in cols_x])
    else:
        X = np.empty((df.shape[0], 0), dtype=np.float)
    metas = [v for v, _ in cols_m]
    if cols_m:
        M = np.column_stack([a for _, a in cols_m])
    else:
        M = None

    domain = Orange.data.Domain(variables, metas=metas)
    return Orange.data.Table.from_numpy(domain, X, None, M)


def main(argv=None):  # pragma: no cover
    app = QApplication(argv or [])
    w = OWCSVFileImport()
    w.show()
    w.raise_()
    app.exec_()
    w.saveSettings()
    w.onDeleteWidget()
    return 0


if __name__ == "__main__":  # pragma: no cover
    sys.exit(main(sys.argv))

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