diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index ac0aea9d3..9cc8cff1d 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -2095,6 +2095,7 @@ bindings.commands: 'prompt', 'caret', 'register'] valtype: name: Dict + none_ok: true keytype: Key valtype: name: Command diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 84ef433d3..c59283fd6 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1031,10 +1031,11 @@ class Dict(BaseType): self.required_keys = required_keys def _validate_keys(self, value): - if (self.fixed_keys is not None and - value.keys() != set(self.fixed_keys)): + if (self.fixed_keys is not None and not + set(value.keys()).issubset(self.fixed_keys)): raise configexc.ValidationError( value, "Expected keys {}".format(self.fixed_keys)) + if (self.required_keys is not None and not set(self.required_keys).issubset(value.keys())): raise configexc.ValidationError( @@ -1055,19 +1056,25 @@ class Dict(BaseType): self.to_py(yaml_val) return yaml_val + def _fill_fixed_keys(self, value): + """Fill missing fixed keys with a None-value.""" + if self.fixed_keys is None: + return value + for key in self.fixed_keys: + if key not in value: + value[key] = self.valtype.to_py(None) + return value + def to_py(self, value): self._basic_py_validation(value, dict) if not value: - if self.fixed_keys is None: - return {} - else: - return {key: self.valtype.to_py(None) - for key in self.fixed_keys} + return self._fill_fixed_keys({}) self._validate_keys(value) - return {self.keytype.to_py(key): self.valtype.to_py(val) - for key, val in value.items()} + d = {self.keytype.to_py(key): self.valtype.to_py(val) + for key, val in value.items()} + return self._fill_fixed_keys(d) def to_str(self, value): if not value: diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 50f281c11..2e4958095 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1398,7 +1398,7 @@ class TestDict: assert typ.from_str('{"answer": 42}') == {"answer": 42} @pytest.mark.parametrize('kind, val, ok', [ - ('fixed', {"one": "1"}, False), # missing key + ('fixed', {"one": "1"}, True), # missing key (gets filled with None) ('fixed', {"one": "1", "two": "2", "three": "3"}, False), # extra key ('fixed', {"one": "1", "two": "2"}, True), @@ -1410,7 +1410,7 @@ class TestDict: def test_keys(self, klass, kind, val, ok, from_str): if kind == 'fixed': d = klass(keytype=configtypes.String(), - valtype=configtypes.String(), + valtype=configtypes.String(none_ok=True), fixed_keys=['one', 'two']) message = 'Expected keys .*' elif kind == 'required':