예제 #1
0
    def startDrag(self, index):
        """start a drag operation with a PandasCellPayload on defined index.
        
        Args:
            index (QModelIndex): model index you want to start the drag operation.
        """

        if not index.isValid():
            return

        dataFrame = self.model().dataFrame()

        # get all infos from dataFrame
        dfindex = dataFrame.iloc[[index.row()]].index
        columnName = dataFrame.columns[index.column()]
        dtype = dataFrame[columnName].dtype
        value = dataFrame[columnName][dfindex]

        # create the mime data
        mimePayload = PandasCellPayload(dfindex, columnName, value, dtype,
                                        hex(id(self.model())))
        mimeData = MimeData()
        mimeData.setData(mimePayload)

        # create the drag icon and start drag operation
        drag = QtGui.QDrag(self)
        drag.setMimeData(mimeData)
        pixmap = QtGui.QPixmap(":/icons/insert-table.png")
        drag.setHotSpot(QtCore.QPoint(pixmap.width() / 3, pixmap.height() / 3))
        drag.setPixmap(pixmap)
예제 #2
0
    def removeDataFrameColumns(self, columns):
        """
        Removes columns from the dataframe.
        :param columns: [(int, str)]
        :return: (bool)
            True on success, False on failure.
        """
        if not self.editable:
            return False

        if columns:
            deleted = 0
            errored = False
            for (position, name) in columns:
                position = position - deleted
                if position < 0:
                    position = 0
                self.beginRemoveColumns(QtCore.QModelIndex(), position,
                                        position)
                try:
                    self._dataFrame.drop(name, axis=1, inplace=True)
                except ValueError as e:
                    errored = True
                    continue
                self.endRemoveColumns()
                deleted += 1
            self.dataChanged.emit()

            if errored:
                return False
            else:
                return True
        return False
예제 #3
0
    def removeDataFrameRows(self, rows):
        """
        Removes rows from the dataframe.

        :param rows: (list)
            of row indexes to removes.
        :return: (bool)
            True on success, False on failure.
        """
        if not self.editable:
            return False

        if rows:
            position = min(rows)
            count = len(rows)
            self.beginRemoveRows(QtCore.QModelIndex(), position,
                                 position + count - 1)

            removedAny = False
            for idx, line in self._dataFrame.iterrows():
                if idx in rows:
                    removedAny = True
                    self._dataFrame.drop(idx, inplace=True)

            if not removedAny:
                return False

            self._dataFrame.reset_index(inplace=True, drop=True)

            self.endRemoveRows()
            return True
        return False
예제 #4
0
    def test_setDelegates(self, qtbot, tableView, index, value, singleStep):
        dlg = createDelegate(numpy.dtype(value), 0, tableView)
        assert dlg is not None

        data = pandas.DataFrame([value], columns=['A'])
        data['A'] = data['A'].astype(value.dtype)
        model = tableView.model()
        model.setDataFrame(data)
        for i, delegate in enumerate([dlg]):
            assert tableView.itemDelegateForColumn(i) == delegate

            option = QtGui.QStyleOptionViewItem()
            option.rect = QtCore.QRect(0, 0, 100, 100)
            editor = delegate.createEditor(tableView, option, index)
            delegate.setEditorData(editor, index)
            assert editor.value() == index.data()
            delegate.setModelData(editor, model, index)

            delegate.updateEditorGeometry(editor, option, index)

            dtype = value.dtype
            if dtype in DataFrameModel._intDtypes:
                info = numpy.iinfo(dtype)
                assert isinstance(delegate, BigIntSpinboxDelegate)
            elif dtype in DataFrameModel._floatDtypes:
                info = numpy.finfo(dtype)
                assert isinstance(delegate, CustomDoubleSpinboxDelegate)
                assert delegate.decimals == DataFrameModel._float_precisions[
                    str(value.dtype)]
            assert delegate.maximum == info.max
            assert editor.maximum() == info.max
            assert delegate.minimum == info.min
            assert editor.minimum() == info.min
            assert delegate.singleStep == singleStep
            assert editor.singleStep() == singleStep

        def clickEvent(index):
            assert index.isValid()

        tableView.clicked.connect(clickEvent)
        with qtbot.waitSignal(tableView.clicked) as blocker:
            qtbot.mouseClick(tableView.viewport(),
                             QtCore.Qt.LeftButton,
                             pos=QtCore.QPoint(10, 10))
        assert blocker.signal_triggered
예제 #5
0
    def columnCount(self, index=QtCore.QModelIndex()):
        """returns number of columns

        Args:
            index (QtCore.QModelIndex, optional): Index to define column and row. defaults to empty QModelIndex

        Returns:
            number of columns
        """
        return len(self.headers)
예제 #6
0
    def rowCount(self, index=QtCore.QModelIndex()):
        """returns number of rows

        Args:
            index (QtCore.QModelIndex, optional): Index to define column and row. defaults to empty QModelIndex

        Returns:
            number of rows
        """
        return len(self._dataFrame.columns)
예제 #7
0
    def __init__(self, parent=None):
        """Constructs the object with the given parent.

        Args:
            parent (QObject, optional): Causes the objected to be owned
                by `parent` instead of Qt. Defaults to `None`.

        """
        super(DelimiterValidator, self).__init__(parent)
        re = QtCore.QRegExp('\S{1}')
        self.setRegExp(re)
예제 #8
0
    def __init__(self, parent=None, iconSize=QtCore.QSize(36, 36)):
        """Constructs the object with the given parent.

        Args:
            parent (QObject, optional): Causes the objected to be owned
                by `parent` instead of Qt. Defaults to `None`.
            iconSize (QSize, optional): Size of edit buttons. Defaults to QSize(36, 36).

        """
        super(DataTableWidget, self).__init__(parent)
        self._iconSize = iconSize
        self.initUi()
예제 #9
0
    def columnCount(self, index=QtCore.QModelIndex()):
        """returns number of columns

        Args:
            index (QtCore.QModelIndex, optional): Index to define column and row. defaults to empty QModelIndex

        Returns:
            number of columns
        """
        # speed comparison:
        # In [23]: %timeit len(df.columns)
        # 10000000 loops, best of 3: 108 ns per loop

        # In [24]: %timeit df.shape[1]
        # 1000000 loops, best of 3: 440 ns per loop
        return len(self._dataFrame.columns)
예제 #10
0
    def addDataFrameRows(self, count=1):
        """

        Adds rows to the dataframe.

        :param count: (int)
            The number of rows to add to the dataframe.
        :return: (bool)
            True on success, False on failure.

        """
        # don't allow any gaps in the data rows.
        # and always append at the end

        if not self.editable:
            return False

        position = self.rowCount()

        if count < 1:
            return False

        if len(self.dataFrame().columns) == 0:
            # log an error message or warning
            return False

        # Note: This function emits the rowsAboutToBeInserted() signal which
        # connected views (or proxies) must handle before the data is
        # inserted. Otherwise, the views may end up in an invalid state.
        self.beginInsertRows(QtCore.QModelIndex(), position,
                             position + count - 1)

        defaultValues = []
        for dtype in self._dataFrame.dtypes:
            if dtype.type == numpy.dtype('<M8[ns]'):
                val = pandas.Timestamp('')
            elif dtype.type == numpy.dtype(object):
                val = ''
            else:
                val = dtype.type()
            defaultValues.append(val)

        for i in range(count):
            self._dataFrame.loc[position + i] = defaultValues
        self._dataFrame.reset_index()
        self.endInsertRows()
        return True
예제 #11
0
    def rowCount(self, index=QtCore.QModelIndex()):
        """returns number of rows

        Args:
            index (QtCore.QModelIndex, optional): Index to define column and row. defaults to empty QModelIndex

        Returns:
            number of rows
        """
        # len(df.index) is faster, so use it:
        # In [12]: %timeit df.shape[0]
        # 1000000 loops, best of 3: 437 ns per loop
        # In [13]: %timeit len(df.index)
        # 10000000 loops, best of 3: 110 ns per loop
        # %timeit df.__len__()
        # 1000000 loops, best of 3: 215 ns per loop
        return len(self._dataFrame.index)
