Merge branch 'mypy'
This commit is contained in:
commit
07c632869d
@ -38,9 +38,11 @@ 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
|
||||
env: TESTENV=mypy
|
||||
- os: linux
|
||||
env: TESTENV=docs
|
||||
addons:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
------
|
||||
|
8
misc/requirements/requirements-mypy.txt
Normal file
8
misc/requirements/requirements-mypy.txt
Normal file
@ -0,0 +1,8 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
mypy==0.641
|
||||
mypy-extensions==0.4.1
|
||||
PyQt5==5.11.3
|
||||
PyQt5-sip==4.19.13
|
||||
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5_stubs
|
||||
typed-ast==1.1.0
|
5
misc/requirements/requirements-mypy.txt-raw
Normal file
5
misc/requirements/requirements-mypy.txt-raw
Normal file
@ -0,0 +1,5 @@
|
||||
mypy
|
||||
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5-stubs
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# @wip#
|
7
misc/requirements/requirements-optional.txt
Normal file
7
misc/requirements/requirements-optional.txt
Normal file
@ -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
|
3
misc/requirements/requirements-optional.txt-raw
Normal file
3
misc/requirements/requirements-optional.txt-raw
Normal file
@ -0,0 +1,3 @@
|
||||
hunter
|
||||
cssutils
|
||||
pympler
|
51
mypy.ini
Normal file
51
mypy.ini
Normal file
@ -0,0 +1,51 @@
|
||||
[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
|
||||
|
||||
# --strict
|
||||
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
|
||||
# disallow_incomplete_defs = True
|
||||
# check_untyped_defs = True
|
||||
# disallow_untyped_decorators = True
|
||||
# no_implicit_optional = True
|
||||
# warn_return_any = True
|
||||
|
||||
[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
|
||||
|
||||
[mypy-qutebrowser.browser.webkit.rfc6266]
|
||||
# subclasses dynamic PyPEG2 classes
|
||||
disallow_subclassing_any = False
|
@ -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
|
||||
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
@ -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
|
||||
@ -645,7 +647,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']:
|
||||
@ -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: typing.Union[str, int], 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.
|
||||
@ -1143,6 +1146,8 @@ class CommandDispatcher:
|
||||
self.tab_next()
|
||||
return
|
||||
|
||||
assert isinstance(index, int)
|
||||
|
||||
if index < 0:
|
||||
index = self._count() + index + 1
|
||||
|
||||
@ -1159,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.
|
||||
@ -1188,6 +1194,7 @@ class CommandDispatcher:
|
||||
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
|
||||
@ -1715,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
|
||||
@ -2067,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:
|
||||
|
@ -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
|
||||
@ -1058,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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
import collections
|
||||
import html
|
||||
import typing # pylint: disable=unused-import
|
||||
|
||||
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 # pylint: disable=unused-import
|
||||
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)
|
||||
@ -295,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 = '<b>{}</b> says:<br/>{}'.format(
|
||||
html.escape(proxy.hostName()),
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -17,18 +17,15 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""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 # pylint: disable=unused-import
|
||||
|
||||
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):
|
||||
|
@ -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."""
|
||||
|
@ -22,6 +22,7 @@
|
||||
import copy
|
||||
import contextlib
|
||||
import functools
|
||||
import typing
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
@ -30,11 +31,16 @@ from qutebrowser.utils import utils, log, jinja
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.keyinput import keyutils
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
# 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 = 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 = []
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
"""Implementation of a basic config cache."""
|
||||
|
||||
import typing
|
||||
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
@ -36,14 +38,14 @@ 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:
|
||||
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)
|
||||
|
@ -52,6 +52,7 @@ import datetime
|
||||
import functools
|
||||
import operator
|
||||
import json
|
||||
import typing # pylint: disable=unused-import
|
||||
|
||||
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
|
||||
|
||||
@ -1118,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]))
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
"""Bridge from QWeb(Engine)Settings to our own settings."""
|
||||
|
||||
import typing # pylint: disable=unused-import
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -145,7 +145,7 @@ class StatusBar(QWidget):
|
||||
resized = pyqtSignal('QRect')
|
||||
moved = pyqtSignal('QPoint')
|
||||
_severity = None
|
||||
_color_flags = []
|
||||
_color_flags = None
|
||||
|
||||
STYLESHEET = _generate_stylesheet()
|
||||
|
||||
|
@ -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):
|
||||
@ -339,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)
|
||||
|
||||
@ -397,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':
|
||||
@ -540,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:
|
||||
@ -620,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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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!
|
||||
|
@ -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,14 @@ from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
default = object() # Sentinel value
|
||||
class Sentinel:
|
||||
|
||||
"""Sentinel value for default argument."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
default = Sentinel()
|
||||
|
||||
|
||||
def init(parent=None):
|
||||
@ -109,7 +117,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,9 +512,13 @@ 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,
|
||||
win_id=None):
|
||||
def session_save(self, name: typing.Union[str, Sentinel] = default,
|
||||
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:
|
||||
@ -518,7 +530,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:
|
||||
|
@ -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:
|
||||
@ -312,6 +313,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)
|
||||
|
||||
|
||||
@ -319,18 +321,19 @@ 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:
|
||||
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()])
|
||||
|
||||
|
||||
@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:
|
||||
|
@ -25,4 +25,4 @@
|
||||
try:
|
||||
from PyQt5 import sip
|
||||
except ImportError:
|
||||
import sip
|
||||
import sip # type: ignore
|
||||
|
@ -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
|
||||
@ -467,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)
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -221,10 +221,15 @@ 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
|
||||
|
||||
|
||||
# Load status of a tab
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
11
tox.ini
11
tox.ini
@ -188,3 +188,14 @@ 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-optional.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-pyqt.txt
|
||||
-r{toxinidir}/misc/requirements/requirements-mypy.txt
|
||||
commands =
|
||||
{envpython} -m mypy qutebrowser {posargs}
|
||||
|
Loading…
Reference in New Issue
Block a user