Esempio n. 1
0
    def __init__(self, settings: SettingsDialog = None) -> None:
        """
        Constructor
        :param settings: settings dialog
        :return: nothing
        """
        QObject.__init__(self)

        self.logger = QGISLogHandler(DatabaseController.__name__)

        self.__db_service = DatabaseService.get_instance()
        self.__last_db_settings = dict()

        self.__settings = None
        self.__config = ConfigHandler()
        self.settings = settings

        db_type = self.__config.get("General", "db_type")
        if db_type != "" and db_type in ["SQLite", "PostgreSQL"]:
            self.settings.DB_type.setCurrentText(db_type)
        else:
            db_type = "SQLite"
            self.__config.set("General", "db_type", db_type)
            self.settings.DB_type.setCurrentText(db_type)

        self.__on_db_type_changed(db_type)
 def __init__(self, only_numbers: bool = False, *args, **kwargs):
     """
     Initialize the object
     :param only_numbers: show only number rows and don't display type column
     :param args: arguments for initialization of the base class
     :param kwargs: arguments for initialization of the base class
     """
     super().__init__(*args, **kwargs)
     self.__only_numbers = only_numbers
     self.logger = QGISLogHandler(self.__class__.__name__)
Esempio n. 3
0
    def __init__(self, data: Dict, selection: Dict, properties: List[PropertyImportData]) -> None:
        """
        :param data: import data parsed from the file to import
        :param selection: dictionary of selected columns
        """
        super().__init__()

        self._logger = QGISLogHandler(self.__class__.__name__)
        self._data: Dict = data
        self._selection: Dict = selection
        self._properties: List[PropertyImportData] = properties
        self._mutex = QMutex()
        self._cancel = False
        self._message = ""
 def __init__(self,
              only_numbers: bool = False,
              parent: QWidget = None,
              *args) -> None:
     """
     Initialize the object
     :param only_numbers: show only number rows and don't display type column
     """
     # noinspection PyArgumentList
     QAbstractTableModel.__init__(self, parent, *args)
     self.__only_numbers = only_numbers
     self.__data_list: List[PropertyImportData] = list()
     self.__header_labels = ["Property"
                             ] if only_numbers else ["Property", "Type"]
     self.logger = QGISLogHandler(self.__class__.__name__)
class ExceptionHandler:
    """
    Singleton class for basic exception handling
    """
    __instance: "ExceptionHandler" = None
    last_exception: str = ""
    __logger: QGISLogHandler = QGISLogHandler("ExceptionHandler")

    def __new__(cls, e: Exception = None) -> "ExceptionHandler":
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)

        if e is not None:
            _, _, exc_traceback = sys.exc_info()
            cls.__instance.last_exception = "Error Message:\n{}\nTraceback:\n{}". \
                format(str(e), ''.join(traceback.format_tb(exc_traceback)))

        return cls.__instance

    def __str__(self) -> str:
        """
        Return last exception as a string
        :return: Return last exception as a string
        """
        return self.last_exception

    def push_last_to_qgis(self, iface: QgisInterface) -> None:
        """
        Show last exception in QGIS log message window and as a message bar hint
        :param iface: QgisInterface representation
        :return: Nothing
        """
        split = self.last_exception.split('\n')
        if len(split) > 1:
            widget = iface.messageBar().createMessage("Error", self.last_exception.split('\n')[1])
        else:
            widget = iface.messageBar().createMessage("Error",
                                                      "An exception occurred during the process. " +
                                                      "For more details, please take a look to the log windows.")

        button = QPushButton(widget)
        button.setText("Show log windows")
        # noinspection PyUnresolvedReferences
        button.pressed.connect(self.iface.openMessageLog)
        widget.layout().addWidget(button)
        iface.messageBar().pushWidget(widget, level=2)

        # noinspection PyCallByClass, PyArgumentList
        QgsMessageLog.logMessage(self.last_exception, level=2)

    def log(self, only_logfile: bool = False):
        """
        log the exception
        :param only_logfile: Don't write output to QGIS interface, even if it is set
        :return: nothing
        """
        self.__logger.error("An exception occurred", self.last_exception, only_logfile=only_logfile)
Esempio n. 6
0
    def __init__(self, dock_widget: GeologicalDataProcessingDockWidget) -> None:
        """
        Initialize the view
        :param dock_widget: current GeologicalDataProcessingDockWidget instance
        """

        self.logger = QGISLogHandler(self.__class__.__name__)
        self.__combos = dict()
        self._dwg = dock_widget

        # initialize user interface
        self._import_service = ImportService.get_instance()
        self._import_service.reset_import.connect(self.reset_import)
        self._import_service.import_columns_changed.connect(self._on_import_columns_changed)

        self._table_view: QTableView or None = None
        self._only_number_in_table_view: bool = False
        self._table_model: PropertyImportModel = PropertyImportModel()
        self._dwg.start_import_button.clicked.connect(self._on_start_import)
        self._controller_thread: ImportControllersInterface or None = None

        super().__init__()
class PropertyImportDelegate(QStyledItemDelegate):
    """
    Derived delegate class for drawing the UnitConstructionModel
    """
    def __init__(self, only_numbers: bool = False, *args, **kwargs):
        """
        Initialize the object
        :param only_numbers: show only number rows and don't display type column
        :param args: arguments for initialization of the base class
        :param kwargs: arguments for initialization of the base class
        """
        super().__init__(*args, **kwargs)
        self.__only_numbers = only_numbers
        self.logger = QGISLogHandler(self.__class__.__name__)

    def createEditor(self, parent: QWidget, option: QStyleOptionViewItem,
                     index: QModelIndex) -> QWidget:
        """
        Creates an editor widget for the given index. Derived function.
        :param parent: parent QWidget
        :param option: QStyleOptionViewItem
        :param index: model index for editor creation
        :return: QWidget which represents the editor for the given model index
        """
        if index.isValid():
            if index.column() == 1 and not self.__only_numbers:
                combobox = QComboBox(parent)
                combobox.addItems(["Integer", "Float", "String"])
                combobox.setFocusPolicy(Qt.StrongFocus)
                return combobox
        return super().createEditor(parent, option, index)

    def setEditorData(self, editor: QComboBox, index: QModelIndex) -> None:
        """
        sets the data to the given editor widget based on the model index. Derived function.
        :param editor: editor widget for which the data has to be set (only type; column == 1)
        :param index: model index from which the editor data has to be set
        :return: Nothing
        """
        if index.isValid() and index.column() == 1 and not self.__only_numbers:
            if index.data() != "":
                editor.setCurrentText(index.data())
            else:
                editor.setCurrentText("String")
            return
        super().setEditorData(editor, index)

    def setModelData(self, editor: QComboBox, model: QAbstractTableModel,
                     index: QModelIndex) -> None:
        """
        Update the model data at the given index from the editor value. Derived function.
        :param editor: data provider
        :param model: data storage
        :param index: index where data has to be updated
        :return: Nothing
        """
        self.logger.debug("Updating model data for index [{}, {}]: {}".format(
            index.column(), index.row(), editor.currentText()))
        if index.isValid() and index.column(
        ) == 1 and not self.__only_numbers:  # only type can is editable
            model.setData(index, editor.currentText())
            return
        super().setModelData(editor, model, index)

    def updateEditorGeometry(self, editor: QComboBox,
                             option: QStyleOptionViewItem,
                             index: QModelIndex) -> None:
        """
        update the editor geometry. Derived function.
        :param editor:
        :param option:
        :param index:
        :return: Nothing
        """
        # if index.isValid():
        #     if isinstance(editor, QCheckBox):
        #         pos_x = int(option.rect.x() + option.rect.width() / 2 - editor.sizeHint().width() / 2)
        #         pos_y = int(option.rect.y() + option.rect.height() / 2 - editor.sizeHint().height() / 2)
        #         editor.setGeometry(QRect(pos_x, pos_y, editor.sizeHint().width(), editor.sizeHint().height()))
        #         return
        #     if index.column() == 4:
        #         editor.setGeometry(option.rect)
        super().updateEditorGeometry(editor, option, index)

    def paint(self, painter: QPainter, option: QStyleOptionViewItem,
              index: QModelIndex) -> None:
        """
        paint event for drawing the model data
        :param painter: QPainter for the drawing
        :param option: QStyleOptionViewItem
        :param index: model index to be drawn
        :return: Nothing
        """
        index_data = index.data()

        # set the distance label
        if isinstance(index_data, int):
            text = "{:,} m".format(index_data).replace(',', ' ')
            rect = QRectF(option.rect)
            rect.setWidth(rect.width() - 5)
            painter.drawText(rect, Qt.AlignRight | Qt.AlignVCenter, text)

        else:
            super().paint(painter, option, index)

    def sizeHint(self, option: QStyleOptionViewItem,
                 index: QModelIndex) -> QSize:
        """
        Returns a size hint for the object at the given index
        :param option: QStyleOptionViewItem
        :param index: model index for the requested size hint
        :return: a QSize object with given hint
        """
        # if isinstance(index.data(), bool):
        #     return self.__checkbox_size
        # if isinstance(index.data(), QColor):
        #     return self.__color_size
        # if isinstance(index.data(), int):
        #     self.__label_tmp.setText("{:,} m".format(index.data()).replace(',', ' '))
        #     size = self.__label_tmp.sizeHint()
        #     size.setWidth(size.width() + 5)
        #     return size
        return super().sizeHint(option, index)
class ImportService(QObject):
    """
    Singleton class controlling the import procedures
    """
    __instance = None
    __dwg = None
    __selectable_columns: List[Tuple[str, str]] = list()
    __number_columns: List[Tuple[str, str]] = list()

    logger = QGISLogHandler("ImportService")

    @staticmethod
    def get_instance(
            dwg: GeologicalDataProcessingDockWidget = None) -> "ImportService":
        """
        Creates a new object instance if no object exists or updates the existing one.
        :return: The single instance of this class
        """

        if ImportService.__instance is None:
            ImportService.logger.debug(ImportService.__class__.__name__,
                                       "Create new ImportService instance")
            ImportService(dwg)

        return ImportService.__instance

    def __init__(self, dwg: GeologicalDataProcessingDockWidget = None):
        """ Virtual private constructor. """
        super().__init__()
        if ImportService.__instance is not None:
            raise BaseException(
                "The ImportService-class is a singleton and can't be called directly. "
                + "Use ImportService.get_instance() instead!")
        else:
            if dwg is not None:
                self.dockwidget = dwg
            ImportService.__instance = self
            self.separator = ","
            self.import_file = ""
            self.__config_handler = ConfigHandler()

    #
    # signals
    #

    crs_changed = pyqtSignal(QgsCoordinateReferenceSystem)
    """signal send, when the selected coordinate reference system changes"""
    import_file_changed = pyqtSignal(str)
    """signal send, when the import file name changes"""
    separator_changed = pyqtSignal(str)
    """signal send, when the selected separator changes"""
    import_columns_changed = pyqtSignal()
    """signal send, when the import columns changed"""
    reset_import = pyqtSignal()
    """signal send, when the reset was requested"""

    #
    # setter and getter
    #

    @property
    def dockwidget(self) -> GeologicalDataProcessingDockWidget:
        """
        Returns the currently active plugin-dockwidget
        :return: returns the currently active plugin-dockwidget
        """
        return self.__dwg

    @dockwidget.setter
    def dockwidget(self, value: GeologicalDataProcessingDockWidget) -> None:
        """
        Sets the currently active plugin-dockwidget
        :return: Nothing
        :raises TypeError: if value is not of type GeologicalDataProcessingDockWidget
        """
        if isinstance(value, GeologicalDataProcessingDockWidget):
            if self.__dwg is not None:
                self.dockwidget.import_file.textChanged.disconnect(
                    self._on_import_file_changed)
                self.dockwidget.select_data_file_button.clicked.disconnect(
                    self.__on_select_data_file)
                self.dockwidget.separator.currentIndexChanged[str].disconnect(
                    self._on_separator_changed)

            self.__dwg = value

            self.dockwidget.start_import_button.setEnabled(False)
            self.dockwidget.import_file.textChanged.connect(
                self._on_import_file_changed)
            self.dockwidget.select_data_file_button.clicked.connect(
                self.__on_select_data_file)
            self.dockwidget.separator.currentIndexChanged[str].connect(
                self._on_separator_changed)

            self.__dwg.separator.addItems(
                [';', ',', '<tabulator>', '.', '-', '_', '/', '\\'])
            self.separator = ','
            self.import_file = ''
        else:
            raise TypeError(
                "committed parameter is not of type GeologicalDataProcessingDockWidget"
            )

    @property
    def import_file(self):
        """
        Returns the currently selected import file
        :return: returns the currently selected import file
        """
        self.__validate()

        return self.dockwidget.import_file.text()

    @import_file.setter
    def import_file(self, filename: str) -> None:
        """
        import file setter
        :param filename: new path to the import file
        :return: Nothing
        :raises ValueError: if the file doesn't exists
        """
        self.__validate()

        self.dockwidget.import_file.setText(str(filename))

    @property
    def separator(self) -> str:
        """
        get / set the separator of the input file
        possible values are: ';', ',', '\t', '.', '-', '_', '/', '\\'
        :return: returns the currently selected separator
        """
        self.__validate()

        sep = self.dockwidget.separator.currentText()
        if sep == "<tabulator>":
            sep = '\t'
        return sep

    @separator.setter
    def separator(self, sep: str) -> None:
        """
        get / set the separator of the input file
        possible values are: ';', ',', '\t', '.', '-', '_', '/', '\\'
        :return: nothing
        :param sep: new separator
        :return: Nothing
        :raises ValueError: if sep is not in list of possible values.
        """
        self.__validate()

        if sep not in [';', ',', '\t', '.', '-', '_', '/', '\\']:
            raise ValueError("{} as separator is not allowed!")

        if sep == '\t':
            sep = "<tabulator>"

        self.dockwidget.separator.setCurrentText(sep)

    def get_crs(self):
        """
        returns the selected coordinate reference system
        :return: the selected coordinate reference system
        """
        # from qgis.gui import QgsProjectionSelectionWidget
        # wdg = QgsProjectionSelectionWidget()
        return self.dockwidget.reference.crs()

    @property
    def crs(self) -> QgsCoordinateReferenceSystem:
        """
        gets / sets the Reference System in QGIS format
        :return: current coordinate reference system
        :raises TypeError: if crs is an instance of QgsCoordinateReferenceSystem
        :raises ValueError: if the committed reference system is not valid
        """
        self.__validate()

        return self.dockwidget.reference.crs()

    @crs.setter
    def crs(self, _crs: QgsCoordinateReferenceSystem) -> None:
        """
        gets / sets the Reference System in QGIS format
        :return: current coordinate reference system
        :raises TypeError: if crs is an instance of QgsCoordinateReferenceSystem
        """
        self.__validate()

        if not isinstance(_crs, QgsCoordinateReferenceSystem):
            raise TypeError(
                "committed value is not of type QgsCoordinateReferenceSystem!")

        if not _crs.isValid():
            raise ValueError("committed reference system is not valid")

        self.dockwidget.reference.setCrs(_crs)

    @property
    def selectable_columns(self) -> List[Tuple[str, str]]:
        """
        Returns a list of selectable columns
        :return: Returns a list of selectable columns
        """
        return ImportService.__selectable_columns

    @property
    def number_columns(self) -> List[Tuple[str, str]]:
        """
        Returns a list of number columns
        :return: Returns a list of number columns
        """
        return ImportService.__number_columns

    #
    # private functions
    #

    def __validate(self):
        """
        Validates, if the service can be executed
        :return: Nothing
        :raises
        """

        if self.dockwidget is None:
            raise AttributeError("No dockwidget is set to the ImportService")

    #
    # public functions
    #

    def reset(self):
        """
        Reset the import service and related fields
        :return: Nothing
        """
        self.__validate()

        self.logger.debug("reset")

        ImportService.__selectable_columns = list()
        ImportService.__number_columns = list()
        self.dockwidget.start_import_button.setEnabled(False)
        self.import_file_changed.emit("")
        self.reset_import.emit()

    def read_import_file(self) -> Dict:
        """
        Read the import file and return the resulting dictionary
        :return: the resulting dictionary
        """
        self.logger.debug("reading import file {}".format(self.import_file))

        try:
            separator = self.separator
            if separator == "<tabulator>":
                separator = '\t'

            result = dict()

            with open(os.path.normpath(self.import_file), 'r') as import_file:

                cols = import_file.readline().strip().split(separator)
                props = import_file.readline().strip().split(separator)

                if len(cols) < 2:
                    raise ImportError(
                        "Cannot read import file, not enough columns for selected separator"
                    )

                self.logger.debug("cols:\t{}".format(cols))
                self.logger.debug("props:\t{}".format(props))

                for i in range(0, len(cols)):
                    try:
                        p = props[i]
                    except IndexError:
                        p = ""

                    result[cols[i]] = {"property": p, "values": list()}

                for line in import_file:
                    line = line.strip().split(separator)
                    for i in range(0, len(line)):
                        if i >= len(cols):
                            break

                        result[cols[i]]["values"].append(line[i])

                    # fill the rest with empty values
                    for i in range(len(line), len(cols)):
                        result[cols[i]]["values"].append("")

            return result

        except IOError:
            ("Cannot open file", "{}".format(self.import_file))
        except Exception as e:
            self.logger.error("Error", str(ExceptionHandler(e)))
            self.reset()

    #
    # slots
    #

    def _on_crs_changed(self) -> None:
        """
        slot called, when the selected coordinate reference system has changed
        :return: Nothing
        """
        self.__validate()

        self.logger.debug("_on_crs_changed")

        self.crs_changed.emit(
            self.dockwidget.mQgsProjectionSelectionWidget.crs())

    def _on_import_file_changed(self, filename: str) -> None:
        """
        slot for textChanged(str) signal of the filename lineedit
        :param filename: newly selected filename
        :return: Nothing
        """
        self.__validate()

        self.logger.debug("_on_import_file_changed")

        if not os.path.isfile(os.path.normpath(filename)):
            self.reset()
            # self.logger.warn("Not a file", filename)
            return

        try:
            separator = self.separator
            self.logger.debug("Selected file", "{}".format(filename))
            if separator == "<tabulator>":
                separator = '\t'

            try:
                import_file = open(os.path.normpath(filename), 'r')
            except IOError:
                self.logger.error("Cannot open file", "{}".format(filename))
                return

            cols = import_file.readline().strip()

            if len(cols.split(separator)) < 3:
                self.separator = self.find_separator(cols)
                separator = self.separator
                self.logger.debug("Selected another separator: {}".format(
                    "<tabulator>" if separator == '\t' else separator))

            cols = cols.split(separator)
            units = import_file.readline().strip().split(separator)
            data = import_file.readline().strip().split(separator)

            self.logger.debug("cols:\t{}".format(cols))
            self.logger.debug("units:\t{}".format(units))
            self.logger.debug("data:\t{}".format(data))

            import_file.close()

            nr_cols = []
            for col in data:
                try:
                    float(col)
                    nr_cols.append(data.index(col))
                except ValueError:
                    pass

            if len(nr_cols) < 3:
                self.logger.warn(
                    "Not enough columns", "Cannot find enough columns. " +
                    "Maybe use a different separator or another import file")
                self.reset()
                return

            ImportService.__selectable_columns = list()
            for i in range(len(cols)):
                name = cols[i]
                try:
                    unit = units[i]
                except IndexError:
                    unit = ""
                ImportService.__selectable_columns.append((name, unit))

            ImportService.__number_columns = list()
            for i in nr_cols:
                name = cols[i]
                try:
                    unit = units[i]
                except IndexError:
                    unit = ""
                ImportService.__number_columns.append((name, unit))

            self.dockwidget.start_import_button.setEnabled(True)
            self.import_file_changed.emit(filename)
            self.import_columns_changed.emit()

        except Exception as e:
            self.logger.error("Error", str(ExceptionHandler(e)))
            self.reset()

    @staticmethod
    def find_separator(line: str) -> str:
        for sep in [';', ',', '\t', '.', '-', '_', '/', '\\']:
            if len(line.split(sep)) >= 3:
                return sep
        else:
            return ';'

    def _on_separator_changed(self, _: str = "") -> None:
        """
        slot called, if another separator was selected
        :param _: unused, but necessary for signal connection
        :return: Nothing
        """
        self.__validate()

        self.logger.debug("_on_separator_changed")

        self._on_import_file_changed(self.import_file)

    def __on_select_data_file(self):
        """
        slot for selecting and checking of a working directory
        :return: Nothing
        """
        self.__validate()

        self.logger.debug("_on_select_data_file")

        path = self.__config_handler.get("General", "current working path")
        # if config.debug:
        # get current module path
        # path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
        # add relative path to test-data
        # path = os.path.join(path, "../../tests/test_data")

        # noinspection PyCallByClass,PyArgumentList
        filename = get_file_name(
            QFileDialog.getOpenFileName(
                self.dockwidget, "Select data file", path,
                "Data Files(*.txt *.csv *.data);;Any File Type (*)"))
        if filename != "":
            self.__config_handler.set("General", "current working path",
                                      os.path.dirname(filename))
            try:
                import_file = open(filename, 'r')
            except IOError as e:
                self.logger.error("Cannot open file", e)
                self.dockwidget.start_import_button.setEnabled(False)
                return

            cols = import_file.readline().strip()
            props = import_file.readline().strip()
            data = import_file.readline().strip()
            import_file.close()

            if '' in [cols, props, data]:
                self.logger.error(
                    "Import File Error",
                    "Cannot process import_tests file, wrong file format!")
                return

            for sep in [';', ',', '\t', '.', '-', '_', '/', '\\']:
                split = cols.split(sep)
                if len(split) < 3:
                    continue

                self.separator = sep
                self.import_file = filename
                break
    def check_required_modules():
        """
        Check all module requirements
        :return: True is all modules with required versions were found, else false
        """
        logger = QGISLogHandler("ModuleService")

        # don't display logging to QGIS
        safed_iface = logger.qgis_iface
        logger.qgis_iface = None

        logger.info("Checking required modules")

        python = ModuleService.get_python()

        cmd = [python, "-m", "pip", "list", "--format", "json", "--user"]

        try:
            logger.info("Checking package versions")
            logger.info("CMD: {}".format(" ".join(cmd)))

            result = run(cmd, stdout=PIPE, stderr=STDOUT, check=True)
            logger.info("run pip info successful")
            packages = json.loads(result.stdout.decode())

        except CalledProcessError as e:
            # restore QGIS logging
            logger.qgis_iface = safed_iface
            logger.error("pip info request failed!")
            logger.error("RETURN-CODE: {} - CMD: {}".format(e.returncode, e.cmd))
            logger.error("OUTPUT: {}".format(e.output))
            return False

        ModuleService.modules = list()
        for module in module_list:
            module_found = False
            for package in packages:
                if package["name"] != module:
                    continue
                module_found = True

                logger.debug("found module {} [{}]".format(package["name"], package["version"]))

                v1 = version.parse(package["version"])
                v2 = version.parse(module_list[module])
                if v1 < v2:
                    logger.warn("Module version [{}] differs from required version [{}]".format(v1, v2))
                    ModuleService.modules.append("{}=={}".format(module, module_list[module]))

            if not module_found:
                logger.debug("Module not found, adding {}=={} to install list".format(module, module_list[module]))
                ModuleService.modules.append("{}=={}".format(module, module_list[module]))

        if len(ModuleService.modules) > 0:
            # restore QGIS logging
            logger.qgis_iface = safed_iface
            logger.info("Missing packages found: {}".format(ModuleService.modules))
            return False

        logger.info("All packages up to date")

        # restore QGIS logging
        logger.qgis_iface = safed_iface
        return True
    def install_packages():
        """
        installs  base packages via pip, e.g. packaging package
        :return: True if packages installed successfully else False
        """
        logger = QGISLogHandler("ModuleService")

        python = ModuleService.get_python()

        requirements = list("{}>={}".format(key, module_list[key]) for key in module_list.keys())
        cmd = [python, "-m", "pip", "install", "--force-reinstall", "--user", "--upgrade"] + requirements

        try:
            logger.info("Installing packages", ", ".join(requirements))
            logger.info("CMD: {}".format(" ".join(cmd)))

            result = run(cmd, stdout=PIPE, stderr=STDOUT, check=True)

            logger.info("installation result", result.stdout.decode())
            return True

        except CalledProcessError as e:
            logger.error("Package installation failed!")
            logger.error("RETURN-CODE: {} - CMD: {}".format(e.returncode, e.cmd))
            logger.error("OUTPUT: {}".format(e.output))
            return False
    def on_start_tests(self) -> None:
        """
        start a test suite
        :return: Nothing
        """
        debug_log = QGISLogHandler(GeologicalDataProcessing.__name__)
        debug_log.qgis_iface = self.iface
        debug_log.save_to_file = True

        stream = StringIO()
        loader = unittest.TestLoader()
        runner = unittest.TextTestRunner(stream=stream)
        suite = unittest.TestSuite()

        suite.addTests(loader.loadTestsFromTestCase(TestExceptionHandlingClass))

        test_cases = loader.getTestCaseNames(TestPointImportClass)
        for name in test_cases:
            suite.addTest(TestPointImportClass(name, iface=self.iface, dockwidget=self.dockwidget))

        # result = runner.run(unittest.makeSuite(TestExceptionHandlingClass))
        result = runner.run(suite)

        level = LogLevel.INFO
        if len(result.errors) > 0 or len(result.failures) > 0:
            level = LogLevel.CRITICAL

        debug_log.push_message("logfile", debug_log.logfile)
        debug_log.push_message("Test runs", str(result.testsRun))
        debug_log.push_message("Test errors", str(len(result.errors)), level=level)
        debug_log.push_message("Failures", str(len(result.failures)), level=level)

        stream.seek(0)
        debug_log.push_message("Test output", '\n' + str(stream.read()), level=level)
Esempio n. 12
0
class DatabaseService:
    """
    Singleton class controlling all database related processes
    """
    __instance = None

    logger = QGISLogHandler("DatabaseService")

    @staticmethod
    def get_instance() -> "DatabaseService":
        """
        Creates a new object instance if no object exists or updates the existing one.
        :return: The single instance of this class
        """
        if DatabaseService.__instance is None:
            DatabaseService.logger.debug("Create new DatabaseService instance")
            DatabaseService()
        else:
            DatabaseService.logger.debug(
                "Returning existing DatabaseService instance")

        return DatabaseService.__instance

    def __init__(self):
        """ Virtually private constructor. """
        super().__init__()
        if DatabaseService.__instance is not None:
            raise BaseException(
                "The DatabaseService-class is a singleton and can't be called directly. "
                + "Use DatabaseService.get_instance() instead!")
        else:
            DatabaseService.__instance = self

        self.__connection = ""
        self.__db_type = ""
        self.__password = ""
        self.__username = ""

        self.__handler = None
        self.__session = None

    #
    # setter and getter
    #

    @property
    def connection(self) -> str:
        """
        Getter for the current connection string
        :return: returns the current connection string
        """
        return self.__connection

    @connection.setter
    def connection(self, value: str) -> None:
        """
        setter for the current connection string
        :return: Nothing
        """
        self.__connection = str(value)

    @property
    def db_type(self) -> str:
        """
        Getter for the database type
        :return: returns the current database type
        """
        return self.__db_type

    @db_type.setter
    def db_type(self, value: str) -> None:
        """
        setter for the database type
        :return: Nothing
        :raises ValueError: if value is not "SQLite" or "PostgreSQL"
        """
        value = str(value)

        if value.lower() not in ("sqlite", "postgresql"):
            raise ValueError("Unknown database format: {}".format(value))

        self.__db_type = value.lower()

    @property
    def password(self) -> str:
        """
        Getter for the current password
        :return: returns the current password
        """
        return self.__password

    @password.setter
    def password(self, value: str) -> None:
        """
        setter for the current password
        :return: Nothing
        """
        self.__password = str(value)

    @property
    def username(self) -> str:
        """
        Getter for the current username
        :return: returns the current username
        """
        return self.__username

    @username.setter
    def username(self, value: str) -> None:
        """
        setter for the current username
        :return: Nothing
        """
        self.__username = str(value)

    #
    # public functions
    #

    def connect(self) -> None:
        """
        Connect a GeologicalToolbox-DBHandler
        :return: Nothing
        :raises ValueError: if database type is unknown
        """
        self.logger.debug("Connecting to a database: [{}]: {}".format(
            self.db_type, self.connection))
        # noinspection SpellCheckingInspection
        if self.db_type == "postgresql":
            # connection string: postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...]
            connection = "postgresql+psycopg2://{}:{}@{}".format(
                self.username, self.password, self.connection)
        elif self.db_type == "sqlite":
            connection = "sqlite:///{}".format(self.connection)
        else:
            self.__handler = None
            raise ValueError("Unknown DB Format: {}".format(self.db_type))

        self.__handler = DBHandler(connection=connection, echo=False)

    def get_session(self) -> Session:
        """
        return a sqlalchemy database session
        :return: a sqlalchemy database session
        :raises ConnectionError: if no database handler is connected
        """
        self.logger.debug("get or create a session")
        if self.__handler is None:
            self.__session = None
            raise ConnectionError(
                "No database handler found, please connect first")

        self.__session = self.__handler.get_session()
        return self.__session

    def close_session(self) -> None:
        """
        close the current session if existing
        :return: Nothing
        """
        self.logger.debug("Closing session")
        if self.__session is not None:
            self.__session.close()

    def check_connection(self) -> str:
        """
        Check database connection
        :return: Error message if test fails, else empty string
        """
        self.logger.debug("check_connection called")

        try:
            self.connect()
            self.get_session()
            self.close_session()
            return ""

        except DBAPIError as e:
            # ExceptionHandler(e).log(only_logfile=True)
            return str(e)
