Skip to content

Commit 9c3fad6

Browse files
committed
Qt_convert_enum can now find potential enum issues
These have a high chance of false positives so this does not actually make the changes, simply output the places to look for changes.
1 parent e568ce3 commit 9c3fad6

File tree

4 files changed

+97
-8
lines changed

4 files changed

+97
-8
lines changed

CAVEATS.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,4 +430,8 @@ does not make backups of the existing files, so make sure to do that first.
430430

431431
To check for enum use regression you can add `--check`. This will change the return
432432
code to the number of enums that require changing. A return code of zero indicates
433-
that no enum changes are required.
433+
that no enum changes are required. This check can only find and fix code that
434+
is directly using enums on Qt class names. If you used `self.EnumName` or other
435+
methods of accessing the enum objects the `--partial` check may find them. This is
436+
unable to automatically fix your code but shows you possible code that you will
437+
need to manually fix.

Qt_convert_enum.py

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import re
2121
import sys
2222
from pathlib import Path
23+
from typing import Optional
2324

2425

2526
def qt_for_binding(binding):
@@ -38,7 +39,7 @@ def qt_for_binding(binding):
3839

3940

4041
class QtEnumConverter:
41-
DEFAULT_IGNORED = ".tox,.eggs,build"
42+
DEFAULT_IGNORED = ".tox,.eggs,build,dist"
4243

4344
def __init__(self, verbosity=0, ignored: str = DEFAULT_IGNORED):
4445
self.ignored = ignored.split(",")
@@ -52,6 +53,7 @@ def __init__(self, verbosity=0, ignored: str = DEFAULT_IGNORED):
5253
# The mapping of short to fully qualified enum names. Used to
5354
# update python scripts.
5455
self.enum_map: dict[str, str] = {}
56+
self.partial_enum: Optional[re.Pattern] = None
5557
# Module level view of all the enums also used to check for naming
5658
# conflicts where the short name maps to multiple long names.
5759
self.enum_module: dict[str, dict[str, list[str]]] = {}
@@ -148,7 +150,45 @@ def convert_enums_in_file(self, filepath: Path, root: Path, dry_run: bool) -> in
148150
f.write(content)
149151
return changes
150152

151-
def convert_all(self, directory: Path, dry_run: bool) -> int:
153+
def find_partials(self, filepath: Path, root: Path) -> int:
154+
"""Find and report potential partial Enum uses
155+
156+
We can't automatically replace them as this has a high likelihood of
157+
false positives.
158+
"""
159+
if not self.enum_map:
160+
self.enums_for_qt_py()
161+
162+
if not self.partial_enum:
163+
enums = {v.split('.')[-1] for v in self.enum_map.values()}
164+
self.partial_enum = re.compile(fr"\.({'|'.join(enums)})(?!\w)")
165+
166+
# Read the content
167+
with filepath.open('r', encoding='utf-8', newline='\n', errors='replace') as f:
168+
content = f.readlines()
169+
170+
changes = 0
171+
relative = filepath.relative_to(root)
172+
for ln, line in enumerate(content):
173+
clean_line = line.rstrip()
174+
matches = set()
175+
for v in self.enum_map.values():
176+
clean_line = clean_line.replace(v, "")
177+
178+
for match in self.partial_enum.findall(clean_line):
179+
matches.add(match)
180+
if matches:
181+
if self.verbosity >= 0:
182+
print(
183+
f'File: "{relative}", line:{ln + 1}, for {", ".join(matches)}'
184+
)
185+
if self.verbosity >= 1:
186+
print(f' {line.strip()}')
187+
changes += 1
188+
189+
return changes
190+
191+
def convert_all(self, directory: Path, dry_run: bool, partial: bool = False) -> int:
152192
"""Search and replace all enums."""
153193
ignored = [directory / i for i in self.ignored]
154194
changes = 0
@@ -174,7 +214,10 @@ def convert_all(self, directory: Path, dry_run: bool) -> int:
174214
continue
175215
if self.verbosity >= 2:
176216
print(f"Checking: {filepath}")
177-
changes += self.convert_enums_in_file(filepath, directory, dry_run)
217+
if partial:
218+
changes += self.find_partials(filepath, directory)
219+
else:
220+
changes += self.convert_enums_in_file(filepath, directory, dry_run)
178221

179222
return changes
180223

