-
-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Rewrite enum access for scoped PyQt enums #5904
Comments
I ended up automating this which was easier than expected! First, a script which parses PyQt's import sys
import pathlib
from typed_ast import ast3
def add_parents(tree):
for node in ast3.walk(tree):
for child in ast.iter_child_nodes(node):
child.parent = node
def find_enums(tree):
for node in ast3.walk(tree):
if not isinstance(node, ast3.Assign):
continue
if node.type_comment is None:
continue
if '.' not in node.type_comment:
continue
if not node.type_comment.startswith("'"):
continue
comment = node.type_comment.strip("'")
mod, cls = comment.rsplit(".", maxsplit=1)
assert len(node.targets) == 1
name = node.targets[0].id
yield (mod, cls, name)
def main():
for filename in sys.argv[1:]:
tree = ast3.parse(pathlib.Path(filename).read_text())
for mod, cls, name in find_enums(tree):
old = f"{mod}.{name}"
new = f"{mod}.{cls}.{name}"
print(f"{old} {new}")
if __name__ == '__main__':
main() (In theory, this should work with the builtin With the current PyQt and PyQtWebEngine modules, I get: Second, a script which takes import pathlib
import sys
import re
script_path = pathlib.Path(__file__).parent
replacements = []
with (script_path / 'enums.txt').open() as f:
for line in f:
orig, replacement = line.split()
orig_re = re.compile(re.escape(orig) + r'(?=\W)')
replacements.append((orig_re, replacement))
for filename in sys.argv[1:]:
path = pathlib.Path(filename)
if path.suffix != '.py':
continue
content = path.read_text()
print(filename)
for orig_re, replacement in replacements:
content = orig_re.sub(replacement, content)
path.write_text(content) Which can get called via For testing, it should be possible to edit a custom PyQt build to remove the enum aliases - from a mailinglist post:
This leads to Not applying this at the moment because it can still wait a bit now that applying the actual change will be straightforward - I'd still like to wait for other (Py)Qt 6 changes before coming back to this again. Other issues to solve:
|
The mypy stub changes are probably a bigger undertaking, so this won't be in v2.0.0. |
As the PyQt5 changelog says: v5.11 23rd June 2018 [...] - Enum members are also visible as attributes of the enum in order to emulate the behaviour of Python enums. The traditional use, where a member is visible at the same scope as the enum, is deprecated but will be supported throughout the life of PyQt5. See also: https://www.riverbankcomputing.com/static/Docs/PyQt5/gotchas.html#enums Done using Florian Bruhin's excellent script: qutebrowser/qutebrowser#5904 (comment)
Enums whose namespace moved with PyQt6 were determined using the tooling in qutebrowser/qutebrowser#5904 Relevant enums for the Anki add-on ecosystem were found by grepping through all AnkiWeb add-ons and a selection of GitHub-released add-ons.
* Alias PyQt5 to PyQt6 on PyQt6 builds Restores basic compatibility with PyQt5 add-ons * Register QtCore early to work around sip error * Monkey-patch unscoped enums that are in use by add-ons back in Enums whose namespace moved with PyQt6 were determined using the tooling in qutebrowser/qutebrowser#5904 Relevant enums for the Anki add-on ecosystem were found by grepping through all AnkiWeb add-ons and a selection of GitHub-released add-ons. * Add full Qt.Key namespace Maintains compatibility with add-ons that allow specifying key bindings via Qt.Key enums * Reintroduce PyQt6.Qt as an alias for QtCore.Qt * Alias classes shifted from QtWidgets to QtGui * Add missing enums Adds ≈200 enums that were missed during the initial grep * Map exec_ calls to exec * Tweak section headers * Fix QtWebEngineWidgets imports failing due to delayed import Addesses: "QtWebEngineWidgets must be imported before a QCoreApplication instance is created" * Register additional aliases for top-level Qt modules Given how we have had to deal with side-effects when not registering other aliased imports ahead of time, it seems safer to also register the remaining few with sys.modules. * Handle calls to deprecated PyQt resource API graciously * Create QtWebEngineWidgets aliases for classes moved to QtWebEngineCore * Alias QShortcut * Restore QWebEnginePage.view() * Alias sip to PyQt6.sip * Alias QtCore.QRegExp to QtCore.QRegularExpression * Restructure aqt.qt into package Pre-requirement for aliasing the PyQt5.Qt namespace correctly. Should hopefully also make it easier to keep an overview as Qt-compat-related modules were proliferating. * Properly alias PyQt5.Qt PyQt5.Qt used to serve as a common namespace for all Qt classes, not just QtCore.Qt.* While this changes does not make all classes accessible via PyQt5.Qt, it does so for the most important Qt submodules, which should cover most add-on breakages. * Simplify Qt resource system legacy handling * Also alias PyQt6.Qt Covers imports of the form `from PyQt5 import import Qt` (due to previous aliasing of PyQt5 to PyQt6) * Add missing enums Better approach to grepping through add-ons yielded additional hits * Run formatters * Satisfy pylint
Mostly done in 0877fb0 (plus various follow-up commits). Would still be nice to have this in |
We have |
Another note since I keep coming back to this thread for other projects: A |
With sip v4.19.9 (around PyQt 5.11), there's a new way to access enums which makes them all scoped. With PyQt 6, this will be the only way Qt enums can be accessed.
Thus, enum access like
QWebEngineSettings.AllowAllUnknownUrlSchemes
should be rewritten to sayQWebEngineSettings.UnknownUrlSchemePolicy.AllowAllUnknownUrlSchemes
instead.A rough way to search for them:
here's a list of all of them (
rg --no-filename 'Q[A-Z][a-z][A-Za-z0-9]+\.[A-Z][a-z][A-Za-z0-9]+' -o | sort | uniq
):(If you want to replicate that locally but don't have ripgrep, you could use something like
grep -R --exclude-dir=.git --exclude-dir=.tox --exclude-dir='.venv*' --exclude-dir='.mypy_cache' --exclude='*.pyc' 'Q[A-Z][a-z][A-Za-z0-9]\+\.[A-Z][a-z][A-Za-z0-9]\+' .
instead)The scope to use for every one of those can be found out like this:
so in theory it should be possible to solve this in a semi-automated way, by starting with the (rip)grep command above, writing a Python script to find the proper types to use (can use
from PyQt5.Qt import *
andfrom PyQt5.QtWebEngineWidgets import *
to get access to most of those classes) and then generating something to feed tosed
(or alternatively, implementing the search/replace in Python).Marking this as "easy" as it's something that can be worked on without knowing much about qutebrowser internals. It will certainly require some manual work after writing such a script (for formatting adjustments and such), but that shouldn't be too much work. If you decide to work on this, please split it up into a couple of smaller PRs rather than one big one.
The text was updated successfully, but these errors were encountered: