From 85b2fb513bdd03666ba864fd83f3cdca2f81333a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 08:30:28 +0100 Subject: [PATCH 01/38] mypy: Add to tox --- misc/requirements/requirements-mypy.txt | 5 +++++ misc/requirements/requirements-mypy.txt-raw | 1 + tox.ini | 10 ++++++++++ 3 files changed, 16 insertions(+) create mode 100644 misc/requirements/requirements-mypy.txt create mode 100644 misc/requirements/requirements-mypy.txt-raw diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt new file mode 100644 index 000000000..3a6548040 --- /dev/null +++ b/misc/requirements/requirements-mypy.txt @@ -0,0 +1,5 @@ +# This file is automatically generated by scripts/dev/recompile_requirements.py + +mypy==0.641 +mypy-extensions==0.4.1 +typed-ast==1.1.0 diff --git a/misc/requirements/requirements-mypy.txt-raw b/misc/requirements/requirements-mypy.txt-raw new file mode 100644 index 000000000..f0aa93ac8 --- /dev/null +++ b/misc/requirements/requirements-mypy.txt-raw @@ -0,0 +1 @@ +mypy diff --git a/tox.ini b/tox.ini index 8e9a54f11..4b80e8dde 100644 --- a/tox.ini +++ b/tox.ini @@ -188,3 +188,13 @@ deps = whitelist_externals = eslint changedir = {toxinidir}/qutebrowser/javascript commands = eslint --color --report-unused-disable-directives . + +[testenv:mypy] +basepython = {env:PYTHON:python3} +passenv = +deps = + -r{toxinidir}/requirements.txt + -r{toxinidir}/misc/requirements/requirements-mypy.txt +commands = + #{envpython} -m mypy --ignore-missing-imports --allow-untyped-decorators --allow-subclassing-any --strict src + {envpython} -m mypy qutebrowser From 0999945af446131604f4dd9c76f5b93dcc867573 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 13:48:34 +0100 Subject: [PATCH 02/38] mypy: Run with --ignore-missing-imports --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 4b80e8dde..9deaa64b0 100644 --- a/tox.ini +++ b/tox.ini @@ -196,5 +196,4 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-mypy.txt commands = - #{envpython} -m mypy --ignore-missing-imports --allow-untyped-decorators --allow-subclassing-any --strict src - {envpython} -m mypy qutebrowser + {envpython} -m mypy --ignore-missing-imports qutebrowser From 4d1b3df5e0156f90d0d7c80380347b5d38ce73c5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 13:51:43 +0100 Subject: [PATCH 03/38] mypy: Fix logging.VDEBUG issues --- qutebrowser/utils/log.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 381e9ca5d..a1c4f74b7 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -76,12 +76,13 @@ LOG_COLORS = { # We first monkey-patch logging to support our VDEBUG level before getting the # loggers. Based on http://stackoverflow.com/a/13638084 +# mypy doesn't know about this, so we need to ignore it. VDEBUG_LEVEL = 9 logging.addLevelName(VDEBUG_LEVEL, 'VDEBUG') -logging.VDEBUG = VDEBUG_LEVEL +logging.VDEBUG = VDEBUG_LEVEL # type: ignore LOG_LEVELS = { - 'VDEBUG': logging.VDEBUG, + 'VDEBUG': logging.VDEBUG, # type: ignore 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, @@ -89,17 +90,6 @@ LOG_LEVELS = { 'CRITICAL': logging.CRITICAL, } -LOGGER_NAMES = [ - 'statusbar', 'completion', 'init', 'url', - 'destroy', 'modes', 'webview', 'misc', - 'mouse', 'procs', 'hints', 'keyboard', - 'commands', 'signals', 'downloads', - 'js', 'qt', 'rfc6266', 'ipc', 'shlexer', - 'save', 'message', 'config', 'sessions', - 'webelem', 'prompt', 'network', 'sql', - 'greasemonkey' -] - def vdebug(self, msg, *args, **kwargs): """Log with a VDEBUG level. @@ -114,7 +104,7 @@ def vdebug(self, msg, *args, **kwargs): # pylint: enable=protected-access -logging.Logger.vdebug = vdebug +logging.Logger.vdebug = vdebug # type: ignore # The different loggers used. @@ -148,6 +138,17 @@ network = logging.getLogger('network') sql = logging.getLogger('sql') greasemonkey = logging.getLogger('greasemonkey') +LOGGER_NAMES = [ + 'statusbar', 'completion', 'init', 'url', + 'destroy', 'modes', 'webview', 'misc', + 'mouse', 'procs', 'hints', 'keyboard', + 'commands', 'signals', 'downloads', + 'js', 'qt', 'rfc6266', 'ipc', 'shlexer', + 'save', 'message', 'config', 'sessions', + 'webelem', 'prompt', 'network', 'sql', + 'greasemonkey' +] + ram_handler = None console_handler = None From 97d0cff93b452de34f5d415ac9c7ccf9d812a01e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 13:54:17 +0100 Subject: [PATCH 04/38] mypy: Ignore ImportError handling See https://github.com/python/mypy/issues/1153 --- qutebrowser/browser/qutescheme.py | 2 +- qutebrowser/misc/checkpyver.py | 8 ++++---- qutebrowser/misc/earlyinit.py | 2 +- qutebrowser/qt.py | 2 +- qutebrowser/utils/qtutils.py | 2 +- qutebrowser/utils/version.py | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 0fa9366a6..b78404de9 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -37,7 +37,7 @@ try: import secrets except ImportError: # New in Python 3.6 - secrets = None + secrets = None # type: ignore from PyQt5.QtCore import QUrlQuery, QUrl, qVersion diff --git a/qutebrowser/misc/checkpyver.py b/qutebrowser/misc/checkpyver.py index 50330ef88..cf8e13810 100644 --- a/qutebrowser/misc/checkpyver.py +++ b/qutebrowser/misc/checkpyver.py @@ -30,12 +30,12 @@ try: except ImportError: # pragma: no cover try: # Python2 - from Tkinter import Tk - import tkMessageBox as messagebox + from Tkinter import Tk # type: ignore + import tkMessageBox as messagebox # type: ignore except ImportError: # Some Python without Tk - Tk = None - messagebox = None + Tk = None # type: ignore + messagebox = None # type: ignore # First we check the version of Python. This code should run fine with python2 diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index b29e1508f..38dffa691 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -38,7 +38,7 @@ import datetime try: import tkinter except ImportError: - tkinter = None + tkinter = None # type: ignore # NOTE: No qutebrowser or PyQt import should be done here, as some early # initialization needs to take place before that! diff --git a/qutebrowser/qt.py b/qutebrowser/qt.py index 2878bbe98..d9f1dc58d 100644 --- a/qutebrowser/qt.py +++ b/qutebrowser/qt.py @@ -25,4 +25,4 @@ try: from PyQt5 import sip except ImportError: - import sip + import sip # type: ignore diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index c634eb95f..a7c30919f 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -39,7 +39,7 @@ from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray, try: from PyQt5.QtWebKit import qWebKitVersion except ImportError: # pragma: no cover - qWebKitVersion = None + qWebKitVersion = None # type: ignore MAXVALS = { diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 7a99a4b65..a52e31ed8 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -42,12 +42,12 @@ from PyQt5.QtWidgets import QApplication try: from PyQt5.QtWebKit import qWebKitVersion except ImportError: # pragma: no cover - qWebKitVersion = None + qWebKitVersion = None # type: ignore try: from PyQt5.QtWebEngineWidgets import QWebEngineProfile except ImportError: # pragma: no cover - QWebEngineProfile = None + QWebEngineProfile = None # type: ignore import qutebrowser from qutebrowser.utils import log, utils, standarddir, usertypes, message From 563e1e829484ef68bc80b7fe3edf890fc9b7f2a7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 13:56:30 +0100 Subject: [PATCH 05/38] mypy: Ignore yaml.CSafe{Loader,Dumper} See https://github.com/python/typeshed/blob/master/third_party/2and3/yaml/__init__.pyi#L9 --- qutebrowser/utils/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 6119675ba..18dce2b05 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -41,7 +41,8 @@ from PyQt5.QtWidgets import QApplication import pkg_resources import yaml try: - from yaml import CSafeLoader as YamlLoader, CSafeDumper as YamlDumper + from yaml import (CSafeLoader as YamlLoader, # type: ignore + CSafeDumper as YamlDumper) YAML_C_EXT = True except ImportError: # pragma: no cover from yaml import SafeLoader as YamlLoader, SafeDumper as YamlDumper From a1e83f07735c293a59643ef9acd8bbf1a67253f1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:07:41 +0100 Subject: [PATCH 06/38] mypy: Use class-based API for enum.IntEnum See https://github.com/python/mypy/issues/4865 --- qutebrowser/browser/downloads.py | 6 +++++- qutebrowser/mainwindow/tabwidget.py | 7 +++++-- qutebrowser/misc/backendproblem.py | 12 ++++++++---- qutebrowser/misc/crashdialog.py | 8 ++++++-- qutebrowser/utils/usertypes.py | 15 +++++++++++---- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 92d846bd8..9c2a88a87 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -40,7 +40,11 @@ from qutebrowser.utils import (usertypes, standarddir, utils, message, log, from qutebrowser.qt import sip -ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole) +class ModelRole(enum.IntEnum): + + """Custom download model roles.""" + + item = Qt.UserRole # Remember the last used directory diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index f2605e7d3..f9c2ac0e6 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -37,8 +37,11 @@ from qutebrowser.misc import objects from qutebrowser.browser import browsertab -PixelMetrics = enum.IntEnum('PixelMetrics', ['icon_padding'], - start=QStyle.PM_CustomBase) +class PixelMetrics(enum.IntEnum): + + """Custom PixelMetrics attributes.""" + + icon_padding = QStyle.PM_CustomBase class TabWidget(QTabWidget): diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index d5f7c9680..8e57a0223 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -38,10 +38,14 @@ from qutebrowser.utils import usertypes, objreg, version, qtutils, log, utils from qutebrowser.misc import objects, msgbox -_Result = enum.IntEnum( - '_Result', - ['quit', 'restart', 'restart_webkit', 'restart_webengine'], - start=QDialog.Accepted + 1) +class _Result(enum.IntEnum): + + """The result code returned by the backend problem dialog.""" + + quit = QDialog.Accepted + 1 + restart = QDialog.Accepted + 2 + restart_webkit = QDialog.Accepted + 3 + restart_webengine = QDialog.Accepted + 4 @attr.s diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index 27dec3345..a846cc59a 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -42,8 +42,12 @@ from qutebrowser.misc import (miscwidgets, autoupdate, msgbox, httpclient, from qutebrowser.config import config, configfiles -Result = enum.IntEnum('Result', ['restore', 'no_restore'], - start=QDialog.Accepted + 1) +class Result(enum.IntEnum): + + """The result code returned by the crash dialog.""" + + restore = QDialog.Accepted + 1 + no_restore = QDialog.Accepted + 2 def parse_fatal_stacktrace(text): diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 039d805f9..82a95c3fd 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -221,10 +221,17 @@ KeyMode = enum.Enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', 'jump_mark', 'record_macro', 'run_macro']) -# Exit statuses for errors. Needs to be an int for sys.exit. -Exit = enum.IntEnum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', - 'err_init', 'err_config', 'err_key_config'], - start=0) +class Exit(enum.IntEnum): + + """Exit statuses for errors. Needs to be an int for sys.exit.""" + + ok = 0 + reserved = 1 + exception = 2 + err_ipc = 3 + err_init = 4 + err_config = 5 + err_key_config = 6 # Load status of a tab From b37dbc45723bee27a5ba0d5e7cfea1c64e0cc1f2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:19:31 +0100 Subject: [PATCH 07/38] mypy: Add missing annotations --- qutebrowser/browser/commands.py | 2 +- qutebrowser/browser/webkit/network/networkmanager.py | 4 +++- qutebrowser/commands/cmdutils.py | 9 +++------ qutebrowser/config/configcache.py | 4 +++- qutebrowser/config/configtypes.py | 5 +++-- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index ceafbc011..e94e55c3d 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -645,7 +645,7 @@ class CommandDispatcher: inc_or_dec='decrement'), 'increment': functools.partial(navigate.incdec, inc_or_dec='increment'), - } + } # type: typing.Dict[str, typing.Callable] try: if where in ['prev', 'next']: diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 8d2523456..31e9e815f 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -21,6 +21,7 @@ import collections import html +import typing import attr from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl, @@ -28,6 +29,7 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl, from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket from qutebrowser.config import config +from qutebrowser.mainwindow import prompt from qutebrowser.utils import (message, log, usertypes, utils, objreg, urlutils, debug) from qutebrowser.browser import shared @@ -37,7 +39,7 @@ from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply, HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%' -_proxy_auth_cache = {} +_proxy_auth_cache = {} # type: typing.Dict[ProxyId, prompt.AuthInfo] @attr.s(frozen=True) diff --git a/qutebrowser/commands/cmdutils.py b/qutebrowser/commands/cmdutils.py index f9ce91b8f..768cc430e 100644 --- a/qutebrowser/commands/cmdutils.py +++ b/qutebrowser/commands/cmdutils.py @@ -17,18 +17,15 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Contains various command utils and a global command dict. - -Module attributes: - cmd_dict: A mapping from command-strings to command objects. -""" +"""Contains various command utils and a global command dict.""" import inspect +import typing from qutebrowser.utils import qtutils, log from qutebrowser.commands import command, cmdexc -cmd_dict = {} +cmd_dict = {} # type: typing.Dict[str, command.Command] def check_overflow(arg, ctype): diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index dfead6664..cdba6456a 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -20,6 +20,8 @@ """Implementation of a basic config cache.""" +import typing + from qutebrowser.config import config @@ -36,7 +38,7 @@ class ConfigCache: """ def __init__(self) -> None: - self._cache = {} + self._cache = {} # type: typing.Dict[str, typing.Any] config.instance.changed.connect(self._on_config_changed) def _on_config_changed(self, attr: str) -> None: diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 5503ea4f3..0314805c0 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -52,6 +52,7 @@ import datetime import functools import operator import json +import typing import attr import yaml @@ -304,7 +305,7 @@ class MappingType(BaseType): MAPPING: The mapping to use. """ - MAPPING = {} + MAPPING = {} # type: typing.Dict[str, typing.Any] def __init__(self, none_ok=False, valid_values=None): super().__init__(none_ok) @@ -576,7 +577,7 @@ class FlagList(List): the valid values of the setting. """ - combinable_values = None + combinable_values = None # type: typing.Optional[typing.Iterable] _show_valtype = False From 052c3f92ada0ad3a09aa46dd1fcc335baa2eb496 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:34:57 +0100 Subject: [PATCH 08/38] mypy: Fix issues with config default values --- qutebrowser/config/config.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 1f2e45741..41309d352 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -22,6 +22,7 @@ import copy import contextlib import functools +import typing from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject @@ -30,11 +31,15 @@ from qutebrowser.utils import utils, log, jinja from qutebrowser.misc import objects from qutebrowser.keyinput import keyutils +MYPY = False +if MYPY: + from qutebrowser.config import configcache + # An easy way to access the config from other code via config.val.foo -val = None -instance = None -key_instance = None -cache = None +val = typing.cast('ConfigContainer', None) +instance = typing.cast('Config', None) +key_instance = typing.cast('KeyConfig', None) +cache = typing.cast('configcache.ConfigCache', None) # Keeping track of all change filters to validate them later. change_filters = [] From c2c3f68828556063c6f1570cfb213ce25728a482 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:37:15 +0100 Subject: [PATCH 09/38] mypy: Move tabbed_browser --- qutebrowser/commands/runners.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index c3f5d87a1..f1c7641e7 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -58,6 +58,9 @@ def _current_url(tabbed_browser): def replace_variables(win_id, arglist): """Utility function to replace variables like {url} in a list of args.""" + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=win_id) + variables = { 'url': lambda: _current_url(tabbed_browser).toString( QUrl.FullyEncoded | QUrl.RemovePassword), @@ -67,13 +70,13 @@ def replace_variables(win_id, arglist): 'clipboard': utils.get_clipboard, 'primary': lambda: utils.get_clipboard(selection=True), } + for key in list(variables): modified_key = '{' + key + '}' variables[modified_key] = lambda x=modified_key: x + values = {} args = [] - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=win_id) def repl_cb(matchobj): """Return replacement for given match.""" From b63ed090d8e1c21d9bd57aa8a6d875d48e23ae97 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:43:23 +0100 Subject: [PATCH 10/38] mypy: Fix log handler access --- qutebrowser/misc/utilcmds.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index d108a56ac..b8d8be447 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -312,6 +312,7 @@ def log_capacity(capacity: int): if capacity < 0: raise cmdexc.CommandError("Can't set a negative log capacity!") else: + assert log.ram_handler is not None log.ram_handler.change_log_capacity(capacity) @@ -326,6 +327,7 @@ def debug_log_level(level: str): level: The log level to set. """ log.change_console_formatter(log.LOG_LEVELS[level.upper()]) + assert log.console_handler is not None log.console_handler.setLevel(log.LOG_LEVELS[level.upper()]) From 08bd47bc16a27c46891b773efcaaec0a0e34d1cb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:46:22 +0100 Subject: [PATCH 11/38] mypy: Fix type for :scroll --- qutebrowser/browser/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index e94e55c3d..0bb3d076b 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -681,7 +681,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) - def scroll(self, direction: typing.Union[str, int], count=1): + def scroll(self, direction: str, count=1): """Scroll the current tab in the given direction. Note you can use `:run-with-count` to have a keybinding with a bigger From 923fd893230a04e733d49513a607f71f1c24c932 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:48:30 +0100 Subject: [PATCH 12/38] mypy: Fix int-issues in commands.py --- qutebrowser/browser/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0bb3d076b..2af938fb5 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -75,7 +75,7 @@ class CommandDispatcher: new_window.show() return new_window.tabbed_browser - def _count(self): + def _count(self) -> int: """Convenience method to get the widget count.""" return self._tabbed_browser.widget.count() @@ -1143,6 +1143,8 @@ class CommandDispatcher: self.tab_next() return + assert isinstance(index, int) + if index < 0: index = self._count() + index + 1 @@ -1184,6 +1186,7 @@ class CommandDispatcher: if config.val.tabs.wrap: new_idx %= self._count() else: + assert isinstance(index, int) # absolute moving if count is not None: new_idx = count - 1 From 808309196711d77efd5e0e65e1eda31747a2b985 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:53:30 +0100 Subject: [PATCH 13/38] mypy: Fix _color_flag init --- qutebrowser/mainwindow/statusbar/bar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index c3ef53b1b..3edb8128a 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -145,7 +145,7 @@ class StatusBar(QWidget): resized = pyqtSignal('QRect') moved = pyqtSignal('QPoint') _severity = None - _color_flags = [] + _color_flags = None STYLESHEET = _generate_stylesheet() From 8b1fd83366eeae0a11146bf50291a66362063a3c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 14:54:36 +0100 Subject: [PATCH 14/38] mypy: Fix AbstractSettings default values --- qutebrowser/config/websettings.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index fb80c543b..659f793f8 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -19,6 +19,8 @@ """Bridge from QWeb(Engine)Settings to our own settings.""" +import typing + from PyQt5.QtGui import QFont from qutebrowser.config import config, configutils @@ -44,10 +46,10 @@ class AbstractSettings: """Abstract base class for settings set via QWeb(Engine)Settings.""" - _ATTRIBUTES = None - _FONT_SIZES = None - _FONT_FAMILIES = None - _FONT_TO_QFONT = None + _ATTRIBUTES = {} # type: typing.Dict[str, AttributeInfo] + _FONT_SIZES = {} # type: typing.Dict[str, typing.Any] + _FONT_FAMILIES = {} # type: typing.Dict[str, typing.Any] + _FONT_TO_QFONT = {} # type: typing.Dict[typing.Any, QFont.StyleHint] def __init__(self, settings): self._settings = settings From 12b26512fc511518d3b37d37eb78b406991e08d2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Nov 2018 15:02:41 +0100 Subject: [PATCH 15/38] mypy: Fix :session-save We use a sentinel value for the argument so we can check whether the default was used. To express that in the type system, it needs a separate class. --- qutebrowser/misc/sessions.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 78adeb983..9d3736cdb 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -23,6 +23,7 @@ import os import os.path import itertools import urllib +import typing from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer from PyQt5.QtWidgets import QApplication @@ -37,7 +38,12 @@ from qutebrowser.mainwindow import mainwindow from qutebrowser.qt import sip -default = object() # Sentinel value +class Sentinel: + + pass + + +default = Sentinel() def init(parent=None): @@ -109,7 +115,7 @@ class SessionManager(QObject): def __init__(self, base_path, parent=None): super().__init__(parent) - self._current = None + self._current = None # type: typing.Optional[str] self._base_path = base_path self._last_window_session = None self.did_load = False @@ -504,8 +510,9 @@ class SessionManager(QObject): @cmdutils.argument('name', completion=miscmodels.session) @cmdutils.argument('win_id', win_id=True) @cmdutils.argument('with_private', flag='p') - def session_save(self, name: str = default, current=False, quiet=False, - force=False, only_active_window=False, with_private=False, + def session_save(self, name: typing.Union[str, Sentinel] = default, + current=False, quiet=False, force=False, + only_active_window=False, with_private=False, win_id=None): """Save a session. @@ -518,7 +525,9 @@ class SessionManager(QObject): only_active_window: Saves only tabs of the currently active window. with_private: Include private windows. """ - if name is not default and name.startswith('_') and not force: + if (not isinstance(name, Sentinel) and + name.startswith('_') and + not force): raise cmdexc.CommandError("{} is an internal session, use --force " "to save anyways.".format(name)) if current: From 3cc2af909b75b2271a51ce916756a8ef6933d466 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 23 Nov 2018 19:40:12 +0100 Subject: [PATCH 16/38] mypy: Add PyQt5 stubs --- misc/requirements/requirements-mypy.txt | 3 +++ misc/requirements/requirements-mypy.txt-raw | 1 + tox.ini | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 3a6548040..3071410a5 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -2,4 +2,7 @@ mypy==0.641 mypy-extensions==0.4.1 +PyQt5==5.11.3 +PyQt5-sip==4.19.13 +PyQt5-stubs==5.11.3.0 typed-ast==1.1.0 diff --git a/misc/requirements/requirements-mypy.txt-raw b/misc/requirements/requirements-mypy.txt-raw index f0aa93ac8..e400122f6 100644 --- a/misc/requirements/requirements-mypy.txt-raw +++ b/misc/requirements/requirements-mypy.txt-raw @@ -1 +1,2 @@ mypy +PyQt5-stubs diff --git a/tox.ini b/tox.ini index 9deaa64b0..cffee3f0e 100644 --- a/tox.ini +++ b/tox.ini @@ -196,4 +196,4 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-mypy.txt commands = - {envpython} -m mypy --ignore-missing-imports qutebrowser + {envpython} -m mypy qutebrowser From bd731593cebe12b4bf2879b1d7fbc099916f2d83 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 24 Nov 2018 20:12:43 +0100 Subject: [PATCH 17/38] mypy: Add {posargs} in tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cffee3f0e..5d956c0ec 100644 --- a/tox.ini +++ b/tox.ini @@ -196,4 +196,4 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-mypy.txt commands = - {envpython} -m mypy qutebrowser + {envpython} -m mypy qutebrowser {posargs} From 01b2c40272ab79906ee3a0f0dfb405933da36847 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 24 Nov 2018 20:12:59 +0100 Subject: [PATCH 18/38] Add requirements-optional.txt --- misc/requirements/requirements-optional.txt | 7 +++++++ misc/requirements/requirements-optional.txt-raw | 3 +++ tox.ini | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 misc/requirements/requirements-optional.txt create mode 100644 misc/requirements/requirements-optional.txt-raw diff --git a/misc/requirements/requirements-optional.txt b/misc/requirements/requirements-optional.txt new file mode 100644 index 000000000..6e1e8b8ff --- /dev/null +++ b/misc/requirements/requirements-optional.txt @@ -0,0 +1,7 @@ +# This file is automatically generated by scripts/dev/recompile_requirements.py + +colorama==0.4.0 +cssutils==1.0.2 +hunter==2.1.0 +Pympler==0.6 +six==1.11.0 diff --git a/misc/requirements/requirements-optional.txt-raw b/misc/requirements/requirements-optional.txt-raw new file mode 100644 index 000000000..a0be23733 --- /dev/null +++ b/misc/requirements/requirements-optional.txt-raw @@ -0,0 +1,3 @@ +hunter +cssutils +pympler diff --git a/tox.ini b/tox.ini index 5d956c0ec..8a4232aaa 100644 --- a/tox.ini +++ b/tox.ini @@ -194,6 +194,8 @@ basepython = {env:PYTHON:python3} passenv = deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/misc/requirements/requirements-optional.txt + -r{toxinidir}/misc/requirements/requirements-pyqt.txt -r{toxinidir}/misc/requirements/requirements-mypy.txt commands = {envpython} -m mypy qutebrowser {posargs} From ee1f7a51877c70d163b8d355d9dac76a328f3d55 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 17:49:25 +0100 Subject: [PATCH 19/38] mypy: Use own copy of PyQt5-stubs The one by upstream need various fixes which aren't merged yet. --- misc/requirements/requirements-mypy.txt | 2 +- misc/requirements/requirements-mypy.txt-raw | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 3071410a5..f2951fdf5 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -4,5 +4,5 @@ mypy==0.641 mypy-extensions==0.4.1 PyQt5==5.11.3 PyQt5-sip==4.19.13 -PyQt5-stubs==5.11.3.0 +-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5_stubs typed-ast==1.1.0 diff --git a/misc/requirements/requirements-mypy.txt-raw b/misc/requirements/requirements-mypy.txt-raw index e400122f6..636ad43a4 100644 --- a/misc/requirements/requirements-mypy.txt-raw +++ b/misc/requirements/requirements-mypy.txt-raw @@ -1,2 +1,5 @@ mypy -PyQt5-stubs +-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5-stubs + +# remove @commit-id for scm installs +#@ replace: @.*# @wip# From 7834e3c7dde19baa7b2fc6bbcb8ec3ad416a2be4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 18:07:52 +0100 Subject: [PATCH 20/38] mypy: Add mypy.ini to ignore missing modules --- mypy.ini | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..9d810b738 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,32 @@ +[mypy] +# We also need to support 3.5, but if we'd chose that here, we'd need to deal +# with conditional imports (like secrets.py). +python_version = 3.6 + +[mypy-faulthandler] +# https://github.com/python/typeshed/pull/2627 +ignore_missing_imports = True + +[mypy-colorama] +# https://github.com/tartley/colorama/issues/206 +ignore_missing_imports = True + +[mypy-hunter] +# https://github.com/ionelmc/python-hunter/issues/43 +ignore_missing_imports = True + +[mypy-pygments.*] +# https://bitbucket.org/birkenfeld/pygments-main/issues/1485/type-hints +ignore_missing_imports = True + +[mypy-cssutils] +# Pretty much inactive currently +ignore_missing_imports = True + +[mypy-pypeg2] +# Pretty much inactive currently +ignore_missing_imports = True + +[mypy-bdb] +# stdlib, missing in typeshed +ignore_missing_imports = True From 4b4b74679148d859cb9a148dd0016221c13a3120 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 18:22:02 +0100 Subject: [PATCH 21/38] mypy: Add type annotations for browsertab.AbstractAction --- qutebrowser/browser/browsertab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index b2be458ce..0cd54be7e 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -136,8 +136,8 @@ class AbstractAction: action_base: The type of the actions (QWeb{Engine,}Page.WebAction) """ - action_class = None - action_base = None + action_class = None # type: type + action_base = None # type: type def __init__(self, tab): self._widget = None From fda807ce9a3375f63e4e226daeb15d4317e137fc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 19:03:07 +0100 Subject: [PATCH 22/38] mypy: Allow trivial --strict options --- mypy.ini | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mypy.ini b/mypy.ini index 9d810b738..797ed0bca 100644 --- a/mypy.ini +++ b/mypy.ini @@ -3,6 +3,22 @@ # with conditional imports (like secrets.py). python_version = 3.6 +# --strict +warn_unused_configs = True +warn_redundant_casts = True +warn_unused_ignores = True +disallow_subclassing_any = True +# disallow_untyped_calls = True +# disallow_untyped_defs = True +# disallow_incomplete_defs = True +# check_untyped_defs = True +# disallow_untyped_decorators = True +# no_implicit_optional = True +# warn_return_any = True + +# disallow_any_generics = True + + [mypy-faulthandler] # https://github.com/python/typeshed/pull/2627 ignore_missing_imports = True @@ -30,3 +46,7 @@ ignore_missing_imports = True [mypy-bdb] # stdlib, missing in typeshed ignore_missing_imports = True + +[mypy-qutebrowser.browser.webkit.rfc6266] +# subclasses dynamic PyPEG2 classes +disallow_subclassing_any = False From 1f36e56e1c724b9a7a4ef4236774b8e1379a1351 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 20:12:03 +0100 Subject: [PATCH 23/38] Complete partial annotations Unfortunately we can't turn on mypy's --disallow-incomplete-defs (yet) due to https://github.com/python/mypy/issues/5954 --- mypy.ini | 1 + qutebrowser/browser/browsertab.py | 4 +-- qutebrowser/browser/commands.py | 28 +++++++++++-------- qutebrowser/browser/downloads.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/browser/webengine/webview.py | 2 +- qutebrowser/browser/webkit/webkittab.py | 2 +- qutebrowser/browser/webkit/webpage.py | 2 +- qutebrowser/config/configcache.py | 2 +- qutebrowser/mainwindow/prompt.py | 3 +- qutebrowser/mainwindow/tabwidget.py | 8 +++--- qutebrowser/misc/sessions.py | 9 ++++-- qutebrowser/misc/utilcmds.py | 13 +++++---- 13 files changed, 44 insertions(+), 34 deletions(-) diff --git a/mypy.ini b/mypy.ini index 797ed0bca..91c314675 100644 --- a/mypy.ini +++ b/mypy.ini @@ -10,6 +10,7 @@ warn_unused_ignores = True disallow_subclassing_any = True # disallow_untyped_calls = True # disallow_untyped_defs = True +# https://github.com/python/mypy/issues/5954 # disallow_incomplete_defs = True # check_untyped_defs = True # disallow_untyped_decorators = True diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 0cd54be7e..02d9b70dd 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -685,7 +685,7 @@ class AbstractAudio(QObject): self._widget = None self._tab = tab - def set_muted(self, muted: bool, override: bool = False): + def set_muted(self, muted: bool, override: bool = False) -> None: """Set this tab as muted or not. Arguments: @@ -699,7 +699,7 @@ class AbstractAudio(QObject): """Whether this tab is muted.""" raise NotImplementedError - def toggle_muted(self, *, override: bool = False): + def toggle_muted(self, *, override: bool = False) -> None: self.set_muted(not self.is_muted(), override=override) def is_recently_audible(self): diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 2af938fb5..5c97aaf53 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -513,7 +513,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('win_id', completion=miscmodels.window) @cmdutils.argument('count', count=True) - def tab_give(self, win_id: int = None, keep=False, count=None): + def tab_give(self, win_id: int = None, keep: bool = False, + count: int = None) -> None: """Give the current tab to a new or existing window if win_id given. If no win_id is given, the tab will get detached into a new window. @@ -601,7 +602,8 @@ class CommandDispatcher: @cmdutils.argument('where', choices=['prev', 'next', 'up', 'increment', 'decrement']) @cmdutils.argument('count', count=True) - def navigate(self, where: str, tab=False, bg=False, window=False, count=1): + def navigate(self, where: str, tab: bool = False, bg: bool = False, + window: bool = False, count: int = 1) -> None: """Open typical prev/next links or navigate using the URL path. This tries to automatically click on typical _Previous Page_ or @@ -665,7 +667,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) - def scroll_px(self, dx: int, dy: int, count=1): + def scroll_px(self, dx: int, dy: int, count: int = 1) -> None: """Scroll the current tab by 'count * dx/dy' pixels. Args: @@ -681,7 +683,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) - def scroll(self, direction: str, count=1): + def scroll(self, direction: str, count: int = 1) -> None: """Scroll the current tab in the given direction. Note you can use `:run-with-count` to have a keybinding with a bigger @@ -719,7 +721,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) @cmdutils.argument('horizontal', flag='x') - def scroll_to_perc(self, perc: float = None, horizontal=False, count=None): + def scroll_to_perc(self, perc: float = None, horizontal: bool = False, + count: int = None) -> None: """Scroll to a specific percentage of the page. The percentage can be given either as argument or as count. @@ -764,7 +767,7 @@ class CommandDispatcher: choices=('next', 'increment')) def scroll_page(self, x: float, y: float, *, top_navigate: str = None, bottom_navigate: str = None, - count=1): + count: int = 1) -> None: """Scroll the frame page-wise. Args: @@ -1120,7 +1123,7 @@ class CommandDispatcher: @cmdutils.argument('index', choices=['last']) @cmdutils.argument('count', count=True) def tab_focus(self, index: typing.Union[str, int] = None, - count=None, no_last=False): + count: int = None, no_last: bool = False) -> None: """Select the tab given as argument/[count]. If neither count nor index are given, it behaves like tab-next. @@ -1161,7 +1164,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('index', choices=['+', '-']) @cmdutils.argument('count', count=True) - def tab_move(self, index: typing.Union[str, int] = None, count=None): + def tab_move(self, index: typing.Union[str, int] = None, + count: int = None) -> None: """Move the current tab according to the argument and [count]. If neither is given, move it to the first position. @@ -1718,10 +1722,10 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('filter_', choices=['id']) - def click_element(self, filter_: str, value, *, + def click_element(self, filter_: str, value: str, *, target: usertypes.ClickTarget = usertypes.ClickTarget.normal, - force_event=False): + force_event: bool = False) -> None: """Click the element matching the given filter. The given filter needs to result in exactly one element, otherwise, an @@ -2070,8 +2074,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_cmd_split=True) - def jseval(self, js_code, file=False, quiet=False, *, - world: typing.Union[usertypes.JsWorld, int] = None): + def jseval(self, js_code: str, file: bool = False, quiet: bool = False, *, + world: typing.Union[usertypes.JsWorld, int] = None) -> None: """Evaluate a JavaScript string. Args: diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 9c2a88a87..1709c7425 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -1062,7 +1062,7 @@ class DownloadModel(QAbstractListModel): @cmdutils.register(instance='download-model', scope='window', maxsplit=0) @cmdutils.argument('count', count=True) - def download_open(self, cmdline: str = None, count=0): + def download_open(self, cmdline: str = None, count: int = 0) -> None: """Open the last/[count]th download. If no specific command is given, this will use the system's default diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 0f1d368e9..9945886fa 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -670,7 +670,7 @@ class WebEngineAudio(browsertab.AbstractAudio): self._tab.url_changed.connect(self._on_url_changed) config.instance.changed.connect(self._on_config_changed) - def set_muted(self, muted: bool, override: bool = False): + def set_muted(self, muted: bool, override: bool = False) -> None: self._overridden = override page = self._widget.page() page.setAudioMuted(muted) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index b10cc5f9a..e70226f30 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -240,7 +240,7 @@ class WebEnginePage(QWebEnginePage): def acceptNavigationRequest(self, url: QUrl, typ: QWebEnginePage.NavigationType, - is_main_frame: bool): + is_main_frame: bool) -> bool: """Override acceptNavigationRequest to forward it to the tab API.""" type_map = { QWebEnginePage.NavigationTypeLinkClicked: diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 2edea1777..c791326ce 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -641,7 +641,7 @@ class WebKitAudio(browsertab.AbstractAudio): """Dummy handling of audio status for QtWebKit.""" - def set_muted(self, muted: bool, override: bool = False): + def set_muted(self, muted: bool, override: bool = False) -> None: raise browsertab.WebTabError('Muting is not supported on QtWebKit!') def is_muted(self): diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index ce985b466..0195ec17f 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -469,7 +469,7 @@ class BrowserPage(QWebPage): def acceptNavigationRequest(self, frame: QWebFrame, request: QNetworkRequest, - typ: QWebPage.NavigationType): + typ: QWebPage.NavigationType) -> bool: """Override acceptNavigationRequest to handle clicked links. Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index cdba6456a..a421ba85c 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -45,7 +45,7 @@ class ConfigCache: if attr in self._cache: self._cache[attr] = config.instance.get(attr) - def __getitem__(self, attr: str): + def __getitem__(self, attr: str) -> typing.Any: if attr not in self._cache: assert not config.instance.get_opt(attr).supports_pattern self._cache[attr] = config.instance.get(attr) diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 5eb76c86e..f6a8b1224 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -391,7 +391,8 @@ class PromptContainer(QWidget): @cmdutils.register(instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt], maxsplit=0) - def prompt_open_download(self, cmdline: str = None, pdfjs=False): + def prompt_open_download(self, cmdline: str = None, + pdfjs: bool = False) -> None: """Immediately open a download. If no specific command is given, this will use the system's default diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index f9c2ac0e6..a3ba0f1da 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -342,7 +342,7 @@ class TabWidget(QTabWidget): qtutils.ensure_valid(url) return url - def update_tab_favicon(self, tab: QWidget): + def update_tab_favicon(self, tab: QWidget) -> None: """Update favicon of the given tab.""" idx = self.indexOf(tab) @@ -400,7 +400,7 @@ class TabBar(QTabBar): return self.parent().currentWidget() @pyqtSlot(str) - def _on_config_changed(self, option: str): + def _on_config_changed(self, option: str) -> None: if option == 'fonts.tabs': self._set_font() elif option == 'tabs.favicons.scale': @@ -543,7 +543,7 @@ class TabBar(QTabBar): return super().mousePressEvent(e) - def minimumTabSizeHint(self, index, ellipsis: bool = True) -> QSize: + def minimumTabSizeHint(self, index: int, ellipsis: bool = True) -> QSize: """Set the minimum tab size to indicator/icon/... text. Args: @@ -623,7 +623,7 @@ class TabBar(QTabBar): return False return widget.data.pinned - def tabSizeHint(self, index: int): + def tabSizeHint(self, index: int) -> QSize: """Override tabSizeHint to customize qb's tab size. https://wiki.python.org/moin/PyQt/Customising%20tab%20bars diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 9d3736cdb..b94566830 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -511,9 +511,12 @@ class SessionManager(QObject): @cmdutils.argument('win_id', win_id=True) @cmdutils.argument('with_private', flag='p') def session_save(self, name: typing.Union[str, Sentinel] = default, - current=False, quiet=False, force=False, - only_active_window=False, with_private=False, - win_id=None): + current: bool = False, + quiet: bool = False, + force: bool = False, + only_active_window: bool = False, + with_private: bool = False, + win_id: int = None) -> None: """Save a session. Args: diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index b8d8be447..b893c8d6e 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -44,7 +44,7 @@ from qutebrowser.qt import sip @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', win_id=True) -def later(ms: int, command, win_id): +def later(ms: int, command: str, win_id: int) -> None: """Execute a command after some time. Args: @@ -75,7 +75,7 @@ def later(ms: int, command, win_id): @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', win_id=True) @cmdutils.argument('count', count=True) -def repeat(times: int, command, win_id, count=None): +def repeat(times: int, command: str, win_id: int, count: int = None) -> None: """Repeat a given command. Args: @@ -96,7 +96,8 @@ def repeat(times: int, command, win_id, count=None): @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', win_id=True) @cmdutils.argument('count', count=True) -def run_with_count(count_arg: int, command, win_id, count=1): +def run_with_count(count_arg: int, command: str, win_id: int, + count: int = 1) -> None: """Run a command with the given count. If run_with_count itself is run with a count, it multiplies count_arg. @@ -303,7 +304,7 @@ def repeat_command(win_id, count=None): @cmdutils.register(debug=True, name='debug-log-capacity') -def log_capacity(capacity: int): +def log_capacity(capacity: int) -> None: """Change the number of log lines to be stored in RAM. Args: @@ -320,7 +321,7 @@ def log_capacity(capacity: int): @cmdutils.argument('level', choices=sorted( (level.lower() for level in log.LOG_LEVELS), key=lambda e: log.LOG_LEVELS[e.upper()])) -def debug_log_level(level: str): +def debug_log_level(level: str) -> None: """Change the log level for console logging. Args: @@ -332,7 +333,7 @@ def debug_log_level(level: str): @cmdutils.register(debug=True) -def debug_log_filter(filters: str): +def debug_log_filter(filters: str) -> None: """Change the log filter for console logging. Args: From 6b5a92fb2d30ad76bf73b0b065da9d0384157209 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 23:24:31 +0100 Subject: [PATCH 24/38] mypy: Update config --- mypy.ini | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mypy.ini b/mypy.ini index 91c314675..e002720d3 100644 --- a/mypy.ini +++ b/mypy.ini @@ -10,16 +10,13 @@ warn_unused_ignores = True disallow_subclassing_any = True # disallow_untyped_calls = True # disallow_untyped_defs = True -# https://github.com/python/mypy/issues/5954 +## https://github.com/python/mypy/issues/5954 # disallow_incomplete_defs = True # check_untyped_defs = True # disallow_untyped_decorators = True # no_implicit_optional = True # warn_return_any = True -# disallow_any_generics = True - - [mypy-faulthandler] # https://github.com/python/typeshed/pull/2627 ignore_missing_imports = True From d2751935e0635a1d9f8ce7657ce307d1dd63267e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 23:24:41 +0100 Subject: [PATCH 25/38] Convert *args to list It being a tuple also happens to work, but is somewhat inconsistent. --- qutebrowser/browser/hints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index d8d5a0624..aa5b5f34c 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -737,7 +737,7 @@ class HintManager(QObject): self._context.baseurl = tabbed_browser.current_url() except qtutils.QtValueError: raise cmdexc.CommandError("No URL set for this page yet!") - self._context.args = args + self._context.args = list(args) self._context.group = group try: From 462e07a578c3a9e4a5e325d58d2c490db1c0d6eb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 23:25:13 +0100 Subject: [PATCH 26/38] Use integer division to set font weight QFont::setFontWeight takes an int only - it looks like PyQt accepts a float and just truncates it. That's the behaviour we actually want here, but let's be explicit about it. --- qutebrowser/config/configtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 0314805c0..38d952639 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1119,7 +1119,7 @@ class QtFont(Font): font.setWeight(weight_map[namedweight]) if weight: # based on qcssparser.cpp:setFontWeightFromValue - font.setWeight(min(int(weight) / 8, 99)) + font.setWeight(min(int(weight) // 8, 99)) if size: if size.lower().endswith('pt'): font.setPointSizeF(float(size[:-2])) From e851480a2bcee3080aad785ac4d2f7d193201cd4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 23:30:21 +0100 Subject: [PATCH 27/38] Use empty tuple instead of None While None works (as logging.py does "if args:" consistently), it was never intended to be used like that. --- qutebrowser/utils/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index a1c4f74b7..bbc025515 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -468,7 +468,7 @@ def qt_message_handler(msg_type, context, msg): stack = ''.join(traceback.format_stack()) else: stack = None - record = qt.makeRecord(name, level, context.file, context.line, msg, None, + record = qt.makeRecord(name, level, context.file, context.line, msg, (), None, func, sinfo=stack) qt.handle(record) From 06afd3604e4656ed9ccaceaf43cb6af9bbe11b7c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 23:30:55 +0100 Subject: [PATCH 28/38] Don't try to iterate over auth info This caused a crash when trying to use a proxy with authentication on QtWebKit. It was introduced in 3a5241b642da666e4517a32e0eb945254d86a6da since it was a namedtuple before and was iterable like this. --- qutebrowser/browser/webkit/network/networkmanager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 31e9e815f..4c44ba887 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -297,9 +297,9 @@ class NetworkManager(QNetworkAccessManager): """Called when a proxy needs authentication.""" proxy_id = ProxyId(proxy.type(), proxy.hostName(), proxy.port()) if proxy_id in _proxy_auth_cache: - user, password = _proxy_auth_cache[proxy_id] - authenticator.setUser(user) - authenticator.setPassword(password) + authinfo = _proxy_auth_cache[proxy_id] + authenticator.setUser(authinfo.user) + authenticator.setPassword(authinfo.password) else: msg = '{} says:
{}'.format( html.escape(proxy.hostName()), From f34a8ba1946901e7f484f2253789bb8f65e5217c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 26 Nov 2018 23:32:20 +0100 Subject: [PATCH 29/38] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 41515d287..51647bafd 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -66,6 +66,7 @@ Fixed like GMail. However, the default for `content.cookies.accept` is still `all` to be in line with what other browsers do. - `:navigate` not incrementing in anchors or queries or anchors. +- Crash when trying to use a proxy requiring authentication with QtWebKit. v1.5.2 ------ From 7934dc9a95a26e0a23337871d94576384f18ae92 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 08:37:59 +0100 Subject: [PATCH 30/38] mypy: Fix assert location --- qutebrowser/browser/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 5c97aaf53..a53cfc75e 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1190,11 +1190,11 @@ class CommandDispatcher: if config.val.tabs.wrap: new_idx %= self._count() else: - assert isinstance(index, int) # absolute moving if count is not None: new_idx = count - 1 elif index is not None: + assert isinstance(index, int) new_idx = index - 1 if index >= 0 else index + self._count() else: new_idx = 0 From ec0dc59b06da1074b70fbb724e090e9f4d8384e5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 08:39:05 +0100 Subject: [PATCH 31/38] mypy: Disable warn_unused_configs Looks like mypy's cache causes config sections to be ignored sometimes. --- mypy.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index e002720d3..578a8c1fc 100644 --- a/mypy.ini +++ b/mypy.ini @@ -4,10 +4,11 @@ python_version = 3.6 # --strict -warn_unused_configs = True warn_redundant_casts = True warn_unused_ignores = True disallow_subclassing_any = True +## https://github.com/python/mypy/issues/5957 +# warn_unused_configs = True # disallow_untyped_calls = True # disallow_untyped_defs = True ## https://github.com/python/mypy/issues/5954 From 80808ee9d242768eb2f21055f3859e4284178751 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 08:54:25 +0100 Subject: [PATCH 32/38] Add docstring --- qutebrowser/misc/sessions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index b94566830..e50d803a2 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -40,6 +40,8 @@ from qutebrowser.qt import sip class Sentinel: + """Sentinel value for default argument.""" + pass From c931654a8d09385f713ab09b5348a57e53901ce4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 08:56:51 +0100 Subject: [PATCH 33/38] Remove old exit status values Those weren't used anymore since bc8176ff21a1aab95f5ad3a34c97e1dd098a0242. Since the values were removed in v1.0.0 and never used since then, it seems fine to re-use them in the future. --- qutebrowser/utils/usertypes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 82a95c3fd..cd36db49a 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -230,8 +230,6 @@ class Exit(enum.IntEnum): exception = 2 err_ipc = 3 err_init = 4 - err_config = 5 - err_key_config = 6 # Load status of a tab From 984970e7655aff7c5f9c25673bd587bf279c8271 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 08:59:13 +0100 Subject: [PATCH 34/38] vulture: Ignore reserved value --- scripts/dev/run_vulture.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index 0015539f9..b5c083546 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -84,6 +84,7 @@ def whitelist_generator(): # noqa yield 'qutebrowser.utils.log.QtWarningFilter.filter' yield 'qutebrowser.browser.pdfjs.is_available' yield 'qutebrowser.misc.guiprocess.spawn_output' + yield 'qutebrowser.utils.usertypes.ExitStatus.reserved' yield 'QEvent.posted' yield 'log_stack' # from message.py yield 'propagate' # logging.getLogger('...).propagate = False From 251531e6d08cfaf4f060c1a4336b2c370fb890b3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 08:59:39 +0100 Subject: [PATCH 35/38] manifest: Exclude mypy.ini --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index ff96264aa..3a29ba690 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -32,6 +32,7 @@ include doc/changelog.asciidoc prune tests prune qutebrowser/3rdparty exclude pytest.ini +exclude mypy.ini exclude qutebrowser/javascript/.eslintrc.yaml exclude qutebrowser/javascript/.eslintignore exclude doc/help From 21edeca3e06548995ed117914193e1c7348deeec Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 09:52:54 +0100 Subject: [PATCH 36/38] pylint: disable=unused-import for typing Apparently marking modules as used based on type comments doesn't work with Python 3.7: https://github.com/PyCQA/pylint/issues/2345 https://github.com/python/typed_ast/issues/60 --- .travis.yml | 2 +- qutebrowser/browser/webkit/network/networkmanager.py | 4 ++-- qutebrowser/commands/cmdutils.py | 2 +- qutebrowser/config/config.py | 2 +- qutebrowser/config/configtypes.py | 2 +- qutebrowser/config/websettings.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b918a393..3212142c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ matrix: # env: TESTENV=py35 OSX=yosemite # osx_image: xcode6.4 - os: linux - env: TESTENV=pylint PYTHON=python3.6 + env: TESTENV=pylint - os: linux env: TESTENV=flake8 - os: linux diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 4c44ba887..0c3148ee4 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -21,7 +21,7 @@ import collections import html -import typing +import typing # pylint: disable=unused-import import attr from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl, @@ -29,7 +29,7 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl, from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket from qutebrowser.config import config -from qutebrowser.mainwindow import prompt +from qutebrowser.mainwindow import prompt # pylint: disable=unused-import from qutebrowser.utils import (message, log, usertypes, utils, objreg, urlutils, debug) from qutebrowser.browser import shared diff --git a/qutebrowser/commands/cmdutils.py b/qutebrowser/commands/cmdutils.py index 768cc430e..41e875202 100644 --- a/qutebrowser/commands/cmdutils.py +++ b/qutebrowser/commands/cmdutils.py @@ -20,7 +20,7 @@ """Contains various command utils and a global command dict.""" import inspect -import typing +import typing # pylint: disable=unused-import from qutebrowser.utils import qtutils, log from qutebrowser.commands import command, cmdexc diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 41309d352..59df90ce8 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -33,7 +33,7 @@ from qutebrowser.keyinput import keyutils MYPY = False if MYPY: - from qutebrowser.config import configcache + from qutebrowser.config import configcache # pylint: disable=unused-import # An easy way to access the config from other code via config.val.foo val = typing.cast('ConfigContainer', None) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 38d952639..31eca988e 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -52,7 +52,7 @@ import datetime import functools import operator import json -import typing +import typing # pylint: disable=unused-import import attr import yaml diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 659f793f8..e6d19db7e 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -19,7 +19,7 @@ """Bridge from QWeb(Engine)Settings to our own settings.""" -import typing +import typing # pylint: disable=unused-import from PyQt5.QtGui import QFont From caca60087ff5a2638513641a94a9a389a0186a6c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 13:24:48 +0100 Subject: [PATCH 37/38] Fix coverage --- qutebrowser/config/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 59df90ce8..1d7e34345 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -33,7 +33,8 @@ from qutebrowser.keyinput import keyutils MYPY = False if MYPY: - from qutebrowser.config import configcache # pylint: disable=unused-import + # pylint: disable=unused-import + from qutebrowser.config import configcache # pragma: no cover # An easy way to access the config from other code via config.val.foo val = typing.cast('ConfigContainer', None) From fca2d9dfb4862e94c792b7b4f3973bf7eb71e364 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 27 Nov 2018 16:15:08 +0100 Subject: [PATCH 38/38] mypy: Add to travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3212142c7..5a8965fd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,8 @@ matrix: env: TESTENV=pylint - os: linux env: TESTENV=flake8 + - os: linux + env: TESTENV=mypy - os: linux env: TESTENV=docs addons: