From 69e2bbd7668ff4a3b3e64e3a4a4d584be8ed6f71 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 29 Jun 2023 23:06:54 +0200 Subject: qt6 mypy: Fix PyQt5 QUrl issues with a more clever approach --- qutebrowser/browser/browsertab.py | 3 +- qutebrowser/browser/commands.py | 5 ++-- qutebrowser/browser/hints.py | 13 ++++----- qutebrowser/browser/pdfjs.py | 6 ++-- qutebrowser/browser/shared.py | 6 ++-- qutebrowser/browser/webengine/notification.py | 6 ++-- qutebrowser/browser/webengine/webenginetab.py | 4 +-- qutebrowser/browser/webkit/webview.py | 5 ++-- qutebrowser/keyinput/keyutils.py | 3 +- qutebrowser/utils/jinja.py | 2 +- qutebrowser/utils/urlutils.py | 41 +++++++++++++++++++++++++-- 11 files changed, 63 insertions(+), 31 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 0fbd15c21..ea4cf8004 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -1331,8 +1331,7 @@ class AbstractTab(QWidget): def __repr__(self) -> str: try: qurl = self.url() - as_unicode = QUrl.ComponentFormattingOption.EncodeUnicode - url = qurl.toDisplayString(as_unicode) + url = qurl.toDisplayString(urlutils.FormatOption.ENCODE_UNICODE) except (AttributeError, RuntimeError) as exc: url = '<{}>'.format(exc.__class__.__name__) else: diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 750f28c9c..0f2496e0d 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -718,9 +718,10 @@ class CommandDispatcher: assert what in ['url', 'pretty-url'], what if what == 'pretty-url': - flags = QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.DecodeReserved + flags = urlutils.FormatOption.DECODE_RESERVED else: - flags = QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded + flags = urlutils.FormatOption.ENCODED + flags |= urlutils.FormatOption.REMOVE_PASSWORD url = QUrl(self._current_url()) url_query = QUrlQuery() diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 5d7862e75..6e028baa4 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -38,7 +38,7 @@ from qutebrowser.keyinput import modeman, modeparsers, basekeyparser from qutebrowser.browser import webelem, history from qutebrowser.commands import runners from qutebrowser.api import cmdutils -from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils +from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils, urlutils if TYPE_CHECKING: from qutebrowser.browser import browsertab @@ -252,9 +252,9 @@ class HintActions: sel = (context.target == Target.yank_primary and utils.supports_selection()) - flags = QUrl.ComponentFormattingOption.FullyEncoded | QUrl.UrlFormattingOption.RemovePassword + flags = urlutils.FormatOption.ENCODED | urlutils.FormatOption.REMOVE_PASSWORD if url.scheme() == 'mailto': - flags |= QUrl.UrlFormattingOption.RemoveScheme + flags |= urlutils.FormatOption.REMOVE_SCHEME urlstr = url.toString(flags) new_content = urlstr @@ -276,15 +276,14 @@ class HintActions: def run_cmd(self, url: QUrl, context: HintContext) -> None: """Run the command based on a hint URL.""" - urlstr = url.toString(QUrl.ComponentFormattingOption.FullyEncoded) + urlstr = url.toString(urlutils.FormatOption.ENCODED) args = context.get_args(urlstr) commandrunner = runners.CommandRunner(self._win_id) commandrunner.run_safely(' '.join(args)) def preset_cmd_text(self, url: QUrl, context: HintContext) -> None: """Preset a commandline text based on a hint URL.""" - flags = QUrl.ComponentFormattingOption.FullyEncoded - urlstr = url.toDisplayString(flags) + urlstr = url.toDisplayString(urlutils.FormatOption.ENCODED) args = context.get_args(urlstr) text = ' '.join(args) if text[0] not in modeparsers.STARTCHARS: @@ -325,7 +324,7 @@ class HintActions: cmd = context.args[0] args = context.args[1:] - flags = QUrl.ComponentFormattingOption.FullyEncoded + flags = urlutils.FormatOption.ENCODED env = { 'QUTE_MODE': 'hints', diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py index 53387d634..fe6851a53 100644 --- a/qutebrowser/browser/pdfjs.py +++ b/qutebrowser/browser/pdfjs.py @@ -24,7 +24,7 @@ import os from qutebrowser.qt.core import QUrl, QUrlQuery -from qutebrowser.utils import resources, javascript, jinja, standarddir, log +from qutebrowser.utils import resources, javascript, jinja, standarddir, log, urlutils from qutebrowser.config import config @@ -95,7 +95,7 @@ def _generate_pdfjs_script(filename): url_query.addQueryItem('filename', filename) url.setQuery(url_query) - js_url = javascript.to_js(url.toString(QUrl.ComponentFormattingOption.FullyEncoded)) + js_url = javascript.to_js(url.toString(urlutils.FormatOption.ENCODED)) return jinja.js_environment.from_string(""" document.addEventListener("DOMContentLoaded", function() { @@ -244,7 +244,7 @@ def get_main_url(filename: str, original_url: QUrl) -> QUrl: query = QUrlQuery() query.addQueryItem('filename', filename) # read from our JS query.addQueryItem('file', '') # to avoid pdfjs opening the default PDF - urlstr = original_url.toString(QUrl.ComponentFormattingOption.FullyEncoded) + urlstr = original_url.toString(urlutils.FormatOption.ENCODED) query.addQueryItem('source', urlstr) url.setQuery(query) return url diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 562442b5a..13080ece6 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -31,7 +31,7 @@ from qutebrowser.qt.core import QUrl, pyqtBoundSignal from qutebrowser.config import config from qutebrowser.utils import (usertypes, message, log, objreg, jinja, utils, - qtutils, version) + qtutils, version, urlutils) from qutebrowser.mainwindow import mainwindow from qutebrowser.misc import guiprocess, objects @@ -229,7 +229,7 @@ def handle_certificate_error( # scheme might not match. is_resource = ( first_party_url.isValid() and - not request_url.matches(first_party_url, QUrl.UrlFormattingOption.RemoveScheme)) + not request_url.matches(first_party_url, urlutils.FormatOption.REMOVE_SCHEME)) if conf == 'ask' or conf == 'ask-block-thirdparty' and not is_resource: err_template = jinja.environment.from_string(""" @@ -259,7 +259,7 @@ def handle_certificate_error( error=error, ) urlstr = request_url.toString( - QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded) + urlutils.FormatOption.REMOVE_PASSWORD | urlutils.FormatOption.ENCODED) title = "Certificate error" try: diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 76ba6b886..2912c8b3e 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -66,7 +66,7 @@ if TYPE_CHECKING: from qutebrowser.config import config from qutebrowser.misc import objects from qutebrowser.utils import ( - qtutils, log, utils, debug, message, objreg, resources, + qtutils, log, utils, debug, message, objreg, resources, urlutils ) from qutebrowser.qt import sip @@ -1179,9 +1179,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): if self._capabilities.kde_origin_name or not is_useful_origin: prefix = None elif self._capabilities.body_markup and self._capabilities.body_hyperlinks: - href = html.escape( - origin_url.toString(QUrl.ComponentFormattingOption.FullyEncoded) - ) + href = html.escape(origin_url.toString(urlutils.FormatOption.ENCODED)) text = html.escape(urlstr, quote=False) prefix = f'{text}' elif self._capabilities.body_markup: diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index f1e3c1e8e..6080a974d 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -39,7 +39,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, webengineinspector) from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, - resources, message, jinja, debug, version) + resources, message, jinja, debug, version, urlutils) from qutebrowser.qt import sip, machinery from qutebrowser.misc import objects, miscwidgets @@ -1437,7 +1437,7 @@ class WebEngineTab(browsertab.AbstractTab): title = self.title() title_url = QUrl(url) title_url.setScheme('') - title_url_str = title_url.toDisplayString(QUrl.UrlFormattingOption.RemoveScheme) + title_url_str = title_url.toDisplayString(urlutils.FormatOption.REMOVE_SCHEME) if title == title_url_str.strip('/'): title = "" diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index aaeba4033..16535e47f 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -28,7 +28,7 @@ from qutebrowser.qt.webkitwidgets import QWebView, QWebPage from qutebrowser.config import config, stylesheet from qutebrowser.keyinput import modeman -from qutebrowser.utils import log, usertypes, utils, objreg, debug +from qutebrowser.utils import log, usertypes, utils, objreg, debug, urlutils from qutebrowser.browser.webkit import webpage @@ -85,8 +85,7 @@ class WebView(QWebView): stylesheet.set_register(self) def __repr__(self): - flags = QUrl.ComponentFormattingOption.EncodeUnicode - urlstr = self.url().toDisplayString(flags) # type: ignore[arg-type] + urlstr = self.url().toDisplayString(urlutils.FormatOption.ENCODE_UNICODE) url = utils.elide(urlstr, 100) return utils.get_repr(self, tab_id=self._tab_id, url=url) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 0052cbb76..38eb563e9 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -352,7 +352,8 @@ def _unset_modifier_bits( """ if machinery.IS_QT5: return cast(_ModifierType, modifiers & ~mask) - return Qt.KeyboardModifier(modifiers.value & ~mask.value) + else: + return Qt.KeyboardModifier(modifiers.value & ~mask.value) @dataclasses.dataclass(frozen=True, order=True) diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 3e0fc7448..7b8dfc677 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -114,7 +114,7 @@ class Environment(jinja2.Environment): url = QUrl('qute://resource') url.setPath('/' + path) urlutils.ensure_valid(url) - urlstr = url.toString(QUrl.ComponentFormattingOption.FullyEncoded) + urlstr = url.toString(urlutils.FormatOption.ENCODED) return urlstr def _data_url(self, path: str) -> str: diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index d8a440f35..e9abb104b 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -26,7 +26,7 @@ import ipaddress import posixpath import urllib.parse import mimetypes -from typing import Optional, Tuple, Union, Iterable +from typing import Optional, Tuple, Union, Iterable, cast from qutebrowser.qt import machinery from qutebrowser.qt.core import QUrl @@ -44,6 +44,13 @@ from qutebrowser.browser.network import pac if machinery.IS_QT6: URL_FLAGS_T = Union[QUrl.UrlFormattingOption, QUrl.ComponentFormattingOption] + class FormatOption: + ENCODED = QUrl.ComponentFormattingOption.FullyEncoded + ENCODE_UNICODE = QUrl.ComponentFormattingOption.EncodeUnicode + DECODE_RESERVED = QUrl.ComponentFormattingOption.DecodeReserved + + REMOVE_SCHEME = QUrl.UrlFormattingOption.RemoveScheme + REMOVE_PASSWORD = QUrl.UrlFormattingOption.RemovePassword else: URL_FLAGS_T = Union[ QUrl.FormattingOptions, @@ -52,6 +59,34 @@ else: QUrl.ComponentFormattingOptions, ] + class _QtFormattingOptions(QUrl.FormattingOptions): + """WORKAROUND for invalid stubs. + + Teach mypy that | works for QUrl.FormattingOptions. + """ + def __or__(self, f: URL_FLAGS_T) -> '_QtFormattingOptions': + return super() | f # type: ignore[operator,return-value] + + class FormatOption: + """WORKAROUND for invalid stubs. + + Pretend that all ComponentFormattingOption values are also valid + QUrl.FormattingOptions values, i.e. can be passed to QUrl.toString(). + """ + ENCODED = cast( + _QtFormattingOptions, QUrl.ComponentFormattingOption.FullyEncoded) + ENCODE_UNICODE = cast( + _QtFormattingOptions, QUrl.ComponentFormattingOption.EncodeUnicode) + DECODE_RESERVED = cast( + _QtFormattingOptions, QUrl.ComponentFormattingOption.DecodeReserved) + + REMOVE_SCHEME = cast( + _QtFormattingOptions, QUrl.UrlFormattingOption.RemoveScheme) + REMOVE_PASSWORD = cast( + _QtFormattingOptions, QUrl.UrlFormattingOption.RemovePassword) + + + # URL schemes supported by QtWebEngine WEBENGINE_SCHEMES = [ @@ -548,7 +583,7 @@ def file_url(path: str) -> str: path: The absolute path to the local file """ url = QUrl.fromLocalFile(path) - return url.toString(QUrl.ComponentFormattingOption.FullyEncoded) + return url.toString(FormatOption.ENCODED) def data_url(mimetype: str, data: bytes) -> QUrl: @@ -639,7 +674,7 @@ def parse_javascript_url(url: QUrl) -> str: raise Error("URL contains unexpected components: {}" .format(url.authority())) - urlstr = url.toString(QUrl.ComponentFormattingOption.FullyEncoded) + urlstr = url.toString(FormatOption.ENCODED) urlstr = urllib.parse.unquote(urlstr) code = urlstr[len('javascript:'):] -- cgit v1.2.3-54-g00ecf