Esempio n. 13
0
class ImportViewInterface(QObject):
    """
    interface class defining signals and slots for all import views
    """

    def __init__(self, dock_widget: GeologicalDataProcessingDockWidget) -> None:
        """
        Initialize the view
        :param dock_widget: current GeologicalDataProcessingDockWidget instance
        """

        self.logger = QGISLogHandler(self.__class__.__name__)
        self.__combos = dict()
        self._dwg = dock_widget

        # initialize user interface
        self._import_service = ImportService.get_instance()
        self._import_service.reset_import.connect(self.reset_import)
        self._import_service.import_columns_changed.connect(self._on_import_columns_changed)

        self._table_view: QTableView or None = None
        self._only_number_in_table_view: bool = False
        self._table_model: PropertyImportModel = PropertyImportModel()
        self._dwg.start_import_button.clicked.connect(self._on_start_import)
        self._controller_thread: ImportControllersInterface or None = None

        super().__init__()

    def _connect_combo_listener(self):
        """
        connects all combobox elements to the on_selection_changed slot
        :return: Nothing
        """
        [combo.currentIndexChanged.connect(self.on_selection_changed) for combo in self.__combos]

    @property
    def combobox_names(self) -> Dict:
        """
        Returns a dictionary of comboboxes for the current view
        :return: returns a dictionary of comboboxes for the current view
        """
        return self.__combos

    @combobox_names.setter
    def combobox_names(self, combo_dict: Dict) -> None:
        """
        Sets a new dictionary to the combobox list. Disconnects the old ones and connects the new to the
        on_selection_changed slot
        :param combo_dict: dictionary with new combobox elements
        :return: Nothing
        :raises TypeError: if a dictionary value is not an instance of QComboBox or a key is not a str. Sets an empty
        dictionary instead
        """

        self._disconnect_selection_changed()

        for key in combo_dict:
            if not isinstance(key, str):
                self.__combos = dict()
                raise TypeError("{} is not a string".format(str(key)))
            if not isinstance(combo_dict[key], QComboBox):
                self.__combos = dict()
                raise TypeError("{} is not an instance of QComboBox".format(str(key)))

        self.__combos = combo_dict
        self._connect_selection_changed()

    @property
    def table_view(self) -> QTableView or None:
        return self._table_view

    @table_view.setter
    def table_view(self, widget: QTableView) -> None:
        if not isinstance(widget, QTableView):
            raise TypeError("submitted object is not of type QTableView: {}", str(widget))

        self._table_model.clear()
        self._table_view = widget
        self._table_view.setModel(self._table_model)

        if self._only_number_in_table_view:
            self._table_view.setItemDelegate(LogImportDelegate())
        else:
            self._table_view.setItemDelegate(PropertyImportDelegate())
        # self._table_view.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        # self._table_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self._table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

    @property
    def dockwidget(self) -> GeologicalDataProcessingDockWidget:
        """
        Returns the current dockwidget
        :return: the current dockwidget
        """
        return self._dwg

    #
    # signals
    #

    selection_changed = pyqtSignal(list)
    """data changed signal which gives the index or name of the changed column and the newly selected text"""

    start_import = pyqtSignal()
    """signal to start the data import through an ImportController Thread"""

    #
    # slots
    #

    def on_selection_changed(self, _=None) -> None:
        """
        Emits the combobox_changed signal with a list of changed text
        :return: Nothing
        """
        selection_list = [self.combobox_names[key].currentText() for key in self.combobox_names]
        self.selection_changed.emit(selection_list)

        if self._table_view is None:
            self.logger.debug("No list widget specified for additional columns")
            return

        if self._only_number_in_table_view:
            cols = diff(self._import_service.number_columns, selection_list)
        else:
            cols = diff(self._import_service.selectable_columns, selection_list)

        self.logger.debug("selectable_columns: " + str(self._import_service.selectable_columns))
        self.logger.debug("selected_cols: " + str(selection_list))
        self.logger.debug("additional cols: " + str(cols))

        if self._table_view is not None:
            self._table_view.show()
            self._table_view.setEnabled(True)
            self._table_view.clearSelection()

        self._table_model.clear()
        for col in cols:
            property_type = PropertyTypes.FLOAT if col in self._import_service.number_columns else PropertyTypes.STRING
            self._table_model.add(PropertyImportData(name=col[0], unit=col[1], property_type=property_type))

    def _on_import_columns_changed(self) -> None:
        """
        change the import columns
        :return: Nothing
        """
        self.logger.debug("(Interface) _on_import_columns_changed")
        self._connect_selection_changed()
        self.on_selection_changed()

    def _on_start_import(self) -> None:
        self.logger.debug("(Interface) _on_start_import")
        self._update_progress_bar(0)
        self._dwg.progress_bar_layout.setVisible(True)

    def _on_import_failed(self, msg: str) -> None:
        self.logger.debug("(Interface) _on_import_failed")
        self.__import_finished()
        self.logger.error("Import failed", msg, to_messagebar=True)

    def _on_import_finished_with_warnings(self, msg: str) -> None:
        self.logger.debug("(Interface) _on_import_finished_with_warnings")
        self.logger.warn("Import finished with warnings", msg, to_messagebar=True)
        self.__import_finished()

    def _on_import_successful(self):
        self.logger.debug("(Interface) _on_import_successful")
        self.logger.info("Import successful", to_messagebar=True)
        self.__import_finished()

    def _on_cancel_import(self):
        self._controller_thread.cancel_import("Import canceled by user")

    def __import_finished(self):
        self._dwg.progress_bar_layout.setVisible(False)
        self._disconnect_thread()

        self._controller_thread.wait(2000)
        self._controller_thread = None

    #
    # public functions
    #

    def combobox_data(self, index: int or str) -> str:
        """
        Returns the currently selected item of the gui element with the given index
        :param index: index of the requested gui element as integer or string
        :return: Returns the data at the given index
        :raises IndexError: if index is not part in the available list
        """

        if isinstance(index, int):
            index = [self.combobox_names.keys()][index]
        else:
            index = str(index)

        if index not in self.combobox_names:
            raise IndexError("{} is not available".format(index))

        return self.combobox_names[index].currentText()

    def get_name(self, index: int) -> str or None:
        """
        Returns the name of the combobox with the given index
        :param index: index of the requested combobox
        :return: Returns the name of the combobox with the given index
        :raises IndexError: if the requested index is not in the list
        :raises ValueError: if the index is not convertible to an integer
        """
        index = int(index)

        if 0 <= index < len(self.combobox_names.keys()):
            return list(self.combobox_names.keys())[0]

    def get_names(self):
        """
        Returns a list of the combobox names
        :return: Returns a list of the combobox names
        """
        return list(self.combobox_names.keys())

    def set_combobox_data(self, index: int or str, values: List[str], default_index: int = 0) -> None:
        """
        Sets the committed values list to the gui combobox elements for the given index
        :param index: index of the requested gui element as integer or string
        :param values: new values for the combo boxes as a list of strings
        :param default_index: default selected index. If no default value is given, or the index is not part of the
                              list, the first entry will be selected by default
        :return: Returns, if the data setting was successful
        :raises IndexError: if index is not part in the available list
        :raises TypeError: if default_index is not an instance of int
        """

        if isinstance(index, int):
            index = [self.combobox_names.keys()][index]
        else:
            index = str(index)

        if index not in self.combobox_names:
            raise IndexError("{} is not available".format(index))

        if not isinstance(default_index, int):
            raise TypeError("default_index({}) is not an instance of int!".format(default_index))

        self.combobox_names[index].clear()
        for item in values:
            self.combobox_names[index].addItem(str(item))

        if not (0 <= default_index <= len(values)):
            default_index = 0
        self.combobox_names[index].setCurrentIndex(default_index)

    def reset_import(self) -> None:
        """
        Clears all import combo boxes, in case of a failure
        :return: Nothing
        """
        [self.set_combobox_data(name, []) for name in self.get_names()]

    def get_property_columns(self) -> List[PropertyImportData]:
        selection = set([x.row() for x in self._table_view.selectedIndexes()])
        self.logger.debug("selected rows indices: {}".format(selection))
        erg = [self._table_model.row(x) for x in selection]
        self.logger.debug("Selection:")
        [self.logger.debug("\t{}".format(x)) for x in erg]
        return erg

    #
    # protected functions
    #

    def _update_progress_bar(self, value):
        """
        slot to set the current progress bar value
        :param value: value in percent
        :return: nothing
        """
        self.logger.debug("Update progressbar with value {} called".format(value))
        if value < 0:
            self.dockwidget.progress_bar.setValue(0)
        elif value > 100:
            self.dockwidget.progress_bar.setValue(100)
        else:
            self.dockwidget.progress_bar.setValue(int(value))

    def _connect_selection_changed(self):
        self.logger.debug("_connect_selection_changed")
        [self.__combos[key].currentTextChanged.connect(self.on_selection_changed) for key in self.__combos]

    def _disconnect_selection_changed(self):
        for key in self.__combos:
            try:
                self.__combos[key].currentTextChanged.disconnect(self.on_selection_changed)
            except TypeError:
                # not connected
                pass

    def _connect_thread(self):
        self._controller_thread.import_finished.connect(self._on_import_successful)
        self._controller_thread.import_failed.connect(self._on_import_failed)
        self._controller_thread.import_finished_with_warnings.connect(self._on_import_finished_with_warnings)
        self._controller_thread.update_progress.connect(self._update_progress_bar)
        self._dwg.cancel_import.clicked.connect(self._on_cancel_import)

    def _disconnect_thread(self):
        self._controller_thread.import_finished.disconnect(self._on_import_successful)
        self._controller_thread.import_failed.disconnect(self._on_import_failed)
        self._controller_thread.import_finished_with_warnings.disconnect(self._on_import_finished_with_warnings)
        self._controller_thread.update_progress.disconnect(self._update_progress_bar)
        self._dwg.cancel_import.clicked.disconnect(self._on_cancel_import)
