Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pyqt5to6] store enums to allow usage without QGIS #59992

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 114 additions & 56 deletions scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,18 @@

import argparse
import ast
import difflib
import glob
import inspect
import json
import os
import sys

from collections import defaultdict
from collections.abc import Sequence
from enum import Enum
from pathlib import Path

from PyQt6 import (
Qsci,
QtCore,
QtGui,
QtNetwork,
QtPrintSupport,
QtSql,
QtSvg,
QtTest,
QtWidgets,
QtXml,
)
from PyQt6.Qsci import * # noqa: F403
from PyQt6.QtCore import * # noqa: F403
from PyQt6.QtGui import * # noqa: F403
from PyQt6.QtNetwork import * # noqa: F403
from PyQt6.QtPrintSupport import * # noqa: F403
from PyQt6.QtSql import * # noqa: F403
from PyQt6.QtTest import * # noqa: F403
from PyQt6.QtWidgets import * # noqa: F403
from PyQt6.QtXml import * # noqa: F403
from tokenize_rt import Offset, Token, reversed_enumerate, src_to_tokens, tokens_to_src

try:
Expand All @@ -79,33 +61,57 @@
import qgis.core as qgis_core # noqa: F403
import qgis.gui as qgis_gui # noqa: F403

from PyQt6 import (
Qsci,
QtCore,
QtGui,
QtNetwork,
QtPrintSupport,
QtSql,
QtSvg,
QtTest,
QtWidgets,
QtXml,
)
from PyQt6.Qsci import * # noqa: F403
from PyQt6.QtCore import * # noqa: F403
from PyQt6.QtGui import * # noqa: F403
from PyQt6.QtNetwork import * # noqa: F403
from PyQt6.QtPrintSupport import * # noqa: F403
from PyQt6.QtSql import * # noqa: F403
from PyQt6.QtTest import * # noqa: F403
from PyQt6.QtWidgets import * # noqa: F403
from PyQt6.QtXml import * # noqa: F403
from qgis._3d import * # noqa: F403
from qgis.analysis import * # noqa: F403
from qgis.core import * # noqa: F403
from qgis.gui import * # noqa: F403

except ImportError:
qgis_core = None
qgis_gui = None
qgis_analysis = None
qgis_3d = None
print(
"QGIS classes not available for introspection, only a partial upgrade will be performed"
)
pass

target_modules = [
QtCore,
QtGui,
QtWidgets,
QtTest,
QtSql,
QtSvg,
QtXml,
QtNetwork,
QtPrintSupport,
Qsci,
]
if qgis_core is not None:
target_modules.extend([qgis_core, qgis_gui, qgis_analysis, qgis_3d])
try:
target_modules = [
QtCore,
QtGui,
QtWidgets,
QtTest,
QtSql,
QtSvg,
QtXml,
QtNetwork,
QtPrintSupport,
Qsci,
qgis_core,
qgis_gui,
qgis_analysis,
qgis_3d,
]
except NameError:
target_modules = None

# TODO REMOVE!!!
target_modules = None

# qmetatype which have been renamed
qmetatype_mapping = {
Expand Down Expand Up @@ -177,12 +183,14 @@
"QRegExp": "QRegExp is removed in Qt6, please use QRegularExpression for Qt5/Qt6 compatibility"
}

# { (class, enum_value) : enum_name }

qt_enums = {}
ambiguous_enums = defaultdict(set)


def fix_file(filename: str, qgis3_compat: bool) -> int:
def fix_file(
filename: str, in_place, qgis3_compat: bool, qt_enums: dict, ambiguous_enums: dict
) -> int:

with open(filename, encoding="UTF-8") as f:
contents = f.read()
Expand Down Expand Up @@ -690,13 +698,22 @@ def visit_import(_node: ast.ImportFrom, _parent):
tokens[i + 2] = tokens[i + 2]._replace(src=f"{enum_name[0]}.{enum_name[1]}")

new_contents = tokens_to_src(tokens)
with open(filename, "w") as f:
f.write(new_contents)

if in_place:
with open(filename, "w") as f:
f.write(new_contents)
else:
diff = difflib.unified_diff(
contents.split("\n"),
new_contents.split("\n"),
fromfile=filename,
tofile="fixed",
)
for line in diff:
print(line)
return new_contents != contents


def get_class_enums(item):
def get_class_enums(item, qt_enums: dict, ambiguous_enums: dict):
if not inspect.isclass(item):
return

Expand Down Expand Up @@ -754,31 +771,72 @@ def all_subclasses(cls):
)

elif inspect.isclass(value):
get_class_enums(value)
enums = get_class_enums(value, qt_enums, ambiguous_enums)


def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("directory")
parser.add_argument("directory", nargs="?", default=os.getcwd())
parser.add_argument(
"-i", "--in-place", action="store_true", help="Apply changes in place"
)
parser.add_argument(
"--update-enum-file",
action="store_true",
help="Update enum file for usage without QGIS available",
)
parser.add_argument(
"--qgis3-incompatible-changes",
action="store_true",
help="Apply modifications that would break behavior on QGIS 3, hence code may not work on QGIS 3",
)
args = parser.parse_args(argv)

# get all scope for all qt enum
for module in target_modules:
for key, value in module.__dict__.items():
get_class_enums(value)
if args.update_enum_file or target_modules is not None:
for module in target_modules:
for value in module.__dict__.values():
get_class_enums(value, qt_enums, ambiguous_enums)

if args.update_enum_file:
with open("enums.json", "w") as enum_file:
my_json_object = json.dump(
{
"qt_enums": {":".join(k): v for k, v in qt_enums.items()},
"ambiguous_enums": {
":".join(k): list(v) for k, v in ambiguous_enums.items()
},
},
enum_file,
)
return

if target_modules is None:
print(
"PyQt or QGIS classes not available for introspection, loading enums from cached file."
)
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
with open(Path(path) / "enums.json") as file:
data = json.load(file)

for key in data["qt_enums"].keys():
qt_enums[tuple(key.split(":"))] = data["qt_enums"][key]
for key in data["ambiguous_enums"].keys():
ambiguous_enums[tuple(key.split(":"))] = set(data["ambiguous_enums"][key])

ret = 0
for filename in glob.glob(os.path.join(args.directory, "**/*.py"), recursive=True):
# print(f'Processing {filename}')
print(f"Processing {filename}")
if "auto_additions" in filename:
continue

ret |= fix_file(filename, not args.qgis3_incompatible_changes)
ret |= fix_file(
filename,
args.in_place,
not args.qgis3_incompatible_changes,
qt_enums,
ambiguous_enums,
)
return ret


Expand Down
Loading