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.
This commit is contained in:
Florian Bruhin 2014-07-28 20:41:42 +02:00
parent 54c7f29f04
commit 8d80ce2628
17 changed files with 82 additions and 73 deletions

View File

@ -69,7 +69,7 @@ Requirements
The following software and libraries are required to run qutebrowser: 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) * http://qt-project.org/[Qt] 5.2 or newer (5.3.1 recommended)
* QtWebKit * QtWebKit
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2 or newer * http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2 or newer

View File

@ -8,7 +8,7 @@ pkgdesc="A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.",
arch=(any) arch=(any)
url="http://www.qutebrowser.org/" url="http://www.qutebrowser.org/"
license=('GPL') 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') 'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-rfc6266')
makedepends=('python' 'python-setuptools') makedepends=('python' 'python-setuptools')
optdepends=('python-colorlog: colored logging output') optdepends=('python-colorlog: colored logging output')

View File

@ -39,8 +39,8 @@ from qutebrowser.utils.qt import qt_ensure_valid
ElemTuple = namedtuple('ElemTuple', 'elem, label') ElemTuple = namedtuple('ElemTuple', 'elem, label')
Target = enum('normal', 'tab', 'tab_bg', 'yank', 'yank_primary', 'cmd', Target = enum('Target', 'normal', 'tab', 'tab_bg', 'yank', 'yank_primary',
'cmd_tab', 'cmd_tab_bg', 'rapid', 'download') 'cmd', 'cmd_tab', 'cmd_tab_bg', 'rapid', 'download')
class HintContext: class HintContext:
@ -280,7 +280,7 @@ class HintManager(QObject):
target = Target.tab_bg target = Target.tab_bg
else: else:
target = self._context.target 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. # FIXME Instead of clicking the center, we could have nicer heuristics.
# e.g. parse (-webkit-)border-radius correctly and click text fields at # e.g. parse (-webkit-)border-radius correctly and click text fields at
# the bottom right, and everything else on the top left or so. # the bottom right, and everything else on the top left or so.

View File

@ -34,7 +34,7 @@ import qutebrowser.config.config as config
from qutebrowser.utils.usertypes import enum from qutebrowser.utils.usertypes import enum
from qutebrowser.utils.misc import get_standard_dir from qutebrowser.utils.misc import get_standard_dir
MapType = enum('attribute', 'setter', 'static_setter') MapType = enum('MapType', 'attribute', 'setter', 'static_setter')
MAPPINGS = { MAPPINGS = {

View File

@ -71,8 +71,8 @@ class BaseKeyParser(QObject):
keystring_updated = pyqtSignal(str) keystring_updated = pyqtSignal(str)
do_log = True do_log = True
Match = enum('partial', 'definitive', 'ambiguous', 'none') Match = enum('Match', 'partial', 'definitive', 'ambiguous', 'none')
Type = enum('chain', 'special') Type = enum('Type', 'chain', 'special')
def __init__(self, parent=None, supports_count=None, def __init__(self, parent=None, supports_count=None,
supports_chains=False): supports_chains=False):

View File

@ -33,7 +33,7 @@ from qutebrowser.utils.log import keyboard as logger
STARTCHARS = ":/?" STARTCHARS = ":/?"
LastPress = enum('none', 'filtertext', 'keystring') LastPress = enum('LastPress', 'none', 'filtertext', 'keystring')
class NormalKeyParser(CommandKeyParser): class NormalKeyParser(CommandKeyParser):
@ -120,8 +120,8 @@ class HintKeyParser(CommandKeyParser):
e.key(), e.text())) e.key(), e.text()))
if e.key() == Qt.Key_Backspace: if e.key() == Qt.Key_Backspace:
logger.debug("Got backspace, mode {}, filtertext '{}', keystring " logger.debug("Got backspace, mode {}, filtertext '{}', keystring "
"'{}'".format(LastPress[self._last_press], "'{}'".format(self._last_press, self._filtertext,
self._filtertext, self._keystring)) self._keystring))
if self._last_press == LastPress.filtertext and self._filtertext: if self._last_press == LastPress.filtertext and self._filtertext:
self._filtertext = self._filtertext[:-1] self._filtertext = self._filtertext[:-1]
self.filter_hints.emit(self._filtertext) self.filter_hints.emit(self._filtertext)

View File

@ -30,7 +30,7 @@ from qutebrowser.utils.usertypes import enum
from qutebrowser.utils.qt import qt_ensure_valid 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): class BaseCompletionModel(QStandardItemModel):

View File

@ -28,7 +28,7 @@ from qutebrowser.utils.usertypes import enum
from qutebrowser.utils.qt import qt_ensure_valid 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): class DownloadModel(QAbstractListModel):

View File

@ -23,6 +23,8 @@ import unittest
from qutebrowser.utils.usertypes import enum from qutebrowser.utils.usertypes import enum
# FIXME: Add some more tests, e.g. for is_int
class EnumTests(unittest.TestCase): class EnumTests(unittest.TestCase):
@ -33,33 +35,28 @@ class EnumTests(unittest.TestCase):
""" """
def setUp(self): def setUp(self):
self.enum = enum('zero', 'one') self.enum = enum('Enum', 'one', 'two')
def test_values(self): def test_values(self):
"""Test if enum members resolve to the right values.""" """Test if enum members resolve to the right values."""
self.assertEqual(self.enum.zero, 0) self.assertEqual(self.enum.one.value, 1)
self.assertEqual(self.enum.one, 1) self.assertEqual(self.enum.two.value, 2)
def test_reverse(self): def test_name(self):
"""Test reverse mapping.""" """Test .name mapping."""
self.assertEqual(self.enum[0], 'zero') self.assertEqual(self.enum.one.name, 'one')
self.assertEqual(self.enum[1], 'one') self.assertEqual(self.enum.two.name, 'two')
def test_unknown(self): def test_unknown(self):
"""Test invalid values which should raise an AttributeError.""" """Test invalid values which should raise an AttributeError."""
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
_ = self.enum.two _ = self.enum.three
def test_unknown_reverse(self):
"""Test reverse mapping with invalid value ."""
with self.assertRaises(KeyError):
_ = self.enum['two']
def test_start(self): def test_start(self):
"""Test the start= argument.""" """Test the start= argument."""
e = enum('three', 'four', start=3) e = enum('Enum', 'three', 'four', start=3)
self.assertEqual(e.three, 3) self.assertEqual(e.three.value, 3)
self.assertEqual(e.four, 4) self.assertEqual(e.four.value, 4)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -40,11 +40,11 @@ except ImportError:
# to stderr. # to stderr.
def check_python_version(): def check_python_version():
"""Check if correct python version is run.""" """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 # We don't use .format() and print_function here just in case someone
# still has < 2.6 installed. # still has < 2.6 installed.
version_str = '.'.join(map(str, sys.version_info[:3])) 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") version_str + " is installed!\n")
if Tk: if Tk:
root = Tk() root = Tk()

View File

@ -17,7 +17,7 @@
"""Things which need to be done really early (e.g. before importing Qt). """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 import os

View File

@ -25,6 +25,7 @@ Module attributes:
import operator import operator
import collections.abc import collections.abc
import enum as pyenum
from PyQt5.QtCore import pyqtSignal, QObject, QTimer from PyQt5.QtCore import pyqtSignal, QObject, QTimer
@ -34,21 +35,20 @@ from qutebrowser.utils.log import misc as logger
_UNSET = object() _UNSET = object()
def enum(*items, start=0): def enum(name, *items, start=1, is_int=False):
"""Factory for simple enumerations. """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: Args:
name: Name of the enum
*items: Items to be sequentally enumerated. *items: Items to be sequentally enumerated.
start: The number to use for the first value. 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 = [(v, i) for (i, v) in enumerate(items, start)]
enums = dict(zip(items, numbers)) base = pyenum.IntEnum if is_int else pyenum.Enum
return EnumBase('Enum', (), enums) base = pyenum.unique(base)
return base(name, enums)
class EnumBase(type): class EnumBase(type):
@ -77,7 +77,7 @@ class NeighborList(collections.abc.Sequence):
_mode: The current mode. _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): def __init__(self, items=None, default=_UNSET, mode=Modes.exception):
"""Constructor. """Constructor.
@ -233,10 +233,10 @@ class NeighborList(collections.abc.Sequence):
# The mode of a Question. # 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. # Where to open a clicked link.
ClickTarget = enum('normal', 'tab', 'tab_bg') ClickTarget = enum('ClickTarget', 'normal', 'tab', 'tab_bg')
class Question(QObject): class Question(QObject):

View File

@ -36,7 +36,8 @@ from qutebrowser.utils.usertypes import enum
from qutebrowser.utils.misc import compact_text 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 = { SELECTORS = {

View File

@ -24,6 +24,11 @@ from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt
from qutebrowser.widgets.webview import LoadStatus from qutebrowser.widgets.webview import LoadStatus
from qutebrowser.widgets.statusbar.textbase import TextBase from qutebrowser.widgets.statusbar.textbase import TextBase
from qutebrowser.config.style import set_register_stylesheet, get_stylesheet 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): class UrlText(TextBase):
@ -34,8 +39,8 @@ class UrlText(TextBase):
STYLESHEET: The stylesheet template. STYLESHEET: The stylesheet template.
Attributes: Attributes:
normal_url: The normal URL to be displayed. normal_url: The normal URL to be displayed as a UrlType instance.
normal_url_type: The type of the normal URL. normal_url_type: The type of the normal URL as a UrlType instance.
hover_url: The URL we're currently hovering over. hover_url: The URL we're currently hovering over.
_ssl_errors: Whether SSL errors occured while loading. _ssl_errors: Whether SSL errors occured while loading.
@ -78,13 +83,20 @@ class UrlText(TextBase):
set_register_stylesheet(self) set_register_stylesheet(self)
self._hover_url = None self._hover_url = None
self._normal_url = None self._normal_url = None
self._normal_url_type = 'normal' self._normal_url_type = UrlType.normal
@pyqtProperty(str) @pyqtProperty(str)
def urltype(self): 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 # pylint: disable=method-hidden
return self._urltype if self._urltype is None:
return ""
else:
return self._urltype.name
@urltype.setter @urltype.setter
def urltype(self, val): def urltype(self, val):
@ -121,7 +133,11 @@ class UrlText(TextBase):
@normal_url_type.setter @normal_url_type.setter
def normal_url_type(self, val): 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._normal_url_type = val
self._update_url() self._update_url()
@ -129,25 +145,26 @@ class UrlText(TextBase):
"""Update the displayed URL if the url or the hover url changed.""" """Update the displayed URL if the url or the hover url changed."""
if self.hover_url is not None: if self.hover_url is not None:
self.setText(self.hover_url) self.setText(self.hover_url)
self.urltype = 'hover' self.urltype = UrlType.hover
elif self.normal_url is not None: elif self.normal_url is not None:
self.setText(self.normal_url) self.setText(self.normal_url)
self.urltype = self.normal_url_type self.urltype = self.normal_url_type
else: else:
self.setText('') self.setText('')
self.urltype = 'normal' self.urltype = UrlType.normal
@pyqtSlot(str) @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. """Slot for load_status_changed. Sets URL color accordingly.
Args: Args:
status: The LoadStatus as string. status_str: The LoadStatus as string.
""" """
if status in ('success', 'error', 'warn'): status = LoadStatus[status_str]
self.normal_url_type = status if status in (LoadStatus.success, LoadStatus.error, LoadStatus.warn):
self.normal_url_type = UrlType[status_str]
else: else:
self.normal_url_type = 'normal' self.normal_url_type = UrlType.normal
@pyqtSlot(str) @pyqtSlot(str)
def set_url(self, s): def set_url(self, s):
@ -157,7 +174,7 @@ class UrlText(TextBase):
s: The URL to set as string. s: The URL to set as string.
""" """
self.normal_url = s self.normal_url = s
self.normal_url_type = 'normal' self.normal_url_type = UrlType.normal
@pyqtSlot(str, str, str) @pyqtSlot(str, str, str)
def set_hover_url(self, link, _title, _text): def set_hover_url(self, link, _title, _text):
@ -181,8 +198,4 @@ class UrlText(TextBase):
"""Update URL if the tab changed.""" """Update URL if the tab changed."""
self.hover_url = None self.hover_url = None
self.normal_url = tab.url_text self.normal_url = tab.url_text
status = LoadStatus[tab.load_status] self.on_load_status_changed(tab.load_status.name)
if status in ('success', 'error', 'warn'):
self.normal_url_type = status
else:
self.normal_url_type = 'normal'

View File

@ -39,7 +39,7 @@ from qutebrowser.utils.usertypes import NeighborList, ClickTarget, enum
from qutebrowser.commands.exceptions import CommandError from qutebrowser.commands.exceptions import CommandError
LoadStatus = enum('none', 'success', 'error', 'warn', 'loading') LoadStatus = enum('LoadStatus', 'none', 'success', 'error', 'warn', 'loading')
class WebView(QWebView): class WebView(QWebView):
@ -138,10 +138,9 @@ class WebView(QWebView):
Emit: Emit:
load_status_changed load_status_changed
""" """
log.webview.debug("load status for {}: {}".format( log.webview.debug("load status for {}: {}".format(repr(self), val))
repr(self), LoadStatus[val]))
self._load_status = val self._load_status = val
self.load_status_changed.emit(LoadStatus[val]) self.load_status_changed.emit(val.name)
@property @property
def url_text(self): def url_text(self):
@ -262,7 +261,7 @@ class WebView(QWebView):
self.open_target = self._force_open_target self.open_target = self._force_open_target
self._force_open_target = None self._force_open_target = None
log.mouse.debug("Setting force target: {}".format( log.mouse.debug("Setting force target: {}".format(
ClickTarget[self.open_target])) self.open_target))
elif (e.button() == Qt.MidButton or elif (e.button() == Qt.MidButton or
e.modifiers() & Qt.ControlModifier): e.modifiers() & Qt.ControlModifier):
if config.get('general', 'background-tabs'): if config.get('general', 'background-tabs'):
@ -270,7 +269,7 @@ class WebView(QWebView):
else: else:
self.open_target = ClickTarget.tab self.open_target = ClickTarget.tab
log.mouse.debug("Middle click, setting target: {}".format( log.mouse.debug("Middle click, setting target: {}".format(
ClickTarget[self.open_target])) self.open_target))
else: else:
self.open_target = ClickTarget.normal self.open_target = ClickTarget.normal
log.mouse.debug("Normal click, setting normal target") log.mouse.debug("Normal click, setting normal target")

View File

@ -54,8 +54,8 @@ def _parse_docstring(func): # noqa
A (short_desc, long_desc, arg_descs) tuple. A (short_desc, long_desc, arg_descs) tuple.
""" """
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
State = enum('short', 'desc', # pylint: disable=invalid-name State = enum('State', 'short', 'desc', 'desc_hidden', 'arg_start',
'desc_hidden', 'arg_start', 'arg_inside', 'misc') 'arg_inside', 'misc')
doc = inspect.getdoc(func) doc = inspect.getdoc(func)
lines = doc.splitlines() lines = doc.splitlines()

View File

@ -111,7 +111,6 @@ setupdata = {
'Operating System :: Microsoft :: Windows :: Windows 7', 'Operating System :: Microsoft :: Windows :: Windows 7',
'Operating System :: POSIX :: Linux', 'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Topic :: Internet', 'Topic :: Internet',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',