From 9440642a2799121a21a9a414c6bc7ae5cd3a5185 Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 7 Oct 2025 17:59:01 +0200 Subject: [PATCH 01/12] processing algoritms --- modelbaker/processing/__init__.py | 0 modelbaker/processing/ili2db_algorithm.py | 324 ++++++++++++++++ modelbaker/processing/ili2db_exporting.py | 401 +++++++++++++++++++ modelbaker/processing/ili2db_importing.py | 377 ++++++++++++++++++ modelbaker/processing/ili2db_validating.py | 430 +++++++++++++++++++++ modelbaker/processing/images/interlis.png | Bin 0 -> 4503 bytes modelbaker/utils/globals.py | 69 ++++ tests/test_processing.py | 232 +++++++++++ 8 files changed, 1833 insertions(+) create mode 100644 modelbaker/processing/__init__.py create mode 100644 modelbaker/processing/ili2db_algorithm.py create mode 100644 modelbaker/processing/ili2db_exporting.py create mode 100644 modelbaker/processing/ili2db_importing.py create mode 100644 modelbaker/processing/ili2db_validating.py create mode 100644 modelbaker/processing/images/interlis.png create mode 100644 tests/test_processing.py diff --git a/modelbaker/processing/__init__.py b/modelbaker/processing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modelbaker/processing/ili2db_algorithm.py b/modelbaker/processing/ili2db_algorithm.py new file mode 100644 index 00000000..666d9887 --- /dev/null +++ b/modelbaker/processing/ili2db_algorithm.py @@ -0,0 +1,324 @@ +import os +from abc import abstractmethod + +from qgis.core import ( + QgsProcessingAlgorithm, + QgsProcessingOutputFile, + QgsProcessingOutputNumber, + QgsProcessingOutputString, + QgsProcessingParameterAuthConfig, + QgsProcessingParameterEnum, + QgsProcessingParameterFile, + QgsProcessingParameterNumber, + QgsProcessingParameterString, +) +from qgis.PyQt.QtCore import QCoreApplication, QSettings +from qgis.PyQt.QtGui import QIcon + +from ..iliwrapper.ili2dbconfig import BaseConfiguration +from ..utils.db_utils import get_authconfig_map, get_service_config + + +class Ili2dbAlgorithm(QgsProcessingAlgorithm): + def __init__(self): + super().__init__() + + def group(self): + return self.tr("ili2db") + + def groupId(self): + return "ili2db" + + def icon(self): + return QIcon(os.path.join(os.path.dirname(__file__), "images/interlis.png")) + + def tr(self, string): + return QCoreApplication.translate("Processing", string) + + def createInstance(self): + return self.__class__() + + @abstractmethod + def connection_input_params(self): + return [] + + @abstractmethod + def connection_output_params(self): + return [] + + @abstractmethod + def get_db_configuration_from_input(self, parameters, context, configuration): + return False + + @abstractmethod + def get_output_from_db_configuration(self, configuration): + return {} + + @abstractmethod + def get_settings_configuration_from_input(self, parameters, context, configuration): + return + + def current_baseconfig(self): + baseconfig = BaseConfiguration() + settings = QSettings() + settings.beginGroup("QgisModelBaker/ili2db") + baseconfig.restore(settings) + return baseconfig + + +class Ili2pgAlgorithm(Ili2dbAlgorithm): + + SERVICE = "SERVICE" + HOST = "HOST" + DBNAME = "DBNAME" + PORT = "PORT" + USERNAME = "USERNAME" + PASSWORD = "PASSWORD" + SCHEMA = "SCHEMA" + SSLMODE = "SSLMODE" + AUTHCFG = "AUTHCFG" + + def __init__(self): + super().__init__() + + def group(self): + return self.tr("ili2db") + + def groupId(self): + return "ili2db" + + def icon(self): + return QIcon(os.path.join(os.path.dirname(__file__), "../images/interlis.png")) + + def connection_input_params(self): + params = [] + + service_param = QgsProcessingParameterString( + self.SERVICE, + self.tr("Service"), + None, + optional=True, + ) + service_param.setHelp(self.tr("The PostgreSQL service config file.")) + params.append(service_param) + + host_param = QgsProcessingParameterString( + self.HOST, + self.tr("Host"), + defaultValue="localhost", + optional=True, + ) + host_param.setHelp( + self.tr("The host of the database server. By default is localhost.") + ) + params.append(host_param) + + port_param = QgsProcessingParameterNumber( + self.PORT, + self.tr("Port"), + type=QgsProcessingParameterNumber.Type.Integer, + defaultValue=5432, + optional=True, + ) + port_param.setHelp( + self.tr("The port of the database server. By default is 5432.") + ) + params.append(port_param) + + dbname_param = QgsProcessingParameterString( + self.DBNAME, + self.tr("Database"), + defaultValue=None, + optional=True, + ) + dbname_param.setHelp(self.tr("The database name. The database should exist.")) + params.append(dbname_param) + + schema_param = QgsProcessingParameterString( + self.SCHEMA, + self.tr("Schema"), + defaultValue=None, + optional=True, + ) + schema_param.setHelp(self.tr("The database schema.")) + params.append(schema_param) + + sslmode_param = QgsProcessingParameterEnum( + self.SSLMODE, + self.tr("SSL Mode"), + ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"], + defaultValue=None, + optional=True, + usesStaticStrings=True, + ) + sslmode_param.setHelp(self.tr("The SSL mode if needed.")) + params.append(sslmode_param) + username_param = QgsProcessingParameterString( + self.USERNAME, + self.tr("Username"), + defaultValue=None, + optional=True, + ) + username_param.setHelp(self.tr("The username to access the database.")) + params.append(username_param) + + password_param = QgsProcessingParameterString( + self.PASSWORD, + self.tr("Password"), + defaultValue=None, + optional=True, + ) + password_param.setHelp(self.tr("The password of the user.")) + params.append(password_param) + + authcfg_param = QgsProcessingParameterAuthConfig( + self.AUTHCFG, + self.tr("Authentification"), + defaultValue=None, + optional=True, + ) + authcfg_param.setHelp( + self.tr( + "When choosing a QGIS Autentification you don't need username and password." + ) + ) + params.append(authcfg_param) + + return params + + def connection_output_params(self): + params = [] + + # outputs for pass through + params.append(QgsProcessingOutputString(self.SERVICE, self.tr("Service"))) + params.append(QgsProcessingOutputString(self.HOST, self.tr("Host"))) + params.append(QgsProcessingOutputString(self.DBNAME, self.tr("Database Name"))) + params.append(QgsProcessingOutputNumber(self.PORT, self.tr("Port Number"))) + params.append(QgsProcessingOutputString(self.USERNAME, self.tr("Username"))) + params.append(QgsProcessingOutputString(self.PASSWORD, self.tr("Password"))) + params.append(QgsProcessingOutputString(self.SCHEMA, self.tr("Schema"))) + params.append(QgsProcessingOutputString(self.SSLMODE, self.tr("SSL Mode"))) + params.append( + QgsProcessingOutputString(self.AUTHCFG, self.tr("Authentication")) + ) + + return params + + def get_db_configuration_from_input(self, parameters, context, configuration): + """ + Returns true if mandatory parameters are given + """ + + configuration.dbservice = self.parameterAsString( + parameters, self.SERVICE, context + ) + service_map, _ = get_service_config(configuration.dbservice) + + if self.parameterAsString(parameters, self.AUTHCFG, context): + configuration.dbauthid = self.parameterAsString( + parameters, self.AUTHCFG, context + ) # needed for passthroug + authconfig_map = get_authconfig_map(configuration.dbauthid) + configuration.dbusr = authconfig_map.get("username") + configuration.dbpwd = authconfig_map.get("password") + else: + configuration.dbusr = self.parameterAsString( + parameters, self.USERNAME, context + ) or service_map.get("user") + configuration.dbpwd = self.parameterAsString( + parameters, self.PASSWORD, context + ) or service_map.get("password") + configuration.dbhost = self.parameterAsString( + parameters, self.HOST, context + ) or service_map.get("host") + configuration.dbport = str( + self.parameterAsInt(parameters, self.PORT, context) + ) or service_map.get("port") + configuration.database = self.parameterAsString( + parameters, self.DBNAME, context + ) or service_map.get("dbname") + configuration.dbschema = self.parameterAsString( + parameters, self.SCHEMA, context + ) + configuration.sslmode = self.parameterAsEnum(parameters, self.SSLMODE, context) + valid = bool( + configuration.dbhost and configuration.database and configuration.dbschema + ) + return valid + + def get_output_from_db_configuration(self, configuration): + """ + Returns an output map + """ + + output_map = { + self.SERVICE: configuration.dbservice, + self.HOST: configuration.dbhost, + self.DBNAME: configuration.database, + self.PORT: configuration.dbport, + self.USERNAME: configuration.dbusr, + self.PASSWORD: configuration.dbpwd, + self.SCHEMA: configuration.dbschema, + self.SSLMODE: configuration.sslmode, + self.AUTHCFG: configuration.dbauthid, + } + return output_map + + +class Ili2gpkgAlgorithm(Ili2dbAlgorithm): + + DBPATH = "DBPATH" + + def __init__(self): + super().__init__() + + def group(self): + return self.tr("ili2db") + + def groupId(self): + return "ili2db" + + def icon(self): + return QIcon(os.path.join(os.path.dirname(__file__), "../images/interlis.png")) + + def connection_input_params(self): + params = [] + + dbpath_param = QgsProcessingParameterFile( + self.DBPATH, + self.tr("Database File"), + defaultValue=None, + optional=True, + ) + dbpath_param.setHelp(self.tr("todo")) + params.append(dbpath_param) + + return params + + def connection_output_params(self): + params = [] + + params.append( + QgsProcessingOutputFile(self.DBPATH, self.tr("Databasefile Path")) + ) + + return params + + def get_db_configuration_from_input(self, parameters, context, configuration): + """ + Returns true if mandatory parameters are given + """ + valid = False + dbpath = self.parameterAsFile(parameters, self.DBPATH, context) + if dbpath and dbpath.endswith(".gpkg"): + configuration.dbfile = dbpath + valid = True + return valid + + def get_output_from_db_configuration(self, configuration): + """ + Returns an output map + """ + + output_map = {self.DBPATH: configuration.dbfile} + return output_map diff --git a/modelbaker/processing/ili2db_exporting.py b/modelbaker/processing/ili2db_exporting.py new file mode 100644 index 00000000..8791b127 --- /dev/null +++ b/modelbaker/processing/ili2db_exporting.py @@ -0,0 +1,401 @@ +""" +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +import re +from typing import Any, Optional + +from qgis.core import ( + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingOutputBoolean, + QgsProcessingOutputFile, + QgsProcessingParameterBoolean, + QgsProcessingParameterEnum, + QgsProcessingParameterFileDestination, + QgsProcessingParameterString, +) +from qgis.PyQt.QtCore import QCoreApplication, QObject + +from ..iliwrapper import iliexporter +from ..iliwrapper.globals import DbIliMode +from ..iliwrapper.ili2dbconfig import ExportConfiguration +from ..iliwrapper.ili2dbutils import JavaNotFoundError +from ..utils.db_utils import get_db_connector +from ..utils.globals import MODELS_BLACKLIST +from .ili2db_algorithm import Ili2gpkgAlgorithm, Ili2pgAlgorithm + + +class ProcessExporter(QObject): + + # Filters + FILTERMODE = "FILTERMODE" # none, models, baskets or datasets + FILTER = "FILTER" # model, basket or dataset names + + # Settings + EXPORTMODEL = "EXPORTMODEL" + DISABLEVALIDATION = "DISABLEVALIDATION" + + XTFFILEPATH = "XTFFILEPATH" + + # Result + ISVALID = "ISVALID" + + def __init__(self, parent): + super().__init__() + self.parent = parent + + def export_input_params(self): + params = [] + + xtffile_param = QgsProcessingParameterFileDestination( + self.XTFFILEPATH, + self.tr("Target Transferfile (XTF)"), + self.tr("INTERLIS Transferfile (*.xtf *.xml)"), + defaultValue=None, + optional=False, + ) + xtffile_param.setHelp( + self.tr("The XTF File where the data should be exported to.") + ) + params.append(xtffile_param) + + filtermode_param = QgsProcessingParameterEnum( + self.FILTERMODE, + self.tr("Filter Mode"), + ["Models", "Baskets", "Datasets"], + optional=True, + usesStaticStrings=True, + ) + filtermode_param.setHelp( + self.tr( + "Whether data should be filtered according to the models, baskets or datasets in which they are stored." + ) + ) + params.append(filtermode_param) + + filter_param = QgsProcessingParameterString( + self.FILTER, self.tr("Filter (semicolon-separated)"), optional=True + ) + filter_param.setHelp( + self.tr( + "A semicolon-separated list of the relevant models, baskets or datasets" + ) + ) + params.append(filter_param) + + exportmodel_param = QgsProcessingParameterString( + self.EXPORTMODEL, + self.tr("Export Models"), + optional=True, + ) + exportmodel_param.setHelp( + self.tr( + """ +

If your data is in the format of the cantonal model, but you want to export it in the format of the national model you need to define this here.

+

Usually, this is one single model. However, it is also possible to pass multiple models, which makes sense if there are multiple base models in the schema you want to export.

+ """ + ) + ) + params.append(exportmodel_param) + + disablevalidation_param = QgsProcessingParameterBoolean( + self.DISABLEVALIDATION, + self.tr("Disable validaton of the data"), + defaultValue=False, + ) + disablevalidation_param.setHelp( + self.tr( + "Ignores geometry errors (--skipGeometryErrors) and constraint validation (--disableValidation)" + ) + ) + params.append(disablevalidation_param) + + return params + + def export_output_params(self): + params = [] + + params.append( + QgsProcessingOutputBoolean(self.ISVALID, self.tr("Export Result")) + ) + params.append( + QgsProcessingOutputFile(self.XTFFILEPATH, self.tr("Transferfile Path")) + ) + + return params + + def initParameters(self): + for connection_input_param in self.parent.connection_input_params(): + self.parent.addParameter(connection_input_param) + for connection_output_param in self.parent.connection_output_params(): + self.parent.addOutput(connection_output_param) + + for export_input_param in self.export_input_params(): + self.parent.addParameter(export_input_param) + for export_output_param in self.export_output_params(): + self.parent.addOutput(export_output_param) + + def run(self, configuration, feedback): + # run + exporter = iliexporter.Exporter(self) + exporter.tool = configuration.tool + + # to do superuser finden? und auch dpparams? + exporter.configuration = configuration + exporter.stdout.connect(feedback.pushInfo) + exporter.stderr.connect(feedback.pushInfo) + + if feedback.isCanceled(): + return {} + + isvalid = False + try: + feedback.pushInfo(f"Run: {exporter.command(True)}") + result = exporter.run(None) + if result == iliexporter.Exporter.SUCCESS: + feedback.pushInfo(self.tr("... export succeeded")) + else: + feedback.pushWarning(self.tr("... export failed")) + isvalid = bool(result == iliexporter.Validator.SUCCESS) + except JavaNotFoundError as e: + raise QgsProcessingException( + self.tr("Java not found error:").format(e.error_string) + ) + + return {self.ISVALID: isvalid, self.XTFFILEPATH: configuration.xtffile} + + def get_configuration_from_input(self, parameters, context, tool): + + configuration = ExportConfiguration() + configuration.base_configuration = self.parent.current_baseconfig() + configuration.tool = tool + + # get database settings form the parent + if not self.parent.get_db_configuration_from_input( + parameters, context, configuration + ): + return None + + # get settings according to the db + configuration.with_exporttid = self._get_tid_handling(configuration) + + # get settings from the input + filtermode = self.parent.parameterAsString(parameters, self.FILTERMODE, context) + filters = self.parent.parameterAsString(parameters, self.FILTER, context) + if filtermode == "Models" and filters: + configuration.ilimodels = filters + elif filtermode == "Datasets" and filters: + configuration.dataset = filters + elif filtermode == "Baskets" and filters: + configuration.baskets = filters + else: + configuration.ilimodels = ";".join(self._get_model_names(configuration)) + + exportmodels = self.parent.parameterAsString( + parameters, self.EXPORTMODEL, context + ) + if exportmodels: + configuration.iliexportmodels = exportmodels + + configuration.disable_validation = self.parent.parameterAsBool( + parameters, self.DISABLEVALIDATION, context + ) + + configuration.xtffile = self.parent.parameterAsFile( + parameters, self.XTFFILEPATH, context + ) + + return configuration + + def _get_tid_handling(self, configuration): + db_connector = get_db_connector(configuration) + if db_connector: + return db_connector.get_tid_handling() + return False + + def _get_model_names(self, configuration): + modelnames = [] + + db_connector = get_db_connector(configuration) + if ( + db_connector + and db_connector.db_or_schema_exists() + and db_connector.metadata_exists() + ): + db_models = db_connector.get_models() + regex = re.compile(r"(?:\{[^\}]*\}|\s)") + for db_model in db_models: + for modelname in regex.split(db_model["modelname"]): + name = modelname.strip() + if name and name not in modelnames and name not in MODELS_BLACKLIST: + modelnames.append(name) + return modelnames + + def tr(self, string): + return QCoreApplication.translate("Processing", string) + + +class ExportingPGAlgorithm(Ili2pgAlgorithm): + """ + This is an algorithm from Model Baker. + It is meant for the data export from a PostgreSQL database. + """ + + def __init__(self): + super().__init__() + + # initialize the exporter with self as parent + self.exporter = ProcessExporter(self) + + def name(self) -> str: + """ + Returns the algorithm name, used for identifying the algorithm. + """ + return "modelbaker_ili2pg_exporter" + + def displayName(self) -> str: + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Export with ili2pg (PostGIS)") + + def tags(self) -> list[str]: + + return [ + "modelbaker", + "interlis", + "model", + "baker", + "export", + "transferfile", + "xtf" "ili2db", + "ili2pg", + "Postgres", + "PostGIS", + ] + + def shortDescription(self) -> str: + """ + Returns a short description string for the algorithm. + """ + return self.tr("Exports data from a PostgreSQL schema with ili2db.") + + def shortHelpString(self) -> str: + """ + Returns a short helper string for the algorithm. + """ + return self.tr("Exports data from a PostgreSQL schema with ili2db.") + + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): + self.exporter.initParameters() + + def processAlgorithm( + self, + parameters: dict[str, Any], + context: QgsProcessingContext, + feedback: QgsProcessingFeedback, + ) -> dict[str, Any]: + """ + Here is where the processing itself takes place. + """ + output_map = {} + configuration = self.exporter.get_configuration_from_input( + parameters, context, DbIliMode.pg + ) + if not configuration: + raise QgsProcessingException( + self.tr("Invalid input parameters. Cannot start export") + ) + else: + output_map.update(self.exporter.run(configuration, feedback)) + output_map.update(self.get_output_from_db_configuration(configuration)) + return output_map + + +class ExportingGPKGAlgorithm(Ili2gpkgAlgorithm): + """ + This is an algorithm from Model Baker. + It is meant for the data export from a GeoPackage file. + """ + + def __init__(self): + super().__init__() + + # initialize the exporter with self as parent + self.exporter = ProcessExporter(self) + + def name(self) -> str: + """ + Returns the algorithm name, used for identifying the algorithm. + """ + return "modelbaker_ili2gpkg_exporter" + + def displayName(self) -> str: + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Export with ili2gpkg (GeoPackage)") + + def tags(self) -> list[str]: + + return [ + "modelbaker", + "interlis", + "model", + "baker", + "export", + "transferfile", + "xtf", + "ili2db", + "ili2gpkg", + "GeoPackage", + "GPKG", + ] + + def shortDescription(self) -> str: + """ + Returns a short description string for the algorithm. + """ + return self.tr("Exports data from a GeoPackage file with ili2db.") + + def shortHelpString(self) -> str: + """ + Returns a short helper string for the algorithm. + """ + return self.tr("Exports data from a GeoPackage file with ili2db.") + + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): + self.exporter.initParameters() + + def processAlgorithm( + self, + parameters: dict[str, Any], + context: QgsProcessingContext, + feedback: QgsProcessingFeedback, + ) -> dict[str, Any]: + """ + Here is where the processing itself takes place. + """ + output_map = {} + configuration = self.exporter.get_configuration_from_input( + parameters, context, DbIliMode.gpkg + ) + if not configuration: + raise QgsProcessingException( + self.tr("Invalid input parameters. Cannot start export") + ) + else: + output_map.update(self.exporter.run(configuration, feedback)) + output_map.update(self.get_output_from_db_configuration(configuration)) + return output_map diff --git a/modelbaker/processing/ili2db_importing.py b/modelbaker/processing/ili2db_importing.py new file mode 100644 index 00000000..d5045a84 --- /dev/null +++ b/modelbaker/processing/ili2db_importing.py @@ -0,0 +1,377 @@ +""" +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +import re +from typing import Any, Optional + +from qgis.core import ( + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingOutputBoolean, + QgsProcessingParameterBoolean, + QgsProcessingParameterFile, + QgsProcessingParameterString, +) +from qgis.PyQt.QtCore import QCoreApplication, QObject + +from ..iliwrapper import iliimporter +from ..iliwrapper.globals import DbIliMode +from ..iliwrapper.ili2dbconfig import ( + Ili2DbCommandConfiguration, + ImportDataConfiguration, + UpdateDataConfiguration, +) +from ..iliwrapper.ili2dbutils import JavaNotFoundError +from ..utils.db_utils import get_db_connector +from ..utils.globals import MODELS_BLACKLIST +from .ili2db_algorithm import Ili2gpkgAlgorithm, Ili2pgAlgorithm + + +class ProcessImporter(QObject): + + # Settings + DISABLEVALIDATION = "DISABLEVALIDATION" + DATASET = "DATASET" + DELETEDATA = "DELETEDATA" + + XTFFILEPATH = "XTFFILEPATH" + + # Result + ISVALID = "ISVALID" + + def __init__(self, parent): + super().__init__() + self.parent = parent + + def import_input_params(self): + params = [] + + xtffile_param = QgsProcessingParameterFile( + self.XTFFILEPATH, + self.tr("Source Transferfile (XTF)"), + defaultValue=None, + optional=False, + ) + xtffile_param.setHelp( + self.tr("The XTF File where the data should be imported from.") + ) + params.append(xtffile_param) + + dataset_param = QgsProcessingParameterString( + self.DATASET, self.tr("Dataset name"), optional=True + ) + dataset_param.setHelp( + self.tr( + "The dataset the data should be connected to (works only when basket handling is active in the existing database)." + ) + ) + params.append(dataset_param) + + deletedata_param = QgsProcessingParameterBoolean( + self.DELETEDATA, + self.tr("Delete data first"), + defaultValue=False, + ) + deletedata_param.setHelp( + self.tr( + "Deletes previously existing data from the target database (when basket handling active it performs a --replace instead of an --update)" + ) + ) + params.append(deletedata_param) + + disablevalidation_param = QgsProcessingParameterBoolean( + self.DISABLEVALIDATION, + self.tr("Disable validaton of the data"), + defaultValue=False, + ) + disablevalidation_param.setHelp( + self.tr( + "Ignores geometry errors (--skipGeometryErrors) and constraint validation (--disableValidation)" + ) + ) + params.append(disablevalidation_param) + + return params + + def import_output_params(self): + params = [] + + params.append( + QgsProcessingOutputBoolean(self.ISVALID, self.tr("Import Result")) + ) + + return params + + def initParameters(self): + for connection_input_param in self.parent.connection_input_params(): + self.parent.addParameter(connection_input_param) + for connection_output_param in self.parent.connection_output_params(): + self.parent.addOutput(connection_output_param) + + for import_input_param in self.import_input_params(): + self.parent.addParameter(import_input_param) + for import_output_param in self.import_output_params(): + self.parent.addOutput(import_output_param) + + def run(self, configuration, feedback): + + # run + importer = iliimporter.Importer(self) + importer.tool = configuration.tool + + # to do superuser finden? und auch dpparams? + importer.configuration = configuration + importer.stdout.connect(feedback.pushInfo) + importer.stderr.connect(feedback.pushInfo) + + if feedback.isCanceled(): + return {} + + isvalid = False + try: + feedback.pushInfo(f"Run: {importer.command(True)}") + result = importer.run(None) + if result == iliimporter.Importer.SUCCESS: + feedback.pushInfo(self.tr("... import succeeded")) + else: + feedback.pushWarning(self.tr("... import failed")) + isvalid = bool(result == iliimporter.Validator.SUCCESS) + except JavaNotFoundError as e: + raise QgsProcessingException( + self.tr("Java not found error:").format(e.error_string) + ) + + return {self.ISVALID: isvalid, self.XTFFILEPATH: configuration.xtffile} + + def get_configuration_from_input(self, parameters, context, tool): + + configuration = Ili2DbCommandConfiguration() + configuration.base_configuration = self.parent.current_baseconfig() + configuration.tool = tool + + # get database settings form the parent + if not self.parent.get_db_configuration_from_input( + parameters, context, configuration + ): + return None + + # get settings according to the db + if not self._basket_handling(configuration): + configuration = ImportDataConfiguration(configuration) + else: + configuration = UpdateDataConfiguration(configuration) + configuration.with_importbid = True + configuration.with_importtid = self._get_tid_handling(configuration) + + # get settings from the input + configuration.disable_validation = self.parent.parameterAsBool( + parameters, self.DISABLEVALIDATION, context + ) + configuration.delete_data = self.parent.parameterAsBool( + parameters, self.DELETEDATA, context + ) + + configuration.xtffile = self.parent.parameterAsFile( + parameters, self.XTFFILEPATH, context + ) + + return configuration + + def _get_tid_handling(self, configuration): + db_connector = get_db_connector(configuration) + if db_connector: + return db_connector.get_tid_handling() + return False + + def _basket_handling(self, configuration): + db_connector = get_db_connector(configuration) + if db_connector: + return db_connector.get_basket_handling() + return False + + def _get_model_names(self, configuration): + modelnames = [] + + db_connector = get_db_connector(configuration) + if ( + db_connector + and db_connector.db_or_schema_exists() + and db_connector.metadata_exists() + ): + db_models = db_connector.get_models() + regex = re.compile(r"(?:\{[^\}]*\}|\s)") + for db_model in db_models: + for modelname in regex.split(db_model["modelname"]): + name = modelname.strip() + if name and name not in modelnames and name not in MODELS_BLACKLIST: + modelnames.append(name) + return modelnames + + def tr(self, string): + return QCoreApplication.translate("Processing", string) + + +class ImportingPGAlgorithm(Ili2pgAlgorithm): + """ + This is an algorithm from Model Baker. + It is meant for the data import to a PostgreSQL database. + """ + + def __init__(self): + super().__init__() + + # initialize the importer with self as parent + self.importer = ProcessImporter(self) + + def name(self) -> str: + """ + Returns the algorithm name, used for identifying the algorithm. + """ + return "modelbaker_ili2pg_importer" + + def displayName(self) -> str: + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Import with ili2pg (PostGIS)") + + def tags(self) -> list[str]: + + return [ + "modelbaker", + "interlis", + "model", + "baker", + "import", + "transferfile", + "xtf" "ili2db", + "ili2pg", + "Postgres", + "PostGIS", + ] + + def shortDescription(self) -> str: + """ + Returns a short description string for the algorithm. + """ + return self.tr("Imports data to a PostgreSQL schema with ili2db.") + + def shortHelpString(self) -> str: + """ + Returns a short helper string for the algorithm. + """ + return self.tr("Imports data to a PostgreSQL schema with ili2db.") + + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): + self.importer.initParameters() + + def processAlgorithm( + self, + parameters: dict[str, Any], + context: QgsProcessingContext, + feedback: QgsProcessingFeedback, + ) -> dict[str, Any]: + """ + Here is where the processing itself takes place. + """ + output_map = {} + configuration = self.exporter.get_configuration_from_input( + parameters, context, DbIliMode.pg + ) + if not configuration: + raise QgsProcessingException( + self.tr("Invalid input parameters. Cannot start import") + ) + else: + output_map.update(self.importer.run(configuration, feedback)) + output_map.update(self.get_output_from_db_configuration(configuration)) + return output_map + + +class ImportingGPKGAlgorithm(Ili2gpkgAlgorithm): + """ + This is an algorithm from Model Baker. + It is meant for the data import to a GeoPackage file. + """ + + def __init__(self): + super().__init__() + + # initialize the importer with self as parent + self.importer = ProcessImporter(self) + + def name(self) -> str: + """ + Returns the algorithm name, used for identifying the algorithm. + """ + return "modelbaker_ili2gpkg_importer" + + def displayName(self) -> str: + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Import with ili2gpkg (GeoPackage)") + + def tags(self) -> list[str]: + + return [ + "modelbaker", + "interlis", + "model", + "baker", + "import", + "transferfile", + "xtf", + "ili2db", + "ili2gpkg", + "GeoPackage", + "GPKG", + ] + + def shortDescription(self) -> str: + """ + Returns a short description string for the algorithm. + """ + return self.tr("Imports data to a GeoPackage file with ili2db.") + + def shortHelpString(self) -> str: + """ + Returns a short helper string for the algorithm. + """ + return self.tr("Imports data to a GeoPackage file with ili2db.") + + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): + self.importer.initParameters() + + def processAlgorithm( + self, + parameters: dict[str, Any], + context: QgsProcessingContext, + feedback: QgsProcessingFeedback, + ) -> dict[str, Any]: + """ + Here is where the processing itself takes place. + """ + output_map = {} + configuration = self.importer.get_configuration_from_input( + parameters, context, DbIliMode.gpkg + ) + if not configuration: + raise QgsProcessingException( + self.tr("Invalid input parameters. Cannot start import") + ) + else: + output_map.update(self.importer.run(configuration, feedback)) + output_map.update(self.get_output_from_db_configuration(configuration)) + return output_map diff --git a/modelbaker/processing/ili2db_validating.py b/modelbaker/processing/ili2db_validating.py new file mode 100644 index 00000000..802878d3 --- /dev/null +++ b/modelbaker/processing/ili2db_validating.py @@ -0,0 +1,430 @@ +""" +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +import os +import re +from datetime import datetime +from typing import Any, Optional + +from qgis.core import ( + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingOutputBoolean, + QgsProcessingOutputString, + QgsProcessingParameterBoolean, + QgsProcessingParameterEnum, + QgsProcessingParameterFile, + QgsProcessingParameterString, +) +from qgis.PyQt.QtCore import QCoreApplication, QObject, QStandardPaths + +from ..iliwrapper import ilivalidator +from ..iliwrapper.globals import DbIliMode +from ..iliwrapper.ili2dbconfig import ValidateConfiguration +from ..iliwrapper.ili2dbutils import JavaNotFoundError +from ..utils.db_utils import get_db_connector +from ..utils.globals import MODELS_BLACKLIST +from .ili2db_algorithm import Ili2gpkgAlgorithm, Ili2pgAlgorithm + + +class ProcessValidator(QObject): + + # Filters + FILTERMODE = "FILTERMODE" # none, models, baskets or datasets + FILTER = "FILTER" # model, basket or dataset names + + # Settings + EXPORTMODEL = "EXPORTMODEL" + SKIPGEOMETRYERRORS = "SKIPGEOMETRYERRORS" + VERBOSE = "VERBOSE" + VALIDATORCONFIGFILEPATH = "VALIDATORCONFIGFILEPATH" + + # Result + ISVALID = "ISVALID" + XTFLOGPATH = "XTFLOGPATH" + + def __init__(self, parent): + super().__init__() + self.parent = parent + + def validation_input_params(self): + params = [] + filtermode_param = QgsProcessingParameterEnum( + self.FILTERMODE, + self.tr("Filter Mode"), + ["Models", "Baskets", "Datasets"], + optional=True, + usesStaticStrings=True, + ) + filtermode_param.setHelp( + self.tr( + "Whether data should be filtered according to the models, baskets or datasets in which they are stored." + ) + ) + params.append(filtermode_param) + + filter_param = QgsProcessingParameterString( + self.FILTER, self.tr("Filter (semicolon-separated)"), optional=True + ) + filter_param.setHelp( + self.tr( + "A semicolon-separated list of the relevant models, baskets or datasets" + ) + ) + params.append(filter_param) + + exportmodel_param = QgsProcessingParameterString( + self.EXPORTMODEL, + self.tr("Export Models"), + optional=True, + ) + exportmodel_param.setHelp( + self.tr( + """ +

If your data is in the format of the cantonal model, but you want to validate it in the format of the national model you need to define this here.

+

Usually, this is one single model. However, it is also possible to pass multiple models, which makes sense if there are multiple base models in the schema you want to validate.

+ """ + ) + ) + params.append(exportmodel_param) + + skipgeom_param = QgsProcessingParameterBoolean( + self.SKIPGEOMETRYERRORS, + self.tr("Skip Geometry Errors"), + defaultValue=False, + ) + skipgeom_param.setHelp( + self.tr( + "Ignores geometry errors (--skipGeometryErrors) and AREA topology validation (--disableAreaValidation)" + ) + ) + params.append(skipgeom_param) + + verbose_param = QgsProcessingParameterBoolean( + self.VERBOSE, + self.tr("Activate Verbose Mode"), + defaultValue=False, + ) + verbose_param.setHelp( + self.tr("Verbose Mode provides you more information in the log output.") + ) + params.append(verbose_param) + + validatorconfig_param = QgsProcessingParameterFile( + self.VALIDATORCONFIGFILEPATH, + self.tr("Validator config file"), + optional=True, + ) + validatorconfig_param.setHelp( + self.tr("You can add a validator config file to control the validation.") + ) + params.append(validatorconfig_param) + + return params + + def validation_output_params(self): + params = [] + + params.append( + QgsProcessingOutputBoolean(self.ISVALID, self.tr("Validation Result")) + ) + params.append( + QgsProcessingOutputString(self.XTFLOGPATH, self.tr("XTF Log File")) + ) + + return params + + def initParameters(self): + for connection_input_param in self.parent.connection_input_params(): + self.parent.addParameter(connection_input_param) + for connection_output_param in self.parent.connection_output_params(): + self.parent.addOutput(connection_output_param) + + for validation_input_param in self.validation_input_params(): + self.parent.addParameter(validation_input_param) + for validation_output_param in self.validation_output_params(): + self.parent.addOutput(validation_output_param) + + def run(self, configuration, feedback): + # run + validator = ilivalidator.Validator(self) + validator.tool = configuration.tool + + # to do superuser finden? und auch dpparams? + validator.configuration = configuration + validator.stdout.connect(feedback.pushInfo) + validator.stderr.connect(feedback.pushInfo) + + if feedback.isCanceled(): + return {} + + isvalid = False + try: + feedback.pushInfo(f"Run: {validator.command(True)}") + result = validator.run(None) + if result == ilivalidator.Validator.SUCCESS: + feedback.pushInfo(self.tr("... validation succeeded")) + else: + feedback.pushWarning(self.tr("... validation failed")) + isvalid = bool(result == ilivalidator.Validator.SUCCESS) + except JavaNotFoundError as e: + raise QgsProcessingException( + self.tr("Java not found error:").format(e.error_string) + ) + + return {self.ISVALID: isvalid, self.XTFLOGPATH: configuration.xtflog} + + def get_configuration_from_input(self, parameters, context, tool): + + configuration = ValidateConfiguration() + configuration.base_configuration = self.parent.current_baseconfig() + configuration.tool = tool + + # get database settings form the parent + if not self.parent.get_db_configuration_from_input( + parameters, context, configuration + ): + return None + + # get static + output_file_name = ( + "modelbakerili2dbvalidatingalgorithm_xtflog_{:%Y%m%d%H%M%S%f}.xtf".format( + datetime.now() + ) + ) + configuration.xtflog = os.path.join( + QStandardPaths.writableLocation( + QStandardPaths.StandardLocation.TempLocation + ), + output_file_name, + ) + + # get settings according to the db + configuration.with_exporttid = self._get_tid_handling(configuration) + + # get settings from the input + filtermode = self.parent.parameterAsString(parameters, self.FILTERMODE, context) + filters = self.parent.parameterAsString(parameters, self.FILTER, context) + if filtermode == "Models" and filters: + configuration.ilimodels = filters + elif filtermode == "Datasets" and filters: + configuration.dataset = filters + elif filtermode == "Baskets" and filters: + configuration.baskets = filters + else: + configuration.ilimodels = ";".join(self._get_model_names(configuration)) + + exportmodels = self.parent.parameterAsString( + parameters, self.EXPORTMODEL, context + ) + if exportmodels: + configuration.iliexportmodels = exportmodels + + configuration.skip_geometry_errors = self.parent.parameterAsBool( + parameters, self.SKIPGEOMETRYERRORS, context + ) + + configuration.verbose = self.parent.parameterAsBool( + parameters, self.VERBOSE, context + ) + + validatorconfigfile = self.parent.parameterAsFile( + parameters, self.VALIDATORCONFIGFILEPATH, context + ) + + if validatorconfigfile: + configuration.valid_config = validatorconfigfile + + return configuration + + def _get_tid_handling(self, configuration): + db_connector = get_db_connector(configuration) + if db_connector: + return db_connector.get_tid_handling() + return False + + def _get_model_names(self, configuration): + modelnames = [] + + db_connector = get_db_connector(configuration) + if ( + db_connector + and db_connector.db_or_schema_exists() + and db_connector.metadata_exists() + ): + db_models = db_connector.get_models() + regex = re.compile(r"(?:\{[^\}]*\}|\s)") + for db_model in db_models: + for modelname in regex.split(db_model["modelname"]): + name = modelname.strip() + if name and name not in modelnames and name not in MODELS_BLACKLIST: + modelnames.append(name) + return modelnames + + def tr(self, string): + return QCoreApplication.translate("Processing", string) + + +class ValidatingPGAlgorithm(Ili2pgAlgorithm): + """ + This is an algorithm from Model Baker. + It is meant for the data validation stored in a PostgreSQL database. + """ + + def __init__(self): + super().__init__() + + # initialize the validator with self as parent + self.validator = ProcessValidator(self) + + def name(self) -> str: + """ + Returns the algorithm name, used for identifying the algorithm. + """ + return "modelbaker_ili2pg_validator" + + def displayName(self) -> str: + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Validate with ili2pg (PostGIS)") + + def tags(self) -> list[str]: + + return [ + "modelbaker", + "interlis", + "model", + "baker", + "validate", + "validation", + "ili2db", + "ili2pg", + "Postgres", + "PostGIS", + ] + + def shortDescription(self) -> str: + """ + Returns a short description string for the algorithm. + """ + return self.tr("Validates data in a PostgreSQL schema with ili2db.") + + def shortHelpString(self) -> str: + """ + Returns a short helper string for the algorithm. + """ + return self.tr("Validates data in a PostgreSQL schema with ili2db.") + + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): + self.validator.initParameters() + + def processAlgorithm( + self, + parameters: dict[str, Any], + context: QgsProcessingContext, + feedback: QgsProcessingFeedback, + ) -> dict[str, Any]: + """ + Here is where the processing itself takes place. + """ + output_map = {} + configuration = self.validator.get_configuration_from_input( + parameters, context, DbIliMode.pg + ) + if not configuration: + raise QgsProcessingException( + self.tr("Invalid input parameters. Cannot start validation") + ) + else: + output_map.update(self.validator.run(configuration, feedback)) + output_map.update(self.get_output_from_db_configuration(configuration)) + return output_map + + +class ValidatingGPKGAlgorithm(Ili2gpkgAlgorithm): + """ + This is an algorithm from Model Baker. + It is meant for the data validation stored in a GeoPackage file. + """ + + def __init__(self): + super().__init__() + + # initialize the validator with self as parent + self.validator = ProcessValidator(self) + + def name(self) -> str: + """ + Returns the algorithm name, used for identifying the algorithm. + """ + return "modelbaker_ili2gpkg_validator" + + def displayName(self) -> str: + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Validate with ili2gpkg (GeoPackage)") + + def tags(self) -> list[str]: + + return [ + "modelbaker", + "interlis", + "model", + "baker", + "validate", + "validation", + "ili2db", + "ili2gpkg", + "GeoPackage", + "GPKG", + ] + + def shortDescription(self) -> str: + """ + Returns a short description string for the algorithm. + """ + return self.tr("Validates data in a GeoPackage file with ili2db.") + + def shortHelpString(self) -> str: + """ + Returns a short helper string for the algorithm. + """ + return self.tr("Validates data in a GeoPackage file with ili2db.") + + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): + self.validator.initParameters() + + def processAlgorithm( + self, + parameters: dict[str, Any], + context: QgsProcessingContext, + feedback: QgsProcessingFeedback, + ) -> dict[str, Any]: + """ + Here is where the processing itself takes place. + """ + output_map = {} + configuration = self.validator.get_configuration_from_input( + parameters, context, DbIliMode.gpkg + ) + if not configuration: + raise QgsProcessingException( + self.tr("Invalid input parameters. Cannot start validation") + ) + else: + output_map.update(self.validator.run(configuration, feedback)) + output_map.update(self.get_output_from_db_configuration(configuration)) + return output_map diff --git a/modelbaker/processing/images/interlis.png b/modelbaker/processing/images/interlis.png new file mode 100644 index 0000000000000000000000000000000000000000..0dfa4a59d96b54b3eec959f1ed0321398001ac8f GIT binary patch literal 4503 zcmX9?c_38n_rG@-`w|&MG>yrwvS$(_vSdk-otY2`uSz4YEE8pkH1tM@ku{O6vSb_C zCX_W~mwn4_Y-4`c_jmue_j&H~oXSxmy_m) zNzE}G%$xkF;)He&0TrU-Er`3{3$KpG$ex;@DukN?svAR4sLRO@eGkpKv z?MI^O?*wf1^b(4``RA`zzqX{`q{znDvil*zZ`ugnvL(I~Z`RVUEz2b1=(FZC0A(!@I~u7dS}&?iQIH@v{3x7+ zcWB4^is_&qn0j;fDhx-iso@6zug20R`MeJ<1|+RV7$S&S_H88u%O7qrzlpacia{Io z?VyXxs>(WWFI2C*FoCMS=9M$J_b)<4P~Xk^g-DJoe1Tk4a%*!ma;)=~Xzg%j2xi zx4o)*&v$oI99sAg#HR|l!Ps|jUCzEy_0q81D?twL0kr?KOm*k#Ac_|D<)&f|j5My2 zxWZJ$+om-x*?-^FO%_NZU-C9S;Z)5#_P4>#d*)0G2X@BI)w}K`$3A5=M`{-RiUaGG zYctBZ4Xk<=W>OFG09Ut{T6Qhd-ebZVtIPDlNyB@VfKp9D*)*5>$?;MRz%O(00>37L zXi~PgBl6)FljRSPtGp^!V&XbTt(aMt3}gVcA%KgBV@up>9>DA;HWa!d);Jg@>^S(~HZVA|CG2!&{fKolyS_fS?365F4Eh0-)X{+;RbqbQx`dFB0sF??J z-)STF`|7&Ob#esu5l%*(LtqC525Gw1a>Y#qfhcVR)@})t)-q^SK>y%$-{5XG%5<&l;j+xP$uNVZ zhv*rVEbj0GGT8dcDq>Od6O}wjRq2GZ23j5G+;}3^0~Yxy@L%P$OQB97*DhzkBJ-W| z5C;)7zN(ne~{|kL7D&yJwp--onJ;G4_*zCT?A{sN;V_k_E@+q_@ck@;$l26=96y9Dsyz2k9*|+> zr44FMV#3+p0~tN*>zIR9cKOE+27Hy5bHPlK&zLS@j{I=dc=!&C&A#M-Yv6dHOQgva z_^}-4efDCz6=SbF;9L4uE&Ay;5Sjx$BPEH*>6c;@<&bGixZy_?kU87tZqo(?k?|e~ zHP`H8ss4irr+Rs?pT{+o`t6-5u>>}*%LlP~)f2elDsO=8S9;{ysN8hsir)l*xLtFw z-D+YkpuKYlQp~KALj2U_Ad6ps@b10`?%wnY0ooopv>aoP=SUZ9T6a3n7>Cq$S^~;4 z|B18iKZ1mw>cH<^de;3)_^=t$Q2ZXq>2U}aX~2DrJd?<|*m*JR$DNLP(j&*Q@95Vk2jPpt zsQa%++0zFO70!25K>+Ua^2Ez|C;WdwE>;+i@uyEu6NH`M3s6EtRc#`OI1O-k9`cGm zD<3jTs(9%>md;Nx!)LY7f5__?~8hga+XqMhvauIBnRSN5cnd(om&@kJu4Hs3kZc^1wLwSKM~8qaiQP0LOF-Q3?va(I@{}={qGgd<9Nj)@WD7 zRG^)cBIKBR%&2MyK`EU4cU%LauM`;!H~&~WjtMoiYV_nyw+Y~(Le#}0){+2@mO8e% zE*$#unhY?h&8q^fjrcQb(nRNZ_Ho4{+2$Z{*xD|1+V-eJ4FXj$D(%|?&n%h@s^@tj zklgmjx!odegdEu}{S{WcErYjprAeWbpXVGA%YEd9TZRk~vmsE7jNj*vOMZp}o=xIl zJmJCGC?!um6X52HLzE)oZ*-_yB8Sa(M8GMOlKX@iU?m!Yx-U4&Y!2nrj@_N|bMeju z8^L{q;1CB{q{d;%dTw2?+qNH@SWW_!84c>yu=8%(YM&@pnW)eC}@bKIP(fLi0oSGN+IFPu}m@jqW zt=`e-#*Yo;d$V&owY3jCrZ!s7Jm}+{+7?|p&;9F`mc??--;UIus@;(E%2}JA9lkFl zPHh>_ZwfxO1k&nGnMkWE@LP*UXgLp2SiMeNXeq1rm0yc+Fo5B=?g`~bR1J*K4hP7D z$DF&3@;h#;1|Al>k`J~O@HfZH75F9WS+rf-##s+q$0NMNcSxcKV1JyO!VWufc7-)F z40mHT=PqwkZO;+#Og*oyYQtPAbG;BzbzhZkY}ac~c4o+M1m#L^r@EDVtcVqOkLOG` z4>;K{@?FMSMmNeB9t#OK2ncRX%Qt&@+w{D_ zQsJcMD*Y5wG*jiJxMN>7=`l5z#8kEzP_>^8O#%w)pd{w^<)zjBfPl()-};K3QG>|> z+wcb$QKtubXj)?nnD625I14iye;yp=8ZzMr=*q|5JJ@=ghmbloFU#vI5T9i(DiX9=7Q&(=l(cEwZ>P=sQ-+mSIs32k-wHlXT-$yZ zhI7VW`9cmG@p+KGA5oZzZq-Z#^e5{s(9BmZYL09%Sy_K|Ffe*sDTFPl7FucsK1H?< zPPF-~y3!aGv2ivVZvZGmnvyzr9j8{O)hm3)V-}^O)XGgM2`-E816EkxUdfW=-KuSX zWQsEeXE9g*{P2dYF4}b#(W96|cEeQr&tQ$yW%S(A)q1N+* zRyOt%4=p4@*xa~EPS!oWn!B5E zbDuwbJ{vLhVFi3K(G(~DczQBq;>jq@p@sV2pcL`z#0VbR)}#;j4m_7Si&pguw?6@C zGU0D@m6pzF?HPz2#P(VNrrsLzyQEKsEcE$jAv1Dx#wA@@OqMF?s7X6A!MP;z_R&eAKjb1{$&w?dRzTE++iuIwC zUJ8|u2VZU-JaxawXd`f)Q1t~ZgCJpK^1j(xix6Xkz*X7W4#y9i(BUzv9EzyY2;AK- zbfKyiLiHt6JyZ?2eaM45)GFtN7WwJ^ieS)RlGo2e&la7XHoph{2+DP6A;~z@0bZ%i zAxVidwHBH^yJHZ5irWBFF)mgzJS(n`zNQ`o3Jm$MZZ@A%a<`+kjBUd4FGknxXG|6k zUEkCKn$V$9>RD}%9Zjl{APJORd82y+KUiZ>5kAs08Mui0^Rx)^-8^1;$Iku4gO}2r^#R2xS($VNhaeB`!XHfXs%R{Bit=Gm& z%QwNSdR2J(8qTe(JU8s^79;_f$OEY$>BKN*_+$Cz)i_UeP!*&SmY3)8QQFYChS{|m z+NVW`gufYv!@umwtY0?BvXgN0a`VYL2kR1sK>7R?EsNXLKsDylUK;_ii6ZCuZi!Pw z+-1<)|9sds@9hb;aIXde^I;?p#J<_50V^o3#1}%uE@otp+}(p~+4+fpXKFUdX6XFG zClDFXUV6GyoY-!d5;CuP16pj0um)%4w2{=CJ1w$$O9F07Y7=HF><_SlbuqmLF zZK@lPNLCS*0miIB>VJRxh61w(4Z))!+b}UVsLDg3jntrC+k6m;@qCA*kOV&cWU1hyz4Q{eXmxK*fYSUtyu z_4B8|`cI>sJ~LB!Aa`^!wW-{P#6@8U0Q!t-zH`ta$Xft4hUaLNu}HpdaiU3a0cH+T zZwM*V$?Rh!Wxz=63A@QaX=B4(cI2cA=o%dX(f_}O8B;5B+lQmElwAX~v4|?@`kXaj zmi0ur$zvet&yy1m#4i8QJ&rBA1A&Mv6+@Pv=7mi)sjN}s5@_XL9lUeA(-(9dwnbpw zVrr$|cPU|;-+ouZ=WR=jsy?uBl%CqRLWG|0ch-rC0m$w5M`_SUYv= zg(|WB#w5X@$VX9Fzp!m?wr8~Cb~E(>8Nn7ENy9f1toOtA6D3PF8Gn+3W8SXi&maAFF}h-=S9lp8`hU72 BeJ=n2 literal 0 HcmV?d00001 diff --git a/modelbaker/utils/globals.py b/modelbaker/utils/globals.py index 64967cde..ffcf8d33 100644 --- a/modelbaker/utils/globals.py +++ b/modelbaker/utils/globals.py @@ -18,6 +18,75 @@ """ from enum import Enum, IntEnum +MODELS_BLACKLIST = [ + "CHBaseEx_MapCatalogue_V1", + "CHBaseEx_WaterNet_V1", + "CHBaseEx_Sewage_V1", + "CHAdminCodes_V1", + "AdministrativeUnits_V1", + "AdministrativeUnitsCH_V1", + "WithOneState_V1", + "WithLatestModification_V1", + "WithModificationObjects_V1", + "GraphicCHLV03_V1", + "GraphicCHLV95_V1", + "NonVector_Base_V2", + "NonVector_Base_V3", + "NonVector_Base_LV03_V3_1", + "NonVector_Base_LV95_V3_1", + "GeometryCHLV03_V1", + "GeometryCHLV95_V1", + "Geometry_V1", + "InternationalCodes_V1", + "Localisation_V1", + "LocalisationCH_V1", + "Dictionaries_V1", + "DictionariesCH_V1", + "CatalogueObjects_V1", + "CatalogueObjectTrees_V1", + "AbstractSymbology", + "CodeISO", + "CoordSys", + "GM03_2_1Comprehensive", + "GM03_2_1Core", + "GM03_2Comprehensive", + "GM03_2Core", + "GM03Comprehensive", + "GM03Core", + "IliRepository09", + "IliSite09", + "IlisMeta07", + "IliVErrors", + "INTERLIS_ext", + "RoadsExdm2ben", + "RoadsExdm2ben_10", + "RoadsExgm2ien", + "RoadsExgm2ien_10", + "StandardSymbology", + "StandardSymbology", + "Time", + "Units", + "", + "CHAdminCodes_V2", + "AdministrativeUnits_V2", + "AdministrativeUnitsCH_V2", + "WithOneState_V2", + "WithLatestModification_V2", + "WithModificationObjects_V2", + "GraphicCHLV03_V2", + "GraphicCHLV95_V2", + "GeometryCHLV03_V2", + "GeometryCHLV95_V2", + "Geometry_V2", + "InternationalCodes_V2", + "Localisation_V2", + "LocalisationCH_V2", + "Dictionaries_V2", + "DictionariesCH_V2", + "CatalogueObjects_V2", + "CatalogueObjectTrees_V2", +] + class DbActionType(Enum): """Defines constants for generate, schema_import, import data, or export actions of modelbaker.""" diff --git a/tests/test_processing.py b/tests/test_processing.py new file mode 100644 index 00000000..e5bc94b0 --- /dev/null +++ b/tests/test_processing.py @@ -0,0 +1,232 @@ +""" +/*************************************************************************** + begin : 27/09/2025 + git sha : :%H$ + copyright : (C) 2025 by Dave Signer + email : david@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import datetime +import logging +import os +import tempfile + +from qgis.core import QgsProcessingContext, QgsProcessingFeedback +from qgis.testing import start_app, unittest + +from modelbaker.iliwrapper import iliimporter +from modelbaker.iliwrapper.globals import DbIliMode +from modelbaker.processing.ili2db_exporting import ExportingGPKGAlgorithm +from modelbaker.processing.ili2db_importing import ImportingGPKGAlgorithm +from modelbaker.processing.ili2db_validating import ValidatingGPKGAlgorithm +from tests.utils import iliimporter_config, testdata_path + +start_app() + + +class TestProcessingAlgorithms(unittest.TestCase): + @classmethod + def setUpClass(cls): + """Run before all tests.""" + cls.basetestpath = tempfile.mkdtemp() + + def iliimporter_pg_config_params(self): + configuration = iliimporter_config(DbIliMode.ili2pg) + params = { + "HOST": configuration.dbhost, + "DBNAME": configuration.database, + "USERNAME": configuration.dbusr, + "PASSWORD": configuration.dbpwd, + } + return params + + def gpkg_file(self, basket_col): + importer = iliimporter.Importer() + importer.tool = DbIliMode.ili2gpkg + importer.configuration.ilifile = testdata_path("ilimodels/RoadsSimple.ili") + importer.configuration.ilimodels = "RoadsSimple" + importer.configuration.dbfile = os.path.join( + self.basetestpath, + "tmp_roads_simple_{:%Y%m%d%H%M%S%f}.gpkg".format(datetime.datetime.now()), + ) + importer.configuration.create_basket_col = basket_col + importer.configuration.inheritance = "smart2" + importer.stdout.connect(self.print_info) + importer.stderr.connect(self.print_error) + return importer.configuration.dbfile + + def pg_schema(self, basket_col): + importer = iliimporter.Importer() + importer.tool = DbIliMode.ili2pg + importer.configuration = iliimporter_config(importer.tool) + importer.configuration.ilifile = testdata_path("ilimodels/RoadsSimple.ili") + importer.configuration.ilimodels = "RoadsSimple" + importer.configuration.dbschema = "roads_simple_{:%Y%m%d%H%M%S%f}".format( + datetime.datetime.now() + ) + importer.configuration.srs_code = 2056 + importer.configuration.inheritance = "smart2" + importer.configuration.create_basket_col = basket_col + importer.stdout.connect(self.print_info) + importer.stderr.connect(self.print_error) + return importer.configuration.dbschema + + def test_algs_gpkg_with_baskets(self): + + conn_parameters = {} + conn_parameters["DBPATH"] = self.gpkg_file(True) + + # validate empty file without specific parameters + validation_parameters = {} + validation_parameters.update(conn_parameters) + alg = ValidatingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(validation_parameters, context, feedback) + assert output["ISVALID"] + + # import valid data now to a dataset called 'validdata' + import_parameters = { + "XTFFILEPATH": testdata_path("xtf/test_roads_simple.xtf"), + "DATASET": "validdata", + } + import_parameters.update(conn_parameters) + alg = ImportingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(import_parameters, context, feedback) + assert output["ISVALID"] + + # import invalid data now to a dataset called 'invaliddata' + import_parameters = { + "XTFFILEPATH": testdata_path("xtf/test_roads_simple_invalid.xtf"), + "DATASET": "invaliddata", + } + import_parameters.update(conn_parameters) + alg = ImportingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(import_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + # import invalid data now to a dataset called 'invaliddata' + # this time we disable the validation + import_parameters = { + "XTFFILEPATH": testdata_path("xtf/test_roads_simple_invalid.xtf"), + "DATASET": "invaliddata", + "DISABLEVALIDATION": True, + } + import_parameters.update(conn_parameters) + alg = ImportingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(import_parameters, context, feedback) + assert output["ISVALID"] + + # validate without specific parameters + validation_parameters = {} + validation_parameters.update(conn_parameters) + alg = ValidatingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(validation_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + # validate again only the dataset 'validdata' + validation_parameters = {"FILTER": "Dataset", "FILTERS": "validdata"} + validation_parameters.update(conn_parameters) + alg = ValidatingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(validation_parameters, context, feedback) + assert output["ISVALID"] + + valid_targetfile = os.path.join(self.basetestpath, "valid_export.xtf") + invalid_targetfile = os.path.join(self.basetestpath, "valid_export.xtf") + + # let's export without specific parameters + export_parameters = {"XTFFILEPATH": valid_targetfile} + export_parameters.update(conn_parameters) + alg = ExportingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(export_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + # let's export again only the dataset 'validdata' + export_parameters = { + "XTFFILEPATH": valid_targetfile, + "FILTER": "Dataset", + "FILTERS": "validdata", + } + export_parameters.update(conn_parameters) + alg = ExportingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(export_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + # let's export the invalid dataset 'invaliddata' + export_parameters = { + "XTFFILEPATH": invalid_targetfile, + "FILTER": "Dataset", + "FILTERS": "invaliddata", + } + export_parameters.update(conn_parameters) + alg = ExportingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(export_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + # let's export the invalid dataset 'invaliddata' and disable validation + export_parameters = { + "XTFFILEPATH": invalid_targetfile, + "FILTER": "Dataset", + "FILTERS": "invaliddata", + "DISABLEVALIDATION": True, + } + export_parameters.update(conn_parameters) + alg = ExportingGPKGAlgorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(export_parameters, context, feedback) + assert output["ISVALID"] + + assert os.path.isfile(valid_targetfile) + assert os.path.isfile(invalid_targetfile) + + def test_validating_alg_pg(self): + parameters = self.iliimporter_pg_config_params() + parameters["SCHEMA"] = self.pg_schema(False) + + def print_info(self, text): + logging.info(text) + + def print_error(self, text): + logging.error(text) From dfba3965d2baeca7bf2f07fea4f383db005bdb49 Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 7 Oct 2025 22:06:34 +0200 Subject: [PATCH 02/12] tests and descriptions --- modelbaker/processing/ili2db_exporting.py | 54 +++++- modelbaker/processing/ili2db_importing.py | 63 +++++-- tests/test_processing.py | 209 ++++++++++++++++++---- 3 files changed, 268 insertions(+), 58 deletions(-) diff --git a/modelbaker/processing/ili2db_exporting.py b/modelbaker/processing/ili2db_exporting.py index 8791b127..66e45c41 100644 --- a/modelbaker/processing/ili2db_exporting.py +++ b/modelbaker/processing/ili2db_exporting.py @@ -165,7 +165,7 @@ def run(self, configuration, feedback): feedback.pushInfo(self.tr("... export succeeded")) else: feedback.pushWarning(self.tr("... export failed")) - isvalid = bool(result == iliexporter.Validator.SUCCESS) + isvalid = bool(result == iliexporter.Exporter.SUCCESS) except JavaNotFoundError as e: raise QgsProcessingException( self.tr("Java not found error:").format(e.error_string) @@ -286,15 +286,33 @@ def tags(self) -> list[str]: def shortDescription(self) -> str: """ - Returns a short description string for the algorithm. + Returns the tooltip text when hovering the algorithm """ - return self.tr("Exports data from a PostgreSQL schema with ili2db.") + return self.tr( + """ +

Exports data from PostgreSQL schema with ili2pg.

+

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Additionally, you can define an export model to specify the format in which you want to export the data (e.g. the base model of your extended model).

+ + """ + ) def shortHelpString(self) -> str: """ - Returns a short helper string for the algorithm. + Returns the help text on the right. """ - return self.tr("Exports data from a PostgreSQL schema with ili2db.") + return self.tr( + """ +

Exports data from PostgreSQL schema with ili2pg.

+

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Additionally, you can define an export model to specify the format in which you want to export the data (e.g. the base model of your extended model).

+ + """ + ) def initAlgorithm(self, config: Optional[dict[str, Any]] = None): self.exporter.initParameters() @@ -365,15 +383,33 @@ def tags(self) -> list[str]: def shortDescription(self) -> str: """ - Returns a short description string for the algorithm. + Returns the tooltip text when hovering the algorithm """ - return self.tr("Exports data from a GeoPackage file with ili2db.") + return self.tr( + """ +

Exports data from GeoPackage file with ili2gpkg.

+

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Additionally, you can define an export model to specify the format in which you want to export the data (e.g. the base model of your extended model).

+ + """ + ) def shortHelpString(self) -> str: """ - Returns a short helper string for the algorithm. + Returns the help text on the right. """ - return self.tr("Exports data from a GeoPackage file with ili2db.") + return self.tr( + """ +

Exports data from GeoPackage file with ili2gpkg.

+

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Additionally, you can define an export model to specify the format in which you want to export the data (e.g. the base model of your extended model).

+ + """ + ) def initAlgorithm(self, config: Optional[dict[str, Any]] = None): self.exporter.initParameters() diff --git a/modelbaker/processing/ili2db_importing.py b/modelbaker/processing/ili2db_importing.py index d5045a84..5f16de54 100644 --- a/modelbaker/processing/ili2db_importing.py +++ b/modelbaker/processing/ili2db_importing.py @@ -144,7 +144,7 @@ def run(self, configuration, feedback): feedback.pushInfo(self.tr("... import succeeded")) else: feedback.pushWarning(self.tr("... import failed")) - isvalid = bool(result == iliimporter.Validator.SUCCESS) + isvalid = bool(result == iliimporter.Importer.SUCCESS) except JavaNotFoundError as e: raise QgsProcessingException( self.tr("Java not found error:").format(e.error_string) @@ -173,6 +173,9 @@ def get_configuration_from_input(self, parameters, context, tool): configuration.with_importtid = self._get_tid_handling(configuration) # get settings from the input + configuration.dataset = self.parent.parameterAsString( + parameters, self.DATASET, context + ) configuration.disable_validation = self.parent.parameterAsBool( parameters, self.DISABLEVALIDATION, context ) @@ -262,15 +265,35 @@ def tags(self) -> list[str]: def shortDescription(self) -> str: """ - Returns a short description string for the algorithm. + Returns the tooltip text when hovering the algorithm + """ + return self.tr( + """ +

Imports data to PostgreSQL schema with ili2pg.

+

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

+

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

+

Additionally, you need to define a dataset name for the import on such databases.

+

On a database where no basket column has been created, the command will be --import (and --deleteData when you choose to delete data previously).

+ """ - return self.tr("Imports data to a PostgreSQL schema with ili2db.") + ) def shortHelpString(self) -> str: """ - Returns a short helper string for the algorithm. + Returns the help text on the right. """ - return self.tr("Imports data to a PostgreSQL schema with ili2db.") + return self.tr( + """ +

Imports data to PostgreSQL schema with ili2pg.

+

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

+

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

+

Additionally, you need to define a dataset name for the import on such databases.

+

On a database where no basket column has been created, the command will be --import (and --deleteData when you choose to delete data previously).

+ + """ + ) def initAlgorithm(self, config: Optional[dict[str, Any]] = None): self.importer.initParameters() @@ -285,7 +308,7 @@ def processAlgorithm( Here is where the processing itself takes place. """ output_map = {} - configuration = self.exporter.get_configuration_from_input( + configuration = self.importer.get_configuration_from_input( parameters, context, DbIliMode.pg ) if not configuration: @@ -341,15 +364,35 @@ def tags(self) -> list[str]: def shortDescription(self) -> str: """ - Returns a short description string for the algorithm. + Returns the tooltip text when hovering the algorithm + """ + return self.tr( + """ +

Imports data to GeoPackage file with ili2gpkg.

+

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin.

+

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

+

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

+

Additionally, you need to define a dataset name for the import on such databases.

+

On a database where no basket column has been created, the command will be --import (and --deleteData when you choose to delete data previously).

+ """ - return self.tr("Imports data to a GeoPackage file with ili2db.") + ) def shortHelpString(self) -> str: """ - Returns a short helper string for the algorithm. + Returns the help text on the right. """ - return self.tr("Imports data to a GeoPackage file with ili2db.") + return self.tr( + """ +

Imports data to GeoPackage file with ili2gpkg.

+

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin.

+

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

+

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

+

Additionally, you need to define a dataset name for the import on such databases.

+

On a database where no basket column has been created, the command will be --import (and --deleteData when you choose to delete data previously).

+ + """ + ) def initAlgorithm(self, config: Optional[dict[str, Any]] = None): self.importer.initParameters() diff --git a/tests/test_processing.py b/tests/test_processing.py index e5bc94b0..d1ad3035 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -26,9 +26,18 @@ from modelbaker.iliwrapper import iliimporter from modelbaker.iliwrapper.globals import DbIliMode -from modelbaker.processing.ili2db_exporting import ExportingGPKGAlgorithm -from modelbaker.processing.ili2db_importing import ImportingGPKGAlgorithm -from modelbaker.processing.ili2db_validating import ValidatingGPKGAlgorithm +from modelbaker.processing.ili2db_exporting import ( + ExportingGPKGAlgorithm, + ExportingPGAlgorithm, +) +from modelbaker.processing.ili2db_importing import ( + ImportingGPKGAlgorithm, + ImportingPGAlgorithm, +) +from modelbaker.processing.ili2db_validating import ( + ValidatingGPKGAlgorithm, + ValidatingPGAlgorithm, +) from tests.utils import iliimporter_config, testdata_path start_app() @@ -63,7 +72,10 @@ def gpkg_file(self, basket_col): importer.configuration.inheritance = "smart2" importer.stdout.connect(self.print_info) importer.stderr.connect(self.print_error) - return importer.configuration.dbfile + if importer.run() == iliimporter.Importer.SUCCESS: + return importer.configuration.dbfile + else: + return None def pg_schema(self, basket_col): importer = iliimporter.Importer() @@ -79,30 +91,61 @@ def pg_schema(self, basket_col): importer.configuration.create_basket_col = basket_col importer.stdout.connect(self.print_info) importer.stderr.connect(self.print_error) - return importer.configuration.dbschema - - def test_algs_gpkg_with_baskets(self): + if importer.run() == iliimporter.Importer.SUCCESS: + return importer.configuration.dbschema + else: + return None + def test_algs_gpkg(self): + conn_parameters_baskets = {} + conn_parameters_baskets["DBPATH"] = self.gpkg_file(True) + self._algs_with_baskets( + conn_parameters_baskets, + ImportingGPKGAlgorithm, + ValidatingGPKGAlgorithm, + ExportingGPKGAlgorithm, + ) conn_parameters = {} - conn_parameters["DBPATH"] = self.gpkg_file(True) + conn_parameters["DBPATH"] = self.gpkg_file(False) + self._algs_without_baskets( + conn_parameters, + ImportingGPKGAlgorithm, + ValidatingGPKGAlgorithm, + ExportingGPKGAlgorithm, + ) - # validate empty file without specific parameters - validation_parameters = {} - validation_parameters.update(conn_parameters) - alg = ValidatingGPKGAlgorithm() - alg.initAlgorithm() - context = QgsProcessingContext() - feedback = QgsProcessingFeedback() - output = alg.processAlgorithm(validation_parameters, context, feedback) - assert output["ISVALID"] + def test_algs_pg(self): + conn_parameters_baskets = self.iliimporter_pg_config_params() + conn_parameters_baskets["SCHEMA"] = self.pg_schema(True) + self._algs_with_baskets( + conn_parameters_baskets, + ImportingPGAlgorithm, + ValidatingPGAlgorithm, + ExportingPGAlgorithm, + ) + conn_parameters = {} + conn_parameters["DBPATH"] = self.gpkg_file(False) + self._algs_without_baskets( + conn_parameters, + ImportingGPKGAlgorithm, + ValidatingGPKGAlgorithm, + ExportingGPKGAlgorithm, + ) + def _algs_with_baskets( + self, + conn_parameters, + importing_algorithm, + validating_algorithm, + exporting_algorithm, + ): # import valid data now to a dataset called 'validdata' import_parameters = { "XTFFILEPATH": testdata_path("xtf/test_roads_simple.xtf"), "DATASET": "validdata", } import_parameters.update(conn_parameters) - alg = ImportingGPKGAlgorithm() + alg = importing_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -115,7 +158,7 @@ def test_algs_gpkg_with_baskets(self): "DATASET": "invaliddata", } import_parameters.update(conn_parameters) - alg = ImportingGPKGAlgorithm() + alg = importing_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -131,7 +174,7 @@ def test_algs_gpkg_with_baskets(self): "DISABLEVALIDATION": True, } import_parameters.update(conn_parameters) - alg = ImportingGPKGAlgorithm() + alg = importing_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -141,7 +184,7 @@ def test_algs_gpkg_with_baskets(self): # validate without specific parameters validation_parameters = {} validation_parameters.update(conn_parameters) - alg = ValidatingGPKGAlgorithm() + alg = validating_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -150,9 +193,9 @@ def test_algs_gpkg_with_baskets(self): assert not output["ISVALID"] # validate again only the dataset 'validdata' - validation_parameters = {"FILTER": "Dataset", "FILTERS": "validdata"} + validation_parameters = {"FILTERMODE": "Datasets", "FILTER": "validdata"} validation_parameters.update(conn_parameters) - alg = ValidatingGPKGAlgorithm() + alg = validating_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -160,12 +203,12 @@ def test_algs_gpkg_with_baskets(self): assert output["ISVALID"] valid_targetfile = os.path.join(self.basetestpath, "valid_export.xtf") - invalid_targetfile = os.path.join(self.basetestpath, "valid_export.xtf") + invalid_targetfile = os.path.join(self.basetestpath, "invalid_export.xtf") # let's export without specific parameters export_parameters = {"XTFFILEPATH": valid_targetfile} export_parameters.update(conn_parameters) - alg = ExportingGPKGAlgorithm() + alg = exporting_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -176,26 +219,25 @@ def test_algs_gpkg_with_baskets(self): # let's export again only the dataset 'validdata' export_parameters = { "XTFFILEPATH": valid_targetfile, - "FILTER": "Dataset", - "FILTERS": "validdata", + "FILTERMODE": "Datasets", + "FILTER": "validdata", } export_parameters.update(conn_parameters) - alg = ExportingGPKGAlgorithm() + alg = exporting_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() output = alg.processAlgorithm(export_parameters, context, feedback) - # fails - assert not output["ISVALID"] + assert output["ISVALID"] # let's export the invalid dataset 'invaliddata' export_parameters = { "XTFFILEPATH": invalid_targetfile, - "FILTER": "Dataset", - "FILTERS": "invaliddata", + "FILTERMODE": "Datasets", + "FILTER": "invaliddata", } export_parameters.update(conn_parameters) - alg = ExportingGPKGAlgorithm() + alg = exporting_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -206,12 +248,12 @@ def test_algs_gpkg_with_baskets(self): # let's export the invalid dataset 'invaliddata' and disable validation export_parameters = { "XTFFILEPATH": invalid_targetfile, - "FILTER": "Dataset", - "FILTERS": "invaliddata", + "FILTERMODE": "Datasets", + "FILTER": "invaliddata", "DISABLEVALIDATION": True, } export_parameters.update(conn_parameters) - alg = ExportingGPKGAlgorithm() + alg = exporting_algorithm() alg.initAlgorithm() context = QgsProcessingContext() feedback = QgsProcessingFeedback() @@ -221,9 +263,98 @@ def test_algs_gpkg_with_baskets(self): assert os.path.isfile(valid_targetfile) assert os.path.isfile(invalid_targetfile) - def test_validating_alg_pg(self): - parameters = self.iliimporter_pg_config_params() - parameters["SCHEMA"] = self.pg_schema(False) + def _algs_without_baskets( + self, + conn_parameters, + importing_algorithm, + validating_algorithm, + exporting_algorithm, + ): + # import valid data now + import_parameters = {"XTFFILEPATH": testdata_path("xtf/test_roads_simple.xtf")} + import_parameters.update(conn_parameters) + alg = importing_algorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(import_parameters, context, feedback) + assert output["ISVALID"] + + # validate without specific parameters + validation_parameters = {} + validation_parameters.update(conn_parameters) + alg = validating_algorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(validation_parameters, context, feedback) + assert output["ISVALID"] + + # import invalid data now + import_parameters = { + "XTFFILEPATH": testdata_path("xtf/test_roads_simple_invalid.xtf") + } + import_parameters.update(conn_parameters) + alg = importing_algorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(import_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + # import invalid data again + # this time we disable the validation + import_parameters = { + "XTFFILEPATH": testdata_path("xtf/test_roads_simple_invalid.xtf"), + "DISABLEVALIDATION": True, + } + import_parameters.update(conn_parameters) + alg = importing_algorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(import_parameters, context, feedback) + assert output["ISVALID"] + + # validate without specific parameters + validation_parameters = {} + validation_parameters.update(conn_parameters) + alg = validating_algorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(validation_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + invalid_targetfile = os.path.join(self.basetestpath, "invalid_export.xtf") + + # let's export without specific parameters + export_parameters = {"XTFFILEPATH": invalid_targetfile} + export_parameters.update(conn_parameters) + alg = exporting_algorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(export_parameters, context, feedback) + # fails + assert not output["ISVALID"] + + # let's export and disable validation + export_parameters = { + "XTFFILEPATH": invalid_targetfile, + "DISABLEVALIDATION": True, + } + export_parameters.update(conn_parameters) + alg = exporting_algorithm() + alg.initAlgorithm() + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + output = alg.processAlgorithm(export_parameters, context, feedback) + assert output["ISVALID"] + + assert os.path.isfile(invalid_targetfile) def print_info(self, text): logging.info(text) From b9cd78f8bab2670430751a32a2f36931cc5dd494 Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 9 Oct 2025 09:25:33 +0200 Subject: [PATCH 03/12] descriptions and superuser-login option --- modelbaker/processing/ili2db_algorithm.py | 39 +++++++------- modelbaker/processing/ili2db_exporting.py | 2 + modelbaker/processing/ili2db_importing.py | 4 ++ modelbaker/processing/ili2db_validating.py | 60 +++++++++++++++++++--- 4 files changed, 78 insertions(+), 27 deletions(-) diff --git a/modelbaker/processing/ili2db_algorithm.py b/modelbaker/processing/ili2db_algorithm.py index 666d9887..d7524c6e 100644 --- a/modelbaker/processing/ili2db_algorithm.py +++ b/modelbaker/processing/ili2db_algorithm.py @@ -3,10 +3,12 @@ from qgis.core import ( QgsProcessingAlgorithm, + QgsProcessingOutputBoolean, QgsProcessingOutputFile, QgsProcessingOutputNumber, QgsProcessingOutputString, QgsProcessingParameterAuthConfig, + QgsProcessingParameterBoolean, QgsProcessingParameterEnum, QgsProcessingParameterFile, QgsProcessingParameterNumber, @@ -76,20 +78,12 @@ class Ili2pgAlgorithm(Ili2dbAlgorithm): PASSWORD = "PASSWORD" SCHEMA = "SCHEMA" SSLMODE = "SSLMODE" + USESUPERUSER = "USESUPERUSER" AUTHCFG = "AUTHCFG" def __init__(self): super().__init__() - def group(self): - return self.tr("ili2db") - - def groupId(self): - return "ili2db" - - def icon(self): - return QIcon(os.path.join(os.path.dirname(__file__), "../images/interlis.png")) - def connection_input_params(self): params = [] @@ -171,6 +165,16 @@ def connection_input_params(self): password_param.setHelp(self.tr("The password of the user.")) params.append(password_param) + usesuperuser_param = QgsProcessingParameterBoolean( + self.USESUPERUSER, + self.tr("Use superuser login from the settings"), + defaultValue=False, + ) + usesuperuser_param.setHelp( + self.tr("Excecute the task with the super user login from the settings") + ) + params.append(usesuperuser_param) + authcfg_param = QgsProcessingParameterAuthConfig( self.AUTHCFG, self.tr("Authentification"), @@ -198,10 +202,12 @@ def connection_output_params(self): params.append(QgsProcessingOutputString(self.PASSWORD, self.tr("Password"))) params.append(QgsProcessingOutputString(self.SCHEMA, self.tr("Schema"))) params.append(QgsProcessingOutputString(self.SSLMODE, self.tr("SSL Mode"))) + params.append( + QgsProcessingOutputBoolean(self.USESUPERUSER, self.tr("Use Superuser")) + ) params.append( QgsProcessingOutputString(self.AUTHCFG, self.tr("Authentication")) ) - return params def get_db_configuration_from_input(self, parameters, context, configuration): @@ -241,6 +247,9 @@ def get_db_configuration_from_input(self, parameters, context, configuration): parameters, self.SCHEMA, context ) configuration.sslmode = self.parameterAsEnum(parameters, self.SSLMODE, context) + configuration.db_use_super_login = self.parameterAsBoolean( + parameters, self.USESUPERUSER, context + ) valid = bool( configuration.dbhost and configuration.database and configuration.dbschema ) @@ -260,6 +269,7 @@ def get_output_from_db_configuration(self, configuration): self.PASSWORD: configuration.dbpwd, self.SCHEMA: configuration.dbschema, self.SSLMODE: configuration.sslmode, + self.USESUPERUSER: configuration.db_use_super_login, self.AUTHCFG: configuration.dbauthid, } return output_map @@ -272,15 +282,6 @@ class Ili2gpkgAlgorithm(Ili2dbAlgorithm): def __init__(self): super().__init__() - def group(self): - return self.tr("ili2db") - - def groupId(self): - return "ili2db" - - def icon(self): - return QIcon(os.path.join(os.path.dirname(__file__), "../images/interlis.png")) - def connection_input_params(self): params = [] diff --git a/modelbaker/processing/ili2db_exporting.py b/modelbaker/processing/ili2db_exporting.py index 66e45c41..f365068e 100644 --- a/modelbaker/processing/ili2db_exporting.py +++ b/modelbaker/processing/ili2db_exporting.py @@ -292,6 +292,7 @@ def shortDescription(self) -> str: """

Exports data from PostgreSQL schema with ili2pg.

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

General Model Baker settings like custom model directories or db parameters are concerned.

The parameter passed to ili2db by default is --exportTid.

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

Additionally, you can define an export model to specify the format in which you want to export the data (e.g. the base model of your extended model).

@@ -307,6 +308,7 @@ def shortHelpString(self) -> str: """

Exports data from PostgreSQL schema with ili2pg.

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

General Model Baker settings like custom model directories or db parameters are concerned.

The parameter passed to ili2db by default is --exportTid.

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

Additionally, you can define an export model to specify the format in which you want to export the data (e.g. the base model of your extended model).

diff --git a/modelbaker/processing/ili2db_importing.py b/modelbaker/processing/ili2db_importing.py index 5f16de54..c29e097c 100644 --- a/modelbaker/processing/ili2db_importing.py +++ b/modelbaker/processing/ili2db_importing.py @@ -271,6 +271,7 @@ def shortDescription(self) -> str: """

Imports data to PostgreSQL schema with ili2pg.

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

General Model Baker settings like custom model directories or db parameters are concerned.

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

Additionally, you need to define a dataset name for the import on such databases.

@@ -287,6 +288,7 @@ def shortHelpString(self) -> str: """

Imports data to PostgreSQL schema with ili2pg.

The ili2pg parameters are set in the same way as in the Model Baker Plugin.

+

General Model Baker settings like custom model directories or db parameters are concerned.

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

Additionally, you need to define a dataset name for the import on such databases.

@@ -370,6 +372,7 @@ def shortDescription(self) -> str: """

Imports data to GeoPackage file with ili2gpkg.

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin.

+

General Model Baker settings like custom model directories concerned.

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

Additionally, you need to define a dataset name for the import on such databases.

@@ -386,6 +389,7 @@ def shortHelpString(self) -> str: """

Imports data to GeoPackage file with ili2gpkg.

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin.

+

General Model Baker settings like custom model directories concerned.

The parameters passed to ili2db by default are --importTid and, on databases where you have created basket columns, --importBid as well.

On a database where you have created basket columns, the command is --update (or --replace when you choose to delete data previously).

Additionally, you need to define a dataset name for the import on such databases.

diff --git a/modelbaker/processing/ili2db_validating.py b/modelbaker/processing/ili2db_validating.py index 802878d3..f39cd1f7 100644 --- a/modelbaker/processing/ili2db_validating.py +++ b/modelbaker/processing/ili2db_validating.py @@ -316,15 +316,37 @@ def tags(self) -> list[str]: def shortDescription(self) -> str: """ - Returns a short description string for the algorithm. + Returns the tooltip text when hovering the algorithm """ - return self.tr("Validates data in a PostgreSQL schema with ili2db.") + return self.tr( + """ +

Validates data in PostgreSQL schema with ili2pg.

+

The ili2pg parameters are set in the same way as in the Model Baker Plugin Validator.

+

General Model Baker settings like custom model directories and db parameters are concerned.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Skip Geometry Errors ignores geometry errors (--skipGeometryErrors) and AREA topology validation (--disableAreaValidation) and the verbose mode provides you more information in the log output.

+

Additionally, you can define an export model to specify the format in which you want to validate the data (e.g. the base model of your extended model). Also You can add a validator config file to control the validation.

+ + """ + ) def shortHelpString(self) -> str: """ - Returns a short helper string for the algorithm. + Returns the help text on the right. """ - return self.tr("Validates data in a PostgreSQL schema with ili2db.") + return self.tr( + """ +

Validates data in PostgreSQL schema with ili2pg.

+

The ili2pg parameters are set in the same way as in the Model Baker Plugin Validator.

+

General Model Baker settings like custom model directories and db parameters are concerned.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Skip Geometry Errors ignores geometry errors (--skipGeometryErrors) and AREA topology validation (--disableAreaValidation) and the verbose mode provides you more information in the log output.

+

Additionally, you can define an export model to specify the format in which you want to validate the data (e.g. the base model of your extended model). Also You can add a validator config file to control the validation.

+ + """ + ) def initAlgorithm(self, config: Optional[dict[str, Any]] = None): self.validator.initParameters() @@ -394,15 +416,37 @@ def tags(self) -> list[str]: def shortDescription(self) -> str: """ - Returns a short description string for the algorithm. + Returns the tooltip text when hovering the algorithm """ - return self.tr("Validates data in a GeoPackage file with ili2db.") + return self.tr( + """ +

Validates data in GeoPackage file with ili2gpkg.

+

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin Validator.

+

General Model Baker settings like custom model directories and db parameters are concerned.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Skip Geometry Errors ignores geometry errors (--skipGeometryErrors) and AREA topology validation (--disableAreaValidation) and the verbose mode provides you more information in the log output.

+

Additionally, you can define an export model to specify the format in which you want to validate the data (e.g. the base model of your extended model). Also You can add a validator config file to control the validation.

+ + """ + ) def shortHelpString(self) -> str: """ - Returns a short helper string for the algorithm. + Returns the help text on the right. """ - return self.tr("Validates data in a GeoPackage file with ili2db.") + return self.tr( + """ +

Validates data in GeoPackage file with ili2gpkg.

+

The ili2gpkg parameters are set in the same way as in the Model Baker Plugin Validator.

+

General Model Baker settings like custom model directories and db parameters are concerned.

+

The parameter passed to ili2db by default is --exportTid.

+

In a database where you have created basket columns, you will have the possibility to filter by baskets or datasets.

+

Skip Geometry Errors ignores geometry errors (--skipGeometryErrors) and AREA topology validation (--disableAreaValidation) and the verbose mode provides you more information in the log output.

+

Additionally, you can define an export model to specify the format in which you want to validate the data (e.g. the base model of your extended model). Also You can add a validator config file to control the validation.

+ + """ + ) def initAlgorithm(self, config: Optional[dict[str, Any]] = None): self.validator.initParameters() From f9a3d7e9929b88928165eac2898b8300d3b296a2 Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 31 Oct 2025 16:02:56 +0100 Subject: [PATCH 04/12] add sslmode to get_configuration_from_sourceprovider --- modelbaker/utils/db_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modelbaker/utils/db_utils.py b/modelbaker/utils/db_utils.py index be1158e9..871b92ac 100644 --- a/modelbaker/utils/db_utils.py +++ b/modelbaker/utils/db_utils.py @@ -86,6 +86,7 @@ def get_configuration_from_sourceprovider(provider, configuration): configuration.dbhost = layer_source.host() or service_map.get("host") configuration.dbport = layer_source.port() or service_map.get("port") configuration.database = layer_source.database() or service_map.get("dbname") + configuration.sslmode = QgsDataSourceUri.encodeSslMode(layer_source.sslMode()) configuration.dbschema = layer_source.schema() valid = bool( configuration.dbhost and configuration.database and configuration.dbschema From 545fae7a0005012d59cd9a3a6fe8555c4c44cbd0 Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 31 Oct 2025 16:03:47 +0100 Subject: [PATCH 05/12] unify parameter names --- modelbaker/processing/ili2db_algorithm.py | 27 +++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/modelbaker/processing/ili2db_algorithm.py b/modelbaker/processing/ili2db_algorithm.py index d7524c6e..6f218c6a 100644 --- a/modelbaker/processing/ili2db_algorithm.py +++ b/modelbaker/processing/ili2db_algorithm.py @@ -69,12 +69,11 @@ def current_baseconfig(self): class Ili2pgAlgorithm(Ili2dbAlgorithm): - SERVICE = "SERVICE" HOST = "HOST" DBNAME = "DBNAME" PORT = "PORT" - USERNAME = "USERNAME" + USER = "USER" PASSWORD = "PASSWORD" SCHEMA = "SCHEMA" SSLMODE = "SSLMODE" @@ -147,14 +146,14 @@ def connection_input_params(self): ) sslmode_param.setHelp(self.tr("The SSL mode if needed.")) params.append(sslmode_param) - username_param = QgsProcessingParameterString( - self.USERNAME, - self.tr("Username"), + user_param = QgsProcessingParameterString( + self.USER, + self.tr("User"), defaultValue=None, optional=True, ) - username_param.setHelp(self.tr("The username to access the database.")) - params.append(username_param) + user_param.setHelp(self.tr("The user to access the database.")) + params.append(user_param) password_param = QgsProcessingParameterString( self.PASSWORD, @@ -183,7 +182,7 @@ def connection_input_params(self): ) authcfg_param.setHelp( self.tr( - "When choosing a QGIS Autentification you don't need username and password." + "When choosing a QGIS Autentification you don't need user and password." ) ) params.append(authcfg_param) @@ -196,9 +195,9 @@ def connection_output_params(self): # outputs for pass through params.append(QgsProcessingOutputString(self.SERVICE, self.tr("Service"))) params.append(QgsProcessingOutputString(self.HOST, self.tr("Host"))) - params.append(QgsProcessingOutputString(self.DBNAME, self.tr("Database Name"))) - params.append(QgsProcessingOutputNumber(self.PORT, self.tr("Port Number"))) - params.append(QgsProcessingOutputString(self.USERNAME, self.tr("Username"))) + params.append(QgsProcessingOutputString(self.DBNAME, self.tr("Database"))) + params.append(QgsProcessingOutputNumber(self.PORT, self.tr("Port"))) + params.append(QgsProcessingOutputString(self.USER, self.tr("User"))) params.append(QgsProcessingOutputString(self.PASSWORD, self.tr("Password"))) params.append(QgsProcessingOutputString(self.SCHEMA, self.tr("Schema"))) params.append(QgsProcessingOutputString(self.SSLMODE, self.tr("SSL Mode"))) @@ -229,7 +228,7 @@ def get_db_configuration_from_input(self, parameters, context, configuration): configuration.dbpwd = authconfig_map.get("password") else: configuration.dbusr = self.parameterAsString( - parameters, self.USERNAME, context + parameters, self.USER, context ) or service_map.get("user") configuration.dbpwd = self.parameterAsString( parameters, self.PASSWORD, context @@ -265,7 +264,7 @@ def get_output_from_db_configuration(self, configuration): self.HOST: configuration.dbhost, self.DBNAME: configuration.database, self.PORT: configuration.dbport, - self.USERNAME: configuration.dbusr, + self.USER: configuration.dbusr, self.PASSWORD: configuration.dbpwd, self.SCHEMA: configuration.dbschema, self.SSLMODE: configuration.sslmode, @@ -287,7 +286,7 @@ def connection_input_params(self): dbpath_param = QgsProcessingParameterFile( self.DBPATH, - self.tr("Database File"), + self.tr("Databasefile Path"), defaultValue=None, optional=True, ) From 99cb0e5f82ec87c2374edcecfffd17514edec8ef Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 31 Oct 2025 16:42:25 +0100 Subject: [PATCH 06/12] LayerSourceParsing util to get db parameters from layer --- modelbaker/processing/util_algorithm.py | 15 ++ .../processing/util_layersourceparsing.py | 170 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 modelbaker/processing/util_algorithm.py create mode 100644 modelbaker/processing/util_layersourceparsing.py diff --git a/modelbaker/processing/util_algorithm.py b/modelbaker/processing/util_algorithm.py new file mode 100644 index 00000000..35496316 --- /dev/null +++ b/modelbaker/processing/util_algorithm.py @@ -0,0 +1,15 @@ +import os + +from qgis.core import QgsProcessingAlgorithm +from qgis.PyQt.QtGui import QIcon + + +class UtilAlgorithm(QgsProcessingAlgorithm): + def __init__(self): + super().__init__() + + def group(self): + return self.tr("Utils") + + def groupId(self): + return "utils" diff --git a/modelbaker/processing/util_layersourceparsing.py b/modelbaker/processing/util_layersourceparsing.py new file mode 100644 index 00000000..7ad59b56 --- /dev/null +++ b/modelbaker/processing/util_layersourceparsing.py @@ -0,0 +1,170 @@ +""" +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +from typing import Any, Optional + +from qgis.core import ( + QgsProcessing, + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingOutputNumber, + QgsProcessingOutputString, + QgsProcessingParameterVectorLayer, +) +from qgis.PyQt.QtCore import QCoreApplication + +from ..utils.db_utils import get_configuration_from_sourceprovider +from ..iliwrapper.ili2dbconfig import ( + Ili2DbCommandConfiguration, +) +from .util_algorithm import UtilAlgorithm + + +class LayerSourceParsingAlgorithm(UtilAlgorithm): + """ + This is an algorithm from Model Baker. + + It is meant for reading the data source parameters from a layer + """ + + # Connection + LAYER = "LAYER" + + # Output + PROVIDER = "PROVIDER" + ## PG + SERVICE = "SERVICE" + HOST = "HOST" + DBNAME = "DBNAME" + PORT = "PORT" + USERNAME = "USERNAME" + PASSWORD = "PASSWORD" + SCHEMA = "SCHEMA" + SSLMODE = "SSLMODE" + AUTHCFG = "AUTHCFG" + ## GPKG + DBPATH = "DBPATH" + + def __init__(self): + super().__init__() + + def name(self) -> str: + """ + Returns the algorithm name, used for identifying the algorithm. + """ + return "modelbaker_util_layerconnection" + + def displayName(self) -> str: + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Get connection from layersource") + + def tags(self) -> list[str]: + + return ["layer", "source", "database", "modelbaker", "ili2db", "interlis"] + + def shortDescription(self) -> str: + """ + Returns a short description string for the algorithm. + """ + return self.tr("Receives connection parameters from layer source.") + + def shortHelpString(self) -> str: + """ + Returns a short helper string for the algorithm. + """ + return self.tr("Receives connection parameters from layer source.") + + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): + + sourcelayer_param = QgsProcessingParameterVectorLayer( + self.LAYER, + self.tr("Layer"), + [QgsProcessing.SourceType.TypeVector], + self.tr("No source layer selected"), + ) + sourcelayer_param.setHelp( + self.tr( + "Source layer to get database connection from. If set, it will be prefered over the other connection settings." + ) + ) + self.addParameter(sourcelayer_param) + + self.addOutput(QgsProcessingOutputString(self.PROVIDER, self.tr("Provider"))) + self.addOutput(QgsProcessingOutputString(self.SERVICE, self.tr("Service"))) + self.addOutput(QgsProcessingOutputString(self.HOST, self.tr("Host"))) + self.addOutput(QgsProcessingOutputString(self.DBNAME, self.tr("Database"))) + self.addOutput(QgsProcessingOutputNumber(self.PORT, self.tr("Port"))) + self.addOutput(QgsProcessingOutputString(self.USERNAME, self.tr("User"))) + self.addOutput(QgsProcessingOutputString(self.PASSWORD, self.tr("Password"))) + self.addOutput(QgsProcessingOutputString(self.SCHEMA, self.tr("Schema"))) + self.addOutput(QgsProcessingOutputString(self.SSLMODE, self.tr('SSL Mode'))) + self.addOutput( + QgsProcessingOutputString(self.AUTHCFG, self.tr("Authentication")) + ) + + self.addOutput( + QgsProcessingOutputString(self.DBPATH, self.tr("Databasefile Path")) + ) + + def processAlgorithm( + self, + parameters: dict[str, Any], + context: QgsProcessingContext, + feedback: QgsProcessingFeedback, + ) -> dict[str, Any]: + """ + Here is where the processing itself takes place. + """ + configuration = Ili2DbCommandConfiguration() + + sourcelayer = self.parameterAsVectorLayer( + parameters, self.LAYER, context + ) + if not sourcelayer: + raise QgsProcessingException( + self.invalidSourceError(parameters, self.LAYER) + ) + return {} + + source_provider = sourcelayer.dataProvider() + valid, mode = get_configuration_from_sourceprovider( + source_provider, configuration + ) + if not (valid and mode): + # error + return {} + + if feedback.isCanceled(): + return {} + + return { + self.PROVIDER: source_provider.name(), + self.SERVICE: configuration.dbservice, + self.HOST: configuration.dbhost, + self.DBNAME: configuration.database, + self.PORT: configuration.dbport, + self.USERNAME: configuration.dbusr, + self.PASSWORD: configuration.dbpwd, + self.SCHEMA: configuration.dbschema, + self.SSLMODE: configuration.sslmode, + self.AUTHCFG: configuration.dbauthid, + self.DBPATH: configuration.dbfile, + } + + def tr(self, string): + return QCoreApplication.translate("Processing", string) + + def createInstance(self): + return self.__class__() From 7da7b6a3690c3d9448983149e285540495894f01 Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 31 Oct 2025 16:55:10 +0100 Subject: [PATCH 07/12] description --- .../processing/util_layersourceparsing.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/modelbaker/processing/util_layersourceparsing.py b/modelbaker/processing/util_layersourceparsing.py index 7ad59b56..e7aa7dd7 100644 --- a/modelbaker/processing/util_layersourceparsing.py +++ b/modelbaker/processing/util_layersourceparsing.py @@ -78,14 +78,28 @@ def shortDescription(self) -> str: """ Returns a short description string for the algorithm. """ - return self.tr("Receives connection parameters from layer source.") - + return self.tr( + """ +

Receives connection parameters from the layer source.

+

Parses the GeoPackage or PostgreSQL source of a layer.

+

Provides the parameters to be used in the Model Baker ili2db algorithms.

+ + """ + ) + def shortHelpString(self) -> str: """ Returns a short helper string for the algorithm. """ - return self.tr("Receives connection parameters from layer source.") - + return self.tr( + """ +

Receives connection parameters from the layer source.

+

Parses the GeoPackage or PostgreSQL source of a layer.

+

Provides the parameters to be used in the Model Baker ili2db algorithms.

+ + """ + ) + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): sourcelayer_param = QgsProcessingParameterVectorLayer( From f041646db730d40639e9f7cca53793bff7dc7afe Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 7 Nov 2025 17:16:54 +0100 Subject: [PATCH 08/12] fix tests --- modelbaker/iliwrapper/iliexecutable.py | 1 - .../processing/util_layersourceparsing.py | 20 ++++++++----------- tests/test_processing.py | 12 +++++------ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/modelbaker/iliwrapper/iliexecutable.py b/modelbaker/iliwrapper/iliexecutable.py index 34f61804..0839dc55 100644 --- a/modelbaker/iliwrapper/iliexecutable.py +++ b/modelbaker/iliwrapper/iliexecutable.py @@ -104,7 +104,6 @@ def command(self, hide_password): get_java_path(self.configuration.base_configuration) ) command_args = ili2db_jar_arg + args - valid_args = [] for command_arg in command_args: valid_args.append(self._escaped_arg(command_arg)) diff --git a/modelbaker/processing/util_layersourceparsing.py b/modelbaker/processing/util_layersourceparsing.py index e7aa7dd7..3e7cfbf0 100644 --- a/modelbaker/processing/util_layersourceparsing.py +++ b/modelbaker/processing/util_layersourceparsing.py @@ -22,10 +22,8 @@ ) from qgis.PyQt.QtCore import QCoreApplication +from ..iliwrapper.ili2dbconfig import Ili2DbCommandConfiguration from ..utils.db_utils import get_configuration_from_sourceprovider -from ..iliwrapper.ili2dbconfig import ( - Ili2DbCommandConfiguration, -) from .util_algorithm import UtilAlgorithm @@ -46,7 +44,7 @@ class LayerSourceParsingAlgorithm(UtilAlgorithm): HOST = "HOST" DBNAME = "DBNAME" PORT = "PORT" - USERNAME = "USERNAME" + USER = "USER" PASSWORD = "PASSWORD" SCHEMA = "SCHEMA" SSLMODE = "SSLMODE" @@ -86,7 +84,7 @@ def shortDescription(self) -> str: """ ) - + def shortHelpString(self) -> str: """ Returns a short helper string for the algorithm. @@ -99,7 +97,7 @@ def shortHelpString(self) -> str: """ ) - + def initAlgorithm(self, config: Optional[dict[str, Any]] = None): sourcelayer_param = QgsProcessingParameterVectorLayer( @@ -120,10 +118,10 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None): self.addOutput(QgsProcessingOutputString(self.HOST, self.tr("Host"))) self.addOutput(QgsProcessingOutputString(self.DBNAME, self.tr("Database"))) self.addOutput(QgsProcessingOutputNumber(self.PORT, self.tr("Port"))) - self.addOutput(QgsProcessingOutputString(self.USERNAME, self.tr("User"))) + self.addOutput(QgsProcessingOutputString(self.USER, self.tr("User"))) self.addOutput(QgsProcessingOutputString(self.PASSWORD, self.tr("Password"))) self.addOutput(QgsProcessingOutputString(self.SCHEMA, self.tr("Schema"))) - self.addOutput(QgsProcessingOutputString(self.SSLMODE, self.tr('SSL Mode'))) + self.addOutput(QgsProcessingOutputString(self.SSLMODE, self.tr("SSL Mode"))) self.addOutput( QgsProcessingOutputString(self.AUTHCFG, self.tr("Authentication")) ) @@ -143,9 +141,7 @@ def processAlgorithm( """ configuration = Ili2DbCommandConfiguration() - sourcelayer = self.parameterAsVectorLayer( - parameters, self.LAYER, context - ) + sourcelayer = self.parameterAsVectorLayer(parameters, self.LAYER, context) if not sourcelayer: raise QgsProcessingException( self.invalidSourceError(parameters, self.LAYER) @@ -169,7 +165,7 @@ def processAlgorithm( self.HOST: configuration.dbhost, self.DBNAME: configuration.database, self.PORT: configuration.dbport, - self.USERNAME: configuration.dbusr, + self.USER: configuration.dbusr, self.PASSWORD: configuration.dbpwd, self.SCHEMA: configuration.dbschema, self.SSLMODE: configuration.sslmode, diff --git a/tests/test_processing.py b/tests/test_processing.py index d1ad3035..09a1974d 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -54,7 +54,7 @@ def iliimporter_pg_config_params(self): params = { "HOST": configuration.dbhost, "DBNAME": configuration.database, - "USERNAME": configuration.dbusr, + "USER": configuration.dbusr, "PASSWORD": configuration.dbpwd, } return params @@ -123,13 +123,13 @@ def test_algs_pg(self): ValidatingPGAlgorithm, ExportingPGAlgorithm, ) - conn_parameters = {} - conn_parameters["DBPATH"] = self.gpkg_file(False) + conn_parameters = self.iliimporter_pg_config_params() + conn_parameters["SCHEMA"] = self.pg_schema(True) self._algs_without_baskets( conn_parameters, - ImportingGPKGAlgorithm, - ValidatingGPKGAlgorithm, - ExportingGPKGAlgorithm, + ImportingPGAlgorithm, + ValidatingPGAlgorithm, + ExportingPGAlgorithm, ) def _algs_with_baskets( From 5cd818dcd2cc3380279e826541a53f52551fa943 Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 7 Nov 2025 17:20:07 +0100 Subject: [PATCH 09/12] style --- modelbaker/processing/util_algorithm.py | 3 --- tests/test_validate.py | 30 ++++++++++++------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/modelbaker/processing/util_algorithm.py b/modelbaker/processing/util_algorithm.py index 35496316..4b23727d 100644 --- a/modelbaker/processing/util_algorithm.py +++ b/modelbaker/processing/util_algorithm.py @@ -1,7 +1,4 @@ -import os - from qgis.core import QgsProcessingAlgorithm -from qgis.PyQt.QtGui import QIcon class UtilAlgorithm(QgsProcessingAlgorithm): diff --git a/tests/test_validate.py b/tests/test_validate.py index 85f518af..cc703fe0 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -344,17 +344,17 @@ def test_validate_skips(self): "value 1314393.469 is out of range in attribute Geometry", "Attribute Name requires a value", "Object should associate 1 to 1 target objects (instead of 0)", - "Set Constraint Brutalism.Buildings.Resident.Constraint1 is not true." + "Set Constraint Brutalism.Buildings.Resident.Constraint1 is not true.", } - errors = set() + errors = set() for i in range(4): errors.add( result_model.index(i, 0).data( int(ilivalidator.ValidationResultModel.Roles.MESSAGE) ) ) - + assert errors == expected_errors # Skip geometry errors @@ -369,17 +369,17 @@ def test_validate_skips(self): expected_errors_no_geom = { "Attribute Name requires a value", "Object should associate 1 to 1 target objects (instead of 0)", - "Set Constraint Brutalism.Buildings.Resident.Constraint1 is not true." + "Set Constraint Brutalism.Buildings.Resident.Constraint1 is not true.", } - errors = set() + errors = set() for i in range(3): errors.add( result_model.index(i, 0).data( int(ilivalidator.ValidationResultModel.Roles.MESSAGE) ) ) - + assert errors == expected_errors_no_geom # Multiplicity off @@ -395,17 +395,17 @@ def test_validate_skips(self): assert result_model.rowCount() == 2 expected_errors_no_multi = { "value 1314393.469 is out of range in attribute Geometry", - "Set Constraint Brutalism.Buildings.Resident.Constraint1 is not true." + "Set Constraint Brutalism.Buildings.Resident.Constraint1 is not true.", } - errors = set() + errors = set() for i in range(2): errors.add( result_model.index(i, 0).data( int(ilivalidator.ValidationResultModel.Roles.MESSAGE) ) ) - + assert errors == expected_errors_no_multi # Constraint off @@ -425,14 +425,14 @@ def test_validate_skips(self): "Object should associate 1 to 1 target objects (instead of 0)", } - errors = set() + errors = set() for i in range(3): errors.add( result_model.index(i, 0).data( int(ilivalidator.ValidationResultModel.Roles.MESSAGE) ) ) - + assert errors == expected_errors_no_const def test_validate_exportmodels_gpkg(self): @@ -496,21 +496,21 @@ def test_validate_exportmodels_gpkg(self): result_model.configuration = validator.configuration result_model.reload() assert result_model.rowCount() == 3 - + expected_errors = { "Needs an ethical evaluation (EthischeBeurteilung)", "Beschreibung and/or Referenzcode must be defined.", - "Beschreibung needed when top secret." + "Beschreibung needed when top secret.", } - errors = set() + errors = set() for i in range(3): errors.add( result_model.index(i, 0).data( int(ilivalidator.ValidationResultModel.Roles.MESSAGE) ) ) - + assert errors == expected_errors # Validate at cantonal level (setting --exportModels KantonaleOrtsplanung_V1_1) From cb466a3fe03586d28bf7a855cc953f39ba085e3b Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 21 Nov 2025 17:36:57 +0100 Subject: [PATCH 10/12] better naming and docs --- modelbaker/processing/ili2db_algorithm.py | 10 +++++----- modelbaker/processing/ili2db_exporting.py | 18 ++++++++---------- modelbaker/processing/ili2db_importing.py | 14 ++++++-------- modelbaker/processing/ili2db_validating.py | 9 +++------ .../processing/util_layersourceparsing.py | 2 +- tests/test_processing.py | 2 +- 6 files changed, 24 insertions(+), 31 deletions(-) diff --git a/modelbaker/processing/ili2db_algorithm.py b/modelbaker/processing/ili2db_algorithm.py index 6f218c6a..3e1dd07a 100644 --- a/modelbaker/processing/ili2db_algorithm.py +++ b/modelbaker/processing/ili2db_algorithm.py @@ -176,13 +176,13 @@ def connection_input_params(self): authcfg_param = QgsProcessingParameterAuthConfig( self.AUTHCFG, - self.tr("Authentification"), + self.tr("Authentication"), defaultValue=None, optional=True, ) authcfg_param.setHelp( self.tr( - "When choosing a QGIS Autentification you don't need user and password." + "When choosing a QGIS Authentication you don't need user and password." ) ) params.append(authcfg_param) @@ -286,11 +286,11 @@ def connection_input_params(self): dbpath_param = QgsProcessingParameterFile( self.DBPATH, - self.tr("Databasefile Path"), + self.tr("Database File Path"), defaultValue=None, optional=True, ) - dbpath_param.setHelp(self.tr("todo")) + dbpath_param.setHelp(self.tr("The database file path (*.gpkg).")) params.append(dbpath_param) return params @@ -299,7 +299,7 @@ def connection_output_params(self): params = [] params.append( - QgsProcessingOutputFile(self.DBPATH, self.tr("Databasefile Path")) + QgsProcessingOutputFile(self.DBPATH, self.tr("Database File Path")) ) return params diff --git a/modelbaker/processing/ili2db_exporting.py b/modelbaker/processing/ili2db_exporting.py index f365068e..f3414bd4 100644 --- a/modelbaker/processing/ili2db_exporting.py +++ b/modelbaker/processing/ili2db_exporting.py @@ -9,7 +9,6 @@ *************************************************************************** """ -import re from typing import Any, Optional from qgis.core import ( @@ -58,8 +57,8 @@ def export_input_params(self): xtffile_param = QgsProcessingParameterFileDestination( self.XTFFILEPATH, - self.tr("Target Transferfile (XTF)"), - self.tr("INTERLIS Transferfile (*.xtf *.xml)"), + self.tr("Target Transfer File (XTF)"), + self.tr("INTERLIS Transfer File (*.xtf *.xml)"), defaultValue=None, optional=False, ) @@ -128,7 +127,7 @@ def export_output_params(self): QgsProcessingOutputBoolean(self.ISVALID, self.tr("Export Result")) ) params.append( - QgsProcessingOutputFile(self.XTFFILEPATH, self.tr("Transferfile Path")) + QgsProcessingOutputFile(self.XTFFILEPATH, self.tr("Transfer File Path")) ) return params @@ -232,12 +231,10 @@ def _get_model_names(self, configuration): and db_connector.metadata_exists() ): db_models = db_connector.get_models() - regex = re.compile(r"(?:\{[^\}]*\}|\s)") for db_model in db_models: - for modelname in regex.split(db_model["modelname"]): - name = modelname.strip() - if name and name not in modelnames and name not in MODELS_BLACKLIST: - modelnames.append(name) + name = db_model["modelname"] + if name and name not in modelnames and name not in MODELS_BLACKLIST: + modelnames.append(name) return modelnames def tr(self, string): @@ -278,7 +275,8 @@ def tags(self) -> list[str]: "baker", "export", "transferfile", - "xtf" "ili2db", + "xtf", + "ili2db", "ili2pg", "Postgres", "PostGIS", diff --git a/modelbaker/processing/ili2db_importing.py b/modelbaker/processing/ili2db_importing.py index c29e097c..6b9818a3 100644 --- a/modelbaker/processing/ili2db_importing.py +++ b/modelbaker/processing/ili2db_importing.py @@ -9,7 +9,6 @@ *************************************************************************** """ -import re from typing import Any, Optional from qgis.core import ( @@ -57,7 +56,7 @@ def import_input_params(self): xtffile_param = QgsProcessingParameterFile( self.XTFFILEPATH, - self.tr("Source Transferfile (XTF)"), + self.tr("Source Transfer File (XTF)"), defaultValue=None, optional=False, ) @@ -211,12 +210,10 @@ def _get_model_names(self, configuration): and db_connector.metadata_exists() ): db_models = db_connector.get_models() - regex = re.compile(r"(?:\{[^\}]*\}|\s)") for db_model in db_models: - for modelname in regex.split(db_model["modelname"]): - name = modelname.strip() - if name and name not in modelnames and name not in MODELS_BLACKLIST: - modelnames.append(name) + name = db_model["modelname"] + if name and name not in modelnames and name not in MODELS_BLACKLIST: + modelnames.append(name) return modelnames def tr(self, string): @@ -257,7 +254,8 @@ def tags(self) -> list[str]: "baker", "import", "transferfile", - "xtf" "ili2db", + "xtf", + "ili2db", "ili2pg", "Postgres", "PostGIS", diff --git a/modelbaker/processing/ili2db_validating.py b/modelbaker/processing/ili2db_validating.py index f39cd1f7..0ebc5202 100644 --- a/modelbaker/processing/ili2db_validating.py +++ b/modelbaker/processing/ili2db_validating.py @@ -10,7 +10,6 @@ """ import os -import re from datetime import datetime from typing import Any, Optional @@ -262,12 +261,10 @@ def _get_model_names(self, configuration): and db_connector.metadata_exists() ): db_models = db_connector.get_models() - regex = re.compile(r"(?:\{[^\}]*\}|\s)") for db_model in db_models: - for modelname in regex.split(db_model["modelname"]): - name = modelname.strip() - if name and name not in modelnames and name not in MODELS_BLACKLIST: - modelnames.append(name) + name = db_model["modelname"] + if name and name not in modelnames and name not in MODELS_BLACKLIST: + modelnames.append(name) return modelnames def tr(self, string): diff --git a/modelbaker/processing/util_layersourceparsing.py b/modelbaker/processing/util_layersourceparsing.py index 3e7cfbf0..25790843 100644 --- a/modelbaker/processing/util_layersourceparsing.py +++ b/modelbaker/processing/util_layersourceparsing.py @@ -127,7 +127,7 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None): ) self.addOutput( - QgsProcessingOutputString(self.DBPATH, self.tr("Databasefile Path")) + QgsProcessingOutputString(self.DBPATH, self.tr("Database File Path")) ) def processAlgorithm( diff --git a/tests/test_processing.py b/tests/test_processing.py index 09a1974d..3461f0c3 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -124,7 +124,7 @@ def test_algs_pg(self): ExportingPGAlgorithm, ) conn_parameters = self.iliimporter_pg_config_params() - conn_parameters["SCHEMA"] = self.pg_schema(True) + conn_parameters["SCHEMA"] = self.pg_schema(False) self._algs_without_baskets( conn_parameters, ImportingPGAlgorithm, From 91674a290af4c801105b9c93da46c93379b59118 Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 21 Nov 2025 17:47:09 +0100 Subject: [PATCH 11/12] process operator base --- modelbaker/processing/ili2db_exporting.py | 33 ++------------ modelbaker/processing/ili2db_importing.py | 39 ++-------------- modelbaker/processing/ili2db_operating.py | 53 ++++++++++++++++++++++ modelbaker/processing/ili2db_validating.py | 34 ++------------ 4 files changed, 63 insertions(+), 96 deletions(-) create mode 100644 modelbaker/processing/ili2db_operating.py diff --git a/modelbaker/processing/ili2db_exporting.py b/modelbaker/processing/ili2db_exporting.py index f3414bd4..5ef17d33 100644 --- a/modelbaker/processing/ili2db_exporting.py +++ b/modelbaker/processing/ili2db_exporting.py @@ -22,18 +22,16 @@ QgsProcessingParameterFileDestination, QgsProcessingParameterString, ) -from qgis.PyQt.QtCore import QCoreApplication, QObject from ..iliwrapper import iliexporter from ..iliwrapper.globals import DbIliMode from ..iliwrapper.ili2dbconfig import ExportConfiguration from ..iliwrapper.ili2dbutils import JavaNotFoundError -from ..utils.db_utils import get_db_connector -from ..utils.globals import MODELS_BLACKLIST from .ili2db_algorithm import Ili2gpkgAlgorithm, Ili2pgAlgorithm +from .ili2db_operating import ProcessOperatorBase -class ProcessExporter(QObject): +class ProcessExporter(ProcessOperatorBase): # Filters FILTERMODE = "FILTERMODE" # none, models, baskets or datasets @@ -49,7 +47,7 @@ class ProcessExporter(QObject): ISVALID = "ISVALID" def __init__(self, parent): - super().__init__() + super().__init__(parent) self.parent = parent def export_input_params(self): @@ -215,31 +213,6 @@ def get_configuration_from_input(self, parameters, context, tool): return configuration - def _get_tid_handling(self, configuration): - db_connector = get_db_connector(configuration) - if db_connector: - return db_connector.get_tid_handling() - return False - - def _get_model_names(self, configuration): - modelnames = [] - - db_connector = get_db_connector(configuration) - if ( - db_connector - and db_connector.db_or_schema_exists() - and db_connector.metadata_exists() - ): - db_models = db_connector.get_models() - for db_model in db_models: - name = db_model["modelname"] - if name and name not in modelnames and name not in MODELS_BLACKLIST: - modelnames.append(name) - return modelnames - - def tr(self, string): - return QCoreApplication.translate("Processing", string) - class ExportingPGAlgorithm(Ili2pgAlgorithm): """ diff --git a/modelbaker/processing/ili2db_importing.py b/modelbaker/processing/ili2db_importing.py index 6b9818a3..4af48826 100644 --- a/modelbaker/processing/ili2db_importing.py +++ b/modelbaker/processing/ili2db_importing.py @@ -20,7 +20,6 @@ QgsProcessingParameterFile, QgsProcessingParameterString, ) -from qgis.PyQt.QtCore import QCoreApplication, QObject from ..iliwrapper import iliimporter from ..iliwrapper.globals import DbIliMode @@ -30,12 +29,11 @@ UpdateDataConfiguration, ) from ..iliwrapper.ili2dbutils import JavaNotFoundError -from ..utils.db_utils import get_db_connector -from ..utils.globals import MODELS_BLACKLIST from .ili2db_algorithm import Ili2gpkgAlgorithm, Ili2pgAlgorithm +from .ili2db_operating import ProcessOperatorBase -class ProcessImporter(QObject): +class ProcessImporter(ProcessOperatorBase): # Settings DISABLEVALIDATION = "DISABLEVALIDATION" @@ -48,7 +46,7 @@ class ProcessImporter(QObject): ISVALID = "ISVALID" def __init__(self, parent): - super().__init__() + super().__init__(parent) self.parent = parent def import_input_params(self): @@ -188,37 +186,6 @@ def get_configuration_from_input(self, parameters, context, tool): return configuration - def _get_tid_handling(self, configuration): - db_connector = get_db_connector(configuration) - if db_connector: - return db_connector.get_tid_handling() - return False - - def _basket_handling(self, configuration): - db_connector = get_db_connector(configuration) - if db_connector: - return db_connector.get_basket_handling() - return False - - def _get_model_names(self, configuration): - modelnames = [] - - db_connector = get_db_connector(configuration) - if ( - db_connector - and db_connector.db_or_schema_exists() - and db_connector.metadata_exists() - ): - db_models = db_connector.get_models() - for db_model in db_models: - name = db_model["modelname"] - if name and name not in modelnames and name not in MODELS_BLACKLIST: - modelnames.append(name) - return modelnames - - def tr(self, string): - return QCoreApplication.translate("Processing", string) - class ImportingPGAlgorithm(Ili2pgAlgorithm): """ diff --git a/modelbaker/processing/ili2db_operating.py b/modelbaker/processing/ili2db_operating.py new file mode 100644 index 00000000..bb1ec0a4 --- /dev/null +++ b/modelbaker/processing/ili2db_operating.py @@ -0,0 +1,53 @@ +""" +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + + +from qgis.PyQt.QtCore import QCoreApplication, QObject + +from ..utils.db_utils import get_db_connector +from ..utils.globals import MODELS_BLACKLIST + + +class ProcessOperatorBase(QObject): + def __init__(self, parent): + super().__init__() + self.parent = parent + + def _get_tid_handling(self, configuration): + db_connector = get_db_connector(configuration) + if db_connector: + return db_connector.get_tid_handling() + return False + + def _basket_handling(self, configuration): + db_connector = get_db_connector(configuration) + if db_connector: + return db_connector.get_basket_handling() + return False + + def _get_model_names(self, configuration): + modelnames = [] + + db_connector = get_db_connector(configuration) + if ( + db_connector + and db_connector.db_or_schema_exists() + and db_connector.metadata_exists() + ): + db_models = db_connector.get_models() + for db_model in db_models: + name = db_model["modelname"] + if name and name not in modelnames and name not in MODELS_BLACKLIST: + modelnames.append(name) + return modelnames + + def tr(self, string): + return QCoreApplication.translate("Processing", string) diff --git a/modelbaker/processing/ili2db_validating.py b/modelbaker/processing/ili2db_validating.py index 0ebc5202..2977547e 100644 --- a/modelbaker/processing/ili2db_validating.py +++ b/modelbaker/processing/ili2db_validating.py @@ -24,18 +24,17 @@ QgsProcessingParameterFile, QgsProcessingParameterString, ) -from qgis.PyQt.QtCore import QCoreApplication, QObject, QStandardPaths +from qgis.PyQt.QtCore import QStandardPaths from ..iliwrapper import ilivalidator from ..iliwrapper.globals import DbIliMode from ..iliwrapper.ili2dbconfig import ValidateConfiguration from ..iliwrapper.ili2dbutils import JavaNotFoundError -from ..utils.db_utils import get_db_connector -from ..utils.globals import MODELS_BLACKLIST from .ili2db_algorithm import Ili2gpkgAlgorithm, Ili2pgAlgorithm +from .ili2db_operating import ProcessOperatorBase -class ProcessValidator(QObject): +class ProcessValidator(ProcessOperatorBase): # Filters FILTERMODE = "FILTERMODE" # none, models, baskets or datasets @@ -52,7 +51,7 @@ class ProcessValidator(QObject): XTFLOGPATH = "XTFLOGPATH" def __init__(self, parent): - super().__init__() + super().__init__(parent) self.parent = parent def validation_input_params(self): @@ -245,31 +244,6 @@ def get_configuration_from_input(self, parameters, context, tool): return configuration - def _get_tid_handling(self, configuration): - db_connector = get_db_connector(configuration) - if db_connector: - return db_connector.get_tid_handling() - return False - - def _get_model_names(self, configuration): - modelnames = [] - - db_connector = get_db_connector(configuration) - if ( - db_connector - and db_connector.db_or_schema_exists() - and db_connector.metadata_exists() - ): - db_models = db_connector.get_models() - for db_model in db_models: - name = db_model["modelname"] - if name and name not in modelnames and name not in MODELS_BLACKLIST: - modelnames.append(name) - return modelnames - - def tr(self, string): - return QCoreApplication.translate("Processing", string) - class ValidatingPGAlgorithm(Ili2pgAlgorithm): """ From 734f0c0b88e0c7306926549bf1da1fe9e830667f Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 21 Nov 2025 21:07:36 +0100 Subject: [PATCH 12/12] use sets to compare lists in test --- tests/test_projectgen.py | 32 ++++++++++++++++---------------- tests/test_projecttopping.py | 8 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/test_projectgen.py b/tests/test_projectgen.py index 26c362ca..0a6e02ea 100644 --- a/tests/test_projectgen.py +++ b/tests/test_projectgen.py @@ -3741,14 +3741,14 @@ def test_kbs_postgis_toppings(self): ) assert belasteter_standort_group is not None belasteter_standort_group_layer = belasteter_standort_group.findLayers() - assert [layer.name() for layer in belasteter_standort_group_layer] == [ + assert {layer.name() for layer in belasteter_standort_group_layer} == { "Belasteter_Standort (Geo_Lage_Punkt)", "Belasteter_Standort (Geo_Lage_Polygon)", - ] + } informationen_group = qgis_project.layerTreeRoot().findGroup("Informationen") assert informationen_group is not None informationen_group_layers = informationen_group.findLayers() - assert [layer.name() for layer in informationen_group_layers] == [ + assert {layer.name() for layer in informationen_group_layers} == { "EGRID_", "Deponietyp_", "ZustaendigkeitKataster", @@ -3767,27 +3767,27 @@ def test_kbs_postgis_toppings(self): "UntersMassn", "Deponietyp", "LanguageCode_ISO639_1", - ] + } text_infos_group = informationen_group.findGroup("Text Infos") assert text_infos_group is not None text_infos_group_layers = text_infos_group.findLayers() - assert [layer.name() for layer in text_infos_group_layers] == [ + assert {layer.name() for layer in text_infos_group_layers} == { "MultilingualMText", "LocalisedMText", "MultilingualText", "LocalisedText", - ] + } other_infos_group = informationen_group.findGroup("Other Infos") self.assertIsNotNone(other_infos_group) other_infos_group_layers = other_infos_group.findLayers() - assert [layer.name() for layer in other_infos_group_layers] == [ + assert {layer.name() for layer in other_infos_group_layers} == { "StatusAltlV", "Standorttyp", "UntersMassn", "Deponietyp", "LanguageCode_ISO639_1", - ] + } # check the node properties belasteter_standort_punkt_layer = None belasteter_standort_polygon_layer = None @@ -4133,16 +4133,16 @@ def test_kbs_geopackage_toppings(self): ) assert belasteter_standort_group is not None belasteter_standort_group_layer = belasteter_standort_group.findLayers() - assert [layer.name() for layer in belasteter_standort_group_layer] == [ + assert {layer.name() for layer in belasteter_standort_group_layer} == { "Belasteter_Standort (Geo_Lage_Punkt)", "Belasteter_Standort", - ] + } informationen_group = qgis_project.layerTreeRoot().findGroup("Informationen") assert informationen_group is not None informationen_group_layers = informationen_group.findLayers() - assert [layer.name() for layer in informationen_group_layers] == [ + assert {layer.name() for layer in informationen_group_layers} == { "EGRID_", "Deponietyp_", "ZustaendigkeitKataster", @@ -4161,27 +4161,27 @@ def test_kbs_geopackage_toppings(self): "UntersMassn", "Deponietyp", "LanguageCode_ISO639_1", - ] + } text_infos_group = informationen_group.findGroup("Text Infos") assert text_infos_group is not None text_infos_group_layers = text_infos_group.findLayers() - assert [layer.name() for layer in text_infos_group_layers] == [ + assert {layer.name() for layer in text_infos_group_layers} == { "MultilingualMText", "LocalisedMText", "MultilingualText", "LocalisedText", - ] + } other_infos_group = informationen_group.findGroup("Other Infos") assert other_infos_group is not None other_infos_group_layers = other_infos_group.findLayers() - assert [layer.name() for layer in other_infos_group_layers] == [ + assert {layer.name() for layer in other_infos_group_layers} == { "StatusAltlV", "Standorttyp", "UntersMassn", "Deponietyp", "LanguageCode_ISO639_1", - ] + } # check the node properties belasteter_standort_punkt_layer = None belasteter_standort_polygon_layer = None diff --git a/tests/test_projecttopping.py b/tests/test_projecttopping.py index 9860dc4f..290d0393 100644 --- a/tests/test_projecttopping.py +++ b/tests/test_projecttopping.py @@ -127,10 +127,10 @@ def test_kbs_postgis_qlr_layers(self): ) assert ili_layers_group is not None ili_layers_group_layers = ili_layers_group.findLayers() - assert [layer.name() for layer in ili_layers_group_layers] == [ + assert {layer.name() for layer in ili_layers_group_layers} == { "Belasteter_Standort (Geo_Lage_Punkt)", "Belasteter_Standort (Geo_Lage_Polygon)", - ] + } qlr_layers_group = qgis_project.layerTreeRoot().findGroup("Other Layers") assert qlr_layers_group is not None @@ -248,10 +248,10 @@ def test_kbs_postgis_source_layers(self): ) assert ili_layers_group is not None ili_layers_group_layers = ili_layers_group.findLayers() - assert [layer.name() for layer in ili_layers_group_layers] == [ + assert {layer.name() for layer in ili_layers_group_layers} == { "Belasteter_Standort (Geo_Lage_Punkt)", "Belasteter_Standort (Geo_Lage_Polygon)", - ] + } source_layers_group = qgis_project.layerTreeRoot().findGroup("Other Layers") assert source_layers_group is not None