@@ -249,13 +292,27 @@ def parse_args():
249292
"have duplicate names on the same class causing ambiguity with short "
250293
"names. dups requires PySide6 and the others require PySide2 installed.",
251294
)
295+
parser.add_argument(
296+
"--partial",
297+
action="store_true",
298+
help="Report any potential enum uses that can not be automatically fixed. "
299+
"This can happen if the code accesses enums from the instance instead of "
300+
"directly from the class name.",
301+
)
252302
parser.add_argument(
253303
'-v',
254304
'--verbosity',
255305
action='count',
256306
default=0,
257307
help="Increase the verbosity of the output.",
258308
)
309+
parser.add_argument(
310+
'-q',
311+
'--quiet',
312+
action="count",
313+
default=0,
314+
help="Decrease the verbosity of the output.",
315+
)
259316
parser.add_argument(
260317
'--check',
261318
action='store_true',
@@ -282,6 +339,9 @@ def add_binding_info(data, qt):
282339
if __name__ == "__main__":
283340
args = parse_args()
284341

342+
# Merge the quiet and verbosity arguments
343+
args.verbosity -= args.quiet
344+
285345
if args.show == "dups":
286346
# Show duplicate enum short names found in PySide6
287347
checker = DuplicateEnums()
@@ -303,10 +363,14 @@ def add_binding_info(data, qt):
303363
print(json.dumps(mappings, indent=4, sort_keys=True))
304364
else:
305365
# Search .py files and update to fully qualified enum names
306-
changes = mapper.convert_all(args.target, dry_run=not args.write)
366+
changes = mapper.convert_all(
367+
args.target, dry_run=not args.write, partial=args.partial
368+
)
307369

308370
# Report the number of enum changes
309-
if args.write:
371+
if args.partial:
372+
print(f"{changes} partial enums found.")
373+
elif args.write:
310374
print(f"{changes} enums changed.")
311375
else:
312376
print(f"{changes} enums require changes.")

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ universal=1
44
[flake8]
55
select = B, C, E, F, N, W, B9
66
extend-ignore =
7+
B907,
78
E203,
89
E501,
910
E722,

tests.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""Tests that run once"""
33
import io
44
import os
5+
import re
56
import sys
67
import imp
78
import shutil
@@ -1646,6 +1647,8 @@ def test_multiple_preferred():
16461647
if QAbstractItemView.Box:
16471648
print(QTreeWidget.Box)
16481649
print(QFrame.Box)
1650+
# Note: Not using a Qt class so this will only be found by the --partial check
1651+
self.Box
16491652
"""
16501653
)
16511654

@@ -1654,6 +1657,7 @@ def test_multiple_preferred():
16541657
u"""
16551658
# a comment QStyle.CC_ComboBox that has a enum in it
16561659
print(QFrame.Box)
1660+
print(self.Box)
16571661
"""
16581662
)
16591663

@@ -1724,15 +1728,31 @@ def test_convert_enum():
17241728
assert "6 enums changed." in output
17251729

17261730
check = enum_file_1.replace("WindowActive", "WindowState.WindowActive")
1727-
check = check.replace("Box", "Shape.Box")
1731+
name_re = re.compile(r"(?<!self)\.Box")
1732+
check = name_re.sub(".Shape.Box", check)
17281733
with open(init_file) as fle:
17291734
assert fle.read() == check
17301735

17311736
check = enum_file_2.replace("CC_ComboBox", "ComplexControl.CC_ComboBox")
1732-
check = check.replace("QFrame.Box", "QFrame.Shape.Box")
1737+
check = name_re.sub(".Shape.Box", check)
17331738
with open(example_file) as fle:
17341739
assert fle.read() == check
17351740

1741+
# Check using `--partial`
1742+
output = subprocess.check_output(
1743+
cmd + ["--partial", "-v"], cwd=self.tempdir, universal_newlines=True
1744+
)
1745+
check = textwrap.dedent(
1746+
u"""\
1747+
File: "__init__.py", line:7, for Box
1748+
self.Box
1749+
File: "api{slash}example.py", line:4, for Box
1750+
print(self.Box)
1751+
2 partial enums found.
1752+
"""
1753+
).format(slash=os.sep)
1754+
assert output == check
1755+
17361756
def test_convert_enum_map():
17371757
"""Test enum map generation for conversion from short to long enums"""
17381758
code_path = os.path.join(os.path.dirname(__file__), "Qt_convert_enum.py")

0 commit comments

Comments
 (0)