Module saePisan.model.TableModel
Classes
class TableModel (data, batch_size=100)-
Expand source code
class TableModel(QtCore.QAbstractTableModel): """A custom table model for handling data in a Qt application with support for undo/redo operations. Attributes: _data (pl.DataFrame): The data to be displayed in the table. undo_stack (QUndoStack): Stack to manage undo/redo operations. batch_size (int): Number of rows to load initially. loaded_rows (int): Number of rows currently loaded. Methods: __init__(data, batch_size=100): Initializes the table model with data and batch size. data(index, role): Returns the data for the given index and role. rowCount(_): Returns the number of rows in the table. columnCount(_): Returns the number of columns in the table. headerData(section, orientation, role): Returns the header data for the given section, orientation, and role. flags(_): Returns the item flags for the table. setData(index, value, role=Qt.ItemDataRole.EditRole): Sets the data for the given index and role. set_data(new_data): Sets the entire data for the table. get_data(): Returns the current data of the table. copy(index): Copies the data at the given index to the clipboard. paste(index): Pastes the data from the clipboard to the given index. undo(): Undoes the last operation. redo(): Redoes the last undone operation. canFetchMore(_): Checks if more rows can be fetched. fetchMore(_): Fetches more rows to be displayed in the table. addRowsBefore(index, count): Adds rows before the given index. addRowsAfter(index, count): Adds rows after the given index. addColumnBefore(index, count): Adds columns before the given index. addColumnAfter(index, count): Adds columns after the given index. deleteRows(start_row, count): Deletes rows starting from the given row index. deleteColumns(start_column, count): Deletes columns starting from the given column index. rename_column(column_index, new_name): Renames the column at the given index to the new name. get_column_type(column_index): Returns the data type of the column at the given index. set_column_type(column_index, new_type): Sets the data type of the column at the given index to the new type.""" def __init__(self, data, batch_size=100): super().__init__() self._data = data self.undo_stack = QUndoStack() self.batch_size = batch_size self.loaded_rows = min(batch_size, self._data.shape[0]) def data(self, index, role): if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole: value = self._data[index.row(), index.column()] return "" if value is None else str(value) def rowCount(self, _): return self.loaded_rows def columnCount(self, _): return self._data.shape[1] def headerData(self, section, orientation, role): if role == Qt.ItemDataRole.DisplayRole: if orientation == Qt.Orientation.Horizontal: if section < len(self._data.columns): column_name = self._data.columns[section] return f"{column_name}" return "" if orientation == Qt.Orientation.Vertical: return str(section + 1) elif role == Qt.ItemDataRole.DecorationRole or role == Qt.ItemDataRole.DisplayRole: if orientation == Qt.Orientation.Horizontal: if section < len(self._data.columns): column_name = self._data.columns[section] dtype = self._data[column_name].dtype if dtype == pl.Utf8: return QtGui.QIcon("assets/nominal.svg") elif dtype == pl.Null: return QtGui.QIcon("assets/null.svg") else: return QtGui.QIcon("assets/numeric.svg") return None def flags(self, _): return ( Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable ) def setData(self, index, value, role=Qt.ItemDataRole.EditRole): if role == Qt.ItemDataRole.EditRole: row = index.row() column = index.column() column_name = self._data.columns[column] dtype = self._data[column_name].dtype old_value = self._data[row, column] if dtype == pl.Null: if isinstance(value, str): val_strip = value.strip() val_dot = val_strip.replace(',', '.') try: float_val = float(val_dot) if '.' in val_dot: new_dtype = pl.Float64 value = float_val else: new_dtype = pl.Int64 value = int(float_val) except ValueError: new_dtype = pl.Utf8 elif isinstance(value, float): new_dtype = pl.Float64 elif isinstance(value, int): new_dtype = pl.Int64 else: new_dtype = pl.Utf8 self._data = self._data.with_columns([pl.col(column_name).cast(new_dtype)]) dtype = new_dtype if dtype == pl.Float64: try: if isinstance(value, str): if value.count(',') == 1 and value.replace(',', '').isdigit(): value = value.replace(',', '.') value = float(value) if value is not None else None except ValueError: error_dialog = QtWidgets.QMessageBox() error_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) error_dialog.setText(f"Invalid value: {value} for column {column_name}. Expected a numeric value.") error_dialog.setInformativeText("Do you want to set the column as a string?") error_dialog.setWindowTitle("Invalid Input") error_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) error_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = error_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: dtype = pl.Utf8 self._data = self._data.with_columns([pl.col(column_name).cast(dtype)]) else: return False if dtype == pl.Int64: try: if isinstance(value, str): value = int(value) if value is not None else None except ValueError: error_dialog = QtWidgets.QMessageBox() error_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) error_dialog.setText(f"Invalid value: {value} for column {column_name}. Expected an integer value.") error_dialog.setInformativeText("Do you want to set column to a string?") error_dialog.setWindowTitle("Invalid Input") error_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) error_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = error_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: dtype = pl.Utf8 self._data = self._data.with_columns([pl.col(column_name).cast(dtype)]) else: return False self._data[row, column] = value self.dataChanged.emit(index, index) command = EditDataCommand(self, row, column, old_value, value) # Pass row, column to command self.undo_stack.push(command) return True return False def set_data(self, new_data): if isinstance(new_data, pl.DataFrame): self.beginResetModel() self._data = new_data self.loaded_rows = min(self.batch_size, self._data.shape[0]) self.endResetModel() else: raise ValueError("Data must be a Polars DataFrame") def get_data(self): return self._data def copy(self, index): if index.isValid(): value = self.data(index, Qt.ItemDataRole.DisplayRole) clipboard = QtGui.QGuiApplication.clipboard() clipboard.setText(value) def paste(self, index): if index.isValid(): clipboard = QtGui.QGuiApplication.clipboard() value = clipboard.text() self.setData(index, value) def undo(self): self.undo_stack.undo() def redo(self): self.undo_stack.redo() def canFetchMore(self, _): return self.loaded_rows < self._data.shape[0] def fetchMore(self, _): if self.loaded_rows >= self._data.shape[0]: return remaining_rows = self._data.shape[0] - self.loaded_rows rows_to_fetch = min(self.batch_size, remaining_rows) self.beginInsertRows(QtCore.QModelIndex(), self.loaded_rows, self.loaded_rows + rows_to_fetch - 1) self.loaded_rows += rows_to_fetch self.endInsertRows() def addRowsBefore(self, index, count): if index.isValid() and count > 0: row = index.row() new_rows = [{col: None if self._data[col].dtype in [pl.Int64, pl.Float64, pl.Null] else "" for col in self._data.columns} for _ in range(count)] self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1) self._data = pl.concat([self._data[:row], pl.DataFrame(new_rows), self._data[row:]]) self.loaded_rows += count self.endInsertRows() command = AddRowsCommand(self, row, new_rows) self.undo_stack.push(command) def addRowsAfter(self, index, count): if index.isValid() and count > 0: row = index.row() + 1 new_rows = [{col: None if self._data[col].dtype in [pl.Int64, pl.Float64, pl.Null] else "" for col in self._data.columns} for _ in range(count)] self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1) self._data = pl.concat([self._data[:row], pl.DataFrame(new_rows), self._data[row:]]) self.loaded_rows += count self.endInsertRows() command = AddRowsCommand(self, row, new_rows) self.undo_stack.push(command) def addColumnBefore(self, index, count): if index.isValid() and count > 0: column = index.column() new_columns = {f"new_col_{i}": [None] * self._data.shape[0] for i in range(count)} self.beginResetModel() self._data = pl.concat([self._data[:, :column], pl.DataFrame(new_columns), self._data[:, column:]], how="horizontal") self.endResetModel() column_names = list(new_columns.keys()) # Extract column names new_columns_data = list(new_columns.values()) # Extract column data command = AddColumnCommand(self, column_names, new_columns_data) self.undo_stack.push(command) def addColumnAfter(self, index, count): if index.isValid() and count > 0: column = index.column() + 1 new_columns = {f"new_col_{i}": [None] * self._data.shape[0] for i in range(count)} self.beginResetModel() self._data = pl.concat([self._data[:, :column], pl.DataFrame(new_columns), self._data[:, column:]], how="horizontal") self.endResetModel() column_names = list(new_columns.keys()) new_columns_data = list(new_columns.values()) command = AddColumnCommand(self, column_names, new_columns_data) self.undo_stack.push(command) def deleteRows(self, start_row, count): if start_row >= 0 and count > 0: old_rows = self._data[start_row:start_row + count].to_dict(as_series=False) self.beginRemoveRows(QtCore.QModelIndex(), start_row, start_row + count - 1) self._data = pl.concat([self._data[:start_row], self._data[start_row + count:]]) self.loaded_rows -= count self.endRemoveRows() command = DeleteRowsCommand(self, start_row, old_rows) self.undo_stack.push(command) def deleteColumns(self, start_column, count): """ Deletes columns starting from `start_column` and spanning `count` columns. Args: start_column (int): The starting column index to delete. count (int): The number of columns to delete. """ if start_column >= 0 and count > 0: # Store original column order original_order = self._data.columns # Store the deleted columns and their data old_columns = { self._data.columns[i]: self._data[:, i].to_list() for i in range(start_column, start_column + count) } self.beginResetModel() columns_to_keep = [ col for i, col in enumerate(self._data.columns) if i < start_column or i >= start_column + count ] self._data = self._data.select(columns_to_keep) self.endResetModel() # Create a DeleteColumnsCommand and push it to the undo stack command = DeleteColumnsCommand(self, start_column, old_columns, original_order) self.undo_stack.push(command) def rename_column(self, column_index, new_name): if isinstance(column_index, int) and 0 <= column_index < len(self._data.columns): old_name = self._data.columns[column_index] self.beginResetModel() self._data = self._data.rename({old_name: new_name}) self.endResetModel() command = RenameColumnCommand(self, column_index, old_name, new_name) self.undo_stack.push(command) def get_column_type(self, column_index): if isinstance(column_index, int) and 0 <= column_index < len(self._data.columns): column_name = self._data.columns[column_index] return self._data[column_name].dtype return None def set_column_type(self, column_index, new_type): if isinstance(column_index, int) and 0 <= column_index < len(self._data.columns): column_name = self._data.columns[column_index] old_dtype = self._data[column_name].dtype old_data = self._data[column_name].to_list() if new_type == "String": new_dtype = pl.Utf8 elif new_type == "Integer": if self._data[column_name].dtype == pl.Utf8: try: warning_dialog = QtWidgets.QMessageBox() warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) warning_dialog.setText(f"The current data type of column {column_name} is String. Converting to Integer will result in loss of non-numeric data.") warning_dialog.setInformativeText("Do you want to proceed with the conversion?") warning_dialog.setWindowTitle("Data Type Conversion Warning") warning_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) warning_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = warning_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: self._data = self._data.with_columns([ pl.when(pl.col(column_name).str.contains(r'\d')) .then(pl.col(column_name).str.extract(r'(\d+)').cast(pl.Int64)) .otherwise(pl.lit(None).cast(pl.Int64)) ]) else: return except Exception: raise ValueError(f"Cannot convert column {column_name} to Integer") new_dtype = pl.Int64 elif new_type == "Float": if self._data[column_name].dtype == pl.Utf8: try: warning_dialog = QtWidgets.QMessageBox() warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) warning_dialog.setText(f"The current data type of column {column_name} is String. Converting to Float will result in loss of non-numeric data.") warning_dialog.setInformativeText("Do you want to proceed with the conversion?") warning_dialog.setWindowTitle("Data Type Conversion Warning") warning_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) warning_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = warning_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: self._data = self._data.with_columns([ pl.when(pl.col(column_name).str.contains(r'^\d+(\.\d+)?$') | pl.col(column_name).str.contains(r'^\d+(,\d+)?$')) .then(pl.col(column_name).str.replace(",", ".").cast(pl.Float64, strict=False)) .otherwise(pl.lit(None).cast(pl.Float64)) ]) else: return except Exception: raise ValueError(f"Cannot convert column {column_name} to Float") new_dtype = pl.Float64 else: raise ValueError("Unsupported data type") self.beginResetModel() self._data = self._data.with_columns([pl.col(column_name).cast(new_dtype)]) self.endResetModel() command = ChangeColumnTypeCommand(self, column_index, old_dtype, new_dtype, old_data, self._data[column_name].to_list()) self.undo_stack.push(command)A custom table model for handling data in a Qt application with support for undo/redo operations.
Attributes
_data:pl.DataFrame- The data to be displayed in the table.
undo_stack:QUndoStack- Stack to manage undo/redo operations.
batch_size:int- Number of rows to load initially.
loaded_rows:int- Number of rows currently loaded.
Methods
init(data, batch_size=100): Initializes the table model with data and batch size. data(index, role): Returns the data for the given index and role. rowCount(): Returns the number of rows in the table. columnCount(): Returns the number of columns in the table. headerData(section, orientation, role): Returns the header data for the given section, orientation, and role. flags(): Returns the item flags for the table. setData(index, value, role=Qt.ItemDataRole.EditRole): Sets the data for the given index and role. set_data(new_data): Sets the entire data for the table. get_data(): Returns the current data of the table. copy(index): Copies the data at the given index to the clipboard. paste(index): Pastes the data from the clipboard to the given index. undo(): Undoes the last operation. redo(): Redoes the last undone operation. canFetchMore(): Checks if more rows can be fetched. fetchMore(_): Fetches more rows to be displayed in the table. addRowsBefore(index, count): Adds rows before the given index. addRowsAfter(index, count): Adds rows after the given index. addColumnBefore(index, count): Adds columns before the given index. addColumnAfter(index, count): Adds columns after the given index. deleteRows(start_row, count): Deletes rows starting from the given row index. deleteColumns(start_column, count): Deletes columns starting from the given column index. rename_column(column_index, new_name): Renames the column at the given index to the new name. get_column_type(column_index): Returns the data type of the column at the given index. set_column_type(column_index, new_type): Sets the data type of the column at the given index to the new type.
Ancestors
- PyQt6.QtCore.QAbstractTableModel
- PyQt6.QtCore.QAbstractItemModel
- PyQt6.QtCore.QObject
- PyQt6.sip.wrapper
- PyQt6.sip.simplewrapper
Methods
def addColumnAfter(self, index, count)-
Expand source code
def addColumnAfter(self, index, count): if index.isValid() and count > 0: column = index.column() + 1 new_columns = {f"new_col_{i}": [None] * self._data.shape[0] for i in range(count)} self.beginResetModel() self._data = pl.concat([self._data[:, :column], pl.DataFrame(new_columns), self._data[:, column:]], how="horizontal") self.endResetModel() column_names = list(new_columns.keys()) new_columns_data = list(new_columns.values()) command = AddColumnCommand(self, column_names, new_columns_data) self.undo_stack.push(command) def addColumnBefore(self, index, count)-
Expand source code
def addColumnBefore(self, index, count): if index.isValid() and count > 0: column = index.column() new_columns = {f"new_col_{i}": [None] * self._data.shape[0] for i in range(count)} self.beginResetModel() self._data = pl.concat([self._data[:, :column], pl.DataFrame(new_columns), self._data[:, column:]], how="horizontal") self.endResetModel() column_names = list(new_columns.keys()) # Extract column names new_columns_data = list(new_columns.values()) # Extract column data command = AddColumnCommand(self, column_names, new_columns_data) self.undo_stack.push(command) def addRowsAfter(self, index, count)-
Expand source code
def addRowsAfter(self, index, count): if index.isValid() and count > 0: row = index.row() + 1 new_rows = [{col: None if self._data[col].dtype in [pl.Int64, pl.Float64, pl.Null] else "" for col in self._data.columns} for _ in range(count)] self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1) self._data = pl.concat([self._data[:row], pl.DataFrame(new_rows), self._data[row:]]) self.loaded_rows += count self.endInsertRows() command = AddRowsCommand(self, row, new_rows) self.undo_stack.push(command) def addRowsBefore(self, index, count)-
Expand source code
def addRowsBefore(self, index, count): if index.isValid() and count > 0: row = index.row() new_rows = [{col: None if self._data[col].dtype in [pl.Int64, pl.Float64, pl.Null] else "" for col in self._data.columns} for _ in range(count)] self.beginInsertRows(QtCore.QModelIndex(), row, row + count - 1) self._data = pl.concat([self._data[:row], pl.DataFrame(new_rows), self._data[row:]]) self.loaded_rows += count self.endInsertRows() command = AddRowsCommand(self, row, new_rows) self.undo_stack.push(command) def canFetchMore(self, _)-
Expand source code
def canFetchMore(self, _): return self.loaded_rows < self._data.shape[0]canFetchMore(self, parent: QModelIndex) -> bool
def columnCount(self, _)-
Expand source code
def columnCount(self, _): return self._data.shape[1]columnCount(self, parent: QModelIndex = QModelIndex()) -> int
def copy(self, index)-
Expand source code
def copy(self, index): if index.isValid(): value = self.data(index, Qt.ItemDataRole.DisplayRole) clipboard = QtGui.QGuiApplication.clipboard() clipboard.setText(value) def data(self, index, role)-
Expand source code
def data(self, index, role): if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole: value = self._data[index.row(), index.column()] return "" if value is None else str(value)data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any
def deleteColumns(self, start_column, count)-
Expand source code
def deleteColumns(self, start_column, count): """ Deletes columns starting from `start_column` and spanning `count` columns. Args: start_column (int): The starting column index to delete. count (int): The number of columns to delete. """ if start_column >= 0 and count > 0: # Store original column order original_order = self._data.columns # Store the deleted columns and their data old_columns = { self._data.columns[i]: self._data[:, i].to_list() for i in range(start_column, start_column + count) } self.beginResetModel() columns_to_keep = [ col for i, col in enumerate(self._data.columns) if i < start_column or i >= start_column + count ] self._data = self._data.select(columns_to_keep) self.endResetModel() # Create a DeleteColumnsCommand and push it to the undo stack command = DeleteColumnsCommand(self, start_column, old_columns, original_order) self.undo_stack.push(command)Deletes columns starting from
start_columnand spanningcountcolumns.Args
start_column:int- The starting column index to delete.
count:int- The number of columns to delete.
def deleteRows(self, start_row, count)-
Expand source code
def deleteRows(self, start_row, count): if start_row >= 0 and count > 0: old_rows = self._data[start_row:start_row + count].to_dict(as_series=False) self.beginRemoveRows(QtCore.QModelIndex(), start_row, start_row + count - 1) self._data = pl.concat([self._data[:start_row], self._data[start_row + count:]]) self.loaded_rows -= count self.endRemoveRows() command = DeleteRowsCommand(self, start_row, old_rows) self.undo_stack.push(command) def fetchMore(self, _)-
Expand source code
def fetchMore(self, _): if self.loaded_rows >= self._data.shape[0]: return remaining_rows = self._data.shape[0] - self.loaded_rows rows_to_fetch = min(self.batch_size, remaining_rows) self.beginInsertRows(QtCore.QModelIndex(), self.loaded_rows, self.loaded_rows + rows_to_fetch - 1) self.loaded_rows += rows_to_fetch self.endInsertRows()fetchMore(self, parent: QModelIndex)
def flags(self, _)-
Expand source code
def flags(self, _): return ( Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable )flags(self, index: QModelIndex) -> Qt.ItemFlag
def get_column_type(self, column_index)-
Expand source code
def get_column_type(self, column_index): if isinstance(column_index, int) and 0 <= column_index < len(self._data.columns): column_name = self._data.columns[column_index] return self._data[column_name].dtype return None def get_data(self)-
Expand source code
def get_data(self): return self._data def headerData(self, section, orientation, role)-
Expand source code
def headerData(self, section, orientation, role): if role == Qt.ItemDataRole.DisplayRole: if orientation == Qt.Orientation.Horizontal: if section < len(self._data.columns): column_name = self._data.columns[section] return f"{column_name}" return "" if orientation == Qt.Orientation.Vertical: return str(section + 1) elif role == Qt.ItemDataRole.DecorationRole or role == Qt.ItemDataRole.DisplayRole: if orientation == Qt.Orientation.Horizontal: if section < len(self._data.columns): column_name = self._data.columns[section] dtype = self._data[column_name].dtype if dtype == pl.Utf8: return QtGui.QIcon("assets/nominal.svg") elif dtype == pl.Null: return QtGui.QIcon("assets/null.svg") else: return QtGui.QIcon("assets/numeric.svg") return NoneheaderData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole) -> Any
def paste(self, index)-
Expand source code
def paste(self, index): if index.isValid(): clipboard = QtGui.QGuiApplication.clipboard() value = clipboard.text() self.setData(index, value) def redo(self)-
Expand source code
def redo(self): self.undo_stack.redo() def rename_column(self, column_index, new_name)-
Expand source code
def rename_column(self, column_index, new_name): if isinstance(column_index, int) and 0 <= column_index < len(self._data.columns): old_name = self._data.columns[column_index] self.beginResetModel() self._data = self._data.rename({old_name: new_name}) self.endResetModel() command = RenameColumnCommand(self, column_index, old_name, new_name) self.undo_stack.push(command) def rowCount(self, _)-
Expand source code
def rowCount(self, _): return self.loaded_rowsrowCount(self, parent: QModelIndex = QModelIndex()) -> int
def setData(self, index, value, role=2)-
Expand source code
def setData(self, index, value, role=Qt.ItemDataRole.EditRole): if role == Qt.ItemDataRole.EditRole: row = index.row() column = index.column() column_name = self._data.columns[column] dtype = self._data[column_name].dtype old_value = self._data[row, column] if dtype == pl.Null: if isinstance(value, str): val_strip = value.strip() val_dot = val_strip.replace(',', '.') try: float_val = float(val_dot) if '.' in val_dot: new_dtype = pl.Float64 value = float_val else: new_dtype = pl.Int64 value = int(float_val) except ValueError: new_dtype = pl.Utf8 elif isinstance(value, float): new_dtype = pl.Float64 elif isinstance(value, int): new_dtype = pl.Int64 else: new_dtype = pl.Utf8 self._data = self._data.with_columns([pl.col(column_name).cast(new_dtype)]) dtype = new_dtype if dtype == pl.Float64: try: if isinstance(value, str): if value.count(',') == 1 and value.replace(',', '').isdigit(): value = value.replace(',', '.') value = float(value) if value is not None else None except ValueError: error_dialog = QtWidgets.QMessageBox() error_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) error_dialog.setText(f"Invalid value: {value} for column {column_name}. Expected a numeric value.") error_dialog.setInformativeText("Do you want to set the column as a string?") error_dialog.setWindowTitle("Invalid Input") error_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) error_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = error_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: dtype = pl.Utf8 self._data = self._data.with_columns([pl.col(column_name).cast(dtype)]) else: return False if dtype == pl.Int64: try: if isinstance(value, str): value = int(value) if value is not None else None except ValueError: error_dialog = QtWidgets.QMessageBox() error_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) error_dialog.setText(f"Invalid value: {value} for column {column_name}. Expected an integer value.") error_dialog.setInformativeText("Do you want to set column to a string?") error_dialog.setWindowTitle("Invalid Input") error_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) error_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = error_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: dtype = pl.Utf8 self._data = self._data.with_columns([pl.col(column_name).cast(dtype)]) else: return False self._data[row, column] = value self.dataChanged.emit(index, index) command = EditDataCommand(self, row, column, old_value, value) # Pass row, column to command self.undo_stack.push(command) return True return FalsesetData(self, index: QModelIndex, value: Any, role: int = Qt.EditRole) -> bool
def set_column_type(self, column_index, new_type)-
Expand source code
def set_column_type(self, column_index, new_type): if isinstance(column_index, int) and 0 <= column_index < len(self._data.columns): column_name = self._data.columns[column_index] old_dtype = self._data[column_name].dtype old_data = self._data[column_name].to_list() if new_type == "String": new_dtype = pl.Utf8 elif new_type == "Integer": if self._data[column_name].dtype == pl.Utf8: try: warning_dialog = QtWidgets.QMessageBox() warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) warning_dialog.setText(f"The current data type of column {column_name} is String. Converting to Integer will result in loss of non-numeric data.") warning_dialog.setInformativeText("Do you want to proceed with the conversion?") warning_dialog.setWindowTitle("Data Type Conversion Warning") warning_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) warning_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = warning_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: self._data = self._data.with_columns([ pl.when(pl.col(column_name).str.contains(r'\d')) .then(pl.col(column_name).str.extract(r'(\d+)').cast(pl.Int64)) .otherwise(pl.lit(None).cast(pl.Int64)) ]) else: return except Exception: raise ValueError(f"Cannot convert column {column_name} to Integer") new_dtype = pl.Int64 elif new_type == "Float": if self._data[column_name].dtype == pl.Utf8: try: warning_dialog = QtWidgets.QMessageBox() warning_dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) warning_dialog.setText(f"The current data type of column {column_name} is String. Converting to Float will result in loss of non-numeric data.") warning_dialog.setInformativeText("Do you want to proceed with the conversion?") warning_dialog.setWindowTitle("Data Type Conversion Warning") warning_dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) warning_dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) ret = warning_dialog.exec() if ret == QtWidgets.QMessageBox.StandardButton.Yes: self._data = self._data.with_columns([ pl.when(pl.col(column_name).str.contains(r'^\d+(\.\d+)?$') | pl.col(column_name).str.contains(r'^\d+(,\d+)?$')) .then(pl.col(column_name).str.replace(",", ".").cast(pl.Float64, strict=False)) .otherwise(pl.lit(None).cast(pl.Float64)) ]) else: return except Exception: raise ValueError(f"Cannot convert column {column_name} to Float") new_dtype = pl.Float64 else: raise ValueError("Unsupported data type") self.beginResetModel() self._data = self._data.with_columns([pl.col(column_name).cast(new_dtype)]) self.endResetModel() command = ChangeColumnTypeCommand(self, column_index, old_dtype, new_dtype, old_data, self._data[column_name].to_list()) self.undo_stack.push(command) def set_data(self, new_data)-
Expand source code
def set_data(self, new_data): if isinstance(new_data, pl.DataFrame): self.beginResetModel() self._data = new_data self.loaded_rows = min(self.batch_size, self._data.shape[0]) self.endResetModel() else: raise ValueError("Data must be a Polars DataFrame") def undo(self)-
Expand source code
def undo(self): self.undo_stack.undo()