Add types for most of qutebrowser.config

This commit is contained in:
Florian Bruhin 2018-12-03 12:14:33 +01:00
parent b0ae4deac8
commit 208d3db475
9 changed files with 385 additions and 216 deletions

View File

@ -61,3 +61,39 @@ disallow_incomplete_defs = True
[mypy-qutebrowser.commands.cmdutils]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.config]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configcache]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configcommands]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configdata]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configdiff]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configexc]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configfiles]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configinit]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.configutils]
disallow_untyped_defs = True
disallow_incomplete_defs = True

View File

@ -23,18 +23,21 @@ import copy
import contextlib
import functools
import typing
from typing import Any
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from qutebrowser.config import configdata, configexc, configutils
from qutebrowser.utils import utils, log, jinja
from qutebrowser.utils import utils, log, jinja, urlmatch
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
if MYPY: # pragma: no cover
# pylint: disable=unused-import,useless-suppression
from typing import Tuple, MutableMapping
from qutebrowser.config import configcache, configfiles
from qutebrowser.misc import savemanager
# An easy way to access the config from other code via config.val.foo
val = typing.cast('ConfigContainer', None)
@ -61,7 +64,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
_function: Whether a function rather than a method is decorated.
"""
def __init__(self, option, function=False):
def __init__(self, option: str, function: bool = False) -> None:
"""Save decorator arguments.
Gets called on parse-time with the decorator arguments.
@ -74,7 +77,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
self._function = function
change_filters.append(self)
def validate(self):
def validate(self) -> None:
"""Make sure the configured option or prefix exists.
We can't do this in __init__ as configdata isn't ready yet.
@ -83,7 +86,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
not configdata.is_valid_prefix(self._option)):
raise configexc.NoOptionError(self._option)
def _check_match(self, option):
def _check_match(self, option: typing.Optional[str]) -> bool:
"""Check if the given option matches the filter."""
if option is None:
# Called directly, not from a config change event.
@ -96,7 +99,7 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
else:
return False
def __call__(self, func):
def __call__(self, func: typing.Callable) -> typing.Callable:
"""Filter calls to the decorated function.
Gets called when a function should be decorated.
@ -114,20 +117,21 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
"""
if self._function:
@functools.wraps(func)
def wrapper(option=None):
def func_wrapper(option: str = None) -> typing.Any:
"""Call the underlying function."""
if self._check_match(option):
return func()
return None
return func_wrapper
else:
@functools.wraps(func)
def wrapper(wrapper_self, option=None):
def meth_wrapper(wrapper_self: typing.Any,
option: str = None) -> typing.Any:
"""Call the underlying function."""
if self._check_match(option):
return func(wrapper_self)
return None
return wrapper
return meth_wrapper
class KeyConfig:
@ -140,17 +144,22 @@ class KeyConfig:
_config: The Config object to be used.
"""
def __init__(self, config):
_ReverseBindings = typing.Dict[str, typing.MutableSequence[str]]
def __init__(self, config: 'Config') -> None:
self._config = config
def _validate(self, key, mode):
def _validate(self, key: keyutils.KeySequence, mode: str) -> None:
"""Validate the given key and mode."""
# Catch old usage of this code
assert isinstance(key, keyutils.KeySequence), key
if mode not in configdata.DATA['bindings.default'].default:
raise configexc.KeybindingError("Invalid mode {}!".format(mode))
def get_bindings_for(self, mode):
def get_bindings_for(
self,
mode: str
) -> typing.Dict[keyutils.KeySequence, str]:
"""Get the combined bindings for the given mode."""
bindings = dict(val.bindings.default[mode])
for key, binding in val.bindings.commands[mode].items():
@ -160,9 +169,9 @@ class KeyConfig:
bindings[key] = binding
return bindings
def get_reverse_bindings_for(self, mode):
def get_reverse_bindings_for(self, mode: str) -> '_ReverseBindings':
"""Get a dict of commands to a list of bindings for the mode."""
cmd_to_keys = {}
cmd_to_keys = {} # type: KeyConfig._ReverseBindings
bindings = self.get_bindings_for(mode)
for seq, full_cmd in sorted(bindings.items()):
for cmd in full_cmd.split(';;'):
@ -175,7 +184,10 @@ class KeyConfig:
cmd_to_keys[cmd].insert(0, str(seq))
return cmd_to_keys
def get_command(self, key, mode, default=False):
def get_command(self,
key: keyutils.KeySequence,
mode: str,
default: bool = False) -> str:
"""Get the command for a given key (or None)."""
self._validate(key, mode)
if default:
@ -184,7 +196,11 @@ class KeyConfig:
bindings = self.get_bindings_for(mode)
return bindings.get(key, None)
def bind(self, key, command, *, mode, save_yaml=False):
def bind(self,
key: keyutils.KeySequence,
command: str, *,
mode: str,
save_yaml: bool = False) -> None:
"""Add a new binding from key to command."""
if command is not None and not command.strip():
raise configexc.KeybindingError(
@ -192,8 +208,8 @@ class KeyConfig:
'mode'.format(key, mode))
self._validate(key, mode)
log.keyboard.vdebug("Adding binding {} -> {} in mode {}.".format(
key, command, mode))
log.keyboard.vdebug( # type: ignore
"Adding binding {} -> {} in mode {}.".format(key, command, mode))
bindings = self._config.get_mutable_obj('bindings.commands')
if mode not in bindings:
@ -201,7 +217,10 @@ class KeyConfig:
bindings[mode][str(key)] = command
self._config.update_mutables(save_yaml=save_yaml)
def bind_default(self, key, *, mode='normal', save_yaml=False):
def bind_default(self,
key: keyutils.KeySequence, *,
mode: str = 'normal',
save_yaml: bool = False) -> None:
"""Restore a default keybinding."""
self._validate(key, mode)
@ -213,7 +232,10 @@ class KeyConfig:
"Can't find binding '{}' in {} mode".format(key, mode))
self._config.update_mutables(save_yaml=save_yaml)
def unbind(self, key, *, mode='normal', save_yaml=False):
def unbind(self,
key: keyutils.KeySequence, *,
mode: str = 'normal',
save_yaml: bool = False) -> None:
"""Unbind the given key in the given mode."""
self._validate(key, mode)
@ -254,24 +276,27 @@ class Config(QObject):
MUTABLE_TYPES = (dict, list)
changed = pyqtSignal(str)
def __init__(self, yaml_config, parent=None):
def __init__(self,
yaml_config: 'configfiles.YamlConfig',
parent: QObject = None) -> None:
super().__init__(parent)
self.changed.connect(_render_stylesheet.cache_clear)
self._mutables = {}
self._mutables = {} # type: MutableMapping[str, Tuple[Any, Any]]
self._yaml = yaml_config
self._init_values()
def _init_values(self):
def _init_values(self) -> None:
"""Populate the self._values dict."""
self._values = {}
self._values = {} # type: typing.Mapping
for name, opt in configdata.DATA.items():
self._values[name] = configutils.Values(opt)
def __iter__(self):
def __iter__(self) -> typing.Iterator[configutils.Values]:
"""Iterate over configutils.Values items."""
yield from self._values.values()
def init_save_manager(self, save_manager):
def init_save_manager(self,
save_manager: 'savemanager.SaveManager') -> None:
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
@ -279,7 +304,10 @@ class Config(QObject):
"""
self._yaml.init_save_manager(save_manager)
def _set_value(self, opt, value, pattern=None):
def _set_value(self,
opt: 'configdata.Option',
value: Any,
pattern: urlmatch.UrlPattern = None) -> None:
"""Set the given option to the given value."""
if not isinstance(objects.backend, objects.NoBackend):
if objects.backend not in opt.backends:
@ -294,12 +322,12 @@ class Config(QObject):
log.config.debug("Config option changed: {} = {}".format(
opt.name, value))
def _check_yaml(self, opt, save_yaml):
def _check_yaml(self, opt: 'configdata.Option', save_yaml: bool) -> None:
"""Make sure the given option may be set in autoconfig.yml."""
if save_yaml and opt.no_autoconfig:
raise configexc.NoAutoconfigError(opt.name)
def read_yaml(self):
def read_yaml(self) -> None:
"""Read the YAML settings from self._yaml."""
self._yaml.load()
for values in self._yaml:
@ -307,7 +335,7 @@ class Config(QObject):
self._set_value(values.opt, scoped.value,
pattern=scoped.pattern)
def get_opt(self, name):
def get_opt(self, name: str) -> 'configdata.Option':
"""Get a configdata.Option object for the given setting."""
try:
return configdata.DATA[name]
@ -318,7 +346,10 @@ class Config(QObject):
name, deleted=deleted, renamed=renamed)
raise exception from None
def get(self, name, url=None, *, fallback=True):
def get(self,
name: str,
url: QUrl = None, *,
fallback: bool = True) -> Any:
"""Get the given setting converted for Python code.
Args:
@ -328,7 +359,7 @@ class Config(QObject):
obj = self.get_obj(name, url=url, fallback=fallback)
return opt.typ.to_py(obj)
def _maybe_copy(self, value):
def _maybe_copy(self, value: Any) -> Any:
"""Copy the value if it could potentially be mutated."""
if isinstance(value, self.MUTABLE_TYPES):
# For mutable objects, create a copy so we don't accidentally
@ -339,7 +370,10 @@ class Config(QObject):
assert value.__hash__ is not None, value
return value
def get_obj(self, name, *, url=None, fallback=True):
def get_obj(self,
name: str, *,
url: QUrl = None,
fallback: bool = True) -> Any:
"""Get the given setting as object (for YAML/config.py).
Note that the returned values are not watched for mutation.
@ -349,7 +383,10 @@ class Config(QObject):
value = self._values[name].get_for_url(url, fallback=fallback)
return self._maybe_copy(value)
def get_obj_for_pattern(self, name, *, pattern):
def get_obj_for_pattern(
self, name: str, *,
pattern: typing.Optional[urlmatch.UrlPattern]
) -> Any:
"""Get the given setting as object (for YAML/config.py).
This gets the overridden value for a given pattern, or
@ -359,7 +396,8 @@ class Config(QObject):
value = self._values[name].get_for_pattern(pattern, fallback=False)
return self._maybe_copy(value)
def get_mutable_obj(self, name, *, pattern=None):
def get_mutable_obj(self, name: str, *,
pattern: urlmatch.UrlPattern = None) -> Any:
"""Get an object which can be mutated, e.g. in a config.py.
If a pattern is given, return the value for that pattern.
@ -384,7 +422,8 @@ class Config(QObject):
return copy_value
def get_str(self, name, *, pattern=None):
def get_str(self, name: str, *,
pattern: urlmatch.UrlPattern = None) -> str:
"""Get the given setting as string.
If a pattern is given, get the setting for the given pattern or
@ -395,7 +434,10 @@ class Config(QObject):
value = values.get_for_pattern(pattern)
return opt.typ.to_str(value)
def set_obj(self, name, value, *, pattern=None, save_yaml=False):
def set_obj(self, name: str,
value: Any, *,
pattern: urlmatch.UrlPattern = None,
save_yaml: bool = False) -> None:
"""Set the given setting from a YAML/config.py object.
If save_yaml=True is given, store the new value to YAML.
@ -406,7 +448,10 @@ class Config(QObject):
if save_yaml:
self._yaml.set_obj(name, value, pattern=pattern)
def set_str(self, name, value, *, pattern=None, save_yaml=False):
def set_str(self, name: str,
value: str, *,
pattern: urlmatch.UrlPattern = None,
save_yaml: bool = False) -> None:
"""Set the given setting from a string.
If save_yaml=True is given, store the new value to YAML.
@ -421,7 +466,9 @@ class Config(QObject):
if save_yaml:
self._yaml.set_obj(name, converted, pattern=pattern)
def unset(self, name, *, save_yaml=False, pattern=None):
def unset(self, name: str, *,
save_yaml: bool = False,
pattern: urlmatch.UrlPattern = None) -> None:
"""Set the given setting back to its default."""
opt = self.get_opt(name)
self._check_yaml(opt, save_yaml)
@ -432,7 +479,7 @@ class Config(QObject):
if save_yaml:
self._yaml.unset(name, pattern=pattern)
def clear(self, *, save_yaml=False):
def clear(self, *, save_yaml: bool = False) -> None:
"""Clear all settings in the config.
If save_yaml=True is given, also remove all customization from the YAML
@ -446,7 +493,7 @@ class Config(QObject):
if save_yaml:
self._yaml.clear()
def update_mutables(self, *, save_yaml=False):
def update_mutables(self, *, save_yaml: bool = False) -> None:
"""Update mutable settings if they changed.
Every time someone calls get_obj() on a mutable object, we save a
@ -461,7 +508,7 @@ class Config(QObject):
self.set_obj(name, new_value, save_yaml=save_yaml)
self._mutables = {}
def dump_userconfig(self):
def dump_userconfig(self) -> str:
"""Get the part of the config which was changed by the user.
Return:
@ -490,7 +537,10 @@ class ConfigContainer:
_pattern: The URL pattern to be used.
"""
def __init__(self, config, configapi=None, prefix='', pattern=None):
def __init__(self, config: Config,
configapi: 'configfiles.ConfigAPI' = None,
prefix: str = '',
pattern: urlmatch.UrlPattern = None) -> None:
self._config = config
self._prefix = prefix
self._configapi = configapi
@ -498,13 +548,13 @@ class ConfigContainer:
if configapi is None and pattern is not None:
raise TypeError("Can't use pattern without configapi!")
def __repr__(self):
def __repr__(self) -> str:
return utils.get_repr(self, constructor=True, config=self._config,
configapi=self._configapi, prefix=self._prefix,
pattern=self._pattern)
@contextlib.contextmanager
def _handle_error(self, action, name):
def _handle_error(self, action: str, name: str) -> typing.Iterator[None]:
try:
yield
except configexc.Error as e:
@ -513,7 +563,7 @@ class ConfigContainer:
text = "While {} '{}'".format(action, name)
self._configapi.errors.append(configexc.ConfigErrorDesc(text, e))
def __getattr__(self, attr):
def __getattr__(self, attr: str) -> Any:
"""Get an option or a new ConfigContainer with the added prefix.
If we get an option which exists, we return the value for it.
@ -540,7 +590,7 @@ class ConfigContainer:
return self._config.get_mutable_obj(
name, pattern=self._pattern)
def __setattr__(self, attr, value):
def __setattr__(self, attr: str, value: Any) -> None:
"""Set the given option in the config."""
if attr.startswith('_'):
super().__setattr__(attr, value)
@ -550,7 +600,7 @@ class ConfigContainer:
with self._handle_error('setting', name):
self._config.set_obj(name, value, pattern=self._pattern)
def _join(self, attr):
def _join(self, attr: str) -> str:
"""Get the prefix joined with the given attribute."""
if self._prefix:
return '{}.{}'.format(self._prefix, attr)
@ -558,8 +608,10 @@ class ConfigContainer:
return attr
def set_register_stylesheet(obj, *, stylesheet=None, update=True):
"""Set the stylesheet for an object based on it's STYLESHEET attribute.
def set_register_stylesheet(obj: QObject, *,
stylesheet: str = None,
update: bool = True) -> None:
"""Set the stylesheet for an object.
Also, register an update when the config is changed.
@ -574,7 +626,7 @@ def set_register_stylesheet(obj, *, stylesheet=None, update=True):
@functools.lru_cache()
def _render_stylesheet(stylesheet):
def _render_stylesheet(stylesheet: str) -> str:
"""Render the given stylesheet jinja template."""
with jinja.environment.no_autoescape():
template = jinja.environment.from_string(stylesheet)
@ -590,7 +642,9 @@ class StyleSheetObserver(QObject):
_stylesheet: The stylesheet template to use.
"""
def __init__(self, obj, stylesheet, update):
def __init__(self, obj: QObject,
stylesheet: typing.Optional[str],
update: bool) -> None:
super().__init__()
self._obj = obj
self._update = update
@ -599,11 +653,11 @@ class StyleSheetObserver(QObject):
if self._update:
self.setParent(self._obj)
if stylesheet is None:
self._stylesheet = obj.STYLESHEET
self._stylesheet = obj.STYLESHEET # type: str
else:
self._stylesheet = stylesheet
def _get_stylesheet(self):
def _get_stylesheet(self) -> str:
"""Format a stylesheet based on a template.
Return:
@ -612,19 +666,15 @@ class StyleSheetObserver(QObject):
return _render_stylesheet(self._stylesheet)
@pyqtSlot()
def _update_stylesheet(self):
def _update_stylesheet(self) -> None:
"""Update the stylesheet for obj."""
self._obj.setStyleSheet(self._get_stylesheet())
def register(self):
"""Do a first update and listen for more.
Args:
update: if False, don't listen for future updates.
"""
def register(self) -> None:
"""Do a first update and listen for more."""
qss = self._get_stylesheet()
log.config.vdebug("stylesheet for {}: {}".format(
self._obj.__class__.__name__, qss))
log.config.vdebug( # type: ignore
"stylesheet for {}: {}".format(self._obj.__class__.__name__, qss))
self._obj.setStyleSheet(qss)
if self._update:
instance.changed.connect(self._update_stylesheet)

View File

@ -19,6 +19,7 @@
"""Commands related to the configuration."""
import typing
import os.path
import contextlib
@ -31,24 +32,34 @@ from qutebrowser.config import configtypes, configexc, configfiles, configdata
from qutebrowser.misc import editor
from qutebrowser.keyinput import keyutils
MYPY = False
if MYPY: # pragma: no cover
# pylint: disable=unused-import,useless-suppression
from qutebrowser.config.config import Config, KeyConfig
class ConfigCommands:
"""qutebrowser commands related to the configuration."""
def __init__(self, config, keyconfig):
def __init__(self,
config: 'Config',
keyconfig: 'KeyConfig') -> None:
self._config = config
self._keyconfig = keyconfig
@contextlib.contextmanager
def _handle_config_error(self):
def _handle_config_error(self) -> typing.Iterator[None]:
"""Catch errors in set_command and raise CommandError."""
try:
yield
except configexc.Error as e:
raise cmdutils.CommandError(str(e))
def _parse_pattern(self, pattern):
def _parse_pattern(
self,
pattern: typing.Optional[str]
) -> typing.Optional[urlmatch.UrlPattern]:
"""Parse a pattern string argument to a pattern."""
if pattern is None:
return None
@ -59,14 +70,15 @@ class ConfigCommands:
raise cmdutils.CommandError("Error while parsing {}: {}"
.format(pattern, str(e)))
def _parse_key(self, key):
def _parse_key(self, key: str) -> keyutils.KeySequence:
"""Parse a key argument."""
try:
return keyutils.KeySequence.parse(key)
except keyutils.KeyParseError as e:
raise cmdutils.CommandError(str(e))
def _print_value(self, option, pattern):
def _print_value(self, option: str,
pattern: typing.Optional[urlmatch.UrlPattern]) -> None:
"""Print the value of the given option."""
with self._handle_config_error():
value = self._config.get_str(option, pattern=pattern)
@ -81,8 +93,9 @@ class ConfigCommands:
@cmdutils.argument('value', completion=configmodel.value)
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
@cmdutils.argument('pattern', flag='u')
def set(self, win_id, option=None, value=None, temp=False, print_=False,
*, pattern=None):
def set(self, win_id: int, option: str = None, value: str = None,
temp: bool = False, print_: bool = False,
*, pattern: str = None) -> None:
"""Set an option.
If the option name ends with '?' or no value is provided, the
@ -108,28 +121,28 @@ class ConfigCommands:
raise cmdutils.CommandError("Toggling values was moved to the "
":config-cycle command")
pattern = self._parse_pattern(pattern)
parsed_pattern = self._parse_pattern(pattern)
if option.endswith('?') and option != '?':
self._print_value(option[:-1], pattern=pattern)
self._print_value(option[:-1], pattern=parsed_pattern)
return
with self._handle_config_error():
if value is None:
self._print_value(option, pattern=pattern)
self._print_value(option, pattern=parsed_pattern)
else:
self._config.set_str(option, value, pattern=pattern,
self._config.set_str(option, value, pattern=parsed_pattern,
save_yaml=not temp)
if print_:
self._print_value(option, pattern=pattern)
self._print_value(option, pattern=parsed_pattern)
@cmdutils.register(instance='config-commands', maxsplit=1,
no_cmd_split=True, no_replace_variables=True)
@cmdutils.argument('command', completion=configmodel.bind)
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
def bind(self, win_id, key=None, command=None, *, mode='normal',
default=False):
def bind(self, win_id: str, key: str = None, command: str = None, *,
mode: str = 'normal', default: bool = False) -> None:
"""Bind a key to a command.
If no command is given, show the current binding for the given key.
@ -174,7 +187,7 @@ class ConfigCommands:
self._keyconfig.bind(seq, command, mode=mode, save_yaml=True)
@cmdutils.register(instance='config-commands')
def unbind(self, key, *, mode='normal'):
def unbind(self, key: str, *, mode: str = 'normal') -> None:
"""Unbind a keychain.
Args:
@ -191,8 +204,9 @@ class ConfigCommands:
@cmdutils.argument('option', completion=configmodel.option)
@cmdutils.argument('values', completion=configmodel.value)
@cmdutils.argument('pattern', flag='u')
def config_cycle(self, option, *values, pattern=None, temp=False,
print_=False):
def config_cycle(self, option: str, *values: str,
pattern: str = None,
temp: bool = False, print_: bool = False) -> None:
"""Cycle an option between multiple values.
Args:
@ -202,15 +216,15 @@ class ConfigCommands:
temp: Set value temporarily until qutebrowser is closed.
print_: Print the value after setting.
"""
pattern = self._parse_pattern(pattern)
parsed_pattern = self._parse_pattern(pattern)
with self._handle_config_error():
opt = self._config.get_opt(option)
old_value = self._config.get_obj_for_pattern(option,
pattern=pattern)
old_value = self._config.get_obj_for_pattern(
option, pattern=parsed_pattern)
if not values and isinstance(opt.typ, configtypes.Bool):
values = ['true', 'false']
values = ('true', 'false')
if len(values) < 2:
raise cmdutils.CommandError("Need at least two values for "
@ -219,25 +233,25 @@ class ConfigCommands:
# Use the next valid value from values, or the first if the current
# value does not appear in the list
with self._handle_config_error():
values = [opt.typ.from_str(val) for val in values]
cycle_values = [opt.typ.from_str(val) for val in values]
try:
idx = values.index(old_value)
idx = (idx + 1) % len(values)
value = values[idx]
idx = cycle_values.index(old_value)
idx = (idx + 1) % len(cycle_values)
value = cycle_values[idx]
except ValueError:
value = values[0]
value = cycle_values[0]
with self._handle_config_error():
self._config.set_obj(option, value, pattern=pattern,
self._config.set_obj(option, value, pattern=parsed_pattern,
save_yaml=not temp)
if print_:
self._print_value(option, pattern=pattern)
self._print_value(option, pattern=parsed_pattern)
@cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.customized_option)
def config_unset(self, option, temp=False):
def config_unset(self, option: str, temp: bool = False) -> None:
"""Unset an option.
This sets an option back to its default and removes it from
@ -252,7 +266,8 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.list_option)
def config_list_add(self, option, value, temp=False):
def config_list_add(self, option: str, value: str,
temp: bool = False) -> None:
"""Append a value to a config option that is a list.
Args:
@ -273,7 +288,8 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.dict_option)
def config_dict_add(self, option, key, value, temp=False, replace=False):
def config_dict_add(self, option: str, key: str, value: str,
temp: bool = False, replace: bool = False) -> None:
"""Add a key/value pair to a dictionary option.
Args:
@ -302,7 +318,8 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.list_option)
def config_list_remove(self, option, value, temp=False):
def config_list_remove(self, option: str, value: str,
temp: bool = False) -> None:
"""Remove a value from a list.
Args:
@ -329,7 +346,8 @@ class ConfigCommands:
@cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.dict_option)
def config_dict_remove(self, option, key, temp=False):
def config_dict_remove(self, option: str, key: str,
temp: bool = False) -> None:
"""Remove a key from a dict.
Args:
@ -354,7 +372,7 @@ class ConfigCommands:
self._config.update_mutables(save_yaml=not temp)
@cmdutils.register(instance='config-commands')
def config_clear(self, save=False):
def config_clear(self, save: bool = False) -> None:
"""Set all settings back to their default.
Args:
@ -364,7 +382,7 @@ class ConfigCommands:
self._config.clear(save_yaml=save)
@cmdutils.register(instance='config-commands')
def config_source(self, filename=None, clear=False):
def config_source(self, filename: str = None, clear: bool = False) -> None:
"""Read a config.py file.
Args:
@ -386,13 +404,13 @@ class ConfigCommands:
raise cmdutils.CommandError(e)
@cmdutils.register(instance='config-commands')
def config_edit(self, no_source=False):
def config_edit(self, no_source: bool = False) -> None:
"""Open the config.py file in the editor.
Args:
no_source: Don't re-source the config file after editing.
"""
def on_file_updated():
def on_file_updated() -> None:
"""Source the new config when editing finished.
This can't use cmdutils.CommandError as it's run async.
@ -410,7 +428,8 @@ class ConfigCommands:
ed.edit_file(filename)
@cmdutils.register(instance='config-commands')
def config_write_py(self, filename=None, force=False, defaults=False):
def config_write_py(self, filename: str = None,
force: bool = False, defaults: bool = False) -> None:
"""Write the current configuration to a config.py file.
Args:
@ -429,13 +448,13 @@ class ConfigCommands:
raise cmdutils.CommandError("{} already exists - use --force to "
"overwrite!".format(filename))
options = [] # type: typing.List
if defaults:
options = [(None, opt, opt.default)
for _name, opt in sorted(configdata.DATA.items())]
bindings = dict(configdata.DATA['bindings.default'].default)
commented = True
else:
options = []
for values in self._config:
for scoped in values:
options.append((scoped.pattern, values.opt, scoped.value))

View File

@ -24,14 +24,18 @@ Module attributes:
DATA: A dict of Option objects after init() has been called.
"""
import typing
from typing import Optional # pylint: disable=unused-import
import functools
import attr
from qutebrowser.config import configtypes
from qutebrowser.utils import usertypes, qtutils, utils
DATA = None
MIGRATIONS = None
DATA = typing.cast(typing.Mapping[str, 'Option'], None)
MIGRATIONS = typing.cast('Migrations', None)
_BackendDict = typing.Mapping[str, typing.Union[str, bool]]
@attr.s
@ -42,15 +46,15 @@ class Option:
Note that this is just an option which exists, with no value associated.
"""
name = attr.ib()
typ = attr.ib()
default = attr.ib()
backends = attr.ib()
raw_backends = attr.ib()
description = attr.ib()
supports_pattern = attr.ib(default=False)
restart = attr.ib(default=False)
no_autoconfig = attr.ib(default=False)
name = attr.ib() # type: str
typ = attr.ib() # type: configtypes.BaseType
default = attr.ib() # type: typing.Any
backends = attr.ib() # type: typing.Iterable[usertypes.Backend]
raw_backends = attr.ib() # type: Optional[typing.Mapping[str, bool]]
description = attr.ib() # type: str
supports_pattern = attr.ib(default=False) # type: bool
restart = attr.ib(default=False) # type: bool
no_autoconfig = attr.ib(default=False) # type: bool
@attr.s
@ -63,11 +67,13 @@ class Migrations:
deleted: A list of option names which have been removed.
"""
renamed = attr.ib(default=attr.Factory(dict))
deleted = attr.ib(default=attr.Factory(list))
renamed = attr.ib(
default=attr.Factory(dict)) # type: typing.Dict[str, str]
deleted = attr.ib(
default=attr.Factory(list)) # type: typing.List[str]
def _raise_invalid_node(name, what, node):
def _raise_invalid_node(name: str, what: str, node: typing.Any) -> None:
"""Raise an exception for an invalid configdata YAML node.
Args:
@ -79,13 +85,16 @@ def _raise_invalid_node(name, what, node):
name, what, node))
def _parse_yaml_type(name, node):
def _parse_yaml_type(
name: str,
node: typing.Union[str, typing.Mapping[str, typing.Any]],
) -> configtypes.BaseType:
if isinstance(node, str):
# e.g:
# type: Bool
# -> create the type object without any arguments
type_name = node
kwargs = {}
kwargs = {} # type: typing.MutableMapping[str, typing.Any]
elif isinstance(node, dict):
# e.g:
# type:
@ -123,7 +132,10 @@ def _parse_yaml_type(name, node):
type_name, node, e))
def _parse_yaml_backends_dict(name, node):
def _parse_yaml_backends_dict(
name: str,
node: _BackendDict,
) -> typing.Sequence[usertypes.Backend]:
"""Parse a dict definition for backends.
Example:
@ -160,7 +172,10 @@ def _parse_yaml_backends_dict(name, node):
return backends
def _parse_yaml_backends(name, node):
def _parse_yaml_backends(
name: str,
node: typing.Union[None, str, _BackendDict],
) -> typing.Sequence[usertypes.Backend]:
"""Parse a backend node in the yaml.
It can have one of those four forms:
@ -187,7 +202,9 @@ def _parse_yaml_backends(name, node):
raise utils.Unreachable
def _read_yaml(yaml_data):
def _read_yaml(
yaml_data: str,
) -> typing.Tuple[typing.Mapping[str, Option], Migrations]:
"""Read config data from a YAML file.
Args:
@ -249,12 +266,12 @@ def _read_yaml(yaml_data):
@functools.lru_cache(maxsize=256)
def is_valid_prefix(prefix):
def is_valid_prefix(prefix: str) -> bool:
"""Check whether the given prefix is a valid prefix for some option."""
return any(key.startswith(prefix + '.') for key in DATA)
def init():
def init() -> None:
"""Initialize configdata from the YAML file."""
global DATA, MIGRATIONS
DATA, MIGRATIONS = _read_yaml(utils.read_file('config/configdata.yml'))

View File

@ -19,6 +19,7 @@
"""Code to show a diff of the legacy config format."""
import typing # pylint: disable=unused-import,useless-suppression
import difflib
import os.path
@ -727,10 +728,10 @@ scroll right
"""
def get_diff():
def get_diff() -> str:
"""Get a HTML diff for the old config files."""
old_conf_lines = []
old_key_lines = []
old_conf_lines = [] # type: typing.MutableSequence[str]
old_key_lines = [] # type: typing.MutableSequence[str]
for filename, dest in [('qutebrowser.conf', old_conf_lines),
('keys.conf', old_key_lines)]:

View File

@ -19,9 +19,10 @@
"""Exceptions related to config parsing."""
import typing
import attr
from qutebrowser.utils import jinja
from qutebrowser.utils import jinja, usertypes
class Error(Exception):
@ -33,7 +34,7 @@ class NoAutoconfigError(Error):
"""Raised when this option can't be set in autoconfig.yml."""
def __init__(self, name):
def __init__(self, name: str) -> None:
super().__init__("The {} setting can only be set in config.py!"
.format(name))
@ -42,7 +43,11 @@ class BackendError(Error):
"""Raised when this setting is unavailable with the current backend."""
def __init__(self, name, backend, raw_backends):
def __init__(
self, name: str,
backend: usertypes.Backend,
raw_backends: typing.Optional[typing.Mapping[str, bool]]
) -> None:
if raw_backends is None or not raw_backends[backend.name]:
msg = ("The {} setting is not available with the {} backend!"
.format(name, backend.name))
@ -57,7 +62,7 @@ class NoPatternError(Error):
"""Raised when the given setting does not support URL patterns."""
def __init__(self, name):
def __init__(self, name: str) -> None:
super().__init__("The {} setting does not support URL patterns!"
.format(name))
@ -71,7 +76,7 @@ class ValidationError(Error):
msg: Additional error message.
"""
def __init__(self, value, msg):
def __init__(self, value: typing.Any, msg: str) -> None:
super().__init__("Invalid value '{}' - {}".format(value, msg))
self.option = None
@ -85,7 +90,9 @@ class NoOptionError(Error):
"""Raised when an option was not found."""
def __init__(self, option, *, deleted=False, renamed=None):
def __init__(self, option: str, *,
deleted: bool = False,
renamed: str = None) -> None:
if deleted:
assert renamed is None
suffix = ' (this option was removed from qutebrowser)'
@ -109,18 +116,18 @@ class ConfigErrorDesc:
traceback: The formatted traceback of the exception.
"""
text = attr.ib()
exception = attr.ib()
traceback = attr.ib(None)
text = attr.ib() # type: str
exception = attr.ib() # type: typing.Union[str, Exception]
traceback = attr.ib(None) # type: str
def __str__(self):
def __str__(self) -> str:
if self.traceback:
return '{} - {}: {}'.format(self.text,
self.exception.__class__.__name__,
self.exception)
return '{}: {}'.format(self.text, self.exception)
def with_text(self, text):
def with_text(self, text: str) -> 'ConfigErrorDesc':
"""Get a new ConfigErrorDesc with the given text appended."""
return self.__class__(text='{} ({})'.format(self.text, text),
exception=self.exception,
@ -131,13 +138,15 @@ class ConfigFileErrors(Error):
"""Raised when multiple errors occurred inside the config."""
def __init__(self, basename, errors):
def __init__(self,
basename: str,
errors: typing.Sequence[ConfigErrorDesc]) -> None:
super().__init__("Errors occurred while reading {}:\n{}".format(
basename, '\n'.join(' {}'.format(e) for e in errors)))
self.basename = basename
self.errors = errors
def to_html(self):
def to_html(self) -> str:
"""Get the error texts as a HTML snippet."""
template = jinja.environment.from_string("""
Errors occurred while reading {{ basename }}:

View File

@ -27,6 +27,7 @@ import textwrap
import traceback
import configparser
import contextlib
import typing
import yaml
from PyQt5.QtCore import pyqtSignal, QObject, QSettings
@ -36,16 +37,21 @@ from qutebrowser.config import configexc, config, configdata, configutils
from qutebrowser.keyinput import keyutils
from qutebrowser.utils import standarddir, utils, qtutils, log, urlmatch
MYPY = False
if MYPY:
# pylint: disable=unused-import, useless-suppression
from qutebrowser.misc import savemanager
# The StateConfig instance
state = None
state = typing.cast('StateConfig', None)
class StateConfig(configparser.ConfigParser):
"""The "state" file saving various application state."""
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._filename = os.path.join(standarddir.data(), 'state')
self.read(self._filename, encoding='utf-8')
@ -59,7 +65,8 @@ class StateConfig(configparser.ConfigParser):
for key in deleted_keys:
self['general'].pop(key, None)
def init_save_manager(self, save_manager):
def init_save_manager(self,
save_manager: 'savemanager.SaveManager') -> None:
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
@ -67,7 +74,7 @@ class StateConfig(configparser.ConfigParser):
"""
save_manager.add_saveable('state-config', self._save)
def _save(self):
def _save(self) -> None:
"""Save the state file to the configured location."""
with open(self._filename, 'w', encoding='utf-8') as f:
self.write(f)
@ -84,17 +91,20 @@ class YamlConfig(QObject):
VERSION = 2
changed = pyqtSignal()
def __init__(self, parent=None):
_SettingsType = typing.Dict[str, typing.Dict[str, typing.Any]]
def __init__(self, parent: QObject = None) -> None:
super().__init__(parent)
self._filename = os.path.join(standarddir.config(auto=True),
'autoconfig.yml')
self._dirty = None
self._dirty = False
self._values = {}
self._values = {} # type: typing.Dict[str, configutils.Values]
for name, opt in configdata.DATA.items():
self._values[name] = configutils.Values(opt)
def init_save_manager(self, save_manager):
def init_save_manager(self,
save_manager: 'savemanager.SaveManager') -> None:
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
@ -102,21 +112,21 @@ class YamlConfig(QObject):
"""
save_manager.add_saveable('yaml-config', self._save, self.changed)
def __iter__(self):
def __iter__(self) -> typing.Iterator[configutils.Values]:
"""Iterate over configutils.Values items."""
yield from self._values.values()
def _mark_changed(self):
def _mark_changed(self) -> None:
"""Mark the YAML config as changed."""
self._dirty = True
self.changed.emit()
def _save(self):
def _save(self) -> None:
"""Save the settings to the YAML file if they've changed."""
if not self._dirty:
return
settings = {}
settings = {} # type: YamlConfig._SettingsType
for name, values in sorted(self._values.items()):
if not values:
continue
@ -135,7 +145,10 @@ class YamlConfig(QObject):
""".lstrip('\n')))
utils.yaml_dump(data, f)
def _pop_object(self, yaml_data, key, typ):
def _pop_object(self,
yaml_data: typing.Any,
key: str,
typ: type) -> typing.Any:
"""Get a global object from the given data."""
if not isinstance(yaml_data, dict):
desc = configexc.ConfigErrorDesc("While loading data",
@ -158,7 +171,7 @@ class YamlConfig(QObject):
return data
def load(self):
def load(self) -> None:
"""Load configuration from the configured YAML file."""
try:
with open(self._filename, 'r', encoding='utf-8') as f:
@ -189,18 +202,19 @@ class YamlConfig(QObject):
self._validate(settings)
self._build_values(settings)
def _load_settings_object(self, yaml_data):
def _load_settings_object(self, yaml_data: typing.Any) -> '_SettingsType':
"""Load the settings from the settings: key."""
return self._pop_object(yaml_data, 'settings', dict)
def _load_legacy_settings_object(self, yaml_data):
def _load_legacy_settings_object(self,
yaml_data: typing.Any) -> '_SettingsType':
data = self._pop_object(yaml_data, 'global', dict)
settings = {}
for name, value in data.items():
settings[name] = {'global': value}
return settings
def _build_values(self, settings):
def _build_values(self, settings: typing.Mapping) -> None:
"""Build up self._values from the values in the given dict."""
errors = []
for name, yaml_values in settings.items():
@ -233,7 +247,8 @@ class YamlConfig(QObject):
if errors:
raise configexc.ConfigFileErrors('autoconfig.yml', errors)
def _migrate_bool(self, settings, name, true_value, false_value):
def _migrate_bool(self, settings: _SettingsType, name: str,
true_value: str, false_value: str) -> None:
"""Migrate a boolean in the settings."""
if name in settings:
for scope, val in settings[name].items():
@ -241,7 +256,7 @@ class YamlConfig(QObject):
settings[name][scope] = true_value if val else false_value
self._mark_changed()
def _handle_migrations(self, settings):
def _handle_migrations(self, settings: _SettingsType) -> '_SettingsType':
"""Migrate older configs to the newest format."""
# Simple renamed/deleted options
for name in list(settings):
@ -299,7 +314,7 @@ class YamlConfig(QObject):
return settings
def _validate(self, settings):
def _validate(self, settings: _SettingsType) -> None:
"""Make sure all settings exist."""
unknown = []
for name in settings:
@ -312,18 +327,19 @@ class YamlConfig(QObject):
for e in sorted(unknown)]
raise configexc.ConfigFileErrors('autoconfig.yml', errors)
def set_obj(self, name, value, *, pattern=None):
def set_obj(self, name: str, value: typing.Any, *,
pattern: urlmatch.UrlPattern = None) -> None:
"""Set the given setting to the given value."""
self._values[name].add(value, pattern)
self._mark_changed()
def unset(self, name, *, pattern=None):
def unset(self, name: str, *, pattern: urlmatch.UrlPattern = None) -> None:
"""Remove the given option name if it's configured."""
changed = self._values[name].remove(pattern)
if changed:
self._mark_changed()
def clear(self):
def clear(self) -> None:
"""Clear all values from the YAML file."""
for values in self._values.values():
values.clear()
@ -346,15 +362,15 @@ class ConfigAPI:
datadir: The qutebrowser data directory, as pathlib.Path.
"""
def __init__(self, conf, keyconfig):
def __init__(self, conf: config.Config, keyconfig: config.KeyConfig):
self._config = conf
self._keyconfig = keyconfig
self.errors = []
self.errors = [] # type: typing.List[configexc.ConfigErrorDesc]
self.configdir = pathlib.Path(standarddir.config())
self.datadir = pathlib.Path(standarddir.data())
@contextlib.contextmanager
def _handle_error(self, action, name):
def _handle_error(self, action: str, name: str) -> typing.Iterator[None]:
"""Catch config-related exceptions and save them in self.errors."""
try:
yield
@ -372,40 +388,40 @@ class ConfigAPI:
text = "While {} '{}' and parsing key".format(action, name)
self.errors.append(configexc.ConfigErrorDesc(text, e))
def finalize(self):
def finalize(self) -> None:
"""Do work which needs to be done after reading config.py."""
self._config.update_mutables()
def load_autoconfig(self):
def load_autoconfig(self) -> None:
"""Load the autoconfig.yml file which is used for :set/:bind/etc."""
with self._handle_error('reading', 'autoconfig.yml'):
read_autoconfig()
def get(self, name, pattern=None):
def get(self, name: str, pattern: str = None) -> typing.Any:
"""Get a setting value from the config, optionally with a pattern."""
with self._handle_error('getting', name):
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
return self._config.get_mutable_obj(name, pattern=urlpattern)
def set(self, name, value, pattern=None):
def set(self, name: str, value: typing.Any, pattern: str = None) -> None:
"""Set a setting value in the config, optionally with a pattern."""
with self._handle_error('setting', name):
urlpattern = urlmatch.UrlPattern(pattern) if pattern else None
self._config.set_obj(name, value, pattern=urlpattern)
def bind(self, key, command, mode='normal'):
def bind(self, key: str, command: str, mode: str = 'normal') -> None:
"""Bind a key to a command, with an optional key mode."""
with self._handle_error('binding', key):
seq = keyutils.KeySequence.parse(key)
self._keyconfig.bind(seq, command, mode=mode)
def unbind(self, key, mode='normal'):
def unbind(self, key: str, mode: str = 'normal') -> None:
"""Unbind a key from a command, with an optional key mode."""
with self._handle_error('unbinding', key):
seq = keyutils.KeySequence.parse(key)
self._keyconfig.unbind(seq, mode=mode)
def source(self, filename):
def source(self, filename: str) -> None:
"""Read the given config file from disk."""
if not os.path.isabs(filename):
filename = str(self.configdir / filename)
@ -416,7 +432,7 @@ class ConfigAPI:
self.errors += e.errors
@contextlib.contextmanager
def pattern(self, pattern):
def pattern(self, pattern: str) -> typing.Iterator[config.ConfigContainer]:
"""Get a ConfigContainer for the given pattern."""
# We need to propagate the exception so we don't need to return
# something.
@ -430,17 +446,21 @@ class ConfigPyWriter:
"""Writer for config.py files from given settings."""
def __init__(self, options, bindings, *, commented):
def __init__(
self,
options: typing.List,
bindings: typing.MutableMapping[str, typing.Mapping[str, str]], *,
commented: bool) -> None:
self._options = options
self._bindings = bindings
self._commented = commented
def write(self, filename):
def write(self, filename: str) -> None:
"""Write the config to the given file."""
with open(filename, 'w', encoding='utf-8') as f:
f.write('\n'.join(self._gen_lines()))
def _line(self, line):
def _line(self, line: str) -> str:
"""Get an (optionally commented) line."""
if self._commented:
if line.startswith('#'):
@ -450,7 +470,7 @@ class ConfigPyWriter:
else:
return line
def _gen_lines(self):
def _gen_lines(self) -> typing.Iterator[str]:
"""Generate a config.py with the given settings/bindings.
Yields individual lines.
@ -459,7 +479,7 @@ class ConfigPyWriter:
yield from self._gen_options()
yield from self._gen_bindings()
def _gen_header(self):
def _gen_header(self) -> typing.Iterator[str]:
"""Generate the initial header of the config."""
yield self._line("# Autogenerated config.py")
yield self._line("# Documentation:")
@ -481,7 +501,7 @@ class ConfigPyWriter:
yield self._line("# config.load_autoconfig()")
yield ''
def _gen_options(self):
def _gen_options(self) -> typing.Iterator[str]:
"""Generate the options part of the config."""
for pattern, opt, value in self._options:
if opt.name in ['bindings.commands', 'bindings.default']:
@ -509,7 +529,7 @@ class ConfigPyWriter:
opt.name, value, str(pattern)))
yield ''
def _gen_bindings(self):
def _gen_bindings(self) -> typing.Iterator[str]:
"""Generate the bindings part of the config."""
normal_bindings = self._bindings.pop('normal', {})
if normal_bindings:
@ -527,7 +547,7 @@ class ConfigPyWriter:
yield ''
def read_config_py(filename, raising=False):
def read_config_py(filename: str, raising: bool = False) -> None:
"""Read a config.py file.
Arguments;
@ -543,8 +563,8 @@ def read_config_py(filename, raising=False):
basename = os.path.basename(filename)
module = types.ModuleType('config')
module.config = api
module.c = container
module.config = api # type: ignore
module.c = container # type: ignore
module.__file__ = filename
try:
@ -589,7 +609,7 @@ def read_config_py(filename, raising=False):
raise configexc.ConfigFileErrors('config.py', api.errors)
def read_autoconfig():
def read_autoconfig() -> None:
"""Read the autoconfig.yml file."""
try:
config.instance.read_yaml()
@ -601,7 +621,7 @@ def read_autoconfig():
@contextlib.contextmanager
def saved_sys_properties():
def saved_sys_properties() -> typing.Iterator[None]:
"""Save various sys properties such as sys.path and sys.modules."""
old_path = sys.path.copy()
old_modules = sys.modules.copy()
@ -614,7 +634,7 @@ def saved_sys_properties():
del sys.modules[module]
def init():
def init() -> None:
"""Initialize config storage not related to the main config."""
global state
state = StateConfig()

View File

@ -19,8 +19,10 @@
"""Initialization of the configuration."""
import argparse
import os.path
import sys
import typing
from PyQt5.QtWidgets import QMessageBox
@ -30,14 +32,14 @@ from qutebrowser.config import (config, configdata, configfiles, configtypes,
from qutebrowser.utils import (objreg, usertypes, log, standarddir, message,
qtutils)
from qutebrowser.config import configcache
from qutebrowser.misc import msgbox, objects
from qutebrowser.misc import msgbox, objects, savemanager
# Error which happened during init, so we can show a message box.
_init_errors = None
def early_init(args):
def early_init(args: argparse.Namespace) -> None:
"""Initialize the part of the config which works without a QApplication."""
configdata.init()
@ -85,7 +87,7 @@ def early_init(args):
_init_envvars()
def _init_envvars():
def _init_envvars() -> None:
"""Initialize environment variables which need to be set early."""
if objects.backend == usertypes.Backend.QtWebEngine:
software_rendering = config.val.qt.force_software_rendering
@ -107,7 +109,7 @@ def _init_envvars():
@config.change_filter('fonts.monospace', function=True)
def _update_monospace_fonts():
def _update_monospace_fonts() -> None:
"""Update all fonts if fonts.monospace was set."""
configtypes.Font.monospace_fonts = config.val.fonts.monospace
for name, opt in configdata.DATA.items():
@ -123,7 +125,7 @@ def _update_monospace_fonts():
config.instance.changed.emit(name)
def get_backend(args):
def get_backend(args: argparse.Namespace) -> usertypes.Backend:
"""Find out what backend to use based on available libraries."""
str_to_backend = {
'webkit': usertypes.Backend.QtWebKit,
@ -136,7 +138,7 @@ def get_backend(args):
return str_to_backend[config.val.backend]
def late_init(save_manager):
def late_init(save_manager: savemanager.SaveManager) -> None:
"""Initialize the rest of the config after the QApplication is created."""
global _init_errors
if _init_errors is not None:
@ -152,7 +154,7 @@ def late_init(save_manager):
configfiles.state.init_save_manager(save_manager)
def qt_args(namespace):
def qt_args(namespace: argparse.Namespace) -> typing.List[str]:
"""Get the Qt QApplication arguments based on an argparse namespace.
Args:
@ -178,7 +180,7 @@ def qt_args(namespace):
return argv
def _qtwebengine_args():
def _qtwebengine_args() -> typing.Iterator[str]:
"""Get the QtWebEngine arguments to use based on the config."""
if not qtutils.version_check('5.11', compiled=False):
# WORKAROUND equivalent to
@ -224,7 +226,7 @@ def _qtwebengine_args():
'never': '--no-referrers',
'same-domain': '--reduced-referrer-granularity',
}
}
} # type: typing.Dict[str, typing.Dict[typing.Any, typing.Optional[str]]]
if not qtutils.version_check('5.11'):
# On Qt 5.11, we can control this via QWebEngineSettings

