From 8d80ce26282f42989719541448475854c0266d67 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 28 Jul 2014 20:41:42 +0200 Subject: [PATCH] Switch to python 3.4 enums. Our home-brewn enum wasn't really liked by pylint (many no-member errors), so instead of adding some workaround, we just use the python 3.4 enum instead. This however also means we need to depend on Python 3.4 and not 3.3. Maybe we should use enum34 on Python < 3.3. --- README.asciidoc | 2 +- pkg/PKGBUILD.qutebrowser-git | 2 +- qutebrowser/browser/hints.py | 6 +-- qutebrowser/config/websettings.py | 2 +- qutebrowser/keyinput/basekeyparser.py | 4 +- qutebrowser/keyinput/modeparsers.py | 6 +-- qutebrowser/models/basecompletion.py | 2 +- qutebrowser/models/downloadmodel.py | 2 +- qutebrowser/test/utils/usertypes/test_enum.py | 29 +++++------ qutebrowser/utils/checkpyver.py | 4 +- qutebrowser/utils/earlyinit.py | 2 +- qutebrowser/utils/usertypes.py | 24 ++++----- qutebrowser/utils/webelem.py | 3 +- qutebrowser/widgets/statusbar/url.py | 51 ++++++++++++------- qutebrowser/widgets/webview.py | 11 ++-- scripts/generate_doc.py | 4 +- scripts/setupcommon.py | 1 - 17 files changed, 82 insertions(+), 73 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index eb6547581..879463ed4 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -69,7 +69,7 @@ Requirements The following software and libraries are required to run qutebrowser: -* http://www.python.org/[Python] 3.3 or newer (3.4 recommended) +* http://www.python.org/[Python] 3.4 * http://qt-project.org/[Qt] 5.2 or newer (5.3.1 recommended) * QtWebKit * http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2 or newer diff --git a/pkg/PKGBUILD.qutebrowser-git b/pkg/PKGBUILD.qutebrowser-git index 9c06be0eb..18f62593e 100644 --- a/pkg/PKGBUILD.qutebrowser-git +++ b/pkg/PKGBUILD.qutebrowser-git @@ -8,7 +8,7 @@ pkgdesc="A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.", arch=(any) url="http://www.qutebrowser.org/" license=('GPL') -depends=('python>=3.3' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2' +depends=('python>=3.4' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2' 'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-rfc6266') makedepends=('python' 'python-setuptools') optdepends=('python-colorlog: colored logging output') diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 1fc93b9a1..707c29006 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -39,8 +39,8 @@ from qutebrowser.utils.qt import qt_ensure_valid ElemTuple = namedtuple('ElemTuple', 'elem, label') -Target = enum('normal', 'tab', 'tab_bg', 'yank', 'yank_primary', 'cmd', - 'cmd_tab', 'cmd_tab_bg', 'rapid', 'download') +Target = enum('Target', 'normal', 'tab', 'tab_bg', 'yank', 'yank_primary', + 'cmd', 'cmd_tab', 'cmd_tab_bg', 'rapid', 'download') class HintContext: @@ -280,7 +280,7 @@ class HintManager(QObject): target = Target.tab_bg else: target = self._context.target - self.set_open_target.emit(Target[target]) + self.set_open_target.emit(target.name) # FIXME Instead of clicking the center, we could have nicer heuristics. # e.g. parse (-webkit-)border-radius correctly and click text fields at # the bottom right, and everything else on the top left or so. diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 58cbb1e5b..14a6c3087 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -34,7 +34,7 @@ import qutebrowser.config.config as config from qutebrowser.utils.usertypes import enum from qutebrowser.utils.misc import get_standard_dir -MapType = enum('attribute', 'setter', 'static_setter') +MapType = enum('MapType', 'attribute', 'setter', 'static_setter') MAPPINGS = { diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index c0d91b08f..4f5046c3d 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -71,8 +71,8 @@ class BaseKeyParser(QObject): keystring_updated = pyqtSignal(str) do_log = True - Match = enum('partial', 'definitive', 'ambiguous', 'none') - Type = enum('chain', 'special') + Match = enum('Match', 'partial', 'definitive', 'ambiguous', 'none') + Type = enum('Type', 'chain', 'special') def __init__(self, parent=None, supports_count=None, supports_chains=False): diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 8a0cadaac..bf250aff6 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -33,7 +33,7 @@ from qutebrowser.utils.log import keyboard as logger STARTCHARS = ":/?" -LastPress = enum('none', 'filtertext', 'keystring') +LastPress = enum('LastPress', 'none', 'filtertext', 'keystring') class NormalKeyParser(CommandKeyParser): @@ -120,8 +120,8 @@ class HintKeyParser(CommandKeyParser): e.key(), e.text())) if e.key() == Qt.Key_Backspace: logger.debug("Got backspace, mode {}, filtertext '{}', keystring " - "'{}'".format(LastPress[self._last_press], - self._filtertext, self._keystring)) + "'{}'".format(self._last_press, self._filtertext, + self._keystring)) if self._last_press == LastPress.filtertext and self._filtertext: self._filtertext = self._filtertext[:-1] self.filter_hints.emit(self._filtertext) diff --git a/qutebrowser/models/basecompletion.py b/qutebrowser/models/basecompletion.py index 6d0453f03..c72f558bc 100644 --- a/qutebrowser/models/basecompletion.py +++ b/qutebrowser/models/basecompletion.py @@ -30,7 +30,7 @@ from qutebrowser.utils.usertypes import enum from qutebrowser.utils.qt import qt_ensure_valid -Role = enum('marks', 'sort', start=Qt.UserRole) +Role = enum('Role', 'marks', 'sort', start=Qt.UserRole, is_int=True) class BaseCompletionModel(QStandardItemModel): diff --git a/qutebrowser/models/downloadmodel.py b/qutebrowser/models/downloadmodel.py index 2902636ea..6cd5dc5c3 100644 --- a/qutebrowser/models/downloadmodel.py +++ b/qutebrowser/models/downloadmodel.py @@ -28,7 +28,7 @@ from qutebrowser.utils.usertypes import enum from qutebrowser.utils.qt import qt_ensure_valid -Role = enum('item', start=Qt.UserRole) +Role = enum('Role', 'item', start=Qt.UserRole, is_int=True) class DownloadModel(QAbstractListModel): diff --git a/qutebrowser/test/utils/usertypes/test_enum.py b/qutebrowser/test/utils/usertypes/test_enum.py index 707b967ee..1ce3a9258 100644 --- a/qutebrowser/test/utils/usertypes/test_enum.py +++ b/qutebrowser/test/utils/usertypes/test_enum.py @@ -23,6 +23,8 @@ import unittest from qutebrowser.utils.usertypes import enum +# FIXME: Add some more tests, e.g. for is_int + class EnumTests(unittest.TestCase): @@ -33,33 +35,28 @@ class EnumTests(unittest.TestCase): """ def setUp(self): - self.enum = enum('zero', 'one') + self.enum = enum('Enum', 'one', 'two') def test_values(self): """Test if enum members resolve to the right values.""" - self.assertEqual(self.enum.zero, 0) - self.assertEqual(self.enum.one, 1) + self.assertEqual(self.enum.one.value, 1) + self.assertEqual(self.enum.two.value, 2) - def test_reverse(self): - """Test reverse mapping.""" - self.assertEqual(self.enum[0], 'zero') - self.assertEqual(self.enum[1], 'one') + def test_name(self): + """Test .name mapping.""" + self.assertEqual(self.enum.one.name, 'one') + self.assertEqual(self.enum.two.name, 'two') def test_unknown(self): """Test invalid values which should raise an AttributeError.""" with self.assertRaises(AttributeError): - _ = self.enum.two - - def test_unknown_reverse(self): - """Test reverse mapping with invalid value .""" - with self.assertRaises(KeyError): - _ = self.enum['two'] + _ = self.enum.three def test_start(self): """Test the start= argument.""" - e = enum('three', 'four', start=3) - self.assertEqual(e.three, 3) - self.assertEqual(e.four, 4) + e = enum('Enum', 'three', 'four', start=3) + self.assertEqual(e.three.value, 3) + self.assertEqual(e.four.value, 4) if __name__ == '__main__': diff --git a/qutebrowser/utils/checkpyver.py b/qutebrowser/utils/checkpyver.py index fcc889bf2..ae4622c06 100644 --- a/qutebrowser/utils/checkpyver.py +++ b/qutebrowser/utils/checkpyver.py @@ -40,11 +40,11 @@ except ImportError: # to stderr. def check_python_version(): """Check if correct python version is run.""" - if sys.hexversion < 0x03030000: + if sys.hexversion < 0x03040000: # We don't use .format() and print_function here just in case someone # still has < 2.6 installed. version_str = '.'.join(map(str, sys.version_info[:3])) - text = ("At least Python 3.3 is required to run qutebrowser, but " + + text = ("At least Python 3.4 is required to run qutebrowser, but " + version_str + " is installed!\n") if Tk: root = Tk() diff --git a/qutebrowser/utils/earlyinit.py b/qutebrowser/utils/earlyinit.py index 3a4605852..85d1ecc0a 100644 --- a/qutebrowser/utils/earlyinit.py +++ b/qutebrowser/utils/earlyinit.py @@ -17,7 +17,7 @@ """Things which need to be done really early (e.g. before importing Qt). -At this point we can be sure we have all python 3.3 features available. +At this point we can be sure we have all python 3.4 features available. """ import os diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 2cf5a75d5..5d17347d5 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -25,6 +25,7 @@ Module attributes: import operator import collections.abc +import enum as pyenum from PyQt5.QtCore import pyqtSignal, QObject, QTimer @@ -34,21 +35,20 @@ from qutebrowser.utils.log import misc as logger _UNSET = object() -def enum(*items, start=0): +def enum(name, *items, start=1, is_int=False): """Factory for simple enumerations. - We really don't need more complex things here, so we don't use python3.4's - enum, because we'd have to backport things to 3.3. - - Based on: http://stackoverflow.com/a/1695250/2085149 - Args: + name: Name of the enum *items: Items to be sequentally enumerated. start: The number to use for the first value. + We use 1 as default so enum members are always True. + is_init: True if the enum should be a Python IntEnum """ - numbers = range(start, len(items) + start) - enums = dict(zip(items, numbers)) - return EnumBase('Enum', (), enums) + enums = [(v, i) for (i, v) in enumerate(items, start)] + base = pyenum.IntEnum if is_int else pyenum.Enum + base = pyenum.unique(base) + return base(name, enums) class EnumBase(type): @@ -77,7 +77,7 @@ class NeighborList(collections.abc.Sequence): _mode: The current mode. """ - Modes = enum('block', 'wrap', 'exception') + Modes = enum('Modes', 'block', 'wrap', 'exception') def __init__(self, items=None, default=_UNSET, mode=Modes.exception): """Constructor. @@ -233,10 +233,10 @@ class NeighborList(collections.abc.Sequence): # The mode of a Question. -PromptMode = enum('yesno', 'text', 'user_pwd', 'alert') +PromptMode = enum('PromptMode', 'yesno', 'text', 'user_pwd', 'alert') # Where to open a clicked link. -ClickTarget = enum('normal', 'tab', 'tab_bg') +ClickTarget = enum('ClickTarget', 'normal', 'tab', 'tab_bg') class Question(QObject): diff --git a/qutebrowser/utils/webelem.py b/qutebrowser/utils/webelem.py index 15163b7de..c100ff288 100644 --- a/qutebrowser/utils/webelem.py +++ b/qutebrowser/utils/webelem.py @@ -36,7 +36,8 @@ from qutebrowser.utils.usertypes import enum from qutebrowser.utils.misc import compact_text -Group = enum('all', 'links', 'images', 'editable', 'url', 'prevnext', 'focus') +Group = enum('Group', 'all', 'links', 'images', 'editable', 'url', 'prevnext', + 'focus') SELECTORS = { diff --git a/qutebrowser/widgets/statusbar/url.py b/qutebrowser/widgets/statusbar/url.py index f254d4fb2..aa4c4c592 100644 --- a/qutebrowser/widgets/statusbar/url.py +++ b/qutebrowser/widgets/statusbar/url.py @@ -24,6 +24,11 @@ from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt from qutebrowser.widgets.webview import LoadStatus from qutebrowser.widgets.statusbar.textbase import TextBase from qutebrowser.config.style import set_register_stylesheet, get_stylesheet +from qutebrowser.utils.usertypes import enum + + +# Note this has entries for success/error/warn from widgets.webview:LoadStatus +UrlType = enum('UrlType', 'success', 'error', 'warn', 'hover', 'normal') class UrlText(TextBase): @@ -34,8 +39,8 @@ class UrlText(TextBase): STYLESHEET: The stylesheet template. Attributes: - normal_url: The normal URL to be displayed. - normal_url_type: The type of the normal URL. + normal_url: The normal URL to be displayed as a UrlType instance. + normal_url_type: The type of the normal URL as a UrlType instance. hover_url: The URL we're currently hovering over. _ssl_errors: Whether SSL errors occured while loading. @@ -78,13 +83,20 @@ class UrlText(TextBase): set_register_stylesheet(self) self._hover_url = None self._normal_url = None - self._normal_url_type = 'normal' + self._normal_url_type = UrlType.normal @pyqtProperty(str) def urltype(self): - """Getter for self.urltype, so it can be used as Qt property.""" + """Getter for self.urltype, so it can be used as Qt property. + + Return: + The urltype as a string (!) + """ # pylint: disable=method-hidden - return self._urltype + if self._urltype is None: + return "" + else: + return self._urltype.name @urltype.setter def urltype(self, val): @@ -121,7 +133,11 @@ class UrlText(TextBase): @normal_url_type.setter def normal_url_type(self, val): - """Setter to update displayed URL when normal_url_type was set.""" + """Setter to update displayed URL when normal_url_type was set. + + Args: + val: The value as an UrlType instance. + """ self._normal_url_type = val self._update_url() @@ -129,25 +145,26 @@ class UrlText(TextBase): """Update the displayed URL if the url or the hover url changed.""" if self.hover_url is not None: self.setText(self.hover_url) - self.urltype = 'hover' + self.urltype = UrlType.hover elif self.normal_url is not None: self.setText(self.normal_url) self.urltype = self.normal_url_type else: self.setText('') - self.urltype = 'normal' + self.urltype = UrlType.normal @pyqtSlot(str) - def on_load_status_changed(self, status): + def on_load_status_changed(self, status_str): """Slot for load_status_changed. Sets URL color accordingly. Args: - status: The LoadStatus as string. + status_str: The LoadStatus as string. """ - if status in ('success', 'error', 'warn'): - self.normal_url_type = status + status = LoadStatus[status_str] + if status in (LoadStatus.success, LoadStatus.error, LoadStatus.warn): + self.normal_url_type = UrlType[status_str] else: - self.normal_url_type = 'normal' + self.normal_url_type = UrlType.normal @pyqtSlot(str) def set_url(self, s): @@ -157,7 +174,7 @@ class UrlText(TextBase): s: The URL to set as string. """ self.normal_url = s - self.normal_url_type = 'normal' + self.normal_url_type = UrlType.normal @pyqtSlot(str, str, str) def set_hover_url(self, link, _title, _text): @@ -181,8 +198,4 @@ class UrlText(TextBase): """Update URL if the tab changed.""" self.hover_url = None self.normal_url = tab.url_text - status = LoadStatus[tab.load_status] - if status in ('success', 'error', 'warn'): - self.normal_url_type = status - else: - self.normal_url_type = 'normal' + self.on_load_status_changed(tab.load_status.name) diff --git a/qutebrowser/widgets/webview.py b/qutebrowser/widgets/webview.py index 7c4d7027c..d238a6abe 100644 --- a/qutebrowser/widgets/webview.py +++ b/qutebrowser/widgets/webview.py @@ -39,7 +39,7 @@ from qutebrowser.utils.usertypes import NeighborList, ClickTarget, enum from qutebrowser.commands.exceptions import CommandError -LoadStatus = enum('none', 'success', 'error', 'warn', 'loading') +LoadStatus = enum('LoadStatus', 'none', 'success', 'error', 'warn', 'loading') class WebView(QWebView): @@ -138,10 +138,9 @@ class WebView(QWebView): Emit: load_status_changed """ - log.webview.debug("load status for {}: {}".format( - repr(self), LoadStatus[val])) + log.webview.debug("load status for {}: {}".format(repr(self), val)) self._load_status = val - self.load_status_changed.emit(LoadStatus[val]) + self.load_status_changed.emit(val.name) @property def url_text(self): @@ -262,7 +261,7 @@ class WebView(QWebView): self.open_target = self._force_open_target self._force_open_target = None log.mouse.debug("Setting force target: {}".format( - ClickTarget[self.open_target])) + self.open_target)) elif (e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier): if config.get('general', 'background-tabs'): @@ -270,7 +269,7 @@ class WebView(QWebView): else: self.open_target = ClickTarget.tab log.mouse.debug("Middle click, setting target: {}".format( - ClickTarget[self.open_target])) + self.open_target)) else: self.open_target = ClickTarget.normal log.mouse.debug("Normal click, setting normal target") diff --git a/scripts/generate_doc.py b/scripts/generate_doc.py index 16f58fdc5..244c1f8e7 100644 --- a/scripts/generate_doc.py +++ b/scripts/generate_doc.py @@ -54,8 +54,8 @@ def _parse_docstring(func): # noqa A (short_desc, long_desc, arg_descs) tuple. """ # pylint: disable=too-many-branches - State = enum('short', 'desc', # pylint: disable=invalid-name - 'desc_hidden', 'arg_start', 'arg_inside', 'misc') + State = enum('State', 'short', 'desc', 'desc_hidden', 'arg_start', + 'arg_inside', 'misc') doc = inspect.getdoc(func) lines = doc.splitlines() diff --git a/scripts/setupcommon.py b/scripts/setupcommon.py index bb86c4d91..e15d5441b 100644 --- a/scripts/setupcommon.py +++ b/scripts/setupcommon.py @@ -111,7 +111,6 @@ setupdata = { 'Operating System :: Microsoft :: Windows :: Windows 7', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Internet', 'Topic :: Internet :: WWW/HTTP',