Esempio n. 14
0
class ImportControllersInterface(QThread):
    """
    Basic interface for all import_tests controller
    """

    def __init__(self, data: Dict, selection: Dict, properties: List[PropertyImportData]) -> None:
        """
        :param data: import data parsed from the file to import
        :param selection: dictionary of selected columns
        """
        super().__init__()

        self._logger = QGISLogHandler(self.__class__.__name__)
        self._data: Dict = data
        self._selection: Dict = selection
        self._properties: List[PropertyImportData] = properties
        self._mutex = QMutex()
        self._cancel = False
        self._message = ""

    def run(self) -> None:
        """
        Thread execution function to import data
        :return: Nothing
        """
        pass

    #
    # signals
    #

    update_progress = pyqtSignal(int)
    """update progress bar signal. Committed value has to be between 0 and 100"""

    import_finished = pyqtSignal()
    """signal emitted, when the import process has finished"""

    import_finished_with_warnings = pyqtSignal(str)
    """signal emitted, when the import process has finished with warnings"""

    import_failed = pyqtSignal(str)
    """signal emitted, when the import process was canceled or failed through a call of the cancel_import slot"""

    #
    # slots
    #

    def cancel_import(self, msg: str = "") -> None:
        """
        slot for canceling the import process. A trigger variable will hint the importer to stop at the next
        possibility. This should ensure the finalization of all started write processes and therefore the integrity of
        all database objects.

        The :func:`~GeologicalDataProcessing.controller.import_controller.ImportControllersInterface.import_cancelled`
        signal will be sent, if the import process was successfully cancelled.
        :return: Nothing
        """
        self._cancel = True
        if msg == "":
            self._message = "Import canceled"
        else:
            self._message = msg

    def _import_done(self, future: Future = None) -> None:
        """
        function called, when import is done or canceled
        :param future: import executing future object
        :return: nothing
        """
        self._logger.debug("import done")

        self._view.dockwidget.progress_bar_layout.setVisible(False)
        self._view.dockwidget.cancel_import.clicked.disconnect(self._stop_import)
        if (future is not None) and future.cancelled():
            self._logger.warn("future run finished, import cancelled!")
        elif future is not None:
            self._logger.info("future run finished, import successful")

        self._logger.info("QThread finished")
        if self.__thread is not None:
            self._logger.debug("waiting for the end...")
            self.__thread.wait()
            self._logger.debug("at the end...")
            self.__thread = None
Esempio n. 15
0
class PropertyImportModel(QAbstractTableModel):
    """
    Derived Table Model for the storage of UnitConstructionData
    """
    def __init__(self,
                 only_numbers: bool = False,
                 parent: QWidget = None,
                 *args) -> None:
        """
        Initialize the object
        :param only_numbers: show only number rows and don't display type column
        """
        # noinspection PyArgumentList
        QAbstractTableModel.__init__(self, parent, *args)
        self.__only_numbers = only_numbers
        self.__data_list: List[PropertyImportData] = list()
        self.__header_labels = ["Property"
                                ] if only_numbers else ["Property", "Type"]
        self.logger = QGISLogHandler(self.__class__.__name__)

    # noinspection PyMethodOverriding
    def add(self, data: PropertyImportData) -> bool:
        """
        adds a new row at the end of the model.
        :param row: row index where to insert the new row
        :param data: data to be insert
        :return: True, if the insert was performed successfully, else False
        """
        self.beginInsertRows(QModelIndex(),
                             self.rowCount() - 1,
                             self.rowCount() - 1)
        self.__data_list.append(data)
        self.endInsertRows()
        return True

    def clear(self) -> None:
        """
        Removes all rows.
        :return: Nothing
        """
        self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1)
        self.__data_list = list()
        self.endRemoveRows()

    def columnCount(self, parent: QModelIndex = ...) -> int:
        """
        returns the current column count of the table model
        :param parent: redundant parameter as this derived class isn't a tree model
        :return: returns the current column count of the table model
        """
        return len(self.__header_labels)

    # noinspection PyMethodOverriding
    def data(self, index: QModelIndex, role):
        """
        returns the data at the given index and the given role. Derived function.
        :param index: index of the requested data
        :param role: role of the requested data
        :return: returns the data at the given index and the given role
        """
        if not index.isValid():
            return QVariant()
        elif role in (Qt.DisplayRole, Qt.EditRole):
            return QVariant(self.__data_list[index.row()][index.column()])
        elif index.column() == 0 and role == Qt.TextAlignmentRole:
            return Qt.AlignLeft
        elif index.column(
        ) == 1 and role == Qt.TextAlignmentRole and not self.__only_numbers:
            return Qt.AlignRight
        return QVariant()

    # noinspection PyTypeChecker
    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
        """
        Set the editable flag for the given model index. Derived function
        :param index: model index for which the flags are requested
        :return: Qt.ItemFlags
        """
        if not index.isValid():
            return Qt.ItemIsEnabled

        if index.column() == 1 and not self.__only_numbers:
            return Qt.ItemIsEditable | super(QAbstractTableModel,
                                             self).flags(index)

        return super(QAbstractTableModel, self).flags(index)

    def headerData(self,
                   section: int,
                   orientation: Qt.Orientation,
                   role: int = Qt.DisplayRole) -> str:
        """
        Derived functions which returns the header data for the given section, orientation and role
        :param section: section of the requested header data
        :param orientation: orientation of the requested header data
        :param role: role of the requested header data
        :return: returns the header data for the given section, orientation and role
        """
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            if -1 < section < len(self.__header_labels):
                return self.__header_labels[section]
        return super(QAbstractTableModel,
                     self).headerData(section, orientation, role)

    # noinspection PyMethodOverriding
    def insertRow(self, row: int, data: PropertyImportData) -> bool:
        """
        inserts a new row into the model. Derived and adapted function.
        :param row: row index where to insert the new row
        :param data: data to be insert
        :return: True, if the insert was performed successfully, else False
        """
        self.beginInsertRows(QModelIndex(), row, row)
        if row < 0:
            self.endInsertRows()
            return False
        self.__data_list.insert(row, data)
        self.endInsertRows()
        return True

    # noinspection PyMethodOverriding
    def removeRow(self, row: int) -> bool:
        """
        Removes the row at the given index "row".
        :param row: index of the row to be removed
        :return: True, if the row was removed successfully, else False
        """
        self.beginRemoveRows(QModelIndex(), row, row)
        if 0 <= row < self.rowCount():
            del self.__data_list[row]
            self.endRemoveRows()
            return True
        self.endRemoveRows()
        return False

    def row(self, index: int) -> PropertyImportData or None:
        """
        returns PropertyImportData-item at given index
        :param index: index of requested PropertyImportData-item
        :return: returns the item at given index
        """
        if 0 <= index < self.rowCount():
            return self.__data_list[index]
        return None

    def rowCount(self, parent: QModelIndex = ...) -> int:
        """
        returns the current row count of the table model
        :param parent: redundant parameter as this derived class isn't a tree model
        :return: returns the current row count of the table model
        """
        return len(self.__data_list)

    def setData(self,
                index: QModelIndex,
                value: str,
                role: int = Qt.EditRole) -> bool:
        """
        Sets the current data at the given model index and role to value
        :param index: model index to be changed (only Type is editable, column 2)
        :param value: new value to be set
        :param role: role of data
        :return: True, if the data was set successfully, else False
        """
        if not index.isValid():
            return False
        if role == Qt.EditRole and index.column(
        ) == 1 and not self.__only_numbers:
            if str(value).lower() == "integer":
                self.__data_list[index.row()].property_type = PropertyTypes.INT
            elif str(value).lower() == "float":
                self.__data_list[
                    index.row()].property_type = PropertyTypes.FLOAT
            else:
                self.__data_list[
                    index.row()].property_type = PropertyTypes.STRING

            self.logger.debug("Setting data for index [{}, {}]: {}".format(
                index.column(), index.row(),
                self.__data_list[index.row()].property_type))
            # noinspection PyUnresolvedReferences
            self.dataChanged.emit(index, index, [Qt.EditRole])
        super().setData(index, value, role)
        return True