예제 #12
0
def createThread(parent, worker, deleteWorkerLater=False):
    """Create a new thread for given worker.

    Args:
        parent (QObject): parent of thread and worker.
        worker (ProgressWorker): worker to use in thread.
        deleteWorkerLater (bool, optional): delete the worker if thread finishes.

    Returns:
        QThread

    """
    thread = QtCore.QThread(parent)
    thread.started.connect(worker.doWork)
    worker.finished.connect(thread.quit)
    if deleteWorkerLater:
        thread.finished.connect(worker.deleteLater)

    worker.moveToThread(thread)
    worker.setParent(parent)
    return thread
예제 #13
0
    def addDataFrameColumn(self, columnName, dtype=str, defaultValue=None):
        """
        Adds a column to the dataframe as long as
        the model's editable property is set to True and the
        dtype is supported.

        :param columnName: str
            name of the column.
        :param dtype: qtpandas.models.SupportedDtypes option
        :param defaultValue: (object)
            to default the column's value to, should be the same as the dtype or None
        :return: (bool)
            True on success, False otherwise.
        """
        if not self.editable or dtype not in SupportedDtypes.allTypes():
            return False

        elements = self.rowCount()
        columnPosition = self.columnCount()

        newColumn = pandas.Series([defaultValue] * elements,
                                  index=self._dataFrame.index,
                                  dtype=dtype)

        self.beginInsertColumns(QtCore.QModelIndex(), columnPosition - 1,
                                columnPosition - 1)
        try:
            self._dataFrame.insert(columnPosition,
                                   columnName,
                                   newColumn,
                                   allow_duplicates=False)
        except ValueError as e:
            # columnName does already exist
            return False

        self.endInsertColumns()

        self.propagateDtypeChanges(columnPosition, newColumn.dtype)

        return True
예제 #14
0
    def __init__(self, parent=None):
        """the __init__ method.

        Args:
            parent (QObject): defaults to None. If parent is 0, the new widget becomes a window.
                If parent is another widget, this widget becomes a child window inside parent.
                The new widget is deleted when its parent is deleted.

        """
        super(BigIntSpinbox, self).__init__(parent)

        self._singleStep = 1
        self._minimum = -18446744073709551616
        self._maximum = 18446744073709551615

        rx = QtCore.QRegExp("[0-9]\\d{0,20}")
        validator = QtGui.QRegExpValidator(rx, self)

        self._lineEdit = QtGui.QLineEdit(self)
        self._lineEdit.setText('0')
        self._lineEdit.setValidator(validator)
        self.setLineEdit(self._lineEdit)
예제 #15
0
    def test_data(self, dataframe):
        model = ColumnDtypeModel(dataFrame=dataframe)
        index = model.index(0, 0)

        # get data for display role
        ret = index.data()
        assert ret == 'Foo'

        # edit role does the same as display role
        ret = index.data(Qt.EditRole)
        assert ret == 'Foo'

        # datatype only defined for column 1
        ret = index.data(DTYPE_ROLE)
        assert ret == None

        # datatype column
        index = index.sibling(0, 1)
        ret = index.data(DTYPE_ROLE)
        assert ret == numpy.dtype(numpy.int64)
        # check translation / display text
        assert index.data(
        ) == 'integer (64 bit)' == SupportedDtypes.description(ret)

        # column not defined
        index = index.sibling(0, 2)
        assert index.data(DTYPE_ROLE) == None

        # invalid index
        index = QtCore.QModelIndex()
        assert model.data(index) == None

        index = model.index(2, 0)

        # get data for display role
        ret = index.data()
        assert ret == 'Spam'
예제 #16
0
 def test_invalidIndex(self, model):
     assert model.setData(QtCore.QModelIndex(), None) == False
예제 #17
0
 def test_invalidIndex(self, model):
     assert model.data(QtCore.QModelIndex()) is None
