diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 638fbfa4..13e49f90 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -148,7 +148,7 @@ jobs: pip3 wheel -r mergin_client.egg-info/requires.txt -w mergin/deps # special care for pygeodiff unzip mergin/deps/pygeodiff-*.whl -d mergin/deps - # remove unncesessary files + # remove unnecessary files rm -rf mergin/deps/*.dist-info rm -rf mergin/deps/*.data rm -rf mergin/deps/pygeodiff.libs diff --git a/Mergin/clone_project_dialog.py b/Mergin/clone_project_dialog.py index 2b2420ce..e022a71f 100644 --- a/Mergin/clone_project_dialog.py +++ b/Mergin/clone_project_dialog.py @@ -6,10 +6,6 @@ from qgis.PyQt.QtWidgets import ( QDialog, QDialogButtonBox, - QFileDialog, - QApplication, - QMessageBox, - QComboBox, ) from qgis.PyQt.QtCore import Qt from qgis.PyQt import uic diff --git a/Mergin/collapsible_message_box.py b/Mergin/collapsible_message_box.py index 960cf056..92f4e511 100644 --- a/Mergin/collapsible_message_box.py +++ b/Mergin/collapsible_message_box.py @@ -2,10 +2,10 @@ # Copyright Lutra Consulting Limited from qgis.PyQt.QtCore import Qt -from qgis.PyQt.QtWidgets import QMessageBox +from qgis.PyQt.QtWidgets import QMessageBox, QWidget -class CollapsibleBox(QtWidgets.QWidget): +class CollapsibleBox(QWidget): def __init__(self, text, details, title="Mergin Maps error", parent=None): msg = QMessageBox() msg.setWindowTitle(title) diff --git a/Mergin/configuration_dialog.py b/Mergin/configuration_dialog.py index 6b8dd23e..1d91985d 100644 --- a/Mergin/configuration_dialog.py +++ b/Mergin/configuration_dialog.py @@ -172,7 +172,7 @@ def test_connection(self): self.ui.test_status.setText(" OK ") return True - self.ui.test_status.setText(f"Follow the instructions in the browser...") + self.ui.test_status.setText("Follow the instructions in the browser...") ok, msg = test_server_connection(self.server_url(), use_sso=True, sso_email=self.get_sso_email()) if url_reachable(self.server_url()): diff --git a/Mergin/create_project_wizard.py b/Mergin/create_project_wizard.py index 874c9acd..27f22981 100644 --- a/Mergin/create_project_wizard.py +++ b/Mergin/create_project_wizard.py @@ -5,10 +5,9 @@ import shutil from pathlib import Path from qgis.PyQt import uic -from qgis.PyQt.QtCore import QSettings, Qt, QVariant, QSortFilterProxyModel +from qgis.PyQt.QtCore import QSettings, Qt, QSortFilterProxyModel from qgis.PyQt.QtWidgets import ( QAbstractItemView, - QApplication, QComboBox, QFileDialog, QHeaderView, diff --git a/Mergin/data_item.py b/Mergin/data_item.py index 1c043177..21a06c26 100644 --- a/Mergin/data_item.py +++ b/Mergin/data_item.py @@ -47,7 +47,6 @@ get_local_mergin_projects_info, ) from .utils_auth import AuthTokenExpiredError -from .mergin.merginproject import MerginProject class MerginRemoteProjectItem(QgsDataItem): @@ -204,7 +203,7 @@ def remove_local_project(self): cur_proj_path = cur_proj.absolutePath() msg = ( "Your local changes will be lost. Make sure your project is synchronised with server. \n\n" - "Do you want to proceed?".format(self.project_name) + "Do you want to proceed?" ) btn_reply = QMessageBox.question( None, @@ -219,10 +218,7 @@ def remove_local_project(self): if os.path.exists(self.path): try: if same_dir(cur_proj_path, self.path): - msg = ( - "The project is currently open. It will get cleared if you proceed.\n\n" - "Proceed anyway?".format(self.project_name) - ) + msg = "The project is currently open. It will get cleared if you proceed.\n\n" "Proceed anyway?" btn_reply = QMessageBox.question( None, "Remove local project", @@ -508,7 +504,7 @@ def fetch_more(self): QMessageBox.information(None, "Fetch Mergin Maps Projects", "All projects already listed.") return page_to_get = floor(len(self.projects) / PROJS_PER_PAGE) + 1 - dummy = self.fetch_projects(page=page_to_get) + self.fetch_projects(page=page_to_get) self.refresh() def reload(self): diff --git a/Mergin/diff.py b/Mergin/diff.py index 00bb8637..82f6d538 100644 --- a/Mergin/diff.py +++ b/Mergin/diff.py @@ -13,14 +13,11 @@ from qgis.PyQt.QtGui import QColor from qgis.core import ( - QgsApplication, QgsVectorLayer, QgsFeature, QgsGeometry, QgsFields, QgsField, - QgsProject, - QgsLayerTreeLayer, QgsConditionalStyle, QgsSymbolLayerUtils, QgsMarkerSymbol, @@ -78,14 +75,22 @@ def get_row_from_db(db_conn, schema_table, entry_changes): Fetches a single row from DB's table based on the values of pkeys in changeset entry """ - c = db_conn.cursor() - where_bits = [] - for i, col in enumerate(schema_table.columns): - if col.pkey: - where_bits.append('"{}" = {}'.format(col.name, old_value_for_column_by_index(entry_changes, i))) + with db_conn.cursor() as c: + where_clauses = [] + query_params = [] - c.execute('SELECT * FROM "{}" WHERE {}'.format(schema_table.name, " AND ".join(where_bits))) - return c.fetchone() + for i, col in enumerate(schema_table.columns): + if col.pkey: + where_clauses.append('"{}" = %s'.format(col.name)) + val = old_value_for_column_by_index(entry_changes, i) + query_params.append(val) + + # We parameterize values securely; table/column names are trusted internal schema objects. + query = 'SELECT * FROM "{}" WHERE {}'.format(schema_table.name, " AND ".join(where_clauses)) # nosec B608 + + c.execute(query, tuple(query_params)) + + return c.fetchone() def parse_gpkg_geom_encoding(wkb_with_gpkg_hdr): @@ -215,7 +220,6 @@ def diff_table_to_features(diff_table, schema_table, fields, cols_to_flds, db_co Input is list of tuples (type, changes) where type is 'insert'/'update'/'delete' and changes is a list of dicts. Each dict with 'column', 'old', 'new' (old/new optional) """ - column_names = [column.name for column in schema_table.columns] features = [] fld_geometry_idx = fields.indexOf("geometry") @@ -225,7 +229,6 @@ def diff_table_to_features(diff_table, schema_table, fields, cols_to_flds, db_co for entry_type, entry_changes in diff_table: f = QgsFeature(fields) - row = [None for i in range(len(column_names))] f["_op"] = entry_type @@ -235,7 +238,7 @@ def diff_table_to_features(diff_table, schema_table, fields, cols_to_flds, db_co for i in range(len(db_row)): if i == geom_col_index: - if db_row[i] == None: + if db_row[i] is None: continue wkb = parse_gpkg_geom_encoding(db_row[i]) g = QgsGeometry() @@ -254,7 +257,7 @@ def diff_table_to_features(diff_table, schema_table, fields, cols_to_flds, db_co i = entry_change["column"] if "old" in entry_change: if i == geom_col_index: - if entry_change["old"] == None: + if entry_change["old"] is None: # Empty geometry continue wkb_with_gpkg_hdr = base64.decodebytes(entry_change["old"].encode("ascii")) @@ -278,7 +281,7 @@ def diff_table_to_features(diff_table, schema_table, fields, cols_to_flds, db_co value = "?" if i == geom_col_index: - if value == None: + if value is None: # Empty geometry continue wkb_with_gpkg_hdr = base64.decodebytes(value.encode("ascii")) @@ -357,7 +360,7 @@ def make_local_changes_layer(mp, layer): "memory", ) if not vl.isValid(): - return None, f"Failed to create memory layer for local changes" + return None, "Failed to create memory layer for local changes" vl.dataProvider().addAttributes(fields) vl.updateFields() @@ -443,7 +446,7 @@ def get_layer_geometry_info(schema_json, table_name): def style_diff_layer(layer, schema_table): """Apply conditional styling and symbology to diff layer""" - ### setup conditional styles! + # setup conditional styles! st = layer.conditionalStyles() color_red = QColor("#ffdce0") color_green = QColor("#dcffe4") diff --git a/Mergin/diff_dialog.py b/Mergin/diff_dialog.py index a2a81046..7ede3e19 100644 --- a/Mergin/diff_dialog.py +++ b/Mergin/diff_dialog.py @@ -6,7 +6,7 @@ from qgis.PyQt import uic from qgis.PyQt.QtCore import Qt, QSettings from qgis.PyQt.QtGui import QIcon, QColor -from qgis.PyQt.QtWidgets import QDialog, QPushButton, QDialogButtonBox, QMenu, QAction +from qgis.PyQt.QtWidgets import QDialog, QPushButton, QMenu, QAction from qgis.core import ( QgsProject, QgsVectorLayerCache, @@ -15,7 +15,6 @@ QgsMessageLog, Qgis, QgsApplication, - QgsWkbTypes, ) from qgis.gui import ( QgsGui, diff --git a/Mergin/plugin.py b/Mergin/plugin.py index 734e5425..372d4a90 100644 --- a/Mergin/plugin.py +++ b/Mergin/plugin.py @@ -3,9 +3,9 @@ # GPLv3 license # Copyright Lutra Consulting Limited try: - import sip + import sip # noqa: F401 except ImportError: - from PyQt6 import sip + pass import os from functools import partial from qgis.PyQt.QtCore import QUrl, QSettings, Qt @@ -351,7 +351,7 @@ def configure_db_sync(self): mp = MerginProject(project_path) try: project_name = mp.project_full_name() - except InvalidProject as e: + except InvalidProject: iface.messageBar().pushMessage( "Mergin", "Current project is not a Mergin project. Please open a Mergin project first.", @@ -401,7 +401,7 @@ def set_current_workspace(self, workspace): # check action required flag try: service_response = self.mc.workspace_service(workspace_id) - except ClientError as e: + except ClientError: return except AuthTokenExpiredError: self.auth_token_expired() @@ -492,8 +492,8 @@ def find_project(self): try: workspaces = self.mc.workspaces_list() dlg.enable_workspace_switching(len(workspaces) > 1) - except: - pass + except ClientError: + dlg.enable_workspace_switching(False) dlg.exec() @@ -501,7 +501,7 @@ def switch_workspace(self): """Open new Switch workspace dialog""" try: workspaces = self.mc.workspaces_list() - except (URLError, ClientError) as e: + except (URLError, ClientError): return # Server does not support workspaces except AuthTokenExpiredError: self.auth_token_expired() @@ -547,9 +547,9 @@ def add_context_menu_actions(self, layers): "arcgisvectortileservice", "vtpkvectortiles", ) - for l in layers: - if l.dataProvider().name() in provider_names: - self.iface.addCustomActionForLayer(self.action_export_mbtiles, l) + for layer in layers: + if layer.dataProvider().name() in provider_names: + self.iface.addCustomActionForLayer(self.action_export_mbtiles, layer) def unload(self): from .utils import pygeodiff diff --git a/Mergin/processing/algs/create_diff.py b/Mergin/processing/algs/create_diff.py index 81037f2d..9fca41d7 100644 --- a/Mergin/processing/algs/create_diff.py +++ b/Mergin/processing/algs/create_diff.py @@ -6,16 +6,13 @@ import os import sqlite3 -import shutil from qgis.PyQt.QtGui import QIcon from qgis.core import ( QgsFeatureSink, - QgsProcessing, QgsProcessingUtils, QgsProcessingException, QgsProcessingAlgorithm, - QgsProcessingContext, QgsProcessingParameterFile, QgsProcessingParameterNumber, QgsProcessingParameterVectorLayer, @@ -25,7 +22,6 @@ from ..postprocessors import StylingPostProcessor from ...mergin.merginproject import MerginProject -from ...mergin.utils import get_versions_with_file_changes from ...mergin.deps import pygeodiff from ...diff import ( @@ -144,7 +140,7 @@ def processAlgorithm(self, parameters, context, feedback): except (ClientError, ValueError) as e: raise QgsProcessingException(f"Error creating Mergin Maps client: {e}") - mp = MerginProject(project_dir) + mp = MerginProject(project_dir) # noqa: F841 feedback.pushInfo("Downloading base file…") base_file = QgsProcessingUtils.generateTempFilename(file_name) @@ -189,7 +185,6 @@ def processAlgorithm(self, parameters, context, feedback): features = diff_table_to_features(diff[table_name], db_schema[table_name], fields, fields_mapping, db_conn) feedback.setProgress(40) - current = 40 step = 60.0 / len(features) if features else 0 for i, f in enumerate(features): if feedback.isCanceled(): diff --git a/Mergin/processing/algs/create_report.py b/Mergin/processing/algs/create_report.py index 6caf9074..04ed54af 100644 --- a/Mergin/processing/algs/create_report.py +++ b/Mergin/processing/algs/create_report.py @@ -2,8 +2,6 @@ from qgis.PyQt.QtGui import QIcon from qgis.core import ( - QgsVectorFileWriter, - QgsProcessing, QgsProcessingException, QgsProcessingAlgorithm, QgsProcessingContext, diff --git a/Mergin/processing/algs/download_vector_tiles.py b/Mergin/processing/algs/download_vector_tiles.py index a14df1ca..42be8ab7 100644 --- a/Mergin/processing/algs/download_vector_tiles.py +++ b/Mergin/processing/algs/download_vector_tiles.py @@ -18,7 +18,6 @@ QgsCsException, QgsCoordinateReferenceSystem, QgsBlockingNetworkRequest, - QgsSqliteUtils, QgsDataSourceUri, QgsVectorTileLayer, QgsCoordinateTransform, @@ -218,9 +217,9 @@ def processAlgorithm(self, parameters, context, feedback): f"{wgs_extent.xMinimum()},{wgs_extent.yMinimum()},{wgs_extent.xMaximum()},{wgs_extent.yMaximum()}" ) writer.set_metadata_value("bounds", bounds_str) - except QgsCsException as e: + except QgsCsException: pass - except AttributeError as e: + except AttributeError: pass step_feedback = QgsProcessingMultiStepFeedback(self.max_zoom + 1, feedback) @@ -284,7 +283,7 @@ def postProcessAlgorithm(self, context, feedback): tile_layer = QgsVectorTileLayer(bytes(ds_uri.encodedUri()).decode(), name) if tile_layer.isValid(): if context.project(): - err = tile_layer.importNamedStyle(self.style_document) + tile_layer.importNamedStyle(self.style_document) metadata = tile_layer.metadata() metadata.setRights(self.attribution) tile_layer.setMetadata(metadata) diff --git a/Mergin/processing/algs/extract_local_changes.py b/Mergin/processing/algs/extract_local_changes.py index 577eccde..e4d07823 100644 --- a/Mergin/processing/algs/extract_local_changes.py +++ b/Mergin/processing/algs/extract_local_changes.py @@ -10,10 +10,8 @@ from qgis.PyQt.QtGui import QIcon from qgis.core import ( QgsFeatureSink, - QgsProcessing, QgsProcessingException, QgsProcessingAlgorithm, - QgsProcessingContext, QgsProcessingParameterFile, QgsProcessingParameterVectorLayer, QgsProcessingParameterFeatureSink, @@ -122,7 +120,6 @@ def processAlgorithm(self, parameters, context, feedback): features = diff_table_to_features(diff[table_name], db_schema[table_name], fields, fields_mapping, db_conn) feedback.setProgress(20) - current = 20 step = 80.0 / len(features) if features else 0 for i, f in enumerate(features): if feedback.isCanceled(): diff --git a/Mergin/processing/provider.py b/Mergin/processing/provider.py index 439196b4..e8a74553 100644 --- a/Mergin/processing/provider.py +++ b/Mergin/processing/provider.py @@ -4,8 +4,6 @@ # Copyright Lutra Consulting Limited -import os - from qgis.PyQt.QtGui import QIcon from qgis.core import QgsProcessingProvider diff --git a/Mergin/project_selection_dialog.py b/Mergin/project_selection_dialog.py index 144d2031..6f262246 100644 --- a/Mergin/project_selection_dialog.py +++ b/Mergin/project_selection_dialog.py @@ -27,7 +27,7 @@ QStandardItemModel, ) -from .mergin.client import MerginProject, ServerType +from .mergin.client import MerginProject from .mergin.common import InvalidProject from .mergin.client import AuthTokenExpiredError from .utils import ( @@ -219,7 +219,7 @@ def run(self): return self.finished.emit(projects) - except (URLError, ClientError) as e: + except (URLError, ClientError): return except AuthTokenExpiredError: return diff --git a/Mergin/project_status_dialog.py b/Mergin/project_status_dialog.py index cabe38d9..9eee433a 100644 --- a/Mergin/project_status_dialog.py +++ b/Mergin/project_status_dialog.py @@ -120,19 +120,19 @@ def _get_info_text(self, has_files_to_replace, has_write_permissions, has_unfini msg = [] if not has_write_permissions: msg.append( - f"You don't have writing permissions to this project. Changes won't be synced!\n" - f"You may package the current project to a writable workspace instead, by selecting Create New Project." + "You don't have writing permissions to this project. Changes won't be synced!\n" + "You may package the current project to a writable workspace instead, by selecting Create New Project." ) if has_files_to_replace: msg.append( - f"Unable to compare some of the modified files with their server version - " - f"their history will be lost if uploaded." + "Unable to compare some of the modified files with their server version - " + "their history will be lost if uploaded." ) if has_unfinished_pull: msg.append( - f"The previous pull has not finished completely: status of some files may be reported incorrectly." + "The previous pull has not finished completely: status of some files may be reported incorrectly." ) return msg diff --git a/Mergin/projects_manager.py b/Mergin/projects_manager.py index e4392aaf..3980dc1b 100644 --- a/Mergin/projects_manager.py +++ b/Mergin/projects_manager.py @@ -52,7 +52,7 @@ class MerginProjectsManager(object): """Class for managing Mergin Maps projects in QGIS.""" - def __init__(self, plugin: "MerginPlugin"): + def __init__(self, plugin: "MerginPlugin"): # noqa F821 self.mc: MerginClient = plugin.mc self.iface = iface self.plugin = plugin diff --git a/Mergin/sync_dialog.py b/Mergin/sync_dialog.py index a86dfca7..64042f6a 100644 --- a/Mergin/sync_dialog.py +++ b/Mergin/sync_dialog.py @@ -118,7 +118,8 @@ def download_start_internal(self): self.reset_operation(success=False, close=True, exception=e) return - assert self.job # if there was no error thrown, we should have a job + if not self.job: + return # use kilobytes as a unit, so we do not need to worry about int overflow with projects of few GB size self.progress.setMaximum(int(self.job.total_size / 1024)) diff --git a/Mergin/test/test_help.py b/Mergin/test/test_help.py index bbeb55c9..8d60d1a9 100644 --- a/Mergin/test/test_help.py +++ b/Mergin/test/test_help.py @@ -5,19 +5,9 @@ import os -import tempfile import urllib.request - -from qgis.PyQt.QtCore import QVariant from qgis.core import ( QgsVectorLayer, - QgsFields, - QgsField, - QgsVectorFileWriter, - QgsWkbTypes, - QgsCoordinateTransformContext, - QgsCoordinateReferenceSystem, - QgsProject, ) from qgis.testing import start_app, unittest from Mergin.help import MerginHelp diff --git a/Mergin/test/test_utils.py b/Mergin/test/test_utils.py index 5c429158..9c3b575a 100644 --- a/Mergin/test/test_utils.py +++ b/Mergin/test/test_utils.py @@ -124,7 +124,7 @@ def test_name_validation(self): (" project", False), (".project", False), ("proj~ect", False), - ("pro\ject", False), + (r"pro\ject", False), ("pro/ject", False), ("pro|ject", False), ("pro+ject", False), diff --git a/Mergin/test/test_validations.py b/Mergin/test/test_validations.py index 9b6071d6..86344dec 100644 --- a/Mergin/test/test_validations.py +++ b/Mergin/test/test_validations.py @@ -87,7 +87,7 @@ def test_attachment_widget(self): # local path config["RelativeStorage"] = QgsFileWidget.RelativeStorage.RelativeProject - config["DefaultRoot"] = "/tmp/photos" + config["DefaultRoot"] = os.path.join(self.temp_dir, "photos") widget_setup = QgsEditorWidgetSetup("ExternalResource", config) self.mem_layer.setEditorWidgetSetup(photo_field_idx, widget_setup) validator.check_attachment_widget() @@ -135,7 +135,7 @@ def test_attachment_widget(self): # right setup, valid expression config["PropertyCollection"]["properties"]["propertyRootPath"]["expression"] = "@project_folder + '/photos'" - config["DefaultRoot"] = "/tmp/photos" # default root should be override + config["DefaultRoot"] = os.path.join(self.temp_dir, "photos") # default root should be override widget_setup = QgsEditorWidgetSetup("ExternalResource", config) self.mem_layer.setEditorWidgetSetup(photo_field_idx, widget_setup) validator.check_attachment_widget() diff --git a/Mergin/utils.py b/Mergin/utils.py index f09cac21..9b9de2ab 100644 --- a/Mergin/utils.py +++ b/Mergin/utils.py @@ -75,28 +75,35 @@ from .mergin.merginproject import MerginProject try: - from .mergin.common import ClientError, ErrorCode, LoginError, InvalidProject, SYNC_ATTEMPTS, SYNC_ATTEMPT_WAIT - from .mergin.client import MerginClient, ServerType, AuthTokenExpiredError - from .mergin.client_pull import ( + from .mergin.common import ( # noqa: F401 + ClientError, + ErrorCode, + LoginError, + InvalidProject, + SYNC_ATTEMPTS, + SYNC_ATTEMPT_WAIT, + ) + from .mergin.client import MerginClient, ServerType, AuthTokenExpiredError # noqa: F401 + from .mergin.client_pull import ( # noqa: F401 download_project_async, download_project_is_running, download_project_finalize, download_project_cancel, ) - from .mergin.client_pull import ( + from .mergin.client_pull import ( # noqa: F401 pull_project_async, pull_project_is_running, pull_project_finalize, pull_project_cancel, ) - from .mergin.client_push import ( + from .mergin.client_push import ( # noqa: F401 push_project_async, push_project_is_running, push_project_finalize, push_project_cancel, get_push_changes_batch, ) - from .mergin.report import create_report + from .mergin.report import create_report # noqa: F401 from .mergin.deps import pygeodiff except ImportError: import sys @@ -104,22 +111,22 @@ this_dir = os.path.dirname(os.path.realpath(__file__)) path = os.path.join(this_dir, "mergin_client.whl") sys.path.append(path) - from mergin.client import MerginClient, ServerType - from mergin.common import ClientError, InvalidProject, LoginError, SYNC_ATTEMPTS, SYNC_ATTEMPT_WAIT + from mergin.client import MerginClient, ServerType, AuthTokenExpiredError # noqa: F401 + from mergin.common import ClientError, InvalidProject, LoginError, SYNC_ATTEMPTS, SYNC_ATTEMPT_WAIT # noqa: F401 - from mergin.client_pull import ( + from mergin.client_pull import ( # noqa: F401 download_project_async, download_project_is_running, download_project_finalize, download_project_cancel, ) - from mergin.client_pull import ( + from mergin.client_pull import ( # noqa: F401 pull_project_async, pull_project_is_running, pull_project_finalize, pull_project_cancel, ) - from mergin.client_push import ( + from mergin.client_push import ( # noqa: F401 push_project_async, push_project_is_running, push_project_finalize, @@ -483,7 +490,7 @@ def create_basic_qgis_project(project_path=None, project_name=None): new_project.addMapLayer(vt_layer) mem_uri = "Point?crs=epsg:3857" mem_layer = QgsVectorLayer(mem_uri, "Survey points", "memory") - res = mem_layer.dataProvider().addAttributes( + mem_layer.dataProvider().addAttributes( [ QgsField("date", QVariant.DateTime), QgsField("notes", QVariant.String), @@ -965,7 +972,7 @@ def get_local_mergin_projects_info(workspace=None): # - needs project dir to load metadata key_parts = key.split("/") if len(key_parts) > 2 and key_parts[2] == "path": - if workspace != None and key_parts[0] != workspace: + if workspace is not None and key_parts[0] != workspace: continue local_path = settings.value(key, None) @@ -1083,7 +1090,7 @@ def remove_prefix(text: str, prefix: str): Similar to str.removeprefix remove once we drop support for 3.22/python 3.8 """ if text.startswith(prefix): - return text[len(prefix) :] + return text[len(prefix) :] # noqa: E203 return text @@ -1108,9 +1115,6 @@ def has_schema_change(mp, layer): Check whether the layer has schema changes using schema representaion in JSON format generated by geodiff. """ - - geodiff = pygeodiff.GeoDiff() - local_path = layer.publicSource().split("|")[0] f_name = os.path.split(local_path)[1] base_path = mp.fpath_meta(f_name) @@ -1192,8 +1196,6 @@ def get_primary_keys(layer): """ Returns list of column names which are used as a primary key """ - geodiff = pygeodiff.GeoDiff() - file_path = layer.publicSource().split("|")[0] table_name = os.path.splitext(os.path.split(file_path)[1])[0] @@ -1436,7 +1438,7 @@ def create_map_sketches_layer(project_path): options.driverName = "GPKG" options.layerName = "Map sketches" - writer = QgsVectorFileWriter.create( + writer = QgsVectorFileWriter.create( # noqa: F841 filename, fields, QgsWkbTypes.MultiLineStringZM, @@ -1707,7 +1709,7 @@ def qvariant_to_string(val: Any) -> str: if s: return s except Exception: - pass + pass # nosec # Fallback return str(val) diff --git a/Mergin/utils_auth.py b/Mergin/utils_auth.py index f9594dd4..0b76f37b 100644 --- a/Mergin/utils_auth.py +++ b/Mergin/utils_auth.py @@ -39,7 +39,7 @@ class LoginType(Enum): """Types of login supported by Mergin Maps.""" - PASSWORD = "password" # classic login with username and password + PASSWORD = "password" # nosec B105 # pragma: allowlist secret SSO = "sso" # login with SSO token def __str__(self) -> str: @@ -295,7 +295,7 @@ def login_sso(server_url: str, oauth2_client_id: str, email: typing.Optional[str "id": "mmmmsso", "name": "Mergin Maps SSO", "objectName": "", - "password": "", + "password": "", # nosec B105 "persistToken": False, "queryPairs": { "state": str(uuid.uuid4()), diff --git a/Mergin/validation.py b/Mergin/validation.py index 7b91b75d..acb86fa2 100644 --- a/Mergin/validation.py +++ b/Mergin/validation.py @@ -31,8 +31,8 @@ is_inside, ) -INVALID_FIELD_NAME_CHARS = re.compile('[\\\/\(\)\[\]\{\}"\n\r]') -PROJECT_VARS = re.compile("\@project_home|\@project_path|\@project_folder") +INVALID_FIELD_NAME_CHARS = re.compile(r'[\\\/\(\)\[\]\{\}"\n\r]') +PROJECT_VARS = re.compile("@project_home|@project_path|@project_folder") class Warning(Enum): @@ -160,7 +160,8 @@ def check_proj_loaded(self): def check_proj_paths_relative(self): """Check if the QGIS project has relative paths, i.e. not absolute ones.""" abs_paths, ok = self.qgis_proj.readEntry("Paths", "/Absolute") - assert ok + if not ok: + raise RuntimeError("Cannot read project paths configuration") if not abs_paths == "false": self.issues.append(MultipleLayersWarning(Warning.ABSOLUTE_PATHS)) diff --git a/Mergin/version_viewer_dialog.py b/Mergin/version_viewer_dialog.py index 36547e5c..778d5311 100644 --- a/Mergin/version_viewer_dialog.py +++ b/Mergin/version_viewer_dialog.py @@ -32,13 +32,12 @@ class QgsTiledSceneLayer: QgsGui, QgsMapToolPan, ) -from qgis.PyQt import QtCore, uic +from qgis.PyQt import uic from qgis.PyQt.QtCore import ( QAbstractTableModel, QItemSelectionModel, QModelIndex, QSettings, - QStringListModel, Qt, QThread, pyqtSignal, @@ -62,7 +61,6 @@ class QgsTiledSceneLayer: from .mergin.merginproject import MerginProject from .mergin.utils import bytes_to_human_size, int_version from .utils import ( - PROJS_PER_PAGE, ClientError, contextual_date, format_datetime, @@ -214,7 +212,7 @@ def __init__(self, mc, mp, version): def run(self): try: version_info = self.mc.project_version_info(self.mp.project_id(), version=f"v{self.version}") - except AuthTokenExpiredError: + except AuthTokenExpiredError as e: self.error_occured.emit(e) return @@ -243,7 +241,7 @@ def run(self): if "diff" not in f: continue try: - file_diffs = self.mc.download_file_diffs(self.mp.dir, f["path"], [f"v{self.version}"]) + self.mc.download_file_diffs(self.mp.dir, f["path"], [f"v{self.version}"]) full_gpkg = self.mp.fpath_cache(f["path"], version=f"v{self.version}") if not os.path.exists(full_gpkg): self.mc.download_file(self.mp.dir, f["path"], full_gpkg, f"v{self.version}") @@ -295,7 +293,7 @@ def has_more_page(self): return self.current_page <= self.nb_page def fetch_another_page(self): - if self.has_more_page() == False: + if self.has_more_page() is False: return self.model.beginFetching() try: @@ -469,7 +467,7 @@ def exec(self): except ClientError: # Some versions e.g CE, EE edition doesn't have pass - except AuthTokenExpiredError as e: + except AuthTokenExpiredError: self.plugin.auth_token_expired() super().exec() @@ -546,7 +544,7 @@ def selected_version_changed(self, current_index: QModelIndex, previous_index): try: item = self.versionModel.item_from_index(current_index) - except: + except Exception: # Click on invalid item like loading return version_name = item["name"] @@ -633,7 +631,7 @@ def toggle_background_layers(self, checked): self.update_canvas(layers, set_extent=False) def update_canvas(self, layers, set_extent=True): - if self.current_diff and self.current_diff.isSpatial() == False: + if self.current_diff and self.current_diff.isSpatial() is False: self.map_canvas.setEnabled(False) self.save_splitters_state() self.splitter_map_table.setSizes([0, 1]) @@ -715,7 +713,7 @@ def handle_exception(self, e: Exception): else: self.failed_to_fetch = True additional_log = str(e) - QgsMessageLog.logMessage(f"Download history error: " + additional_log, "Mergin") + QgsMessageLog.logMessage("Download history error: " + additional_log, "Mergin") self.label_info.setText( "There was an issue loading this version. Please try again later or contact our support if the issue persists. Refer to the QGIS messages log for more details." )