Esempio n. 16
0
class DatabaseController(QObject):
    """
    Controller class for database interaction
    """
    def __init__(self, settings: SettingsDialog = None) -> None:
        """
        Constructor
        :param settings: settings dialog
        :return: nothing
        """
        QObject.__init__(self)

        self.logger = QGISLogHandler(DatabaseController.__name__)

        self.__db_service = DatabaseService.get_instance()
        self.__last_db_settings = dict()

        self.__settings = None
        self.__config = ConfigHandler()
        self.settings = settings

        db_type = self.__config.get("General", "db_type")
        if db_type != "" and db_type in ["SQLite", "PostgreSQL"]:
            self.settings.DB_type.setCurrentText(db_type)
        else:
            db_type = "SQLite"
            self.__config.set("General", "db_type", db_type)
            self.settings.DB_type.setCurrentText(db_type)

        self.__on_db_type_changed(db_type)

    #
    # slots
    #

    def __on_db_type_changed(self, db_type: str):
        """
        Slot called, when the database type was changed to show / hide specific elements
        :param db_type: selected database type
        :return: nothing
        :raises ValueError: if type is unknown
        """
        self.logger.debug("Selecting new database type: {}".format(db_type))
        if db_type == "SQLite":
            self.settings.create_DB_button.show()
            self.settings.select_DB_button.show()
            self.settings.password_label.hide()
            self.settings.password.hide()
            self.settings.username_label.hide()
            self.settings.username.hide()
            self.settings.save_password.hide()

            tempdir = "/tmp" if platform.system(
            ) == "Darwin" else tempfile.gettempdir()
            filename = "geology.sqlite"
            self.settings.database_connection.setPlaceholderText(
                os.path.join(tempdir, filename))

        elif db_type == "PostgreSQL":
            self.settings.create_DB_button.hide()
            self.settings.select_DB_button.hide()
            self.settings.password_label.show()
            self.settings.password.show()
            self.settings.username_label.show()
            self.settings.username.show()
            if found_keyring:
                self.settings.save_password.show()
            else:
                self.settings.save_password.hide()

            self.settings.database_connection.setPlaceholderText(
                "localhost:5432/geology")
            self.settings.username.setPlaceholderText("postgres")
            self.settings.password.setPlaceholderText("")
        else:
            self.settings.database_connection.setText("")
            self.settings.username.setText("")
            self.settings.password.setText("")
            self.settings.database_connection.setPlaceholderText("")
            self.settings.username.setPlaceholderText("")
            self.settings.password.setPlaceholderText("")
            raise ValueError("Unknown DB Format: {}".format(db_type))

        if self.__config.has_section(db_type):
            self.settings.database_connection.setText(
                self.__config.get(db_type, "connection"))
            self.settings.username.setText(
                self.__config.get(db_type, "username"))
            self.settings.password.setText("")

        else:
            self.settings.database_connection.setText("")
            self.settings.username.setText("")
            self.settings.password.setText("")

        self.__update_db_service()

    def __on_create_db_clicked(self) -> None:
        """
        slot for creating a new database
        :return: Nothing
        """

        self.__validate()
        # noinspection PyCallByClass,PyArgumentList
        filename = get_file_name(
            QFileDialog.getSaveFileName(
                parent=self.settings,
                caption="Select database file",
                directory="",
                filter="Databases(*.db *.sqlite *.data);;Any File Type (*)"))

        if filename != "":
            # noinspection PyTypeChecker
            if os.path.splitext(filename)[-1].lower().lstrip('.') not in [
                    "db", "data", "sqlite"
            ]:
                filename += ".data"
            self.settings.database_connection.setText(filename)

    def __on_select_db(self) -> None:
        """
        slot for selecting a sqlite database file and set the result to the related lineedit
        :return: Nothing
        """

        self.__validate()
        # noinspection PyCallByClass,PyArgumentList
        filename = get_file_name(
            QFileDialog.getOpenFileName(
                self.settings, "Select database file", "",
                "Databases(*.db *.sqlite *.data);;Any File Type (*)"))

        if filename != "":
            # noinspection PyTypeChecker
            if os.path.splitext(filename)[-1].lower().lstrip('.') not in [
                    "db", "data", "sqlite"
            ]:
                filename += ".data"
            self.settings.database_connection.setText(filename)

    def __on_check_connection(self):
        """
        Check the requested database connection
        :return: if the connection check was successful
        :raises ValueError: if database type is unknown
        """
        result = self.__db_service.check_connection()

        if result == "":
            self.logger.info("Connection test successful")
            return True

        else:
            self.logger.error("connection test failed", result)
            return False

    def __on_save(self):
        self.__last_db_settings = self.__get_db_settings()
        self.__update_db_service()
        if self.__on_check_connection():
            self.__update_config()
            self.settings.accept()
        else:
            self.__restore_db_settings(self.__last_db_settings)

    def __on_cancel(self):
        self.__on_db_type_changed(self.settings.DB_type.currentText())
        self.settings.reject()

    #
    # private functions
    #

    def __update_db_service(self, _: object = None) -> None:
        """
        Update the database service, if a GUI input element changed
        :param _: temporary parameter for QLineEdit update
        :return: Nothing
        """

        self.__db_service.db_type = self.settings.DB_type.currentText()
        self.__db_service.connection = self.settings.database_connection.text()
        self.__db_service.username = self.settings.username.text()
        self.__db_service.password = self.settings.password.text()

        if self.__db_service.connection == "":
            self.__db_service.connection = self.settings.database_connection.placeholderText(
            )
        if self.__db_service.username == "":
            self.__db_service.username = self.settings.username.placeholderText(
            )
        if self.__db_service.password == "" and found_keyring:
            # empty password ? try request from system keystore
            self.__db_service.password = keyring.get_password(
                "Postgres {}".format(self.__db_service.connection),
                self.__db_service.username)

        # self.logger.debug("Connection settings:\ndatabase:\t{}\nconnection:\t{}\nusername:\t{}\npassword:\t{}".format(
        #    self.__db_service.db_type, self.__db_service.connection,
        #    self.__db_service.username, self.__db_service.password))

    def __get_db_settings(self) -> Dict:
        """
        Returns the current database connection settings as dictionary
        :return: current database connection settings
        """
        return {
            "db": self.__db_service.db_type,
            "connection": self.__db_service.connection,
            "username": self.__db_service.username,
            "password": self.__db_service.password
        }

    def __restore_db_settings(self, values: Dict) -> None:
        """
        Restores the database connection settings
        :param values: dictionary with connection settings
        :return: Nothing
        """
        try:
            self.__db_service.db_type = values["db"]
            self.__db_service.connection = values["connection"]
            self.__db_service.username = values["username"]
            self.__db_service.password = values["password"]
        except KeyError as e:
            self.logger.error("Can't restore database settings: {}", str(e))

    def __update_config(self):
        db_type = self.settings.DB_type.currentText()

        if db_type == "PostgreSQL":
            self.__config.set("PostgreSQL", "connection",
                              self.__db_service.connection)
            self.__config.set("PostgreSQL", "username",
                              self.__db_service.username)

            if self.settings.save_password.isChecked() and found_keyring:
                keyring.set_password(
                    "Postgres {}".format(self.__db_service.connection),
                    self.__db_service.username, self.__db_service.password)

        elif db_type == "SQLite":
            self.__config.set("SQLite", "connection",
                              self.__db_service.connection)

        if db_type in ["PostgreSQL", "SQLite"]:
            self.__config.set("General", "db_type", db_type)
            self.__on_db_type_changed(self.__config.get("General", "db_type"))

    def __validate(self):
        """
        Validates, if the service can be executed
        :return: Nothing
        :raises
        """

        if self.settings is None:
            raise AttributeError("No settings dialog is set")

        if self.__config is None:
            raise AttributeError("No config is set")

    #
    # setter and getter
    #

    @property
    def settings(self) -> SettingsDialog:
        """
        Returns the currently active settings dialog
        :return: returns the currently active settings dialog
        """
        return self.__settings

    @settings.setter
    def settings(self, value: SettingsDialog) -> None:
        """
        Sets the currently active settings dialog
        :return: returns the currently active settings dialog
        :raises TypeError: if value is not of type SettingsDialog
        """

        if isinstance(value, SettingsDialog):
            if self.__settings is not None:
                self.__settings.create_DB_button.clicked.disconnect(
                    self.__on_create_db_clicked)
                self.__settings.select_DB_button.clicked.disconnect(
                    self.__on_select_db)
                self.__settings.DB_type.currentIndexChanged[str].disconnect(
                    self.__on_db_type_changed)
                self.__settings.save_button.clicked.disconnect(self.__on_save)
                self.__settings.cancel_button.clicked.disconnect(
                    self.__on_cancel)

            self.__settings = value

            self.__settings.create_DB_button.clicked.connect(
                self.__on_create_db_clicked)
            self.__settings.select_DB_button.clicked.connect(
                self.__on_select_db)
            self.__settings.DB_type.currentIndexChanged[str].connect(
                self.__on_db_type_changed)
            self.__settings.save_button.clicked.connect(self.__on_save)
            self.__settings.cancel_button.clicked.connect(self.__on_cancel)

        else:
            raise TypeError(
                "committed parameter is not of type SettingsDialog")
    def run(self) -> None:
        """Run method that loads and starts the plugin"""

        if not self.pluginIsActive:
            self.pluginIsActive = True

            try:
                # initialize logger
                logger = QGISLogHandler()
                logger.qgis_iface = self.iface
                logger.save_to_file = True

                if packages_found == "NO_PACKAGES" or not ModuleService.check_required_modules():
                    logger.info("installing or updating packages")
                    if not ModuleService.install_packages():
                        logger.error("package installation failed, please restart QGIS to try again.")
                    else:
                        logger.info("package installation successful, please restart QGIS")
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Information)
                        msg.setText("packages installation successful")
                        msg.setInformativeText("Please restart QGIS to use the GeologicalDataProcessing extension")

                        msg.setWindowTitle("package update")
                        msg.exec_()
                        return

                    return
                else:
                    logger.debug("all required packages up2date")

                # dockwidget may not exist if:
                #    first run of plugin
                #    removed on close (see self.onClosePlugin method)
                if self.dockwidget is None:
                    # Create the dockwidget (after translation) and keep reference
                    self.dockwidget = GeologicalDataProcessingDockWidget()

                if self.settings_dialog is None:
                    self.settings_dialog = SettingsDialog(parent=self.dockwidget)
                    self.settings_dialog.setModal(True)
                    self.dockwidget.settings_button.clicked.connect(self.settings_dialog.exec)

                # connect to provide cleanup on closing of dockwidget
                self.dockwidget.closingPlugin.connect(self.onClosePlugin)

                # show the dockwidget
                # TODO: fix to allow choice of dock location
                self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dockwidget)
                self.dockwidget.show()

                from GeologicalDataProcessing.controller.database_controller import DatabaseController
                from GeologicalDataProcessing.services.import_service import ImportService
                from GeologicalDataProcessing.views.import_views import LineImportView, PointImportView, \
                    WellImportView, PropertyImportView, WellLogImportView

                ImportService.get_instance(self.dockwidget)

                # initialize the gui and connect signals and slots
                self.dockwidget.import_type.currentChanged.connect(self.on_import_type_changed_event)

                # start tests button
                # -> only visible and active when the debug flag is True
                if config.debug:
                    self.dockwidget.start_tests_button.clicked.connect(self.on_start_tests)
                else:
                    self.dockwidget.start_tests_button.setVisible(False)
                    self.dockwidget.start_tests_separator.setVisible(False)

                self.dockwidget.progress_bar_layout.setVisible(False)

                self.__views["import_points"] = PointImportView(self.dockwidget)
                self.__views["import_lines"] = LineImportView(self.dockwidget)
                self.__views["import_wells"] = WellImportView(self.dockwidget)
                self.__views["import_properties"] = PropertyImportView(self.dockwidget)
                self.__views["import_well_logs"] = WellLogImportView(self.dockwidget)

                self.__db_controller = DatabaseController(self.settings_dialog)

                if config.debug:
                    self.dockwidget.import_file.setText(
                        "/Users/stephan/Library/Application Support/QGIS/QGIS3/profiles/" +
                        "default/python/plugins/GeologicalDataProcessing/tests/test_data/point_data.txt")

            except Exception as e:
                ExceptionHandler(e).log()