fix: kompatibilita s Qt6 / QGIS 4

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ý.
This commit is contained in:
david-spacil committed 2026-09-01 23:05:02 +02:00
1 parent 2218719f98
commit d398d2cd1b
4 files changed
+68 -16

No files matched your search

+52
View File
@@ -83,6 +83,58 @@ Další pravidla:
definice pole (`QgsField`), naplnění hodnoty z dokumentu, překlad hlavičky definice pole (`QgsField`), naplnění hodnoty z dokumentu, překlad hlavičky
sloupce a export atributů. 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 ## Verzování a release
- Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`). - Verze pluginu žije v **`amcr_viewer/metadata.txt`** (`version=`).
+4 -4
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import csv import csv
import requests import requests
@@ -90,7 +90,7 @@ def parse_codelist_file(filename, target_dict=None):
except Exception as e: except Exception as e:
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
f"AMČR Codelist Read Error for {filename}: {e}", f"AMČR Codelist Read Error for {filename}: {e}",
"AMČR", Qgis.Critical) "AMČR", Qgis.MessageLevel.Critical)
return target_dict return target_dict
@@ -216,7 +216,7 @@ def fetch_set(base_url, internal_name, api_set, task=None):
except Exception as e: except Exception as e:
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
f"Chyba u setu {api_set}: {e}", f"Chyba u setu {api_set}: {e}",
"AMČR", Qgis.Warning) "AMČR", Qgis.MessageLevel.Warning)
break break
return dataset return dataset
@@ -240,7 +240,7 @@ def download_heslare(task=None):
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
f"Zpracovávám kategorii: {interni}...", f"Zpracovávám kategorii: {interni}...",
"AMČR", Qgis.Info) "AMČR", Qgis.MessageLevel.Info)
# Pass the task correctly to the updated fetch function # Pass the task correctly to the updated fetch function
data = fetch_set(base_url, interni, api_nazev, task=task) data = fetch_set(base_url, interni, api_nazev, task=task)
+5 -5
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from qgis.PyQt.QtWidgets import (QDialog, QVBoxLayout, from qgis.PyQt.QtWidgets import (QDialog, QVBoxLayout,
QLineEdit, QDialogButtonBox, QLineEdit, QDialogButtonBox,
QCheckBox, QGroupBox, QPushButton, QCheckBox, QGroupBox, QPushButton,
@@ -38,7 +38,7 @@ _ACTIVE_TASKS = []
class UpdateCodelistsTask(QgsTask): class UpdateCodelistsTask(QgsTask):
def __init__(self, description): def __init__(self, description):
super().__init__(description, QgsTask.CanCancel) super().__init__(description, QgsTask.Flag.CanCancel)
self.success = False self.success = False
self.exception = None self.exception = None
@@ -59,18 +59,18 @@ class UpdateCodelistsTask(QgsTask):
refresh_globals() refresh_globals()
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
"Hesláře AMČR byly úspěšně aktualizovány.", "Hesláře AMČR byly úspěšně aktualizovány.",
"AMČR", Qgis.Info "AMČR", Qgis.MessageLevel.Info
) )
else: else:
if self.isCanceled(): if self.isCanceled():
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
"Aktualizace heslářů byla zrušena.", "Aktualizace heslářů byla zrušena.",
"AMČR", Qgis.Warning "AMČR", Qgis.MessageLevel.Warning
) )
else: else:
QgsMessageLog.logMessage( QgsMessageLog.logMessage(
f"Chyba aktualizace: {self.exception}", f"Chyba aktualizace: {self.exception}",
"AMČR", Qgis.Critical "AMČR", Qgis.MessageLevel.Critical
) )
+7 -7
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry, from qgis.core import (QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
QgsField, QgsCoordinateReferenceSystem, QgsField, QgsCoordinateReferenceSystem,
QgsCoordinateTransform, QgsWkbTypes, Qgis, QgsCoordinateTransform, QgsWkbTypes, Qgis,
@@ -1138,11 +1138,11 @@ def load_amcr_data(canvas, bb, filters=None,
if geom.isGeosValid(): if geom.isGeosValid():
t = geom.type() t = geom.type()
target_list = None target_list = None
if t == QgsWkbTypes.PolygonGeometry: if t == QgsWkbTypes.GeometryType.PolygonGeometry:
target_list = feats_p target_list = feats_p
elif t == QgsWkbTypes.LineGeometry: elif t == QgsWkbTypes.GeometryType.LineGeometry:
target_list = feats_l target_list = feats_l
elif t == QgsWkbTypes.PointGeometry: elif t == QgsWkbTypes.GeometryType.PointGeometry:
target_list = feats_pt target_list = feats_pt
if target_list is None: if target_list is None:
@@ -1227,11 +1227,11 @@ def load_amcr_data(canvas, bb, filters=None,
if geom.isGeosValid(): if geom.isGeosValid():
t = geom.type() t = geom.type()
target_list = None target_list = None
if t == QgsWkbTypes.PolygonGeometry: if t == QgsWkbTypes.GeometryType.PolygonGeometry:
target_list = feats_p target_list = feats_p
elif t == QgsWkbTypes.LineGeometry: elif t == QgsWkbTypes.GeometryType.LineGeometry:
target_list = feats_l target_list = feats_l
elif t == QgsWkbTypes.PointGeometry: elif t == QgsWkbTypes.GeometryType.PointGeometry:
target_list = feats_pt target_list = feats_pt
if target_list is None: if target_list is None: