From c3b2277cacc750bc1dd3cae78c05b97f41b57eca Mon Sep 17 00:00:00 2001 From: rnousia Date: Mon, 16 Oct 2023 15:47:32 +0300 Subject: [PATCH] style: use ruff for linting instead of flake8 --- .flake8 | 21 --- .gitignore | 1 + .pre-commit-config.yaml | 26 ++-- pyproject.toml | 64 ++++++++- quality-result-gui.code-workspace | 111 ++++++++------- requirements.in | 33 ++--- requirements.txt | 131 +++++++----------- setup.py | 2 +- .../api/quality_api_client.py | 6 +- .../api/types/quality_error.py | 4 +- src/quality_result_gui/layer_mapping.py | 4 +- .../quality_data_fetcher.py | 8 +- .../quality_error_manager.py | 4 +- .../quality_error_visualizer.py | 9 +- .../quality_errors_filters.py | 54 +++++--- .../quality_errors_tree_model.py | 72 +++++----- src/quality_result_gui/quality_layer.py | 11 +- src/quality_result_gui/style/default_style.py | 10 +- .../ui/quality_error_tree_view.py | 15 +- .../ui/quality_errors_dock.py | 8 +- src/quality_result_gui/utils/layer_utils.py | 8 +- src/quality_result_gui/utils/styling_utils.py | 10 +- src/quality_result_gui_plugin/__init__.py | 5 +- .../dev_tools/dev_tools_dialog.py | 3 +- .../dev_tools/mock_api_client.py | 8 +- .../dev_tools/response_parser.py | 9 +- src/quality_result_gui_plugin/plugin.py | 8 +- test/conftest.py | 15 +- test/integration/conftest.py | 16 +-- test/integration/test_checkbox_actions.py | 3 +- test/integration/test_filters.py | 17 ++- test/integration/test_tree_view.py | 10 +- .../test_quality_data_fetcher.py | 6 +- .../test_quality_error_visualizer.py | 30 ++-- .../test_quality_errors_filters.py | 2 - .../test_quality_errors_manager.py | 11 +- .../test_quality_errors_tree_model.py | 49 +++---- .../quality_result_gui/test_quality_layer.py | 11 +- .../ui/test_quality_errors_dock.py | 9 +- .../quality_result_gui_plugin/test_plugin.py | 5 +- 40 files changed, 405 insertions(+), 424 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index d3e28c1..0000000 --- a/.flake8 +++ /dev/null @@ -1,21 +0,0 @@ -[flake8] -max-line-length = 88 -extend-ignore = - # whitespace before ':' - E203 - # missing type annotation for self in method - ANN101 - # handle error-cases first - SIM106 - # fixture '{name}' does not return anything, add leading underscore - PT004 - # pytest.raises({exception}) is too broad - PT011 - -per-file-ignores = - # simplify tests by ignoring some unnecessary rules - test/*:E501,INP001,SC200,ANN001,ANN201,ANN202,QGS105 - -ban-relative-imports = true -spellcheck-targets = names -dictionaries = en_US,python,technical diff --git a/.gitignore b/.gitignore index 75ef666..2b65f7b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__ .coverage coverage.xml +.ruff_cache dist build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7efb306..d3c6c26 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,35 +10,27 @@ repos: - id: check-added-large-files - id: mixed-line-ending args: [--fix=lf] - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.292 hooks: - - id: isort + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.7.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.931 + rev: v1.4.1 hooks: - id: mypy - repo: https://github.com/PyCQA/flake8 - rev: 3.9.1 + rev: 6.0.0 hooks: - id: flake8 additional_dependencies: - - flake8-bugbear==21.4.3 - - pep8-naming==0.11.1 - - flake8-annotations==2.6.2 + - flake8-pyproject==1.2.3 + - flake8-spellcheck==0.28.0 - flake8-qgis==1.0.0 - - flake8-print==4.0.0 - - flake8-tidy-imports==4.4.1 - - flake8-comprehensions==3.6.1 - - flake8-spellcheck==0.24.0 - - flake8-simplify==0.14.1 - - flake8-pytest-style==1.5.0 - - flake8-pie==0.14.0 - - flake8-no-pep420==1.1.1 - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.32.2 hooks: diff --git a/pyproject.toml b/pyproject.toml index 59f2f44..71d818d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,65 @@ -[tool.isort] -profile = "black" +[tool.ruff] +ignore = [ + "ANN101", # Missing type annotation for self in method + ] +line-length = 88 + +# List of all rules https://docs.astral.sh/ruff/rules/ +select = [ + "ANN", # flake8-annotations + "B", # flake8-bugbear + "C", # flake8-comprehensions + "C90", # flake8, mccabe + "E", # flake8, pycodestyle + "F", # flake8, Pyflakes + "I", # isort + "INP", # flake8-no-pep420 + "PIE", # flake8-pie + "PGH", # pygrep-hooks + "PL", # pylint + "PT", # flake8-pytest-style + "RUF", # Ruff-specific rules + "SIM", # flake8-simplify + "T", # flake8-print + "ICN", # flake8-import-conventions + "TCH", # flake8-type-checking + "TID", # flake8-tidy-imports + "W", # flake8, pycodestyle + "UP", # pyupgrade + ] + +# Avoiding flagging (and removing) `SC200` from any `# noqa` +# directives, despite Ruff's lack of support for `flake8-spellcheck`. +external = ["SC200"] + +target-version = "py39" + +[tool.ruff.flake8-tidy-imports] +ban-relative-imports = "all" + +[tool.ruff.per-file-ignores] +"test*" = [ + "INP001", + "ANN201", + "ANN202", + "ANN401", + "E501", + "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable + "PLR0913", # Too many arguments to function call (len(args) > 5) + ] + + +[tool.flake8] +max-line-length = 88 +spellcheck-targets = "names" +dictionaries = "en_US,python,technical" +extend-ignore = [ + "E203", # whitespace before ':' + "E501", # line length (checked by ruff now, possible mismatches) +] +per-file-ignores = [ + "test/*:INP001,SC200,QGS105", +] [tool.mypy] follow_imports = "normal" diff --git a/quality-result-gui.code-workspace b/quality-result-gui.code-workspace index 71ec88b..a7ccb14 100644 --- a/quality-result-gui.code-workspace +++ b/quality-result-gui.code-workspace @@ -1,53 +1,62 @@ { - "extensions": { - "recommendations": [ - "editorconfig.editorconfig", - "mikestead.dotenv", - ] - }, - "folders": [ - { - "path": "." - } - ], - "settings": { - "python.languageServer": "Pylance", - "python.testing.pytestEnabled": true, - "python.testing.unittestEnabled": false, - "python.linting.enabled": true, - "python.linting.flake8Enabled": true, - "python.linting.mypyEnabled": true, - "python.linting.pylintEnabled": false, - "python.formatting.provider": "black", - "editor.formatOnSave": true, - "[python]": { - "editor.codeActionsOnSave": { - "source.organizeImports": true - } - }, - "files.associations": { - "*.ts": "xml" - }, - "editor.bracketPairColorization.enabled": true, - "editor.guides.bracketPairs": "active", - }, - "launch": { - "configurations": [ - { - "name": "QGIS debugpy", - "type": "python", - "request": "attach", - "connect": { - "host": "localhost", - "port": 5678 - }, - "pathMappings": [ - { - "localRoot": "${workspaceFolder}", - "remoteRoot": "${workspaceFolder}" - } - ] - } - ], - } + "extensions": { + "recommendations": [ + "editorconfig.editorconfig", + "mikestead.dotenv", + "ms-python.python", + "ms-python.mypy-type-checker", + "ms-python.black-formatter", + "ms-python.flake8", + "charliermarsh.ruff", + ] + }, + "folders": [ + { + "path": "." + } + ], + "settings": { + "python.languageServer": "Pylance", + // Tests + "python.testing.pytestEnabled": true, + // Linting + "flake8.importStrategy": "fromEnvironment", + "ruff.importStrategy": "fromEnvironment", + "mypy-type-checker.importStrategy": "fromEnvironment", + // Formatting + "isort.check": false, + "black-formatter.importStrategy": "fromEnvironment", + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports.ruff": true, + "source.fixAll": true + }, + "editor.defaultFormatter": "ms-python.black-formatter", + }, + "files.associations": { + "*.ts": "xml" + }, + "editor.bracketPairColorization.enabled": true, + "editor.guides.bracketPairs": "active", + }, + "launch": { + "configurations": [ + { + "name": "QGIS debugpy", + "type": "python", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5678 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}" + } + ] + } + ], + } } diff --git a/requirements.in b/requirements.in index 5f97de8..9323dc7 100644 --- a/requirements.in +++ b/requirements.in @@ -8,33 +8,26 @@ pytest-timeout==1.4.2 pytest-order==1.0.0 pytest-dotenv==0.5.2 +#stubs +pyqt5-stubs==5.15.6.0 + # lock here since 1.4.1 has no binaries atomicwrites==1.4.0 # linting -pre-commit==3.2.2 -mypy==0.931 -isort==5.12.0 -black==22.3.0 -flake8==3.9.1 -flake8-bugbear==21.4.3 -pep8-naming==0.11.1 -flake8-annotations==2.6.2 +pre-commit==3.4.0 +mypy==1.4.1 +black==23.7.0 +ruff==0.0.292 + +# flake8 libraries not included in ruff +flake8==6.0.0 +flake8-pyproject==1.2.3 +flake8-spellcheck==0.28.0 flake8-qgis==1.0.0 -flake8-print==4.0.0 -flake8-tidy-imports==4.4.1 -flake8-comprehensions==3.6.1 -flake8-spellcheck==0.24.0 -flake8-simplify==0.14.1 -flake8-pytest-style==1.5.0 -flake8-pie==0.14.0 -flake8-no-pep420==1.1.1 # tools -qgis-plugin-dev-tools==0.6.0 - -# typing -PyQt5-stubs==5.15.6.0 +qgis-plugin-dev-tools==0.5.0 # NOTE: Runtime requirements for QGIS plugin are defined in setup.cfg diff --git a/requirements.txt b/requirements.txt index 9ff51a5..3386f25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,125 +4,90 @@ # # pip-compile requirements.in # + -e file:. # via -r requirements.in astor==0.8.1 - # via - # flake8-qgis - # flake8-simplify + # via flake8-qgis atomicwrites==1.4.0 # via # -r requirements.in # pytest -attrs==22.1.0 - # via - # flake8-bugbear - # pytest -black==22.3.0 +attrs==23.1.0 + # via pytest +black==23.7.0 # via -r requirements.in -certifi==2022.9.24 +certifi==2023.7.22 # via requests -cfgv==3.3.1 +cfgv==3.4.0 # via pre-commit -charset-normalizer==2.1.1 +charset-normalizer==3.3.0 # via requests -click==8.1.3 +click==8.1.7 # via black -colorama==0.4.5 +colorama==0.4.6 # via # click # pytest -coverage[toml]==6.5.0 - # via pytest-cov -distlib==0.3.6 +coverage[toml]==7.3.2 + # via + # coverage + # pytest-cov +distlib==0.3.7 # via virtualenv -filelock==3.8.0 +filelock==3.12.4 # via virtualenv -flake8==3.9.1 +flake8==6.0.0 # via # -r requirements.in - # flake8-annotations - # flake8-bugbear - # flake8-comprehensions - # flake8-no-pep420 - # flake8-polyfill - # flake8-print + # flake8-pyproject # flake8-qgis - # flake8-simplify # flake8-spellcheck - # flake8-tidy-imports -flake8-annotations==2.6.2 - # via -r requirements.in -flake8-bugbear==21.4.3 - # via -r requirements.in -flake8-comprehensions==3.6.1 - # via -r requirements.in -flake8-no-pep420==1.1.1 - # via -r requirements.in -flake8-pie==0.14.0 - # via -r requirements.in -flake8-plugin-utils==1.3.2 - # via flake8-pytest-style -flake8-polyfill==1.0.2 - # via pep8-naming -flake8-print==4.0.0 - # via -r requirements.in -flake8-pytest-style==1.5.0 +flake8-pyproject==1.2.3 # via -r requirements.in flake8-qgis==1.0.0 # via -r requirements.in -flake8-simplify==0.14.1 +flake8-spellcheck==0.28.0 # via -r requirements.in -flake8-spellcheck==0.24.0 - # via -r requirements.in -flake8-tidy-imports==4.4.1 - # via -r requirements.in -identify==2.5.6 +identify==2.5.30 # via pre-commit idna==3.4 # via requests -importlib-metadata==5.0.0 +importlib-metadata==6.8.0 # via qgis-plugin-dev-tools -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest -isort==5.12.0 - # via -r requirements.in -mccabe==0.6.1 +mccabe==0.7.0 # via flake8 -mypy==0.931 +mypy==1.4.1 # via -r requirements.in -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via # black # mypy -nodeenv==1.7.0 +nodeenv==1.8.0 # via pre-commit -packaging==21.3 +packaging==23.2 # via + # black # pytest # qgis-plugin-dev-tools -pathspec==0.10.1 +pathspec==0.11.2 # via black -pep8-naming==0.11.1 - # via -r requirements.in -platformdirs==2.5.2 +platformdirs==3.11.0 # via # black # virtualenv -pluggy==1.0.0 +pluggy==1.3.0 # via pytest -pre-commit==3.2.2 +pre-commit==3.4.0 # via -r requirements.in py==1.11.0 # via pytest -pycodestyle==2.7.0 - # via - # flake8 - # flake8-print -pyflakes==2.3.1 +pycodestyle==2.10.0 + # via flake8 +pyflakes==3.0.1 # via flake8 -pyparsing==3.0.9 - # via packaging pyqt5-stubs==5.15.6.0 # via -r requirements.in pytest==6.2.5 @@ -149,38 +114,38 @@ pytest-qt==3.3.0 # via -r requirements.in pytest-timeout==1.4.2 # via -r requirements.in -python-dotenv==0.21.0 +python-dotenv==1.0.0 # via # pytest-dotenv # qgis-plugin-dev-tools -pyyaml==6.0 +pyyaml==6.0.1 # via pre-commit -qgis-plugin-dev-tools==0.6.0 +qgis-plugin-dev-tools==0.5.0 # via -r requirements.in -qgis-plugin-tools==0.3.0 +qgis-plugin-tools==0.3.1 # via quality-result-gui -requests==2.28.1 +requests==2.31.0 # via qgis-plugin-dev-tools -six==1.16.0 - # via flake8-print +ruff==0.0.292 + # via -r requirements.in toml==0.10.2 # via pytest tomli==2.0.1 # via # black # coverage + # flake8-pyproject # mypy # qgis-plugin-dev-tools -typing-extensions==4.4.0 +typing-extensions==4.8.0 # via # black - # flake8-pie # mypy -urllib3==1.26.12 +urllib3==2.0.6 # via requests -virtualenv==20.16.5 +virtualenv==20.24.5 # via pre-commit -zipp==3.10.0 +zipp==3.17.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/setup.py b/setup.py index aba6e57..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,3 @@ -from setuptools import setup # noqa: INP001 +from setuptools import setup setup() diff --git a/src/quality_result_gui/api/quality_api_client.py b/src/quality_result_gui/api/quality_api_client.py index b90be7d..d21ae41 100644 --- a/src/quality_result_gui/api/quality_api_client.py +++ b/src/quality_result_gui/api/quality_api_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -18,7 +18,7 @@ # along with quality-result-gui. If not, see . from abc import ABC, abstractmethod -from typing import List, Optional +from typing import Optional from qgis.core import QgsCoordinateReferenceSystem @@ -27,7 +27,7 @@ class QualityResultClient(ABC): @abstractmethod - def get_results(self) -> Optional[List[QualityError]]: + def get_results(self) -> Optional[list[QualityError]]: """ Retrieve latest quality errors from API diff --git a/src/quality_result_gui/api/types/quality_error.py b/src/quality_result_gui/api/types/quality_error.py index 3e3bc8e..a1a016d 100644 --- a/src/quality_result_gui/api/types/quality_error.py +++ b/src/quality_result_gui/api/types/quality_error.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -95,5 +95,5 @@ class QualityError: geometry: QgsGeometry is_user_processed: bool - def __getitem__(self, item: str) -> Any: + def __getitem__(self, item: str) -> Any: # noqa: ANN401 return getattr(self, item) diff --git a/src/quality_result_gui/layer_mapping.py b/src/quality_result_gui/layer_mapping.py index ef79fff..b9a698c 100644 --- a/src/quality_result_gui/layer_mapping.py +++ b/src/quality_result_gui/layer_mapping.py @@ -18,14 +18,14 @@ # along with quality-result-gui. If not, see . from dataclasses import dataclass -from typing import Dict, Optional +from typing import Optional from qgis.core import QgsField, QgsProject, QgsVectorLayer @dataclass class LayerMapping: - layer_map: Optional[Dict[str, str]] + layer_map: Optional[dict[str, str]] def get_layer_alias(self, feature_type: str) -> str: layer = self._get_layer(feature_type) diff --git a/src/quality_result_gui/quality_data_fetcher.py b/src/quality_result_gui/quality_data_fetcher.py index 20fddc9..c75c09c 100644 --- a/src/quality_result_gui/quality_data_fetcher.py +++ b/src/quality_result_gui/quality_data_fetcher.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -19,7 +19,7 @@ import logging from enum import Enum, auto -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Optional from qgis.PyQt.QtCore import QObject, QThread, QTimer, pyqtSignal, pyqtSlot from qgis_plugin_tools.tools.i18n import tr @@ -112,7 +112,7 @@ def _check_api(self) -> None: except (QualityResultClientError, QualityResultServerError) as e: LOGGER.warning( - f"failed to check quality results api: {str(e)}", stack_info=True + f"failed to check quality results api: {e!s}", stack_info=True ) self.status_changed.emit(CheckStatus.RESULT_FAILED) @@ -145,7 +145,7 @@ def _worker_status_changed(self, status: CheckStatus) -> None: self.status_changed.emit(status) @pyqtSlot(list) - def _worker_results_received(self, results: List["QualityError"]) -> None: + def _worker_results_received(self, results: list["QualityError"]) -> None: self.results_received.emit(results) @pyqtSlot() diff --git a/src/quality_result_gui/quality_error_manager.py b/src/quality_result_gui/quality_error_manager.py index 0edcf04..07717fb 100644 --- a/src/quality_result_gui/quality_error_manager.py +++ b/src/quality_result_gui/quality_error_manager.py @@ -18,7 +18,7 @@ # along with quality-result-gui. If not, see . -from typing import TYPE_CHECKING, Dict, Optional, cast +from typing import TYPE_CHECKING, Optional, cast from qgis.gui import QgisInterface from qgis.PyQt.QtCore import QObject, pyqtSignal @@ -183,7 +183,7 @@ def add_filter(self, filter: AbstractQualityErrorFilter) -> None: self.dock_widget.filter_menu.add_filter_menu(filter.menu) self._filter_model.add_filter(filter) - def set_layer_mapping(self, layer_mapping: Dict[str, str]) -> None: + def set_layer_mapping(self, layer_mapping: dict[str, str]) -> None: QualityResultManagerSettings.get().set_layer_mapping( LayerMapping(layer_map=layer_mapping) ) diff --git a/src/quality_result_gui/quality_error_visualizer.py b/src/quality_result_gui/quality_error_visualizer.py index 11d21ce..9bee9ca 100644 --- a/src/quality_result_gui/quality_error_visualizer.py +++ b/src/quality_result_gui/quality_error_visualizer.py @@ -19,20 +19,23 @@ import json import logging +from collections.abc import Iterable from pathlib import Path -from typing import Iterable, List, Optional, cast +from typing import TYPE_CHECKING, Optional, cast from qgis.core import QgsAnnotationLayer, QgsCoordinateReferenceSystem, QgsProject from qgis.gui import QgisInterface from qgis.utils import iface as utils_iface from quality_result_gui.api.types.quality_error import QualityError -from quality_result_gui.configuration import QualityLayerStyleConfig from quality_result_gui.env import IS_DEVELOPMENT_MODE, TEST_JSON_FILE_PATH from quality_result_gui.quality_layer import QualityErrorLayer from quality_result_gui.ui.quality_error_tree_view import SelectionType from quality_result_gui.utils import layer_utils +if TYPE_CHECKING: + from quality_result_gui.configuration import QualityLayerStyleConfig + iface = cast(QgisInterface, utils_iface) LOGGER = logging.getLogger(__name__) @@ -143,7 +146,7 @@ def remove_quality_error_layer(self) -> None: QgsProject.instance().removeMapLayer(layer.id()) def zoom_to_geometries_and_flash( - self, quality_errors: List[QualityError], preserve_scale: bool = False + self, quality_errors: list[QualityError], preserve_scale: bool = False ) -> None: if len(quality_errors) > 0: layer_utils.zoom_to_geometries_and_flash( diff --git a/src/quality_result_gui/quality_errors_filters.py b/src/quality_result_gui/quality_errors_filters.py index 3513d57..f09529e 100644 --- a/src/quality_result_gui/quality_errors_filters.py +++ b/src/quality_result_gui/quality_errors_filters.py @@ -25,11 +25,7 @@ TYPE_CHECKING, Any, Callable, - Dict, - Hashable, - List, Optional, - Set, cast, ) @@ -45,6 +41,8 @@ from quality_result_gui.quality_errors_tree_model import QualityErrorTreeItemType if TYPE_CHECKING: + from collections.abc import Hashable + from qgis.PyQt.QtWidgets import QWidget @@ -58,9 +56,9 @@ def __init__(self, title: str, parent: Optional["QWidget"] = None) -> None: self._select_all_section_enabled = False self._sorted = False - self._filter_actions: List[QAction] = [] + self._filter_actions: list[QAction] = [] - def mouseReleaseEvent(self, e: QMouseEvent) -> None: # noqa: N802 (qt override) + def mouseReleaseEvent(self, e: QMouseEvent) -> None: # (qt override) if not self.activeAction() or not self.activeAction().isEnabled(): super().mouseReleaseEvent(e) else: @@ -123,7 +121,7 @@ def _add_select_all_section(self) -> None: existing_actions = self.actions() first_action = existing_actions[0] if existing_actions else None - separator = self.insertSeparator(first_action) # type: ignore + separator = self.insertSeparator(first_action) self.insertAction(separator, deselect_all_action) self.insertAction(deselect_all_action, select_all_action) @@ -229,13 +227,15 @@ def __init__(self, title: str, parent: Optional[QObject] = None) -> None: """ super().__init__(parent=parent) - self._accepted_values: Set[Any] = set() - self._filter_value_action_map: Dict[Hashable, QAction] = {} + self._accepted_values: set[Any] = set() + self._filter_value_action_map: dict[Hashable, QAction] = {} self.menu = FilterMenu(title) @abstractmethod - def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bool: + def accept_row( + self, item_type: QualityErrorTreeItemType, item_value: Any # noqa: ANN401 + ) -> bool: """The actual filter function that inherited classes should be implemented. Args: @@ -250,7 +250,7 @@ def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bo """ raise NotImplementedError() - def update_filter_from_errors(self, quality_errors: List["QualityError"]) -> None: + def update_filter_from_errors(self, quality_errors: list["QualityError"]) -> None: """Updates filters dynamically from a given list of quality errors. Should be implemented in inherited classes if feature is wanted. @@ -264,7 +264,7 @@ def update_filter_from_errors(self, quality_errors: List["QualityError"]) -> Non """ raise NotImplementedError() - def _sync_filtered(self, value: Any, checked: bool) -> None: + def _sync_filtered(self, value: Any, checked: bool) -> None: # noqa: ANN401 """Syncs accepted filter values Should be connected to checkable action's toggle signal with functool.partial @@ -282,7 +282,7 @@ def _sync_filtered(self, value: Any, checked: bool) -> None: self.filters_changed.emit() - def _refresh_filters(self, new_filters: Dict[Any, str]) -> None: + def _refresh_filters(self, new_filters: dict[Any, str]) -> None: """Adds filters not yet present and removes filters not present anymore. Args: @@ -327,7 +327,9 @@ def _refresh_error_type_filters( filter_label = new_filters[filter_value]() self._add_filter_item(filter_value, filter_label) - def _add_filter_item(self, filter_value: Any, filter_label: str) -> None: + def _add_filter_item( + self, filter_value: Any, filter_label: str # noqa: ANN401 + ) -> None: """Adds a filter item to the filter Args: @@ -343,7 +345,7 @@ def _add_filter_item(self, filter_value: Any, filter_label: str) -> None: self.filters_changed.emit() - def _remove_filter_item(self, filter_value: Any) -> None: + def _remove_filter_item(self, filter_value: Any) -> None: # noqa: ANN401 """Removes the filter item Args: @@ -366,7 +368,7 @@ class ErrorTypeFilter(AbstractQualityErrorFilter): This is a static filter that shows always all the defined error types. """ - _accepted_values: Set[QualityErrorTreeItemType] + _accepted_values: set[QualityErrorTreeItemType] def __init__(self) -> None: super().__init__(self.get_error_type_filter_menu_label()) @@ -379,7 +381,9 @@ def __init__(self) -> None: def get_error_type_filter_menu_label() -> str: return tr("Error type") - def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bool: + def accept_row( + self, item_type: QualityErrorTreeItemType, item_value: Any # noqa: ANN401 + ) -> bool: if item_type == QualityErrorTreeItemType.ERROR: return cast(QualityError, item_value).error_type in self._accepted_values @@ -394,7 +398,7 @@ class FeatureTypeFilter(AbstractQualityErrorFilter): received errors. """ - _accepted_values: Set[str] + _accepted_values: set[str] def __init__(self) -> None: super().__init__(self.get_feature_type_filter_menu_label()) @@ -405,13 +409,15 @@ def __init__(self) -> None: def get_feature_type_filter_menu_label() -> str: return tr("Feature type") - def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bool: + def accept_row( + self, item_type: QualityErrorTreeItemType, item_value: Any # noqa: ANN401 + ) -> bool: if item_type == QualityErrorTreeItemType.FEATURE_TYPE: return cast(str, item_value) in self._accepted_values return True - def update_filter_from_errors(self, quality_errors: List["QualityError"]) -> None: + def update_filter_from_errors(self, quality_errors: list["QualityError"]) -> None: """ Args: @@ -438,7 +444,7 @@ class AttributeFilter(AbstractQualityErrorFilter): received errors. """ - _accepted_values: Set[str] + _accepted_values: set[str] def __init__(self) -> None: super().__init__(self.get_attribute_name_filter_menu_label()) @@ -449,7 +455,9 @@ def __init__(self) -> None: def get_attribute_name_filter_menu_label() -> str: return tr("Attribute Filter") - def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bool: + def accept_row( + self, item_type: QualityErrorTreeItemType, item_value: Any # noqa: ANN401 + ) -> bool: if item_type == QualityErrorTreeItemType.ERROR: attribute_name = cast(QualityError, item_value).attribute_name if attribute_name: @@ -457,7 +465,7 @@ def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bo return True - def update_filter_from_errors(self, quality_errors: List["QualityError"]) -> None: + def update_filter_from_errors(self, quality_errors: list["QualityError"]) -> None: attribute_names_in_errors = { # Dict[filter_value, filter_label] error.attribute_name: self._get_label_value( error.feature_type, error.attribute_name diff --git a/src/quality_result_gui/quality_errors_tree_model.py b/src/quality_result_gui/quality_errors_tree_model.py index 8ca98d0..925efb2 100644 --- a/src/quality_result_gui/quality_errors_tree_model.py +++ b/src/quality_result_gui/quality_errors_tree_model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -21,17 +21,12 @@ import enum import logging from abc import abstractmethod +from collections.abc import Iterable, Iterator from typing import ( TYPE_CHECKING, Any, - Dict, - Iterable, - Iterator, - List, NewType, Optional, - Set, - Tuple, Union, cast, overload, @@ -92,14 +87,14 @@ def get_error_description_label() -> str: def get_error_feature_types( - quality_results: List[QualityError], -) -> Set[str]: + quality_results: list[QualityError], +) -> set[str]: return {errors.feature_type for errors in quality_results} def get_error_feature_attributes( - quality_errors: List[QualityError], -) -> Set[str]: + quality_errors: list[QualityError], +) -> set[str]: return {error.attribute_name for error in quality_errors if error.attribute_name} @@ -157,14 +152,14 @@ class QualityErrorTreeItemType(enum.Enum): ErrorDataType = NewType( "ErrorDataType", - Tuple[QualityErrorTreeItemType, Any], + tuple[QualityErrorTreeItemType, Any], ) class QualityErrorTreeItem: def __init__( self, - data: List[Any], + data: list[Any], key: str, item_type: QualityErrorTreeItemType, parent: Optional["QualityErrorTreeItem"] = None, @@ -172,8 +167,8 @@ def __init__( self.key = key self._item_parent = parent self._item_data = data - self._child_items: List["QualityErrorTreeItem"] = [] - self._child_item_map: Dict[str, int] = {} + self._child_items: list["QualityErrorTreeItem"] = [] + self._child_item_map: dict[str, int] = {} self.item_type = item_type @@ -204,7 +199,7 @@ def row(self) -> int: def column_count(self) -> int: return len(self._item_data) - def data( + def data( # noqa: C901, PLR0911, PLR0912 self, column_index: int, role: Qt.ItemDataRole = Qt.DisplayRole ) -> QVariant: if not (column_index >= 0 and column_index < len(self._item_data)): @@ -351,7 +346,7 @@ def parent( return self.createIndex(parent_item.row(), 0, parent_item) - def rowCount(self, parent: QModelIndex) -> int: # noqa: N802 (qt override) + def rowCount(self, parent: QModelIndex) -> int: # (qt override) if parent.column() > 0: return 0 @@ -362,7 +357,7 @@ def rowCount(self, parent: QModelIndex) -> int: # noqa: N802 (qt override) return parent_item.child_count() - def columnCount(self, parent: QModelIndex) -> int: # noqa: N802 (qt override) + def columnCount(self, parent: QModelIndex) -> int: # (qt override) if not parent.isValid(): parent_item = self._root_item else: @@ -380,7 +375,7 @@ def data( return item.data(index.column(), role) - def headerData( # noqa: N802 (qt override) + def headerData( # (qt override) self, section: int, orientation: Qt.Orientation, @@ -400,8 +395,11 @@ def headerData( # noqa: N802 (qt override) return QVariant() return QVariant() - def setData( # noqa: N802 (qt override) - self, index: QModelIndex, value: Any, role: Qt.ItemDataRole = Qt.EditRole + def setData( # (qt override) + self, + index: QModelIndex, + value: Any, # noqa: ANN401 + role: Qt.ItemDataRole = Qt.EditRole, ) -> bool: if not index.isValid() or role == Qt.EditRole: return False @@ -448,7 +446,7 @@ def flags( # (qt override) return super().flags(index) - def refresh_model(self, quality_errors: List[QualityError]) -> None: + def refresh_model(self, quality_errors: list[QualityError]) -> None: updated_quality_error_ids = { error.unique_identifier for error in quality_errors } @@ -476,7 +474,7 @@ def refresh_model(self, quality_errors: List[QualityError]) -> None: if error.unique_identifier in new_error_ids ) - errors_to_be_deleted: List[Tuple[QualityErrorTreeItem, QModelIndex]] = [] + errors_to_be_deleted: list[tuple[QualityErrorTreeItem, QModelIndex]] = [] for i in range(self.rowCount(QModelIndex())): for index in _get_quality_errors_indexes( @@ -493,7 +491,7 @@ def refresh_model(self, quality_errors: List[QualityError]) -> None: def _update_model_data( self, errors_to_be_added: Iterable[QualityError], - errors_to_be_deleted: List[Tuple[QualityErrorTreeItem, QModelIndex]], + errors_to_be_deleted: list[tuple[QualityErrorTreeItem, QModelIndex]], ) -> None: """ Updates model data based on new and deleted quality errors. @@ -663,7 +661,7 @@ def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) self.setFilterRole(Qt.UserRole) - def filterAcceptsRow( # noqa: N802 (qt override) + def filterAcceptsRow( # (qt override) self, source_row: int, source_parent: QModelIndex ) -> bool: source_index = self.sourceModel().index(source_row, 0, source_parent) @@ -689,7 +687,9 @@ def filterAcceptsRow( # noqa: N802 (qt override) @abstractmethod def accept_row( - self, tree_item_type: QualityErrorTreeItemType, tree_item_value: Any + self, + tree_item_type: QualityErrorTreeItemType, + tree_item_value: Any, # noqa: ANN401 ) -> bool: raise NotImplementedError() @@ -705,7 +705,7 @@ class FilterProxyModel(AbstractFilterProxyModel): def __init__(self, parent: Optional[QObject] = None) -> None: super().__init__(parent) - self._filters: List["AbstractQualityErrorFilter"] = [] + self._filters: list["AbstractQualityErrorFilter"] = [] def add_filter(self, filter: "AbstractQualityErrorFilter") -> None: filter.filters_changed.connect(self.invalidateFilter) @@ -713,7 +713,9 @@ def add_filter(self, filter: "AbstractQualityErrorFilter") -> None: self.invalidateFilter() - def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bool: + def accept_row( + self, item_type: QualityErrorTreeItemType, item_value: Any # noqa: ANN401 + ) -> bool: # TODO: Check only the changed filters. # Now this checks all the filters when one changes return all( @@ -721,7 +723,7 @@ def accept_row(self, item_type: QualityErrorTreeItemType, item_value: Any) -> bo for quality_filter in self._filters ) - def headerData( # noqa: N802 (qt override) + def headerData( # (qt override) self, section: int, orientation: Qt.Orientation, @@ -774,7 +776,9 @@ def set_enabled(self, enabled: bool) -> None: self.set_extent(None) def accept_row( - self, tree_item_type: QualityErrorTreeItemType, tree_item_value: Any + self, + tree_item_type: QualityErrorTreeItemType, + tree_item_value: Any, # noqa: ANN401 ) -> bool: if not self._extent: return True @@ -785,7 +789,7 @@ def accept_row( return quality_error.geometry.intersects(self._extent) - def headerData( # noqa: N802 (qt override) + def headerData( # (qt override) self, section: int, orientation: Qt.Orientation, @@ -825,7 +829,9 @@ def set_show_processed_errors(self, show_processed_errors: bool) -> None: self.invalidateFilter() def accept_row( - self, tree_item_type: QualityErrorTreeItemType, tree_item_value: Any + self, + tree_item_type: QualityErrorTreeItemType, + tree_item_value: Any, # noqa: ANN401 ) -> bool: return not ( self._show_processed_errors is False @@ -833,7 +839,7 @@ def accept_row( and cast(QualityError, tree_item_value).is_user_processed is True ) - def headerData( # noqa: N802 (qt override) + def headerData( # (qt override) self, section: int, orientation: Qt.Orientation, diff --git a/src/quality_result_gui/quality_layer.py b/src/quality_result_gui/quality_layer.py index 84255f7..7854e90 100644 --- a/src/quality_result_gui/quality_layer.py +++ b/src/quality_result_gui/quality_layer.py @@ -18,7 +18,8 @@ # along with quality-result-gui. If not, see . import logging -from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Union +from collections.abc import Iterable +from typing import TYPE_CHECKING, Optional, Union from qgis.core import ( QgsAnnotationLayer, @@ -59,7 +60,7 @@ class QualityErrorLayer: LAYER_ID_PROPERTY = "quality-result-gui-layer" def __init__(self) -> None: - self._annotation_ids: Dict[str, List[str]] = {} + self._annotation_ids: dict[str, list[str]] = {} self.style: "QualityLayerStyleConfig" = DefaultStyleConfig() @property @@ -165,14 +166,14 @@ def remove_annotations( # Consume exception, feature is not found pass - def _create_annotations( + def _create_annotations( # noqa: C901, PLR0912 self, quality_error: "QualityError", use_highlighted_style: bool, - ) -> List[ + ) -> list[ Union[QgsAnnotationMarkerItem, QgsAnnotationPolygonItem, QgsAnnotationLineItem] ]: - annotations: List[ + annotations: list[ Union[ QgsAnnotationMarkerItem, QgsAnnotationPolygonItem, QgsAnnotationLineItem ] diff --git a/src/quality_result_gui/style/default_style.py b/src/quality_result_gui/style/default_style.py index 005acc4..ffbd9fa 100644 --- a/src/quality_result_gui/style/default_style.py +++ b/src/quality_result_gui/style/default_style.py @@ -19,7 +19,7 @@ from dataclasses import dataclass from importlib.resources import as_file, files -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING, Optional, Union from qgis.core import ( QgsCentroidFillSymbolLayer, @@ -131,7 +131,6 @@ def __init__( self, quality_error: "QualityError", ) -> None: - self.style: QualityLayerStyle = QualityLayerStyle( COLORS_FOR_ERRORS, line_width=1.2, @@ -299,7 +298,7 @@ def _get_point_symbol(self, highlighted: bool) -> QgsMarkerSymbol: def _set_enabled_expression( self, priority_symbol_layer: QgsSymbolLayer, - geometry_layers: List[QgsSymbolLayer], + geometry_layers: list[QgsSymbolLayer], ) -> None: if ( self.icon_symbol_enabled_expression is not None @@ -318,7 +317,6 @@ def _set_enabled_expression( def _create_priority_symbol_layer( self, priority: QualityErrorPriority ) -> QgsSvgMarkerSymbolLayer: - file_path = files(resources).joinpath("icons") if priority == QualityErrorPriority.FATAL: @@ -328,9 +326,9 @@ def _create_priority_symbol_layer( elif priority == QualityErrorPriority.INFO: file_path = file_path.joinpath("quality_error_info.svg") else: - raise ValueError(f"Unknown priority {str(priority)}") + raise ValueError(f"Unknown priority {priority!s}") - style = dict({}) + style = {} with as_file(file_path) as svg_file: style["name"] = str(svg_file) diff --git a/src/quality_result_gui/ui/quality_error_tree_view.py b/src/quality_result_gui/ui/quality_error_tree_view.py index 7c843cc..324480c 100644 --- a/src/quality_result_gui/ui/quality_error_tree_view.py +++ b/src/quality_result_gui/ui/quality_error_tree_view.py @@ -17,8 +17,9 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . +from collections.abc import Generator from types import GeneratorType -from typing import TYPE_CHECKING, Generator, List, Optional, cast +from typing import TYPE_CHECKING, Optional, cast from qgis.PyQt.QtCore import QAbstractItemModel, QModelIndex, Qt, QVariant, pyqtSignal from qgis.PyQt.QtWidgets import QTreeView, QWidget @@ -70,7 +71,7 @@ def __init__( self.setStyleSheet(TREE_VIEW_STYLE) - def setModel( # noqa: N802 (override qt method) + def setModel( # (override qt method) self, model: Optional[QAbstractItemModel] ) -> None: super().setModel(model) @@ -82,9 +83,7 @@ def setModel( # noqa: N802 (override qt method) model.rowsInserted.connect(self._on_model_rows_inserted) model.rowsAboutToBeRemoved.connect(self._on_rows_about_to_be_removed) - def mousePressEvent( # noqa: N802 (override qt method) - self, event: "QMouseEvent" - ) -> None: + def mousePressEvent(self, event: "QMouseEvent") -> None: # (override qt method) if event.button() == Qt.MouseButton.LeftButton: self.current_selection_type = SelectionType.LeftClick elif event.button() == Qt.MouseButton.RightButton: @@ -95,9 +94,7 @@ def mousePressEvent( # noqa: N802 (override qt method) self.current_selection_type = SelectionType.Other - def keyPressEvent( # noqa: N802 (override qt method) - self, event: "QKeyEvent" - ) -> None: + def keyPressEvent(self, event: "QKeyEvent") -> None: # (override qt method) self.current_selection_type = SelectionType.Keyboard # Calling super will trigger currentChanged if arrow keys used @@ -105,7 +102,7 @@ def keyPressEvent( # noqa: N802 (override qt method) self.current_selection_type = SelectionType.Other - def get_all_quality_errors(self) -> List[QualityError]: + def get_all_quality_errors(self) -> list[QualityError]: return [ error for i in range(self.model().rowCount()) diff --git a/src/quality_result_gui/ui/quality_errors_dock.py b/src/quality_result_gui/ui/quality_errors_dock.py index 1123af5..ab8224c 100644 --- a/src/quality_result_gui/ui/quality_errors_dock.py +++ b/src/quality_result_gui/ui/quality_errors_dock.py @@ -18,7 +18,7 @@ # along with quality-result-gui. If not, see . import logging -from typing import TYPE_CHECKING, Optional, Type +from typing import TYPE_CHECKING, Optional from qgis.core import QgsApplication from qgis.gui import QgsGui @@ -46,10 +46,10 @@ LOGGER = logging.getLogger(__name__) -DockWidgetUi: Type[QDockWidget] = load_ui_file(__package__, "quality_errors_dock.ui") +DockWidgetUi: type[QDockWidget] = load_ui_file(__package__, "quality_errors_dock.ui") -class QualityErrorsDockWidget(DockWidgetUi): # type: ignore +class QualityErrorsDockWidget(DockWidgetUi): # type: ignore[valid-type] """ Graphical user interface for quality errors dock widget. """ @@ -102,7 +102,7 @@ def show(self) -> None: self._register_shortcut() return super().show() - def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802 (qt override) + def closeEvent(self, event: QCloseEvent) -> None: # (qt override) QgsGui.shortcutsManager().unregisterShortcut(self.shortcut_for_toggle_errors) self.closed.emit() diff --git a/src/quality_result_gui/utils/layer_utils.py b/src/quality_result_gui/utils/layer_utils.py index f286206..9879309 100644 --- a/src/quality_result_gui/utils/layer_utils.py +++ b/src/quality_result_gui/utils/layer_utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import List, Optional +from typing import Optional from qgis.core import ( QgsCoordinateReferenceSystem, @@ -46,7 +46,7 @@ def set_visibility_checked(layer: QgsVectorLayer, checked: bool) -> None: def zoom_to_geometries_and_flash( - geometries: List[QgsGeometry], + geometries: list[QgsGeometry], crs: QgsCoordinateReferenceSystem, preserve_scale: bool = False, min_extent_height: Optional[int] = None, @@ -75,7 +75,7 @@ def zoom_to_geometries_and_flash( iface.mapCanvas().redrawAllLayers() -def get_extent_from_geometries(geometries: List[QgsGeometry]) -> Optional[QgsRectangle]: +def get_extent_from_geometries(geometries: list[QgsGeometry]) -> Optional[QgsRectangle]: if len(geometries) == 0: return None diff --git a/src/quality_result_gui/utils/styling_utils.py b/src/quality_result_gui/utils/styling_utils.py index 9435324..ee30cfa 100644 --- a/src/quality_result_gui/utils/styling_utils.py +++ b/src/quality_result_gui/utils/styling_utils.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Dict, Tuple, Union +from typing import Union from qgis.core import ( QgsDrawSourceEffect, @@ -31,7 +31,7 @@ def set_symbol_layer_data_defined_property_expressions( - symbol_layer: QgsSymbolLayer, data_defined_property_expressions: Dict[str, str] + symbol_layer: QgsSymbolLayer, data_defined_property_expressions: dict[str, str] ) -> None: """ Sets the symbol layer data defined properties with expressions. @@ -58,8 +58,8 @@ def set_symbol_layer_data_defined_property_expressions( def set_symbol_layer_simple_outer_glow_effect( symbol_layer: QgsSymbolLayer, color_rgba: str, - spread: Tuple[float, str] = (2.0, "MM"), - blur: Tuple[float, str] = (2.0, "MM"), + spread: tuple[float, str] = (2.0, "MM"), + blur: tuple[float, str] = (2.0, "MM"), opacity: float = 1, ) -> None: """ @@ -89,7 +89,7 @@ def set_symbol_layer_simple_outer_glow_effect( def get_color( - hex_or_rgb: Union[str, Tuple[int, int, int]], opacity: int = 100 + hex_or_rgb: Union[str, tuple[int, int, int]], opacity: int = 100 ) -> QColor: color = QColor(hex_or_rgb) color.setAlpha(int(opacity / 100 * 255)) diff --git a/src/quality_result_gui_plugin/__init__.py b/src/quality_result_gui_plugin/__init__.py index 272d9c2..306a39c 100644 --- a/src/quality_result_gui_plugin/__init__.py +++ b/src/quality_result_gui_plugin/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -22,8 +22,7 @@ from quality_result_gui_plugin.plugin import QualityResultGuiPlugin -def classFactory( # noqa: N802 (qgis naming) +def classFactory( # (qgis naming) iface: QgisInterface, ) -> "QualityResultGuiPlugin": - return QualityResultGuiPlugin() diff --git a/src/quality_result_gui_plugin/dev_tools/dev_tools_dialog.py b/src/quality_result_gui_plugin/dev_tools/dev_tools_dialog.py index 262a348..80a56a1 100644 --- a/src/quality_result_gui_plugin/dev_tools/dev_tools_dialog.py +++ b/src/quality_result_gui_plugin/dev_tools/dev_tools_dialog.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -35,7 +35,6 @@ class DevToolsDialog(QDialog, FORM_CLASS): - quality_errors_data_file_widget: QgsFileWidget btn_open_quality_errors_dialog: QPushButton diff --git a/src/quality_result_gui_plugin/dev_tools/mock_api_client.py b/src/quality_result_gui_plugin/dev_tools/mock_api_client.py index eb92756..964951b 100644 --- a/src/quality_result_gui_plugin/dev_tools/mock_api_client.py +++ b/src/quality_result_gui_plugin/dev_tools/mock_api_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -20,12 +20,12 @@ import json from dataclasses import dataclass from pathlib import Path -from typing import List, Optional +from typing import Optional from qgis.core import QgsCoordinateReferenceSystem - from quality_result_gui.api.quality_api_client import QualityResultClient from quality_result_gui.api.types.quality_error import QualityError + from quality_result_gui_plugin.dev_tools.response_parser import QualityErrorResponse @@ -33,7 +33,7 @@ class MockQualityResultClient(QualityResultClient): json_file_path: Path - def get_results(self) -> Optional[List[QualityError]]: + def get_results(self) -> Optional[list[QualityError]]: """ Retrieve latest quality errors from API diff --git a/src/quality_result_gui_plugin/dev_tools/response_parser.py b/src/quality_result_gui_plugin/dev_tools/response_parser.py index bd55562..3e32cb2 100644 --- a/src/quality_result_gui_plugin/dev_tools/response_parser.py +++ b/src/quality_result_gui_plugin/dev_tools/response_parser.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -18,10 +18,9 @@ # along with quality-result-gui. If not, see . from dataclasses import dataclass, field -from typing import Any, Dict, List +from typing import Any from qgis.core import QgsGeometry - from quality_result_gui.api.types.quality_error import ( QualityError, QualityErrorPriority, @@ -31,8 +30,8 @@ @dataclass class QualityErrorResponse: - quality_results: List[QualityError] = field(init=False) - _errors_obj: List[Dict[str, Any]] + quality_results: list[QualityError] = field(init=False) + _errors_obj: list[dict[str, Any]] def __post_init__(self) -> None: self.quality_results = [ diff --git a/src/quality_result_gui_plugin/plugin.py b/src/quality_result_gui_plugin/plugin.py index aa8fc66..ca919ea 100644 --- a/src/quality_result_gui_plugin/plugin.py +++ b/src/quality_result_gui_plugin/plugin.py @@ -22,6 +22,7 @@ from typing import Optional, cast import qgis_plugin_tools +import quality_result_gui from qgis.core import QgsApplication, QgsProject from qgis.gui import QgisInterface from qgis.PyQt.QtCore import QCoreApplication, Qt, QTranslator @@ -31,11 +32,10 @@ from qgis_plugin_tools.tools.custom_logging import setup_loggers from qgis_plugin_tools.tools.i18n import setup_translation, tr from qgis_plugin_tools.tools.resources import resources_path - -import quality_result_gui -import quality_result_gui_plugin from quality_result_gui.env import IS_DEVELOPMENT_MODE, TEST_JSON_FILE_PATH from quality_result_gui.quality_error_manager import QualityResultManager + +import quality_result_gui_plugin from quality_result_gui_plugin.dev_tools.dev_tools_dialog import DevToolsDialog from quality_result_gui_plugin.dev_tools.mock_api_client import MockQualityResultClient @@ -64,7 +64,7 @@ def __init__(self) -> None: self.dev_tool_action: Optional[QAction] = None self._test_json_file_path = "" - def initGui(self) -> None: # noqa: N802 (qgis naming) + def initGui(self) -> None: # (qgis naming) self._teardown_loggers = setup_loggers( quality_result_gui.__name__, quality_result_gui_plugin.__name__, diff --git a/test/conftest.py b/test/conftest.py index 0616a07..997e59f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -17,14 +17,13 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Any, Callable, List, Optional +from typing import Any, Callable, Optional import pytest from pytest_mock import MockerFixture from qgis.core import QgsCoordinateReferenceSystem, QgsGeometry from qgis.PyQt.QtWidgets import QAction, QMenu from qgis_plugin_tools.tools.messages import MsgBar - from quality_result_gui.api.quality_api_client import QualityResultClient from quality_result_gui.api.types.quality_error import ( QualityError, @@ -34,7 +33,7 @@ class MockQualityResultClient(QualityResultClient): - def get_results(self) -> Optional[List[QualityError]]: + def get_results(self) -> Optional[list[QualityError]]: return [] def get_crs(self) -> QgsCoordinateReferenceSystem: @@ -47,7 +46,7 @@ def mock_api_client() -> QualityResultClient: @pytest.fixture() -def bypass_log_if_fails(mocker: MockerFixture) -> None: +def _bypass_log_if_fails(mocker: MockerFixture) -> None: """Throws unhandled exception even though it is caught with log_if_fails""" def mock_msg_bar(*args: Any, **kwargs: Any): @@ -64,7 +63,7 @@ def mock_msg_bar(*args: Any, **kwargs: Any): @pytest.fixture() -def quality_errors() -> List[QualityError]: +def quality_errors() -> list[QualityError]: return [ QualityError( QualityErrorPriority.FATAL, @@ -152,7 +151,7 @@ def error_feature_attributes() -> list[str]: @pytest.fixture() -def single_quality_error() -> List[QualityError]: +def single_quality_error() -> list[QualityError]: return [ QualityError( QualityErrorPriority.FATAL, @@ -187,8 +186,8 @@ def _get_submenu_from_menu(menu: QMenu, menu_title: str) -> Optional[QMenu]: @pytest.fixture() -def get_checked_menu_items() -> Callable[[QMenu], List[str]]: - def _get_checked_menu_items(menu: QMenu) -> List[str]: +def get_checked_menu_items() -> Callable[[QMenu], list[str]]: + def _get_checked_menu_items(menu: QMenu) -> list[str]: return [ action.text() for action in menu.actions() diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 257a322..229d684 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -17,27 +17,27 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import TYPE_CHECKING, Generator, List +from collections.abc import Generator +from typing import TYPE_CHECKING import pytest from pytestqt.qtbot import QtBot from qgis.core import QgsField, QgsProject, QgsVectorLayer, edit from qgis.PyQt.QtCore import QVariant -from quality_result_gui.ui.quality_errors_tree_filter_menu import ( - QualityErrorsTreeFilterMenu, -) - if TYPE_CHECKING: from quality_result_gui.api.quality_api_client import QualityResultClient from quality_result_gui.api.types.quality_error import QualityError from quality_result_gui.quality_error_manager import QualityResultManager + from quality_result_gui.ui.quality_errors_tree_filter_menu import ( + QualityErrorsTreeFilterMenu, + ) @pytest.fixture() def quality_result_manager( qgis_new_project: None, - bypass_log_if_fails: None, + _bypass_log_if_fails: None, qtbot: QtBot, mock_api_client: "QualityResultClient", ) -> Generator["QualityResultManager", None, None]: @@ -55,7 +55,7 @@ def quality_result_manager( @pytest.fixture() def quality_result_manager_with_data( quality_result_manager: "QualityResultManager", - quality_errors: List["QualityError"], + quality_errors: list["QualityError"], qtbot: QtBot, ) -> "QualityResultManager": with qtbot.waitSignal( @@ -70,7 +70,7 @@ def quality_result_manager_with_data( @pytest.fixture() def quality_result_manager_with_data_and_layer_mapping( quality_result_manager: "QualityResultManager", - quality_errors: List["QualityError"], + quality_errors: list["QualityError"], qtbot: QtBot, ) -> Generator["QualityResultManager", None, None]: layer = QgsVectorLayer("NoGeometry", "mock", "memory") diff --git a/test/integration/test_checkbox_actions.py b/test/integration/test_checkbox_actions.py index dc8b2fe..30f8fda 100644 --- a/test/integration/test_checkbox_actions.py +++ b/test/integration/test_checkbox_actions.py @@ -22,7 +22,6 @@ from qgis.core import QgsRectangle from qgis.gui import QgisInterface from qgis.PyQt.QtCore import QAbstractItemModel, QModelIndex - from quality_result_gui.api.types.quality_error import ( ERROR_PRIORITY_LABEL, QualityErrorPriority, @@ -75,7 +74,7 @@ def _count_children_rows(model: QAbstractItemModel, priority_index: QModelIndex) "Only feature 2b89a0b0 within view extent", ], ) -def test_filter_with_map_extent_check_box( # noqa: QGS105 +def test_filter_with_map_extent_check_box( qgis_iface: QgisInterface, quality_result_manager_with_data: QualityResultManager, mocker: MockerFixture, diff --git a/test/integration/test_filters.py b/test/integration/test_filters.py index a75831f..e038969 100644 --- a/test/integration/test_filters.py +++ b/test/integration/test_filters.py @@ -17,14 +17,13 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Callable, List, Optional +from typing import Callable, Optional import pytest from pytestqt.qtbot import QtBot from qgis.core import QgsFeature, QgsGeometry from qgis.PyQt.QtCore import QModelIndex from qgis.PyQt.QtWidgets import QAction, QMenu - from quality_result_gui.api.types.quality_error import ( ERROR_PRIORITY_LABEL, ERROR_TYPE_LABEL, @@ -89,7 +88,7 @@ def attribute_menu( @pytest.fixture() -def quality_errors_with_fence() -> List[QualityError]: +def quality_errors_with_fence() -> list[QualityError]: return [ QualityError( QualityErrorPriority.FATAL, @@ -134,7 +133,7 @@ def quality_errors_with_fence() -> List[QualityError]: @pytest.fixture() -def quality_errors_without_chimney_point() -> List[QualityError]: +def quality_errors_without_chimney_point() -> list[QualityError]: return [ QualityError( QualityErrorPriority.FATAL, @@ -166,7 +165,7 @@ def quality_errors_without_chimney_point() -> List[QualityError]: @pytest.fixture() -def quality_errors_without_building_part_area() -> List[QualityError]: +def quality_errors_without_building_part_area() -> list[QualityError]: return [ QualityError( QualityErrorPriority.FATAL, @@ -329,7 +328,7 @@ def test_reset_filters_action_restores_check_boxes( ], ) def test_actions_are_connected_to_correct_implementation_methods_and_filters_are_applied( - get_checked_menu_items: Callable[[QMenu], List[str]], + get_checked_menu_items: Callable[[QMenu], list[str]], get_submenu_from_menu: Callable[[QMenu, str], Optional[QMenu]], trigger_action: Callable[[QMenu, str], None], error_type_menu: QMenu, @@ -339,7 +338,7 @@ def test_actions_are_connected_to_correct_implementation_methods_and_filters_are error_feature_types: list[str], error_feature_attributes: list[str], selected_filter_condition: str, - selected_filter_values: List[str], + selected_filter_values: list[str], expected_feature_types: list[str], expected_feature_attributes: list[str], expected_error_types: list[int], @@ -482,11 +481,11 @@ def test_is_any_filter_active_returns_true_all_false_based_on_filter( ) def test_filters_are_retained_when_data_changes( qtbot: QtBot, - quality_errors: List[QualityError], + quality_errors: list[QualityError], error_feature_types: list[str], error_feature_attributes: list[str], quality_result_manager_with_data: QualityResultManager, - get_checked_menu_items: Callable[[QMenu], List[str]], + get_checked_menu_items: Callable[[QMenu], list[str]], trigger_action: Callable[[QMenu, str], None], feature_type_menu: QMenu, attribute_menu: QMenu, diff --git a/test/integration/test_tree_view.py b/test/integration/test_tree_view.py index 595fce0..0a0d0df 100644 --- a/test/integration/test_tree_view.py +++ b/test/integration/test_tree_view.py @@ -18,7 +18,6 @@ # along with quality-result-gui. If not, see . from copy import copy -from typing import List import pytest from pytest_mock import MockerFixture @@ -26,7 +25,6 @@ from qgis.core import QgsGeometry, QgsRectangle from qgis.gui import QgisInterface from qgis.PyQt.QtCore import QAbstractItemModel, QCoreApplication, QModelIndex, Qt - from quality_result_gui.api.types.quality_error import ( QualityError, QualityErrorPriority, @@ -109,7 +107,7 @@ def test_quality_error_tree_view_performance_with_big_dataset( def test_quality_error_tree_view_updates_view_partially_when_data_is_refreshed( quality_result_manager_with_data: QualityResultManager, - quality_errors: List[QualityError], + quality_errors: list[QualityError], ) -> None: model = quality_result_manager_with_data.dock_widget.error_tree_view.model() original_quality_errors = copy(quality_errors) @@ -171,7 +169,7 @@ def test_quality_error_tree_view_updates_view_partially_when_data_is_refreshed( "attribute-error-selected", ], ) -def test_clicking_tree_view_row_zooms_to_feature_if_feature_or_quality_error_selected( # noqa: QGS105 +def test_clicking_tree_view_row_zooms_to_feature_if_feature_or_quality_error_selected( quality_result_manager_with_data: QualityResultManager, qtbot: QtBot, qgis_iface: QgisInterface, @@ -275,7 +273,7 @@ def test_changing_model_data_sends_error_geometries_to_visualizer( ) assert m_add_or_replace_annotation.call_count == 2 - quality_errors: List[QualityError] = [ + quality_errors: list[QualityError] = [ call_args[0][1] for call_args in m_add_or_replace_annotation.call_args_list ] assert len(quality_errors) == 2 @@ -286,7 +284,7 @@ def test_changing_model_data_sends_error_geometries_to_visualizer( def test_model_reset_expands_error_rows_recursively_on_tree_view( quality_result_manager_with_data: QualityResultManager, - quality_errors: List[QualityError], + quality_errors: list[QualityError], ) -> None: quality_result_manager_with_data._fetcher.results_received.emit(quality_errors) diff --git a/test/unit/quality_result_gui/test_quality_data_fetcher.py b/test/unit/quality_result_gui/test_quality_data_fetcher.py index c3aedcc..77b99a5 100644 --- a/test/unit/quality_result_gui/test_quality_data_fetcher.py +++ b/test/unit/quality_result_gui/test_quality_data_fetcher.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -17,13 +17,13 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Any, Generator, Optional +from collections.abc import Generator +from typing import Any, Optional from unittest.mock import MagicMock import pytest from pytest_mock import MockerFixture from pytestqt.qtbot import QtBot - from quality_result_gui.api.quality_api_client import ( QualityResultClient, QualityResultClientError, diff --git a/test/unit/quality_result_gui/test_quality_error_visualizer.py b/test/unit/quality_result_gui/test_quality_error_visualizer.py index c9b1231..1bb3b9c 100644 --- a/test/unit/quality_result_gui/test_quality_error_visualizer.py +++ b/test/unit/quality_result_gui/test_quality_error_visualizer.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Generator, List +from collections.abc import Generator from unittest.mock import ANY import pytest @@ -29,7 +29,6 @@ QgsRectangle, ) from qgis.gui import QgisInterface - from quality_result_gui import SelectionType from quality_result_gui.api.types.quality_error import ( QualityError, @@ -58,7 +57,7 @@ def visualizer() -> Generator[QualityErrorVisualizer, None, None]: @pytest.fixture() -def visualized_errors() -> List[QualityError]: +def visualized_errors() -> list[QualityError]: return [ _create_test_quality_error( QualityErrorPriority.FATAL, "1", QgsGeometry.fromWkt("Point(1 1)") @@ -101,7 +100,7 @@ def test_add_new_errors_adds_geometries_to_annotation_layer( assert layer is not None - for key in visualizer._quality_error_layer._annotation_ids.keys(): + for key in visualizer._quality_error_layer._annotation_ids: assert key in [ "1", "2", @@ -109,7 +108,7 @@ def test_add_new_errors_adds_geometries_to_annotation_layer( assert len(layer.items()) == 2 - for key in layer.items().keys(): + for key in layer.items(): assert key in sum(visualizer._quality_error_layer._annotation_ids.values(), []) assert layer.item(key).geometry().isEmpty() is False @@ -117,7 +116,6 @@ def test_add_new_errors_adds_geometries_to_annotation_layer( def test_add_new_errors_does_nothing_with_empty_input( visualizer: QualityErrorVisualizer, ): - # Test visualizer.add_new_errors([]) @@ -127,7 +125,6 @@ def test_add_new_errors_does_nothing_with_empty_input( def test_add_new_errors_does_nothing_with_empty_input_geometry( visualizer: QualityErrorVisualizer, ): - # Test visualizer.add_new_errors( [_create_test_quality_error(QualityErrorPriority.FATAL, "1", QgsGeometry())] @@ -139,7 +136,6 @@ def test_add_new_errors_does_nothing_with_empty_input_geometry( def test_add_new_errors_works_with_multiple_geoms_with_same_geometry_type( visualizer: QualityErrorVisualizer, ): - priority = QualityErrorPriority.FATAL errors = [ _create_test_quality_error(priority, "1", QgsGeometry.fromWkt("Point(2 3)")), @@ -166,7 +162,7 @@ def test_add_new_errors_works_with_multiple_geoms_with_same_geometry_type( ) def test_remove_errors_removes_features_from_annotation_layer( visualizer: QualityErrorVisualizer, - visualized_errors: List[QualityError], + visualized_errors: list[QualityError], remove_selected_error: bool, ): visualizer.add_new_errors(visualized_errors) @@ -184,7 +180,7 @@ def test_remove_errors_removes_features_from_annotation_layer( if remove_selected_error: assert get_num_visualized_features(visualizer) == num_errors_before_removal - 3 - for key in visualizer._quality_error_layer._annotation_ids.keys(): + for key in visualizer._quality_error_layer._annotation_ids: assert key == visualized_errors[2].unique_identifier else: assert get_num_visualized_features(visualizer) == num_errors_before_removal - 2 @@ -195,7 +191,7 @@ def test_remove_errors_removes_features_from_annotation_layer( def test_remove_errors_does_nothing_with_empty_input( - visualizer: QualityErrorVisualizer, visualized_errors: List[QualityError] + visualizer: QualityErrorVisualizer, visualized_errors: list[QualityError] ): visualizer.add_new_errors(visualized_errors) visualizer.refresh_selected_error(visualized_errors[0]) @@ -213,7 +209,7 @@ def test_remove_errors_does_nothing_with_empty_input( def test_hide_errors_changes_quality_layer_visibility( - visualizer: QualityErrorVisualizer, visualized_errors: List[QualityError] + visualizer: QualityErrorVisualizer, visualized_errors: list[QualityError] ): root: QgsLayerTree = QgsProject.instance().layerTreeRoot() @@ -235,9 +231,8 @@ def test_hide_errors_changes_quality_layer_visibility( def test_add_new_errors_replaces_annotation_features_if_same_id( visualizer: QualityErrorVisualizer, - visualized_errors: List[QualityError], + visualized_errors: list[QualityError], ): - visualizer.add_new_errors(visualized_errors) assert get_num_visualized_features(visualizer) == len(visualized_errors) @@ -254,9 +249,8 @@ def test_add_new_errors_replaces_annotation_features_if_same_id( def test_refresh_selected_errors_replaces_selected_features_only( - visualizer: QualityErrorVisualizer, visualized_errors: List[QualityError] + visualizer: QualityErrorVisualizer, visualized_errors: list[QualityError] ): - visualizer.add_new_errors(visualized_errors) visualizer.refresh_selected_error(visualized_errors[0]) @@ -339,11 +333,11 @@ def test_on_error_selected( "empty list", ], ) -def test_zoom_to_geometries_and_flash( # noqa: QGS105 +def test_zoom_to_geometries_and_flash( visualizer: QualityErrorVisualizer, qgis_iface: QgisInterface, preserve_scale: bool, - input_geoms: List[QgsGeometry], + input_geoms: list[QgsGeometry], should_zoom_to_feature: bool, ): qgis_iface.mapCanvas().setExtent(QgsRectangle(100, 100, 200, 200)) diff --git a/test/unit/quality_result_gui/test_quality_errors_filters.py b/test/unit/quality_result_gui/test_quality_errors_filters.py index 10f613c..23fc4a8 100644 --- a/test/unit/quality_result_gui/test_quality_errors_filters.py +++ b/test/unit/quality_result_gui/test_quality_errors_filters.py @@ -22,7 +22,6 @@ import pytest from qgis.PyQt.QtWidgets import QAction, QMenu - from quality_result_gui.api.types.quality_error import ERROR_TYPE_LABEL from quality_result_gui.quality_errors_filters import ErrorTypeFilter, FilterMenu @@ -154,7 +153,6 @@ def test_deselect_action_unchecks_all( get_action_from_menu: Callable[[QMenu, str], Optional[QAction]], trigger_action: Callable[[QMenu, str], None], ): - error_type_filter_menu = ErrorTypeFilter() error_type_filter_menu._refresh_error_type_filters(ERROR_TYPE_LABEL) diff --git a/test/unit/quality_result_gui/test_quality_errors_manager.py b/test/unit/quality_result_gui/test_quality_errors_manager.py index 6a7d820..92e3da5 100644 --- a/test/unit/quality_result_gui/test_quality_errors_manager.py +++ b/test/unit/quality_result_gui/test_quality_errors_manager.py @@ -17,7 +17,8 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Callable, Generator, List, Optional +from collections.abc import Generator +from typing import Callable, Optional from unittest.mock import MagicMock import pytest @@ -27,7 +28,6 @@ from qgis.gui import QgsGui from qgis.PyQt.QtCore import QModelIndex, Qt from qgis.PyQt.QtWidgets import QMenu - from quality_result_gui.api.quality_api_client import QualityResultClient from quality_result_gui.api.types.quality_error import QualityError from quality_result_gui.configuration import QualityLayerStyleConfig @@ -56,7 +56,7 @@ def quality_result_manager( @pytest.fixture() def quality_result_manager_with_data( quality_result_manager: QualityResultManager, - quality_errors: List[QualityError], + quality_errors: list[QualityError], qtbot: QtBot, ) -> QualityResultManager: with qtbot.waitSignal( @@ -127,7 +127,6 @@ def test_show_dock_widget_starts_fetcher_and_shows_widget( def test_close_and_reopen_preserves_error_visibility_on_map( mock_api_client: QualityResultClient, ) -> None: - quality_result_manager = QualityResultManager(mock_api_client, None) def _check_quality_layer_visibility(expected_visibility: bool) -> None: @@ -184,7 +183,6 @@ def test_model_set_data_user_processed( expected_callback_value: bool, callback_called: bool, ) -> None: - quality_result_manager_with_data.error_checked.connect(m_user_processed_callback) model = quality_result_manager_with_data._styled_model @@ -206,7 +204,7 @@ def test_model_set_data_user_processed( def test_override_quality_layer_style_changes_annotation_style( qtbot: QtBot, mock_api_client: QualityResultClient, - single_quality_error: List[QualityError], + single_quality_error: list[QualityError], ): class MockStyle(QualityLayerStyleConfig): def create_error_symbol(self, quality_error: QualityError) -> ErrorSymbol: @@ -237,7 +235,6 @@ def create_error_symbol(self, quality_error: QualityError) -> ErrorSymbol: def test_shortcut_for_toggle_errors_is_unregistered_after_unload( quality_result_manager: QualityResultManager, ) -> None: - quality_result_manager.show_dock_widget() shortcut_name = ( diff --git a/test/unit/quality_result_gui/test_quality_errors_tree_model.py b/test/unit/quality_result_gui/test_quality_errors_tree_model.py index 2cad113..cc10f07 100644 --- a/test/unit/quality_result_gui/test_quality_errors_tree_model.py +++ b/test/unit/quality_result_gui/test_quality_errors_tree_model.py @@ -17,12 +17,11 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Dict, List, NamedTuple, Optional, Set +from typing import NamedTuple, Optional import pytest from pytestqt.modeltest import ModelTester from qgis.PyQt.QtCore import QAbstractItemModel, QModelIndex, Qt, QVariant - from quality_result_gui.api.types.quality_error import ( ERROR_TYPE_LABEL, QualityError, @@ -44,17 +43,17 @@ ) -def _feature_type_filters(quality_errors: List[QualityError]) -> Set[str]: +def _feature_type_filters(quality_errors: list[QualityError]) -> set[str]: return get_error_feature_types(quality_errors) def _feature_attribute_filters( - quality_errors: List[QualityError], -) -> Set[str]: + quality_errors: list[QualityError], +) -> set[str]: return get_error_feature_attributes(quality_errors) -def _error_type_filters() -> Set[QualityErrorType]: +def _error_type_filters() -> set[QualityErrorType]: return set(QualityErrorType) @@ -126,7 +125,7 @@ def _priority_1_feature_type_1_feature_1_error_2_description_index( @pytest.fixture() def feature_type_filter( - quality_errors: List[QualityError], + quality_errors: list[QualityError], ) -> FeatureTypeFilter: feature_type_filter = FeatureTypeFilter() for feature_type in get_error_feature_types(quality_errors): @@ -141,10 +140,9 @@ def base_model() -> QualityErrorsTreeBaseModel: @pytest.fixture() def model( - quality_errors: List[QualityError], + quality_errors: list[QualityError], base_model: QualityErrorsTreeBaseModel, ) -> FilterByExtentProxyModel: - styled_model = StyleProxyModel(None) styled_model.setSourceModel(base_model) @@ -174,9 +172,8 @@ class ModelAndFilters(NamedTuple): @pytest.fixture() def filter_proxy_model_and_filters( base_model: QualityErrorsTreeBaseModel, - quality_errors: List[QualityError], + quality_errors: list[QualityError], ) -> ModelAndFilters: - filter_model = FilterProxyModel() filter_model.setSourceModel(base_model) @@ -206,7 +203,7 @@ def filter_proxy_model_and_filters( def test_base_model( base_model: QualityErrorsTreeBaseModel, qtmodeltester: ModelTester, - quality_errors: List[QualityError], + quality_errors: list[QualityError], ) -> None: base_model.refresh_model(quality_errors) @@ -323,7 +320,6 @@ def test_model_header_data(model: FilterByExtentProxyModel): def test_total_number_of_errors_is_shown_in_header( filter_proxy_model_and_filters: ModelAndFilters, ): - ( _, model, @@ -572,11 +568,11 @@ def test_model_checkable_flags(model: FilterByExtentProxyModel): ) def test_model_data_count_changes_when_filter_is_applied( filter_proxy_model_and_filters: ModelAndFilters, - quality_errors: List[QualityError], - accepted_error_types: Optional[Set[QualityErrorType]], - accepted_feature_types: Optional[Set[str]], - accepted_attribute_names: Optional[Set[str]], - expected_counts: Dict[str, int], + quality_errors: list[QualityError], + accepted_error_types: Optional[set[QualityErrorType]], + accepted_feature_types: Optional[set[str]], + accepted_attribute_names: Optional[set[str]], + expected_counts: dict[str, int], ): accepted_feature_types = ( accepted_feature_types @@ -585,9 +581,7 @@ def test_model_data_count_changes_when_filter_is_applied( ) for ( filter_value - ) in ( - filter_proxy_model_and_filters.feature_type_filter._filter_value_action_map.keys() - ): + ) in filter_proxy_model_and_filters.feature_type_filter._filter_value_action_map: filter_proxy_model_and_filters.feature_type_filter._sync_filtered( filter_value, filter_value in accepted_feature_types ) @@ -599,9 +593,7 @@ def test_model_data_count_changes_when_filter_is_applied( ) for ( filter_value - ) in ( - filter_proxy_model_and_filters.attribute_name_filter._filter_value_action_map.keys() - ): + ) in filter_proxy_model_and_filters.attribute_name_filter._filter_value_action_map: filter_proxy_model_and_filters.attribute_name_filter._sync_filtered( filter_value, filter_value in accepted_attribute_names ) @@ -613,9 +605,7 @@ def test_model_data_count_changes_when_filter_is_applied( ) for ( filter_value - ) in ( - filter_proxy_model_and_filters.error_type_filter._filter_value_action_map.keys() - ): + ) in filter_proxy_model_and_filters.error_type_filter._filter_value_action_map: filter_proxy_model_and_filters.error_type_filter._sync_filtered( filter_value, filter_value in accepted_error_types ) @@ -645,7 +635,7 @@ def test_model_data_count_changes_when_filter_is_applied( def test_refresh_model_updates_data_partially_when_data_is_refreshed( base_model: QualityErrorsTreeBaseModel, - quality_errors: List[QualityError], + quality_errors: list[QualityError], ): base_model.refresh_model(quality_errors) @@ -669,7 +659,7 @@ def test_refresh_model_updates_data_partially_when_data_is_refreshed( def test_refresh_model_does_nothing_if_data_does_not_change( base_model: QualityErrorsTreeBaseModel, - quality_errors: List[QualityError], + quality_errors: list[QualityError], ): base_model.refresh_model(quality_errors) @@ -691,7 +681,6 @@ def test_refresh_model_does_nothing_if_data_does_not_change( def test_no_rows_visible_when_all_user_processed( filter_proxy_model_and_filters: ModelAndFilters, ): - model = FilterByShowUserProcessedProxyModel() model.setSourceModel(filter_proxy_model_and_filters.filter_proxy_model) diff --git a/test/unit/quality_result_gui/test_quality_layer.py b/test/unit/quality_result_gui/test_quality_layer.py index 69f0bde..e5717e1 100644 --- a/test/unit/quality_result_gui/test_quality_layer.py +++ b/test/unit/quality_result_gui/test_quality_layer.py @@ -17,12 +17,11 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Iterator, List +from collections.abc import Iterator from unittest.mock import ANY import pytest from qgis.core import QgsAnnotationLayer, QgsGeometry, QgsProject - from quality_result_gui.api.types.quality_error import QualityErrorPriority from quality_result_gui.quality_error_visualizer import QualityError from quality_result_gui.quality_layer import QualityErrorLayer @@ -37,7 +36,7 @@ def _create_test_quality_error( @pytest.fixture() -def quality_layer(qgis_new_project) -> QualityErrorLayer: +def quality_layer(qgis_new_project: None) -> QualityErrorLayer: return QualityErrorLayer() @@ -140,7 +139,7 @@ def test_add_or_replace_annotation_with_new_quality_errors( assert list(quality_layer_created._annotation_ids.keys()) == ["1"] assert len(annotation_layer.items()) == num_resulting_annotations - for key in annotation_layer.items().keys(): + for key in annotation_layer.items(): assert key in sum(quality_layer_created._annotation_ids.values(), []) assert annotation_layer.item(key).geometry().isEmpty() is False @@ -192,7 +191,7 @@ def test_add_or_replace_annotation_with_updated_quality_errors( old_geom: QgsGeometry, num_old_items: int, new_geom: QgsGeometry, - expected_geoms_as_wkt: List[str], + expected_geoms_as_wkt: list[str], ): assert not old_geom.isNull(), "Input WKT was not valid" assert not new_geom.isNull(), "Input WKT was not valid" @@ -211,7 +210,7 @@ def test_add_or_replace_annotation_with_updated_quality_errors( assert len(annotation_layer.items()) == len(expected_geoms_as_wkt) - for key in annotation_layer.items().keys(): + for key in annotation_layer.items(): assert annotation_layer.item(key).geometry().asWkt() in expected_geoms_as_wkt diff --git a/test/unit/quality_result_gui/ui/test_quality_errors_dock.py b/test/unit/quality_result_gui/ui/test_quality_errors_dock.py index e15b168..36bee83 100644 --- a/test/unit/quality_result_gui/ui/test_quality_errors_dock.py +++ b/test/unit/quality_result_gui/ui/test_quality_errors_dock.py @@ -17,15 +17,18 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . +from typing import TYPE_CHECKING + import pytest from pytest_mock import MockerFixture from pytestqt.qtbot import QtBot from qgis.gui import QgsGui from qgis.PyQt.QtCore import Qt -from qgis.PyQt.QtWidgets import QShortcut - from quality_result_gui.ui.quality_errors_dock import QualityErrorsDockWidget +if TYPE_CHECKING: + from qgis.PyQt.QtWidgets import QShortcut + @pytest.fixture() def dock_widget(qtbot: QtBot) -> QualityErrorsDockWidget: @@ -53,7 +56,6 @@ def test_quality_errors_dock_opens_and_closes(dock_widget: QualityErrorsDockWidg ], ) def test_update_filter_menu_icon_state(mocker: MockerFixture, has_filters_active: bool): - dock_widget = QualityErrorsDockWidget() m_is_any_filter_active = mocker.patch.object( dock_widget.filter_menu, @@ -72,7 +74,6 @@ def test_update_filter_menu_icon_state(mocker: MockerFixture, has_filters_active def test_shortcut_for_toggle_errors_toggles_checkbox( dock_widget: QualityErrorsDockWidget, ) -> None: - dock_widget.show() shortcut = dock_widget.shortcut_for_toggle_errors diff --git a/test/unit/quality_result_gui_plugin/test_plugin.py b/test/unit/quality_result_gui_plugin/test_plugin.py index 50164b6..3871aa5 100644 --- a/test/unit/quality_result_gui_plugin/test_plugin.py +++ b/test/unit/quality_result_gui_plugin/test_plugin.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 National Land Survey of Finland +# Copyright (C) 2022-2023 National Land Survey of Finland # (https://www.maanmittauslaitos.fi/en). # # @@ -17,13 +17,12 @@ # You should have received a copy of the GNU General Public License # along with quality-result-gui. If not, see . -from typing import Iterator +from collections.abc import Iterator import pytest from pytest_mock import MockerFixture from qgis.core import QgsSettings from qgis.utils import iface - from quality_result_gui_plugin import classFactory from quality_result_gui_plugin.dev_tools.dev_tools_dialog import DevToolsDialog from quality_result_gui_plugin.plugin import QualityResultGuiPlugin