View File

@ -21,11 +21,19 @@
"""Utilities and data structures used by various config code."""
import attr
import typing
from qutebrowser.utils import utils
import attr
from PyQt5.QtCore import QUrl
from qutebrowser.utils import utils, urlmatch
from qutebrowser.config import configexc
MYPY = False
if MYPY: # pragma: no cover
# pylint: disable=unused-import,useless-suppression
from qutebrowser.config import configdata
class _UnsetObject:
@ -33,7 +41,7 @@ class _UnsetObject:
__slots__ = ()
def __repr__(self):
def __repr__(self) -> str:
return '<UNSET>'
@ -50,8 +58,8 @@ class ScopedValue:
pattern: The UrlPattern for the value, or None for global values.
"""
value = attr.ib()
pattern = attr.ib()
value = attr.ib() # type: typing.Any
pattern = attr.ib() # type: typing.Optional[urlmatch.UrlPattern]
class Values:
@ -73,15 +81,17 @@ class Values:
opt: The Option being customized.
"""
def __init__(self, opt, values=None):
def __init__(self,
opt: 'configdata.Option',
values: typing.MutableSequence = None) -> None:
self.opt = opt
self._values = values or []
def __repr__(self):
def __repr__(self) -> str:
return utils.get_repr(self, opt=self.opt, values=self._values,
constructor=True)
def __str__(self):
def __str__(self) -> str:
"""Get the values as human-readable string."""
if not self:
return '{}: <unchanged>'.format(self.opt.name)
@ -96,7 +106,7 @@ class Values:
scoped.pattern, self.opt.name, str_value))
return '\n'.join(lines)
def __iter__(self):
def __iter__(self) -> typing.Iterator['ScopedValue']:
"""Yield ScopedValue elements.
This yields in "normal" order, i.e. global and then first-set settings
@ -104,23 +114,25 @@ class Values:
"""
yield from self._values
def __bool__(self):
def __bool__(self) -> bool:
"""Check whether this value is customized."""
return bool(self._values)
def _check_pattern_support(self, arg):
def _check_pattern_support(
self, arg: typing.Optional[urlmatch.UrlPattern]) -> None:
"""Make sure patterns are supported if one was given."""
if arg is not None and not self.opt.supports_pattern:
raise configexc.NoPatternError(self.opt.name)
def add(self, value, pattern=None):
def add(self, value: typing.Any,
pattern: urlmatch.UrlPattern = None) -> None:
"""Add a value with the given pattern to the list of values."""
self._check_pattern_support(pattern)
self.remove(pattern)
scoped = ScopedValue(value, pattern)
self._values.append(scoped)
def remove(self, pattern=None):
def remove(self, pattern: urlmatch.UrlPattern = None) -> bool:
"""Remove the value with the given pattern.
If a matching pattern was removed, True is returned.
@ -131,11 +143,11 @@ class Values:
self._values = [v for v in self._values if v.pattern != pattern]
return old_len != len(self._values)
def clear(self):
def clear(self) -> None:
"""Clear all customization for this value."""
self._values = []
def _get_fallback(self, fallback):
def _get_fallback(self, fallback: typing.Any) -> typing.Any:
"""Get the fallback global/default value."""
for scoped in self._values:
if scoped.pattern is None:
@ -146,7 +158,8 @@ class Values:
else:
return UNSET
def get_for_url(self, url=None, *, fallback=True):
def get_for_url(self, url: QUrl = None, *,
fallback: bool = True) -> typing.Any:
"""Get a config value, falling back when needed.
This first tries to find a value matching the URL (if given).
@ -165,7 +178,9 @@ class Values:
return self._get_fallback(fallback)
def get_for_pattern(self, pattern, *, fallback=True):
def get_for_pattern(self,
pattern: typing.Optional[urlmatch.UrlPattern], *,
fallback: bool = True) -> typing.Any:
"""Get a value only if it's been overridden for the given pattern.
This is useful when showing values to the user.