Skip to content

Feat/pyqt wrapper bulk import rewrite#7128

Closed
toofar wants to merge 31 commits intomasterfrom
feat/pyqt_wrapper_bulk_import_rewrite
Closed

Feat/pyqt wrapper bulk import rewrite#7128
toofar wants to merge 31 commits intomasterfrom
feat/pyqt_wrapper_bulk_import_rewrite

Conversation

@toofar
Copy link
Copy Markdown
Member

@toofar toofar commented Apr 17, 2022

Here's another attempt at adding a PyQt wrapper. This one attempts to re-write all the imports so that the PyQt module are loaded as objects from the qutebrowser.qt namespace.

There is a fair bit more work left to tie off loose ends but it should be far enough along to see what the approach and impact looks like.

For background (which we should flesh out the related issue with details of), we have imports like from PyQt5.QtGui import QKeyEvent, QIcon, QPixmap all over the codebase. Putting try/catch conditions to handle PyQt5 or PyQt6 everywhere that happens would be a bit tedious to maintain. Options for making that work with both PyQt 5 and 6 are changing that to:

  1. from qutebrowser.qt.QtGui import QKeyEvent, QIcon, QPixmap
    • basically just s/PyQt5/qutebrowser.qt/ and you are done.
    • options for making that work with python imports are a) have qutebrowser/qt/gui.py etc that do from PyQt5.QtGui import * b) have a qutebrowser/qt/__init__.py that has a sneaky dynamic loader thing
  2. from qutebrowser.qt import QtGui
    • then have all the places in the file that refer to QKeyEvent etc refer to QtGui.QKeyEvent

(1) seems difficult to get working with static analysers. mypy straight up doesn't support dynamic imports. We could maybe install the pyqt-stubs at qutebrowser.qt-stubs but it would be nice to work through some other options before attempting that.
(2) should hopefully work better with static analysers but it requires re-writing a lot of references in the code (and test mocks).

We've had some quick attempts at 1.a (https://github.com/qutebrowser/qutebrowser/compare/feat/pyqt_wrapper_lazy_attempt) and 1.b (bba952c and a couple of other commits on https://github.com/qutebrowser/qutebrowser/compare/qt6-v2).

This is an attempt at (2) to see how feasible that is.

Automatically re-writing references

For this I am attempting to use a script based on https://github.com/Instagram/LibCST. I'm using the rename codemod with some additions in this repo. The additions are a couple of bug fixes and wildcard support. The wildcard support is so we can change from PyQt5.QtCore import a, b; a + b into from qutebrowser.qt import QtCore; QtCore.a + QtCore.b without having to run the script for every attribute of every PyQt module (just once for each module). It's also a bit shotgun-surgery-ish, it seems to work well enough for us though.
It takes about 15m to run on my machine, and less time for subsequent no-op runs. It's CPU bound so having more CPUs help.

Here is a truncated example of what it does

diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py
index 8adb7ea20201..d8f1e645fcf4 100644
--- a/qutebrowser/browser/qtnetworkdownloads.py
+++ b/qutebrowser/browser/qtnetworkdownloads.py
@@ -25,10 +25,7 @@ import shutil
 import functools
 import dataclasses
 from typing import Dict, IO, Optional
-
-from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, QUrl
-from PyQt5.QtWidgets import QApplication
-from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
+from qutebrowser.qt import QtWidgets
 
 from qutebrowser.config import config, websettings
 from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg
@@ -36,13 +33,14 @@ from qutebrowser.misc import quitter
 from qutebrowser.browser import downloads
 from qutebrowser.browser.webkit import http
 from qutebrowser.browser.webkit.network import networkmanager
+from qutebrowser.qt import QtNetwork, QtCore
 
 
 @dataclasses.dataclass
 class _RetryInfo:
 
-    request: QNetworkRequest
-    manager: QNetworkAccessManager
+    request: QtNetwork.QNetworkRequest
+    manager: QtNetwork.QNetworkAccessManager
 
 
 class DownloadItem(downloads.AbstractDownloadItem):