예제 #18
0
class DataFrameModelManager(QtCore.QObject):
    """
    A central storage unit for managing
    DataFrameModels.
    """
    signalNewModelRead = QtCore.Signal(str)
    signalModelDestroyed = QtCore.Signal(str)

    def __init__(self):
        QtCore.QObject.__init__(self)
        self._models = {}
        self._updates = defaultdict(list)
        self._paths_read = []
        self._paths_updated = []

    @property
    def file_paths(self):
        """Returns a list of the currently stored file paths"""
        return list(self._models.keys())

    @property
    def models(self):
        """Returns a list of all currently stored DataFrameModels"""
        return list(self._models.values())

    @property
    def last_path_read(self):
        """Returns the last path read (via the DataFrameModelManager.read_file method)"""
        if self._paths_read:
            return self._paths_read[-1]
        else:
            return None

    @property
    def last_path_updated(self):
        """Returns the last path to register an update. (or None)"""
        if self._paths_updated:
            return self._paths_updated[-1]
        else:
            return None

    def save_file(self, filepath, save_as=None, keep_orig=False, **kwargs):
        """
        Saves a DataFrameModel to a file.

        :param filepath: (str)
            The filepath of the DataFrameModel to save.
        :param save_as: (str, default None)
            The new filepath to save as.
        :param keep_orig: (bool, default False)
            True keeps the original filepath/DataFrameModel if save_as is specified.
        :param kwargs:
            pandas.DataFrame.to_excel(**kwargs) if .xlsx
            pandas.DataFrame.to_csv(**kwargs) otherwise.
        :return: None
        """
        df = self._models[filepath].dataFrame()
        kwargs['index'] = kwargs.get('index', False)

        if save_as is not None:
            to_path = save_as
        else:
            to_path = filepath

        ext = os.path.splitext(to_path)[1].lower()

        if ext == ".xlsx":
            kwargs.pop('sep', None)
            df.to_excel(to_path, **kwargs)

        elif ext in ['.csv', '.txt']:
            df.to_csv(to_path, **kwargs)

        else:
            raise NotImplementedError(
                "Cannot save file of type {}".format(ext))

        if save_as is not None:
            if keep_orig is False:
                # Re-purpose the original model
                # Todo - capture the DataFrameModelManager._updates too
                model = self._models.pop(filepath)
                model._filePath = to_path
            else:
                # Create a new model.
                model = DataFrameModel()
                model.setDataFrame(df, copyDataFrame=True, filePath=to_path)

            self._models[to_path] = model

    def set_model(self, df_model, file_path):
        """
        Sets a DataFrameModel and registers it to the given file_path.
        :param df_model: (DataFrameModel)
            The DataFrameModel to register.
        :param file_path:
            The file path to associate with the DataFrameModel.
            *Overrides the current filePath on the DataFrameModel (if any)
        :return: None
        """
        assert isinstance(
            df_model,
            DataFrameModel), "df_model argument must be a DataFrameModel!"
        df_model._filePath = file_path

        try:
            self._models[file_path]
        except KeyError:
            self.signalNewModelRead.emit(file_path)

        self._models[file_path] = df_model

    def get_model(self, filepath):
        """
        Returns the DataFrameModel registered to filepath
        """
        return self._models[filepath]

    def get_frame(self, filepath):
        """Returns the DataFrameModel.dataFrame() registered to filepath """
        return self._models[filepath].dataFrame()

    def update_file(self, filepath, df, notes=None):
        """
        Sets a new DataFrame for the DataFrameModel registered to filepath.
        :param filepath (str)
            The filepath to the DataFrameModel to be updated
        :param df (pandas.DataFrame)
            The new DataFrame to register to the model.

        :param notes (str, default None)
            Optional notes to register along with the update.

        """
        assert isinstance(
            df,
            pd.DataFrame), "Cannot update file with type '{}'".format(type(df))

        self._models[filepath].setDataFrame(df, copyDataFrame=False)

        if notes:
            update = dict(date=pd.Timestamp(datetime.datetime.now()),
                          notes=notes)

            self._updates[filepath].append(update)
        self._paths_updated.append(filepath)

    def remove_file(self, filepath):
        """
        Removes the DataFrameModel from being registered.
        :param filepath: (str)
            The filepath to delete from the DataFrameModelManager.
        :return: None
        """
        self._models.pop(filepath)
        self._updates.pop(filepath, default=None)
        self.signalModelDestroyed.emit(filepath)

    def read_file(self, filepath, **kwargs):
        """
        Reads a filepath into a DataFrameModel and registers
        it.
        Example use:
            dfmm = DataFrameModelManger()
            dfmm.read_file(path_to_file)
            dfm = dfmm.get_model(path_to_file)
            df = dfm.get_frame(path_to_file)

        :param filepath: (str)
            The filepath to read
        :param kwargs:
            .xlsx files: pandas.read_excel(**kwargs)
            .csv files: pandas.read_csv(**kwargs)
        :return: DataFrameModel
        """
        try:
            model = self._models[filepath]
        except KeyError:
            model = read_file(filepath, **kwargs)
            self._models[filepath] = model
            self.signalNewModelRead.emit(filepath)
        finally:
            self._paths_read.append(filepath)

        return self._models[filepath]
예제 #19
0
class DataFrameExportDialog(CSVExportDialog):
    """
    Extends the CSVExportDialog with support for
    exporting to .txt and .xlsx
    """
    signalExportFilenames = QtCore.Signal(str, str)
    signalModelChanged = QtCore.Signal(DataFrameModel)

    def __init__(self, model=None, parent=None):
        CSVExportDialog.__init__(self, model=None, parent=None)
        if model is not None:
            self._filename = model.filePath
        else:
            self._filename = None
        self._windowTitle = "Export Data"
        self.setWindowTitle(self._windowTitle)

    @Slot(DataFrameModel)
    def swapModel(self, model):
        good = self.setExportModel(model)
        if good:
            self.signalModelChanged.emit(model)

    @Slot()
    def accepted(self):
        """Successfully close the widget and emit an export signal.

        This method is also a `SLOT`.
        The dialog will be closed, when the `Export Data` button is
        pressed. If errors occur during the export, the status bar
        will show the error message and the dialog will not be closed.

        """
        #return super(DataFrameExportDialog, self).accepted
        try:
            self._saveModel()
        except Exception as err:
            self._statusBar.showMessage(str(err))
            raise
        else:
            self._resetWidgets()
            self.exported.emit(True)
            self.accept()

    @Slot()
    def rejected(self):
        """Close the widget and reset its inital state.

        This method is also a `SLOT`.
        The dialog will be closed and all changes reverted, when the
        `cancel` button is pressed.

        """
        self._resetWidgets()
        self.exported.emit(False)
        self.reject()

    def _saveModel(self):
        """
        Reimplements _saveModel to utilize all of the
        Pandas export options based on file extension.
        :return: None
        """
        delimiter = self._delimiterBox.currentSelected()
        header = self._headerCheckBox.isChecked()  # column labels
        if self._filename is None:
            filename = self._filenameLineEdit.text()
        else:
            filename = self._filename
        ext = os.path.splitext(filename)[1].lower()
        index = False  # row labels

        encodingIndex = self._encodingComboBox.currentIndex()
        encoding = self._encodingComboBox.itemText(encodingIndex)
        encoding = _calculateEncodingKey(encoding.lower())

        try:
            dataFrame = self._model.dataFrame()
        except AttributeError as err:
            raise AttributeError('No data loaded to export.')
        else:
            print("Identifying export type for {}".format(filename))
            try:
                if ext in ['.txt', '.csv']:
                    dataFrame.to_csv(filename,
                                     encoding=encoding,
                                     header=header,
                                     index=index,
                                     sep=delimiter)
                elif ext == '.tsv':
                    sep = '\t'
                    dataFrame.to_csv(filename,
                                     encoding=encoding,
                                     header=header,
                                     index=index,
                                     sep=delimiter)
                elif ext in ['.xlsx', '.xls']:
                    dataFrame.to_excel(filename,
                                       encoding=encoding,
                                       header=header,
                                       index=index,
                                       sep=delimiter)
            except IOError as err:
                raise IOError('No filename given')
            except UnicodeError as err:
                raise UnicodeError(
                    'Could not encode all data. Choose a different encoding')
            except Exception:
                raise
            self.signalExportFilenames.emit(self._model._filePath, filename)