From e2c5fe6262564d9d85806bfa9d4486a411cf5045 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 8 Apr 2021 09:51:16 +0200 Subject: Fix enum stringification for Python 3.10 a7+ https://bugs.python.org/issue40066 https://mail.python.org/archives/list/python-dev@python.org/message/CHQW6THTDYNPPFWQ2KDDTUYSAJDCZFNP/ https://github.com/python/cpython/commit/b775106d940e3d77c8af7967545bb9a5b7b162df --- qutebrowser/browser/browsertab.py | 4 ++-- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/keyinput/modeman.py | 10 ++++++---- qutebrowser/utils/utils.py | 20 ++++++++++++++++++- tests/end2end/features/downloads.feature | 24 +++++++++++------------ tests/end2end/features/misc.feature | 2 +- tests/end2end/features/qutescheme.feature | 2 +- tests/end2end/features/test_downloads_bdd.py | 2 +- tests/unit/utils/test_debug.py | 2 ++ tests/unit/utils/test_utils.py | 28 +++++++++++++++++++++++++++ 10 files changed, 73 insertions(+), 23 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index cc358a810..cbe698009 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -1003,7 +1003,7 @@ class AbstractTab(QWidget): """Setter for load_status.""" if not isinstance(val, usertypes.LoadStatus): raise TypeError("Type {} is no LoadStatus member!".format(val)) - log.webview.debug("load status for {}: {}".format(repr(self), val)) + log.webview.debug(f"load status for {self!r}: {utils.pyenum_str(val)}") self._load_status = val self.load_status_changed.emit(val) @@ -1063,7 +1063,7 @@ class AbstractTab(QWidget): url = utils.elide(navigation.url.toDisplayString(), 100) log.webview.debug("navigation request: url {}, type {}, is_main_frame " "{}".format(url, - navigation.navigation_type, + utils.pyenum_str(navigation.navigation_type), navigation.is_main_frame)) if navigation.is_main_frame: diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 754b764cb..c03e94408 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -464,7 +464,7 @@ class WebEngineCaret(browsertab.AbstractCaret): # `:selection-toggle` is executed and before this callback function # is asynchronously called. log.misc.debug("Ignoring caret selection callback in {}".format( - self._mode_manager.mode)) + utils.pyenum_str(self._mode_manager.mode))) return if state_str is None: message.error("Error toggling caret selection") diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 3c47fafe3..21a6be052 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -284,8 +284,8 @@ class ModeManager(QObject): curmode = self.mode parser = self.parsers[curmode] if curmode != usertypes.KeyMode.insert: - log.modes.debug("got keypress in mode {} - delegating to " - "{}".format(curmode, utils.qualname(parser))) + log.modes.debug("got keypress in mode {} - delegating to {}".format( + utils.pyenum_str(curmode), utils.qualname(parser))) match = parser.handle(event, dry_run=dry_run) has_modifier = event.modifiers() not in [ @@ -361,7 +361,8 @@ class ModeManager(QObject): return log.modes.debug("Entering mode {}{}".format( - mode, '' if reason is None else ' (reason: {})'.format(reason))) + utils.pyenum_str(mode), + '' if reason is None else ' (reason: {})'.format(reason))) if mode not in self.parsers: raise ValueError("No keyparser for mode {}".format(mode)) if self.mode == mode or (self.mode in PROMPT_MODES and @@ -429,7 +430,8 @@ class ModeManager(QObject): raise NotInModeError("Not in mode {}!".format(mode)) log.modes.debug("Leaving mode {}{}".format( - mode, '' if reason is None else ' (reason: {})'.format(reason))) + utils.pyenum_str(mode), + '' if reason is None else ' (reason: {})'.format(reason))) # leaving a mode implies clearing keychain, see # https://github.com/qutebrowser/qutebrowser/issues/1805 self.clear_keychain() diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 762d2d370..56ebe45c4 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -375,6 +375,18 @@ def is_enum(obj: Any) -> bool: return False +def pyenum_str(value: enum.Enum) -> str: + """Get a string representation of a Python enum value. + + This will have the form of "EnumType.membername", which is the default string + representation for Python up to 3.10. Unfortunately, that changes with Python 3.10: + https://bugs.python.org/issue40066 + """ + if sys.version_info[:2] >= (3, 10): + return repr(value) + return str(value) + + def get_repr(obj: Any, constructor: bool = False, **attrs: Any) -> str: """Get a suitable __repr__ string for an object. @@ -387,8 +399,14 @@ def get_repr(obj: Any, constructor: bool = False, **attrs: Any) -> str: cls = qualname(obj.__class__) parts = [] items = sorted(attrs.items()) + for name, val in items: - parts.append('{}={!r}'.format(name, val)) + if isinstance(val, enum.Enum): + s = pyenum_str(val) + else: + s = repr(val) + parts.append(f'{name}={s}') + if constructor: return '{}({})'.format(cls, ', '.join(parts)) else: diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index dfdb24704..6448e7809 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -79,7 +79,7 @@ Feature: Downloading things from a website. And I set downloads.location.prompt to true And I open data/downloads/issue1243.html And I hint with args "links download" and follow a - And I wait for "Asking question option=None text=* title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log Then the error "Download error: No handler found for qute://" should be shown And "NotFoundError while handling qute://* URL" should be logged @@ -88,7 +88,7 @@ Feature: Downloading things from a website. And I set downloads.location.prompt to true And I open data/data_link.html And I hint with args "links download" and follow s - And I wait for "Asking question option=None text=* title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :mode-leave Then no crash should happen @@ -96,7 +96,7 @@ Feature: Downloading things from a website. When I set downloads.location.suggestion to filename And I set downloads.location.prompt to true And I open data/downloads/download.bin in a new window without waiting - And I wait for "Asking question *" in the log + And I wait for "Asking question option=None text='Please enter a location for http://localhost:*/data/downloads/download.bin' title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :prompt-accept COM1 And I run :mode-leave Then the error "Invalid filename" should be shown @@ -173,7 +173,7 @@ Feature: Downloading things from a website. Scenario: Downloading a file to a drive-relative working directory When I set downloads.location.prompt to true And I open data/downloads/download.bin without waiting - And I wait for "Asking question option=None text='Please enter a location for http://localhost:*/data/downloads/download.bin' title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :prompt-accept C:foobar And I run :mode-leave Then the error "Invalid filename" should be shown @@ -247,14 +247,14 @@ Feature: Downloading things from a website. Scenario: :download with a filename and directory which doesn't exist When I run :download --dest (tmpdir)(dirsep)downloads(dirsep)somedir(dirsep)file http://localhost:(port)/data/downloads/download.bin - And I wait for "Asking question option=None text='* does not exist. Create it?' title='Create directory?'>, *" in the log + And I wait for "Asking question , *" in the log And I run :prompt-accept yes And I wait until the download is finished Then the downloaded file somedir/file should exist Scenario: :download with a directory which doesn't exist When I run :download --dest (tmpdir)(dirsep)downloads(dirsep)somedir(dirsep) http://localhost:(port)/data/downloads/download.bin - And I wait for "Asking question option=None text='* does not exist. Create it?' title='Create directory?'>, *" in the log + And I wait for "Asking question , *" in the log And I run :prompt-accept yes And I wait until the download is finished Then the downloaded file somedir/download.bin should exist @@ -279,13 +279,13 @@ Feature: Downloading things from a website. When I set downloads.location.prompt to true And I open data/title.html And I run :download --mhtml - And I wait for "Asking question option=None text='Please enter a location for http://localhost:*/data/title.html' title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :prompt-accept And I wait for "File successfully written." in the log And I run :download --mhtml - And I wait for "Asking question option=None text='Please enter a location for http://localhost:*/data/title.html' title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :prompt-accept - And I wait for "Asking question option=None text='* already exists. Overwrite?' title='Overwrite existing file?'>, *" in the log + And I wait for "Asking question , *" in the log And I run :prompt-accept yes And I wait for "File successfully written." in the log Then the downloaded file Test title.mhtml should exist @@ -655,9 +655,9 @@ Feature: Downloading things from a website. Scenario: Answering a question for a cancelled download (#415) When I set downloads.location.prompt to true And I run :download http://localhost:(port)/data/downloads/download.bin - And I wait for "Asking question option=None text=* title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :download http://localhost:(port)/data/downloads/download2.bin - And I wait for "Asking question option=None text=* title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :download-cancel with count 2 And I run :prompt-accept And I wait until the download is finished diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index e6a02e038..3951dd2b0 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -196,7 +196,7 @@ Feature: Various utility commands. # We can't use "When I open" because we don't want to wait for load # finished When I run :open http://localhost:(port)/redirect-later?delay=-1 - And I wait for "emitting: cur_load_status_changed() (tab *)" in the log + And I wait for "emitting: cur_load_status_changed(*loading*) (tab *)" in the log And I wait 1s And I run :stop And I open redirect-later-continue in a new tab diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature index 039434f1c..bb556df53 100644 --- a/tests/end2end/features/qutescheme.feature +++ b/tests/end2end/features/qutescheme.feature @@ -192,7 +192,7 @@ Feature: Special qute:// pages And I open data/misc/test.pdf without waiting And I wait for "[qute://pdfjs/*] PDF * (PDF.js: *)" in the log And I run :jseval document.getElementById("download").click() - And I wait for "Asking question option=None text=* title='Save file to:'>, *" in the log + And I wait for "Asking question , *" in the log And I run :mode-leave Then no crash should happen diff --git a/tests/end2end/features/test_downloads_bdd.py b/tests/end2end/features/test_downloads_bdd.py index 804ed40fe..f7b19b4b0 100644 --- a/tests/end2end/features/test_downloads_bdd.py +++ b/tests/end2end/features/test_downloads_bdd.py @@ -28,7 +28,7 @@ bdd.scenarios('downloads.feature') PROMPT_MSG = ("Asking question option=None " + "default={!r} mode=PromptMode.download option=None " "text=* title='Save file to:'>, *") diff --git a/tests/unit/utils/test_debug.py b/tests/unit/utils/test_debug.py index 62d0f4a7b..4fcf781ba 100644 --- a/tests/unit/utils/test_debug.py +++ b/tests/unit/utils/test_debug.py @@ -19,6 +19,8 @@ """Tests for qutebrowser.utils.debug.""" +import sys +import enum import logging import re import time diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 57adc883c..42b95a1a8 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -546,6 +546,34 @@ class TestIsEnum: assert not utils.is_enum(23) +class SomeEnum(enum.Enum): + + some_value = enum.auto() + + +class TestPyEnumStr: + + @pytest.fixture + def val(self): + return SomeEnum.some_value + + def test_fake_old_python_version(self, monkeypatch, val): + monkeypatch.setattr(sys, 'version_info', (3, 9, 2)) + assert utils.pyenum_str(val) == str(val) + + def test_fake_new_python_version(self, monkeypatch, val): + monkeypatch.setattr(sys, 'version_info', (3, 10, 0)) + assert utils.pyenum_str(val) == repr(val) + + def test_real_result(self, val): + assert utils.pyenum_str(val) == 'SomeEnum.some_value' + + @pytest.mark.skipif(sys.version_info[:2] < (3, 10), reason='Needs Python 3.10+') + def test_needed(self, val): + """Fail if this change gets revered before the final 3.10 release.""" + assert str(val) != 'SomeEnum.some_value' + + class TestRaises: """Test raises.""" -- cgit v1.2.3-54-g00ecf