@@ -83,7 +81,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
     """
 
     _MAX_REDIRECTS = 10
-    adopt_download = pyqtSignal(object)  # DownloadItem
+    adopt_download = QtCore.pyqtSignal(object)  # DownloadItem
 
     def __init__(self, reply, manager):
         """Constructor.
@@ -162,8 +160,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
         # We could have got signals before we connected slots to them.
         # Here no signals are connected to the DownloadItem yet, so we use a
         # singleShot QTimer to emit them after they are connected.
-        if reply.error() != QNetworkReply.NoError:
-            QTimer.singleShot(0, lambda: self._die(reply.errorString()))
+        if reply.error() != QtNetwork.QNetworkReply.NoError:
+            QtCore.QTimer.singleShot(0, lambda: self._die(reply.errorString()))
 
     def _do_cancel(self):
         self._read_timer.stop()
@@ -176,7 +174,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
             self.fileobj.close()
         self.cancelled.emit()

-    @pyqtSlot()
+    @QtCore.pyqtSlot()
     def retry(self):
         """Retry a failed download."""
         assert self.done

Hopefully this will save us a lot of regexes. Although you may have spotted it has a "duplicate" import line now. See the issues section.

Running the rename tool

The basic example is like so:

python3 -m libcst.tool codemod rename_pyqt.RenameCommand qutebrowser/browser/browsertab.py --old_name=PyQt5.QtGui.* --new_name=qutebrowser.qt:QtGui.* --no-format

If you don't put --no-format on there it complains about not having an autoformatter configured in the config file. And for good reason, if you look in the next section it doesn't wrap lines it makes longer or handle grouping imports.

Hardcoding the modules in the script so we don't have to run it for each module may be a future optimization, it would make it faster overall. For now you can run it like this:

date ; for mod in QtCore QtDBus QtGui QtNetwork QtPrintSupport QtQml QtSql QtWebEngine QtWebEngineCore QtWebEngineWidgets QtWebKit QtWebKitWidgets QtWidgets QtTest; do
  echo "renaming $mod"
  python3 -m libcst.tool codemod rename_pyqt.RenameCommand qutebrowser/ tests --old_name=PyQt5.$mod.* --new_name=qutebrowser.qt:$mod.* --no-format
done ; date

You can probably include all the PyQt modules from the pyi files without issues but I used semgrep to get them in the multiple step process below:

semgrep --lang=py -e 'from PyQt5.$SUBMODULE import ($IMPORTEES)' -o findings.json --json qutebrowser tests/

And then

with open("findings.json") as f: data=json.load(f)

results = data['results']
submodules = sorted(set(r['extra']['metavars']['$SUBMODULE']['abstract_content'] for r in results))
for m in submodules: print(m)

Issues with the rename tool and follow ons

