From d398d2cd1b28c74617e02b28a0e7de91dbc20773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Sp=C3=A1=C4=8Dil?= Date: Tue, 1 Sep 2026 23:05:02 +0200 Subject: [PATCH] fix: kompatibilita s Qt6 / QGIS 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Oficiální kontrola pyqgis4-checker (skript pyqt5_to_pyqt6.py z QGISu) hlásila u pluginu 13 nekompatibilit a tři soubory vůbec nepřečetla. Odstraněn UTF-8 BOM z amcr_codelists.py, amcr_dialog.py a amcr_tools.py. Checker čte zdroják jako UTF-8 bez utf-8-sig a na BOM padá na SyntaxError: invalid non-printable character U+FEFF, takže se ty soubory nezkontrolovaly vůbec. Pythonu při běhu BOM nevadí, proto to nikdy nevyskočilo. heslar.csv si BOM ponechává, tam je kvůli Excelu. Enumy převedeny na plně kvalifikované tvary (13 míst): Qgis.{Info,Warning,Critical} -> Qgis.MessageLevel.*, QgsTask.CanCancel -> QgsTask.Flag.CanCancel, QgsWkbTypes.*Geometry -> QgsWkbTypes.GeometryType.*Geometry. Zkrácené tvary v QGIS 4.2.1 zatím fungují, ale kontrola je vytýká a aliasy do budoucna mizí. Zbytek kódu už scoped tvary používal. Do AGENTS.md doplněna sekce o kompatibilitě s Qt6 / QGIS 4 se závaznými pravidly a příkazy na ověření. Ověřeno spuštěním v QGIS 3.44.13 (Qt 5.15.18) i QGIS 4.2.1 (Qt 6.10.3): importy, vytvoření tasku i všechny tři filtrační dialogy fungují shodně. Log pyqgis4-checkeru je nově prázdný. --- AGENTS.md | 52 +++++++++++++++++++++++++++++++++++ amcr_viewer/amcr_codelists.py | 8 +++--- amcr_viewer/amcr_dialog.py | 10 +++---- amcr_viewer/amcr_tools.py | 14 +++++----- 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 251cf42..b6107b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,6 +83,58 @@ Další pravidla: definice pole (`QgsField`), naplnění hodnoty z dokumentu, překlad hlavičky sloupce a export atributů. +### Kompatibilita s Qt6 / QGIS 4 + +Plugin cílí na QGIS 3.44 i na QGIS 4 (`qgisMaximumVersion=4.99.0`), tedy na +Qt5 i Qt6 zároveň. **Tohle se drží rigorózně** – ne až před releasem, ale při +každé změně kódu. Chování obou větví se liší tiše: pod Qt5 projde i to, co +QGIS 4 odmítne, takže lokální „funguje mi to“ nic nedokazuje. + +Závazná pravidla: + +- **Nikdy neimportuj přímo z `PyQt5` ani z `PyQt6`.** Vždy přes shim + `qgis.PyQt.*`. Ten mimo jiné pod Qt6 přetahuje `QAction`, `QActionGroup` + a `QShortcut` z `QtGui`, takže import z `qgis.PyQt.QtWidgets` je správně. +- **Enumy vždy plně kvalifikované (scoped).** `Qgis.MessageLevel.Info`, ne + `Qgis.Info`; `QgsTask.Flag.CanCancel`, ne `QgsTask.CanCancel`; + `QgsWkbTypes.GeometryType.PointGeometry`, ne `QgsWkbTypes.PointGeometry`. + Totéž pro Qt: `Qt.CheckState.Checked`, `QDialogButtonBox.StandardButton.Ok`. + Zkrácené tvary sice v QGIS 4.2 zatím fungují, ale oficiální kontrola je + hlásí a do budoucna mizí. +- **Zdrojové `.py` soubory ukládej bez BOM.** Kontrolní skript čte soubor + jako UTF-8 bez `utf-8-sig` a na BOM spadne s + `SyntaxError: invalid non-printable character U+FEFF`, takže se takový + soubor **vůbec nezkontroluje**. (`codelists/heslar.csv` BOM mít smí, tam je + kvůli Excelu.) +- Nepoužívej API zrušená v Qt6: `exec_()`, `QRegExp`, `QDesktopWidget`, + `QApplication.desktop()`, `Qt.MidButton`, `QFontMetrics.width()`, + `setResizeMode`, atributy `AA_EnableHighDpiScaling` / `AA_UseHighDpiPixmaps`. +- `supportsQt6=True` v `metadata.txt` **nepatří** – bylo zrušeno; o zařazení + mezi „QGIS 4 Ready“ rozhoduje rozsah `qgisMinimumVersion` až + `qgisMaximumVersion`. + +Ověření před PR, který mění Python kód: + +```sh +# oficiální kontrola, kterou pouští i plugins.qgis.org (pyqgis4-checker) +docker run --rm --pull always --user $(id -u):$(id -g) \ + --workdir /workspace/ -v "$(pwd):/workspace/" \ + ghcr.io/qgis/pyqgis4-checker:main-ubuntu \ + pyqt5_to_pyqt6.py --dry_run --logfile /workspace/pyqt6_checker.log . +``` + +Prázdný log = čisté. Kontrola je na plugins.qgis.org informativní +(neblokuje schválení), ale nález znamená, že plugin v QGIS 4 dříve nebo +později přestane fungovat. + +Když je po ruce QGIS 4 (např. flatpak `org.qgis.qgis`), ověř navíc, že se +plugin pod Qt6 opravdu načte: + +```sh +flatpak run --command=sh org.qgis.qgis -c \ + 'PYTHONPATH=/app/share/qgis/python python3 -c "import qgis.core"' +``` + ## Verzování a release - Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`). diff --git a/amcr_viewer/amcr_codelists.py b/amcr_viewer/amcr_codelists.py index 962b928..d537f81 100644 --- a/amcr_viewer/amcr_codelists.py +++ b/amcr_viewer/amcr_codelists.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- import os import csv import requests @@ -90,7 +90,7 @@ def parse_codelist_file(filename, target_dict=None): except Exception as e: QgsMessageLog.logMessage( f"AMČR Codelist Read Error for {filename}: {e}", - "AMČR", Qgis.Critical) + "AMČR", Qgis.MessageLevel.Critical) return target_dict @@ -216,7 +216,7 @@ def fetch_set(base_url, internal_name, api_set, task=None): except Exception as e: QgsMessageLog.logMessage( f"Chyba u setu {api_set}: {e}", - "AMČR", Qgis.Warning) + "AMČR", Qgis.MessageLevel.Warning) break return dataset @@ -240,7 +240,7 @@ def download_heslare(task=None): QgsMessageLog.logMessage( f"Zpracovávám kategorii: {interni}...", - "AMČR", Qgis.Info) + "AMČR", Qgis.MessageLevel.Info) # Pass the task correctly to the updated fetch function data = fetch_set(base_url, interni, api_nazev, task=task) diff --git a/amcr_viewer/amcr_dialog.py b/amcr_viewer/amcr_dialog.py index 645a64c..ba6401a 100644 --- a/amcr_viewer/amcr_dialog.py +++ b/amcr_viewer/amcr_dialog.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- from qgis.PyQt.QtWidgets import (QDialog, QVBoxLayout, QLineEdit, QDialogButtonBox, QCheckBox, QGroupBox, QPushButton, @@ -38,7 +38,7 @@ _ACTIVE_TASKS = [] class UpdateCodelistsTask(QgsTask): def __init__(self, description): - super().__init__(description, QgsTask.CanCancel) + super().__init__(description, QgsTask.Flag.CanCancel) self.success = False self.exception = None @@ -59,18 +59,18 @@ class UpdateCodelistsTask(QgsTask): refresh_globals() QgsMessageLog.logMessage( "Hesláře AMČR byly úspěšně aktualizovány.", - "AMČR", Qgis.Info + "AMČR", Qgis.MessageLevel.Info ) else: if self.isCanceled(): QgsMessageLog.logMessage( "Aktualizace heslářů byla zrušena.", - "AMČR", Qgis.Warning + "AMČR", Qgis.MessageLevel.Warning ) else: QgsMessageLog.logMessage( f"Chyba aktualizace: {self.exception}", - "AMČR", Qgis.Critical + "AMČR", Qgis.MessageLevel.Critical ) diff --git a/amcr_viewer/amcr_tools.py b/amcr_viewer/amcr_tools.py index d0d55ac..cb6a51c 100644 --- a/amcr_viewer/amcr_tools.py +++ b/amcr_viewer/amcr_tools.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry, QgsField, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsWkbTypes, Qgis, @@ -1138,11 +1138,11 @@ def load_amcr_data(canvas, bb, filters=None, if geom.isGeosValid(): t = geom.type() target_list = None - if t == QgsWkbTypes.PolygonGeometry: + if t == QgsWkbTypes.GeometryType.PolygonGeometry: target_list = feats_p - elif t == QgsWkbTypes.LineGeometry: + elif t == QgsWkbTypes.GeometryType.LineGeometry: target_list = feats_l - elif t == QgsWkbTypes.PointGeometry: + elif t == QgsWkbTypes.GeometryType.PointGeometry: target_list = feats_pt if target_list is None: @@ -1227,11 +1227,11 @@ def load_amcr_data(canvas, bb, filters=None, if geom.isGeosValid(): t = geom.type() target_list = None - if t == QgsWkbTypes.PolygonGeometry: + if t == QgsWkbTypes.GeometryType.PolygonGeometry: target_list = feats_p - elif t == QgsWkbTypes.LineGeometry: + elif t == QgsWkbTypes.GeometryType.LineGeometry: target_list = feats_l - elif t == QgsWkbTypes.PointGeometry: + elif t == QgsWkbTypes.GeometryType.PointGeometry: target_list = feats_pt if target_list is None: