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(CSVImportDialog, self).__init__(parent) self._modal = True self._windowTitle = u'Import CSV' self._encodingKey = None self._filename = None self._delimiter = None self._header = None self._detector = Detector() self._initUI()
class CSVImportDialog(QtGui.QDialog): """A dialog to import any csv file into a pandas data frame. This modal dialog enables the user to enter any path to a csv file and parse this file with or without a header and with special delimiter characters. On a successful load, the data can be previewed and the column data types may be edited by the user. After all configuration is done, the dataframe and the underlying model may be used by the main application. Attributes: load (QtCore.pyqtSignal): This signal is emitted, whenever the dialog is successfully closed, e.g. when the ok button is pressed. Returns DataFrameModel and path of chosen csv file. """ load = Signal('QAbstractItemModel', str) 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(CSVImportDialog, self).__init__(parent) self._modal = True self._windowTitle = u'Import CSV' self._encodingKey = None self._filename = None self._delimiter = None self._header = None self._detector = Detector() self._initUI() def _initUI(self): """Initiates the user interface with a grid layout and several widgets. """ self.setModal(self._modal) self.setWindowTitle(self._windowTitle) layout = QtGui.QGridLayout() self._filenameLabel = QtGui.QLabel(u'Choose File', self) self._filenameLineEdit = QtGui.QLineEdit(self) self._filenameLineEdit.textEdited.connect(self._updateFilename) chooseFileButtonIcon = QtGui.QIcon( QtGui.QPixmap(':/icons/document-open.png')) self._chooseFileAction = QtGui.QAction(self) self._chooseFileAction.setIcon(chooseFileButtonIcon) self._chooseFileAction.triggered.connect(self._openFile) self._chooseFileButton = QtGui.QToolButton(self) self._chooseFileButton.setDefaultAction(self._chooseFileAction) layout.addWidget(self._filenameLabel, 0, 0) layout.addWidget(self._filenameLineEdit, 0, 1, 1, 2) layout.addWidget(self._chooseFileButton, 0, 3) self._encodingLabel = QtGui.QLabel(u'File Encoding', self) encoding_names = map(lambda x: x.upper(), sorted(list(set(_encodings.viewvalues())))) self._encodingComboBox = QtGui.QComboBox(self) self._encodingComboBox.addItems(encoding_names) self._encodingComboBox.activated.connect(self._updateEncoding) layout.addWidget(self._encodingLabel, 1, 0) layout.addWidget(self._encodingComboBox, 1, 1, 1, 1) self._hasHeaderLabel = QtGui.QLabel(u'Header Available?', self) self._headerCheckBox = QtGui.QCheckBox(self) self._headerCheckBox.toggled.connect(self._updateHeader) layout.addWidget(self._hasHeaderLabel, 2, 0) layout.addWidget(self._headerCheckBox, 2, 1) self._delimiterLabel = QtGui.QLabel(u'Column Delimiter', self) self._delimiterBox = DelimiterSelectionWidget(self) self._delimiter = self._delimiterBox.currentSelected() self._delimiterBox.delimiter.connect(self._updateDelimiter) layout.addWidget(self._delimiterLabel, 3, 0) layout.addWidget(self._delimiterBox, 3, 1, 1, 3) self._tabWidget = QtGui.QTabWidget(self) self._previewTableView = QtGui.QTableView(self) self._datatypeTableView = QtGui.QTableView(self) self._tabWidget.addTab(self._previewTableView, u'Preview') self._tabWidget.addTab(self._datatypeTableView, u'Change Column Types') layout.addWidget(self._tabWidget, 4, 0, 3, 4) self._datatypeTableView.horizontalHeader().setDefaultSectionSize(200) self._datatypeTableView.setItemDelegateForColumn( 1, DtypeComboDelegate(self._datatypeTableView)) self._loadButton = QtGui.QPushButton(u'Load Data', self) #self.loadButton.setAutoDefault(False) self._cancelButton = QtGui.QPushButton(u'Cancel', self) # self.cancelButton.setDefault(False) # self.cancelButton.setAutoDefault(True) self._buttonBox = QtGui.QDialogButtonBox(self) self._buttonBox.addButton(self._loadButton, QtGui.QDialogButtonBox.AcceptRole) self._buttonBox.addButton(self._cancelButton, QtGui.QDialogButtonBox.RejectRole) self._buttonBox.accepted.connect(self.accepted) self._buttonBox.rejected.connect(self.rejected) layout.addWidget(self._buttonBox, 9, 2, 1, 2) self._loadButton.setDefault(False) self._filenameLineEdit.setFocus() self._statusBar = QtGui.QStatusBar(self) self._statusBar.setSizeGripEnabled(False) layout.addWidget(self._statusBar, 8, 0, 1, 4) self.setLayout(layout) @Slot('QString') def updateStatusBar(self, message): """Updates the status bar widget of this dialog with the given message. This method is also a `SLOT()`. The message will be shown for only 5 seconds. Args: message (QString): The new message which will be displayed. """ self._statusBar.showMessage(message, 5000) @Slot() def _openFile(self): """Opens a file dialog and sets a value for the QLineEdit widget. This method is also a `SLOT`. """ ret = QtGui.QFileDialog.getOpenFileName( self, self.tr(u'open file'), filter='Comma Separated Values (*.csv)') if ret: self._filenameLineEdit.setText(ret) self._updateFilename() @Slot('QBool') def _updateHeader(self, toggled): """Changes the internal flag, whether the csv file contains a header or not. This method is also a `SLOT`. In addition, after toggling the corresponding checkbox, the `_previewFile` method will be called. Args: toggled (boolean): A flag indicating the status of the checkbox. The flag will be used to update an internal variable. """ self._header = 0 if toggled else None self._previewFile() @Slot() def _updateFilename(self): """Calls several methods after the filename changed. This method is also a `SLOT`. It checks the encoding of the changed filename and generates a preview of the data. """ self._filename = self._filenameLineEdit.text() self._guessEncoding(self._filename) self._previewFile() def _guessEncoding(self, path): """Opens a file from the given `path` and checks the file encoding. The file must exists on the file system and end with the extension `.csv`. The file is read line by line until the encoding could be guessed. On a successfull identification, the widgets of this dialog will be updated. Args: path (string): Path to a csv file on the file system. """ if os.path.exists(path) and path.lower().endswith('csv'): encoding = self._detector.detect(path) if encoding is not None: if encoding.startswith('utf'): encoding = encoding.replace('-', '') encoding = encoding.replace('-', '_') viewValue = _encodings.get(encoding) self._encodingKey = encoding index = self._encodingComboBox.findText(viewValue.upper()) self._encodingComboBox.setCurrentIndex(index) @Slot('int') def _updateEncoding(self, index): """Changes the value of the encoding combo box to the value of given index. This method is also a `SLOT`. After the encoding is changed, the file will be reloaded and previewed. Args: index (int): An valid index of the combo box. """ encoding = self._encodingComboBox.itemText(index) encoding = encoding.lower() self._encodingKey = _calculateEncodingKey(encoding) self._previewFile() @Slot('QString') def _updateDelimiter(self, delimiter): """Changes the value of the delimiter for the csv file. This method is also a `SLOT`. Args: delimiter (string): The new delimiter. """ self._delimiter = delimiter self._previewFile() def _previewFile(self): """Updates the preview widgets with new models for both tab panes. """ dataFrame = self._loadCSVDataFrame() dataFrameModel = DataFrameModel(dataFrame) dataFrameModel.enableEditing(True) self._previewTableView.setModel(dataFrameModel) columnModel = dataFrameModel.columnDtypeModel() columnModel.changeFailed.connect(self.updateStatusBar) self._datatypeTableView.setModel(columnModel) def _loadCSVDataFrame(self): """Loads the given csv file with pandas and generate a new dataframe. The file will be loaded with the configured encoding, delimiter and header.git If any execptions will occur, an empty Dataframe is generated and a message will appear in the status bar. Returns: pandas.DataFrame: A dataframe containing all the available information of the csv file. """ if self._filename and os.path.exists( self._filename) and self._filename.endswith('.csv'): # default fallback if no encoding was found/selected encoding = self._encodingKey or 'uft8' try: dataFrame = pandas.read_csv(self._filename, sep=self._delimiter, encoding=encoding, header=self._header) dataFrame = dataFrame.apply(fillNoneValues) dataFrame = dataFrame.apply(convertTimestamps) except Exception, err: self.updateStatusBar(str(err)) return pandas.DataFrame() self.updateStatusBar('Preview generated.') return dataFrame self.updateStatusBar('File does not exists or does not end with .csv') return pandas.DataFrame()
class CSVImportDialog(QtWidgets.QDialog): """A dialog to import any csv file into a pandas data frame. This modal dialog enables the user to enter any path to a csv file and parse this file with or without a header and with special delimiter characters. On a successful load, the data can be previewed and the column data types may be edited by the user. After all configuration is done, the dataframe and the underlying model may be used by the main application. Attributes: load (QtCore.pyqtSignal): This signal is emitted, whenever the dialog is successfully closed, e.g. when the ok button is pressed. Returns DataFrameModel and path of chosen csv file. """ load = Signal('QAbstractItemModel', str) 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(CSVImportDialog, self).__init__(parent) self._modal = True self._windowTitle = u'Import CSV' self._encodingKey = None self._filename = None self._delimiter = None self._header = None self._detector = Detector() self._initUI() def _initUI(self): """Initiates the user interface with a grid layout and several widgets. """ self.setModal(self._modal) self.setWindowTitle(self._windowTitle) layout = QtWidgets.QGridLayout() self._filenameLabel = QtWidgets.QLabel(u'Choose File', self) self._filenameLineEdit = QtWidgets.QLineEdit(self) self._filenameLineEdit.textEdited.connect(self._updateFilename) chooseFileButtonIcon = QtGui.QIcon(QtGui.QPixmap(':/icons/document-open.png')) self._chooseFileAction = QtWidgets.QAction(self) self._chooseFileAction.setIcon(chooseFileButtonIcon) self._chooseFileAction.triggered.connect(self._openFile) self._chooseFileButton = QtWidgets.QToolButton(self) self._chooseFileButton.setDefaultAction(self._chooseFileAction) layout.addWidget(self._filenameLabel, 0, 0) layout.addWidget(self._filenameLineEdit, 0, 1, 1, 2) layout.addWidget(self._chooseFileButton, 0, 3) self._encodingLabel = QtWidgets.QLabel(u'File Encoding', self) encoding_names = map(lambda x: x.upper(), sorted(list(set(_encodings.viewvalues())))) self._encodingComboBox = QtWidgets.QComboBox(self) self._encodingComboBox.addItems(encoding_names) self._encodingComboBox.activated.connect(self._updateEncoding) layout.addWidget(self._encodingLabel, 1, 0) layout.addWidget(self._encodingComboBox, 1, 1, 1, 1) self._hasHeaderLabel = QtWidgets.QLabel(u'Header Available?', self) self._headerCheckBox = QtWidgets.QCheckBox(self) self._headerCheckBox.toggled.connect(self._updateHeader) layout.addWidget(self._hasHeaderLabel, 2, 0) layout.addWidget(self._headerCheckBox, 2, 1) self._delimiterLabel = QtWidgets.QLabel(u'Column Delimiter', self) self._delimiterBox = DelimiterSelectionWidget(self) self._delimiter = self._delimiterBox.currentSelected() self._delimiterBox.delimiter.connect(self._updateDelimiter) layout.addWidget(self._delimiterLabel, 3, 0) layout.addWidget(self._delimiterBox, 3, 1, 1, 3) self._tabWidget = QtWidgets.QTabWidget(self) self._previewTableView = QtWidgets.QTableView(self) self._datatypeTableView = QtWidgets.QTableView(self) self._tabWidget.addTab(self._previewTableView, u'Preview') self._tabWidget.addTab(self._datatypeTableView, u'Change Column Types') layout.addWidget(self._tabWidget, 4, 0, 3, 4) self._datatypeTableView.horizontalHeader().setDefaultSectionSize(200) self._datatypeTableView.setItemDelegateForColumn(1, DtypeComboDelegate(self._datatypeTableView)) self._loadButton = QtWidgets.QPushButton(u'Load Data', self) #self.loadButton.setAutoDefault(False) self._cancelButton = QtWidgets.QPushButton(u'Cancel', self) # self.cancelButton.setDefault(False) # self.cancelButton.setAutoDefault(True) self._buttonBox = QtWidgets.QDialogButtonBox(self) self._buttonBox.addButton(self._loadButton, QtWidgets.QDialogButtonBox.AcceptRole) self._buttonBox.addButton(self._cancelButton, QtWidgets.QDialogButtonBox.RejectRole) self._buttonBox.accepted.connect(self.accepted) self._buttonBox.rejected.connect(self.rejected) layout.addWidget(self._buttonBox, 9, 2, 1, 2) self._loadButton.setDefault(False) self._filenameLineEdit.setFocus() self._statusBar = QtWidgets.QStatusBar(self) self._statusBar.setSizeGripEnabled(False) layout.addWidget(self._statusBar, 8, 0, 1, 4) self.setLayout(layout) @Slot('QString') def updateStatusBar(self, message): """Updates the status bar widget of this dialog with the given message. This method is also a `SLOT()`. The message will be shown for only 5 seconds. Args: message (QString): The new message which will be displayed. """ self._statusBar.showMessage(message, 5000) @Slot() def _openFile(self): """Opens a file dialog and sets a value for the QLineEdit widget. This method is also a `SLOT`. """ ret = QtWidgets.QFileDialog.getOpenFileName(self, self.tr(u'open file'), filter='Comma Separated Values (*.csv)') if ret: self._filenameLineEdit.setText(ret) self._updateFilename() @Slot('QBool') def _updateHeader(self, toggled): """Changes the internal flag, whether the csv file contains a header or not. This method is also a `SLOT`. In addition, after toggling the corresponding checkbox, the `_previewFile` method will be called. Args: toggled (boolean): A flag indicating the status of the checkbox. The flag will be used to update an internal variable. """ self._header = 0 if toggled else None self._previewFile() @Slot() def _updateFilename(self): """Calls several methods after the filename changed. This method is also a `SLOT`. It checks the encoding of the changed filename and generates a preview of the data. """ self._filename = self._filenameLineEdit.text() self._guessEncoding(self._filename) self._previewFile() def _guessEncoding(self, path): """Opens a file from the given `path` and checks the file encoding. The file must exists on the file system and end with the extension `.csv`. The file is read line by line until the encoding could be guessed. On a successfull identification, the widgets of this dialog will be updated. Args: path (string): Path to a csv file on the file system. """ if os.path.exists(path) and path.lower().endswith('csv'): encoding = self._detector.detect(path) if encoding is not None: if encoding.startswith('utf'): encoding = encoding.replace('-', '') encoding = encoding.replace('-','_') viewValue = _encodings.get(encoding) self._encodingKey = encoding index = self._encodingComboBox.findText(viewValue.upper()) self._encodingComboBox.setCurrentIndex(index) @Slot('int') def _updateEncoding(self, index): """Changes the value of the encoding combo box to the value of given index. This method is also a `SLOT`. After the encoding is changed, the file will be reloaded and previewed. Args: index (int): An valid index of the combo box. """ encoding = self._encodingComboBox.itemText(index) encoding = encoding.lower() self._encodingKey = _calculateEncodingKey(encoding) self._previewFile() @Slot('QString') def _updateDelimiter(self, delimiter): """Changes the value of the delimiter for the csv file. This method is also a `SLOT`. Args: delimiter (string): The new delimiter. """ self._delimiter = delimiter self._previewFile() def _previewFile(self): """Updates the preview widgets with new models for both tab panes. """ dataFrame = self._loadCSVDataFrame() dataFrameModel = DataFrameModel(dataFrame) dataFrameModel.enableEditing(True) self._previewTableView.setModel(dataFrameModel) columnModel = dataFrameModel.columnDtypeModel() columnModel.changeFailed.connect(self.updateStatusBar) self._datatypeTableView.setModel(columnModel) def _loadCSVDataFrame(self): """Loads the given csv file with pandas and generate a new dataframe. The file will be loaded with the configured encoding, delimiter and header.git If any execptions will occur, an empty Dataframe is generated and a message will appear in the status bar. Returns: pandas.DataFrame: A dataframe containing all the available information of the csv file. """ if self._filename and os.path.exists(self._filename) and self._filename.endswith('.csv'): # default fallback if no encoding was found/selected encoding = self._encodingKey or 'uft8' try: dataFrame = pandas.read_csv(self._filename, sep=self._delimiter, encoding=encoding, header=self._header) dataFrame = dataFrame.apply(fillNoneValues) dataFrame = dataFrame.apply(convertTimestamps) except Exception as err: self.updateStatusBar(str(err)) return pandas.DataFrame() self.updateStatusBar('Preview generated.') return dataFrame self.updateStatusBar('File does not exists or does not end with .csv') return pandas.DataFrame() def _resetWidgets(self): """Resets all widgets of this dialog to its inital state. """ self._filenameLineEdit.setText('') self._encodingComboBox.setCurrentIndex(0) self._delimiterBox.reset() self._headerCheckBox.setChecked(False) self._statusBar.showMessage('') self._previewTableView.setModel(None) self._datatypeTableView.setModel(None) @Slot() def accepted(self): """Successfully close the widget and return the loaded model. This method is also a `SLOT`. The dialog will be closed, when the `ok` button is pressed. If a `DataFrame` was loaded, it will be emitted by the signal `load`. """ model = self._previewTableView.model() if model is not None: df = model.dataFrame().copy() dfModel = DataFrameModel(df) self.load.emit(dfModel, self._filename) self._resetWidgets() 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.reject()