There is of course some manual cleanup to do after this. Mostly in the test codebase around mocks and regarding how we do conditional imports. I wanted to look at semgrep for some of the fixes but ended up doing most things manually with a couple of regexs

  • Some imports in TYPE_CHECKING blocks don't get renamed, possibly only ones that are only used in string type hints? ✔️
    • maybe we can re-run after setting TYPE_CHECKING=True everywhere?
    • addressed manually in 144c039 ("Some leftovers and special cases pyqt wrapper re-write"), it was only one file
  • Module names in strings type hints (and importorskip and mocking directives) don't get renamed.
    • stringly type hints (are we doing that "modernize annotations ticket for 3.0)
    • importorskip marks
    • mocking directives in test
    • don't have a plan for that yet, likely regexes will be involved
    • I ended up grepping for 'Qt and 'Q etc. I may have missed some, probably the mypy output will tell us (I see some on browsertab :))
  • pytest.importorskip is variously either not working anymore (imports don't fail) or is still referring to the PyQt module directly ✔️
    • if we stick with this path we should maybe replace it with our own one for Qt stuff, there isn't that many
    • I've added a replacement that checks attributes on the qutebrowser.qt module. We'll see how that goes
  • some mocks in the tests need adjusting ✔️
    • addressed manually by running (unit) tests and fixing things as they came up
  • Imports don't get reliably re-ordered/grouped to pass lint. Including some now "duplicate" import froms
    • maybe if we did isort first!
    • or rebase -i exec darker
  • Re-written lines don't get re-wrapped to pass lint.
    • maybe if we did black first!
    • or rebase -i exec darker
  • Haven't tested PyQt6 support yet
    • Haven't handled the webengine stuff that moved between modules in 6.2, might
      have to pull some of that up into the qt.py file or so, this wrapper module current only handles top level modules
  • Imports not at the top of the file often get moved up there. If they were done in a try/except they get replaced with pass (why!). ✔️
    • Looks like few enough that they can be manually addressed (search for eg ^+ * pass$), 144c039
    • If any are left it may make re-running this migration on PRs complicated. Possibly we can add an ignore directive for this tool. Whether that is an issue or not depends on how we handle the manual fixups. How I have them handled now re-running the rename tool seems to be a no-op.
  • Unused imports seem to get replaced with just qutebrowser.qt ✔️
    • Looks like few enough that they can be manually addressed (search for eg import qutebrowser.qt$ and import qutebrowser.qt 7e07ce8
    • If more renaming we may want to try to make the rename tool recognize ignore directives
    • Telling the rename tool to ignore these would be difficult because when it is renaming imports it sometimes instead adds new ones and relies on some "remove unused imports" to get rid of the old ones. So we don't want to break that.
  • Backend conditional imports
    • Stuff like webkit, QSsl and old stuff
    • A lot of this did conditional logic on ImportErrors. Having everything imported into one module that everything else imports means we can't throw errors for stuff that is optional to leaf files. How I've got it at the moment means we have to adapt all the calling code to handle the things they import being None instead
    • in some cases where ImportErrors where supposed to be thrown a couple of places down the stack (eg import webenginetab and expect it to throw an ImportError when it can't import the Qt stuff) I've added a guard import with QtWebengine or QtWebKit before it
    • there is some weirdness with the top level PyQt modules being importable when you have PyQt installed but not the relevant Qt modules. The PyQt modules just don't have many of the usual attributes (eg, no C++ classes). We'll see how this goes I guess. Might have to work out some new heuristics.
  • Takes about 15m to run on my machine, would be faster to do in one run with hardcoded modules-to-replace instead of once for each module.
    • The rename tool probably needs a bit of a refactor before this becomes straightforward though, it's already a bit of a mess of bandaids.
  • userscripts/ and scripts/ need to be handled specially. They can't depend on being able to import qutebrowser. Possibly the scripts should be upgraded to PyQt6 and left at that. Userscripts could maybe use a copy of the wrapper that is shipped with them.
  • careful handling of of import order at early init
    • Probably I have mess something up there getting tests working today. It was complaining about registering qute: late a while ago, then it went away
    • Still should check if it makes startups slower in some cases

CI

  • mypy, the main reason for this endeavour
    • couple of small things to clean up, I think the small number of errors shows that the approach is working
  • pylint and flake
    • flake8 has nothing major, pylint is complaining about too long lines and ungrouped imports.
    • I don't intend on address the long lines and grouped imports untill we have decided on a way forward and an order for landing things
  • unit tests ✔️
    • they were working locally, I may have messed something up when cleaning stuff up just now
    • possibly the setup in the CI docker is different than what I managed locally and we have to do the conditional backed import stuff differently
  • end2end tests
    • I haven't looked at these yet, but I'm seeing the same errors locally at least
    • not getting scroll position changed log messages is weird

Re-running this on PRs

It would be nice to provide instructions to PR authors regarding how to adapt their PRs to the disruptive re-writes.

  • hopefully this rename tool should be good for it and hopefully most of the special cases won't come up in most PRs
  • making sure it is idempotent will be important
  • advice on whether to, and how to, rebase or merge and what order to do stuff would be good too

Closes: #995

toofar added 2 commits April 16, 2022 13:53
Copies the upstream rename codemod because it has shown pretty good
results but I want to change it to support wildcards. For example:

    python -m libcst.tool codemod rename.RenameCommand qutebrowser/browser/browsertab.py --old_name=qutebrowser.qt.QtCore.pyqtSignal --new_name=qutebrowser.qt:QtCore.pyqtSignal --no-format

With the vanilla codemod correctly handles the pyqtSignal import and all
the references but I don't want to have to run it for every Qt type. I
would like to be able to specify names like:

    --old_name=qutebrowser.qt.QtCore.* --new_name=qutebrowser.qt:QtCore.*
It works! With lots of printf debugging!

Now that I've gone through and made it work I need to go back and try to
understand it and think about all the cases I haven't tested and might
have broken.

This is my minimal example:

before:
    from qutebrowser.qt.QtCore import (QUrl,
                                       QPoint)
    from qutebrowser.qt.QtGui import QIcon
    foo = QUrl("about:blank")
    bar = QPoint()
    qux = QIcon()

after:
    from qutebrowser.qt import QtCore, QtGui
    foo = QtCore.QUrl("about:blank")
    bar = QtCore.QPoint()
    qux = QtGui.QIcon()

I've already ran into one case that I didn't cover with that:

    from qutebrowser.qt.QtCore import Qt

    foo = Qt.Key_Enter
@toofar toofar force-pushed the feat/pyqt_wrapper_bulk_import_rewrite branch 2 times, most recently from 319c7da to 3440fad Compare April 17, 2022 08:15
@The-Compiler The-Compiler mentioned this pull request Apr 21, 2022
toofar added 7 commits April 30, 2022 13:34
Fix this Instagram/LibCST#675 (comment)
And supports wildcard renaming. So that we don't have to run it for
every attribute of every PyQt module we import. Just for every PyQt
module we import. (We can change it to hardcode them since this copy is
just in this repo!)
When the target rename was something like "a.b.c" and the file happened
to do a simple "import a" it would fail with `CSTValidationError:
Cannot have empty name identifier.` when trying to construct a new
import alias. Because the result of gen_replacement_module() in this
case was an empty string. Which is apparently used for "no replacement
required", which is accurate in this case.

This is a bandaid, the problem is more likely to be that the
`old_name.startswith(import_alias_full_name+'.')` conditional is too
simplistic.
(From my LibCST fork e6b990836204b)

The rename codemod currently has a few variables cached on `self` which
are used to transfer information between different visitor methods.
For example one of the nodes being visited inside an import from
statement might cause the import to be skipped in `leave_Import` using
`self.bypass_import`.
Or in the test case below relevant seen imports are cached in
`self.scheduled_removals` so they can be given special attention in
`leave_Module`.

The lifecycles don't work out though as the transforms that codemods run
ass are only initialised once for a whole run. So stuff can leak between
modules. There is probably a better place for them, but this works for
now. I'm not sure if this is reproducable outside of this wildcard
rename support.

Here is an example:

1. Have a folder with two test files

    ==> context_test/1.py <==
    from a.b import c

    c.x.foo

    ==> context_test/2.py <==
    from a.b.g import d

    d.bar

2. Rename something from the first file

    python3 -m libcst.tool codemod rename.RenameCommand context_test/ -j 1 --old_name=a.b.c.* --new_name=a.b:c.* --no-format ; head -n-0 context_test/*

3. Observe a removed import from the first file leaks into the second

    ==> context_test/1.py <==
    from a.b import c

    c.x.foo

    ==> context_test/2.py <==
    from a.b.g import d
    from a.b import c

    d.bar
Still some manual fixes to make, see #995 and linked PRs.

This was done like:

    date ; for mod in QtCore QtDBus QtGui QtNetwork QtPrintSupport QtQml QtSql QtWebEngine QtWebEngineCore QtWebEngineWidgets QtWebKit QtWebKitWidgets QtWidgets; do
      echo "renaming $mod"
      python3 -m libcst.tool codemod rename_pyqt.RenameCommand qutebrowser/ tests --old_name=PyQt5.$mod.* --new_name=qutebrowser.qt:$mod.* --no-format
    done ; dat

Where the list of modules was obtained via:

    semgrep --lang=py -e 'from PyQt5.$SUBMODULE import ($IMPORTEES)' -o findings.json --json qutebrowser scripts/ tests/

and then

    with open("findings.json") as f: data=json.load(f)

    results = data['results']
    submodules = sorted(set(r['extra']['metavars']['$SUBMODULE']['abstract_content'] for r in results))
    for m in submodules: print(m)
Import all the PyQt modules that we use into qt.py so that we only have to
have a PyQt5/6 conditional in one place.

I'm using importlib instead of try/excepts to reduce the amount of duplication
and because you can't do `from pyqt import QtCore` when pyqt is a local
variable.

TODO:
* maybe some more conditional imports (for earlyinit stuff like no QSsl
  support)
* adapt places looking for import errors to just importing None now
* decide whether being in qt.py is fine or we should move to `qt/__init__.py`
Fixups for commit PyQt import rewrite commit "Re-write PyQt5 imports to be via our wrapper."

Import not at the top of the file where mostly moved up there. Where
they were inside a try/except block they were replaced with `pass`
(why!). With the current setup of having everything imported into qt.py
we don't get import errors anymore anyway. So affected places have been
changed to check for None.

This has not currently been well tested. We might have to move some of
the conditional checks into qt.py.

I identified these pieces of code by looking for `^+ * pass$` in the
diff.
Some test imports that didn't have the things there were importing used
got dropped by the rename tool. This restores them.

Found by searching in the diff for eg `import qutebrowser.qt$` and
`import qutebrowser.qt `

Relates to #995
@toofar toofar force-pushed the feat/pyqt_wrapper_bulk_import_rewrite branch from fa71f4e to 2d92da4 Compare April 30, 2022 05:49
toofar added 5 commits April 30, 2022 18:23
I super don't like the extra duplication but we are all servants of our
linters.
Making mypy happy is the main reason I'm going through this renaming
stuff effort so here it is.

Remaining failures are because of string types that didn't get renamed
and imports in TYPE_CHECKING conditionals.

Options for for fixing those are:
* running the rename tool again with TYPE_CHECKING=True everywhere
* scraping mypys errors for things to update and;
* regular expressions
Now that we are importing all of the PyQt modules from the same places
raising an ImportError when an import fails would be inconvenient for
people trying to run the browser with only one backend available.
Instead they are set to `None`. This adjusts the PyQt related
importorskip directives to use our wrapper that'll also skip if the
target is `None`.

I don't expect pylint to be happy about the import orders.
I haven't tested without webengine available (and with webkit).
I forgot to include QtTest in the renamer it seems.
Import in TYPE_CHECKING conditions are not consistently handled, possibly when
they are only used in string types.
Not sure what happened with the duplicates in the logging modules.

Found by grepping PyQt5
These require a bit of special casing due to how they handle imports.
    git grep -l "['\"]PyQt5" tests/ | xargs sed -i -e 's/"PyQt5/"qutebrowser.qt/' -e "s/'PyQt5/'qutebrowser.qt/"
@toofar toofar force-pushed the feat/pyqt_wrapper_bulk_import_rewrite branch from 2d92da4 to 0edd1dd Compare April 30, 2022 06:26
toofar added 11 commits April 30, 2022 19:30
Mostly replaces "68007002fc64 Add pytest.importorskip wrapper", but
after import strings got renamed in "1faab7e5609e Update string PyQt5
references in tests".

pytest.importskip uses `__import__('qutebrowser.qt.QtCore')` etc. Which
doesn't work without QtCore being it's own file in the qutebrowser.qt
package. That's not what we are going for here (if it was we wouldn't
have to re-write all the references!).

So I've replaced the previous attempt with something that'll just look
for attributes on the qutebrowser.qt module.

Additionally there were some imports that were just done to get the
module inside a function, and guard. I've changed them to two lines, our
custom one with a relevent Qt module as a guard and then
pytest.importorskip to get the actual module they want.
Now that we are importing the PyQt modules and then accessing stuff as
attributes on them the mocks in the tests should be done the same way.
This was done manually, I didn't even try to automate it.
Partially corrects "Manual fixups for ImportErrors lost in rewrite"

Instead of looking for import errors looks for attributes being None.
have conditional defaults.

In qlocalsocket_mock I had to move the original attribute gathering to even
earlier otherwise we were just picking up mocks.
I had this in earlier but then didn't seem to need it. Now CI is
failing.

I though I had it with a WebKit install that had PyQtWebEngine installed
as well (but not the C++ QtWebEngine). It could install PyQtWebEngine
but didn't have anything in it.
So if this works it might be a sign that more stuff will trip over it to
come. For example `from PyQt5 import QtWebEngine` might work but `from
PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION_STR` wouldn't. So we
might be saddling ourselves with a false sense of security just
switching on the top level stuff in the PyQt wrapper class.
I did a mass rename of PyQt5 -> qutebrowser.qt in 80033f4 but in
this instance the underlying canonical QPoint name is logged. So change
these back but wildcard the module name so hopefully it'll work with
PyQt5 and 6.
The LibCST rename tool got a bit confused by imports done in `if
TYPE_CHECKING:` blocks. For ones that were used it moved them out of
those blocks. For ones that were only used in string type hints it half
changed the imports and didn't updated the strings.

A couple of instances were fixed in previous commits. These are the last
few that mypy could see.
Addressed manually. These fixers based on LibCST
https://github.com/python-qt-tools/PyQt6-stubs/tree/main/fixes look like
they could be useful. But they don't have any documentations of tests or
anything. So it's much faster to fix these manually then to try to
understand what all that is doing, if any of it is useful for us,
extract it, test it etc.
a few directives that got lost when adapting stuff for a pyqt wrapper
adjust indent of trailing lines because the leading ones got longer when
re-writing imports in 46d426a
"I open" in the BDD tests can match against a message like this:

    01:24:53.227 DEBUG    downloads  qtnetworkdownloads:fetch:563 fetch: PyQt5.QtCore.QUrl('http://localhost:58917/500-inline') -> attachment.jpg

The QUrl class name there still references the specific PyQt module, so
make the match expression less specific.

Similar to 3676386 for the scroll position e2e tests.

Caused by the regex rename in 80033f4
Looks like there is still a few places like:

    thing = {
      really.hecking.long.name = foo,
    }

where black is unable to reformat them to be under 88 chars. Possibly
adding backslashes would help, I might manually edit them to have part
of the right hand side referred to via a shorter local variable.

ran like:

    $ cat pyproject.toml
    [tool.black]
    skip-string-normalization = true
    target-version = 'py37'

    darker diff -W 8 -r 488dc17 qutebrowser tests/
toofar added 6 commits April 30, 2022 19:30
Running darker over the pyqt reference renames (see previous commit)
worked pretty well. Unfortnately we ran into
psf/black#620 a lot with dicts with long
attributes. Things that used to be manually wrapped like:

    some_map = {
      quote_long_attribute_name:
        also_long_value,
    }

get turned into:

    some_map = {
      quote_long_attribute_name: also_long_value,
    }

which is quite often over 88 chars with Qt enum values in the map.
I've manually added aliases and such for the Qt enums and classes to
shorted the lines.

This is likely to conflict entirely with the enum scoped changes on the
qt6 branches.
Unfortunately isort seems to run on all imports in modified files, instead of
just modified ones. Possibly there is some way to tell it to only touch
`qutebrowser.qt` imports but I couldn't find it (or to filter the diff
output). So there is a bit more noise in this one than the black one.

It might be helpful to try to exclude it from running on some files (like
`qutebrowser/qt.py`) using noqa directives or something.

I had to tell it our source directories explicitly else it moved `from
helpers` up in the test files.

It did touch imports in `if TYPE_CHECKING:` blocks, it didn't touch stuff in
`if PyQt6:` blocks. I guess I had mypy installed, and not PyQt6.

There are a couple of options that can reduce the diff. They cause
pylint to be unhappy though:

length_sort_straight = true
Causes string imports to be sorted by length (eg os, sys, importlib)
instead of alphabetically. This fits with the existing style better but
it causes, for example, `os` and `os.path` to be sorted differently
which makes pylint complain.

length_sort = true
Similar to the above option but for all imports and import aliases.
Import aliases in particular are not currently sorted so it doesn't end
up reducing the diff substantially.

only_sections = true
Causes isort to not re-order imports, only move them between sections.
Which is great for reducing the diff but it doesn't put the moved
imports into a well sorted place, it just puts them at the top/bottom of
the section they are moved to. So, for the tests in particular, it was
ending up with `qutebrowser.qt`, `helpers`, `qutebrowser.browser...`
which also made pylint complain about ungrouped imports.

It would be great if I could do `only_sections` and then tell it to only
sort the first party section. But sorting only specific sections doesn't
seem to be an option!

ran like:

    $ cat pyproject.toml
    [tool.black]
    skip-string-normalization = true
    target-version = 'py37'

    [tool.isort]
    #length_sort_straight = true
    group_by_package = true
    #only_sections = true
    profile = 'black'
    honor_noqa = true
    src_paths = ["qutebrowser", "tests"]

    $ darker -i -W 8 -r 488dc17 qutebrowser tests/
pylint told me to fix them.

Presumably the utils.py one confused isort with the try/catch above it
these will probably conflict with the recent PyQt stubs changes
The type hint one could probably resolved with annotations from the
future. The inspector/miscwidgets one will probably need refactoring.
@toofar toofar force-pushed the feat/pyqt_wrapper_bulk_import_rewrite branch from 0edd1dd to 3a305cd Compare April 30, 2022 07:32
@The-Compiler The-Compiler mentioned this pull request May 26, 2022
18 tasks
toofar added a commit that referenced this pull request Jun 26, 2022
See #7128 for discussion. Mypy doesn't see to follow wildcard imports
like those used in /qt/gui.py etc. Importing the Qt modules as
attributes seems to work better for that. It does mean we can't import
stuff from Qt modules though. We have to import the module and then
reference the things in it as attributes.

The _Machinery class and renaming at the bottom of the file are quick
hacks to make it compatible with the qts-like approach. Mostly this
qt.py file is copied from a PyQt5-only version and it could use a little
refactoring in it's current state.
toofar added a commit that referenced this pull request Jun 26, 2022
See #7128 for discussion. Mypy doesn't see to follow wildcard imports
like those used in /qt/gui.py etc. Importing the Qt modules as
attributes seems to work better for that. It does mean we can't import
stuff from Qt modules though. We have to import the module and then
reference the things in it as attributes.

The _Machinery class and renaming at the bottom of the file are quick
hacks to make it compatible with the qts-like approach. Mostly this
qt.py file is copied from a PyQt5-only version and it could use a little
refactoring in it's current state.
toofar added a commit that referenced this pull request Jul 3, 2022
See #7128 for discussion. Mypy doesn't see to follow wildcard imports
like those used in /qt/gui.py etc. Importing the Qt modules as
attributes seems to work better for that. It does mean we can't import
stuff from Qt modules though. We have to import the module and then
reference the things in it as attributes.

The _Machinery class and renaming at the bottom of the file are quick
hacks to make it compatible with the qts-like approach. Mostly this
qt.py file is copied from a PyQt5-only version and it could use a little
refactoring in it's current state.
toofar added a commit that referenced this pull request Jul 3, 2022
See #7128 for discussion. Mypy doesn't see to follow wildcard imports
like those used in /qt/gui.py etc. Importing the Qt modules as
attributes seems to work better for that. It does mean we can't import
stuff from Qt modules though. We have to import the module and then
reference the things in it as attributes.

The _Machinery class and renaming at the bottom of the file are quick
hacks to make it compatible with the qts-like approach. Mostly this
qt.py file is copied from a PyQt5-only version and it could use a little
refactoring in it's current state.
@toofar
Copy link
Copy Markdown
Member Author

toofar commented Oct 18, 2022

Closing this as it turns out the issues with wildcard imports were much less widespread than they originally seemed.

@toofar toofar closed this Oct 18, 2022
@toofar toofar mentioned this pull request Jun 25, 2023
@toofar toofar deleted the feat/pyqt_wrapper_bulk_import_rewrite branch October 27, 2023 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Add wrapper modules for PyQt

2 participants