Add required_keys for configtypes.Dict

This commit is contained in:
Florian Bruhin 2017-06-30 22:03:34 +02:00
parent bc526cf0ce
commit 2ba637891a
5 changed files with 46 additions and 9 deletions

View File

@ -83,10 +83,10 @@ new_instance_open_target_window:
searchengines: searchengines:
default: default:
# FIXME:conf what if the user deletes/renames DEFAULT?
DEFAULT: https://duckduckgo.com/?q={} DEFAULT: https://duckduckgo.com/?q={}
type: type:
name: Dict name: Dict
required_keys: ['DEFAULT']
keytype: String keytype: String
valtype: SearchEngineUrl valtype: SearchEngineUrl
desc: >- desc: >-

View File

@ -1030,7 +1030,8 @@ class Dict(BaseType):
"""A dictionary of values.""" """A dictionary of values."""
def __init__(self, keytype, valtype, *, fixed_keys=None, none_ok=False): def __init__(self, keytype, valtype, *, fixed_keys=None,
required_keys=None, none_ok=False):
super().__init__(none_ok) super().__init__(none_ok)
# If the keytype is not a string, we'll get problems with showing it as # If the keytype is not a string, we'll get problems with showing it as
# json in to_str() as json converts keys to strings. # json in to_str() as json converts keys to strings.
@ -1038,12 +1039,17 @@ class Dict(BaseType):
self.keytype = keytype self.keytype = keytype
self.valtype = valtype self.valtype = valtype
self.fixed_keys = fixed_keys self.fixed_keys = fixed_keys
self.required_keys = required_keys
def _validate_keys(self, value): def _validate_keys(self, value):
if (self.fixed_keys is not None and if (self.fixed_keys is not None and
value.keys() != set(self.fixed_keys)): value.keys() != set(self.fixed_keys)):
raise configexc.ValidationError( raise configexc.ValidationError(
value, "Expected keys {}".format(self.fixed_keys)) 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(
value, "Required keys {}".format(self.required_keys))
def _none_value(self, value=None): def _none_value(self, value=None):
"""Return the value to be used when the setting is None. """Return the value to be used when the setting is None.

View File

@ -73,3 +73,8 @@ def test_partial_compare_not_equal(val1, val2, error):
]) ])
def test_pattern_match(pattern, value, expected): def test_pattern_match(pattern, value, expected):
assert utils.pattern_match(pattern=pattern, value=value) == expected assert utils.pattern_match(pattern=pattern, value=value) == expected
def test_nop_contextmanager():
with utils.nop_contextmanager():
pass

View File

@ -23,6 +23,7 @@
import re import re
import pprint import pprint
import os.path import os.path
import contextlib
import pytest import pytest
@ -170,3 +171,8 @@ def abs_datapath():
"""Get the absolute path to the end2end data directory.""" """Get the absolute path to the end2end data directory."""
file_abs = os.path.abspath(os.path.dirname(__file__)) file_abs = os.path.abspath(os.path.dirname(__file__))
return os.path.join(file_abs, '..', 'end2end', 'data') return os.path.join(file_abs, '..', 'end2end', 'data')
@contextlib.contextmanager
def nop_contextmanager():
yield

View File

@ -40,6 +40,7 @@ from PyQt5.QtNetwork import QNetworkProxy
from qutebrowser.config import configtypes, configexc from qutebrowser.config import configtypes, configexc
from qutebrowser.utils import debug, utils, qtutils from qutebrowser.utils import debug, utils, qtutils
from qutebrowser.browser.network import pac from qutebrowser.browser.network import pac
from tests.helpers import utils as testutils
class Font(QFont): class Font(QFont):
@ -1390,16 +1391,35 @@ class TestDict:
valtype=configtypes.Int()) valtype=configtypes.Int())
assert typ.from_str('{"answer": 42}') == {"answer": 42} assert typ.from_str('{"answer": 42}') == {"answer": 42}
@pytest.mark.parametrize('val', [ @pytest.mark.parametrize('kind, val, ok', [
{"one": "1"}, # missing key ('fixed', {"one": "1"}, False), # missing key
{"one": "1", "two": "2", "three": "3"}, # extra key ('fixed', {"one": "1", "two": "2", "three": "3"}, False), # extra key
('fixed', {"one": "1", "two": "2"}, True),
('required', {"one": "1"}, False), # missing key
('required', {"one": "1", "two": "2", "three": "3"}, True), # extra
('required', {"one": "1", "two": "2"}, True),
]) ])
@pytest.mark.parametrize('from_str', [True, False]) @pytest.mark.parametrize('from_str', [True, False])
def test_fixed_keys(self, klass, val, from_str): def test_keys(self, klass, kind, val, ok, from_str):
d = klass(keytype=configtypes.String(), valtype=configtypes.String(), if kind == 'fixed':
d = klass(keytype=configtypes.String(),
valtype=configtypes.String(),
fixed_keys=['one', 'two']) fixed_keys=['one', 'two'])
message = 'Expected keys .*'
elif kind == 'required':
d = klass(keytype=configtypes.String(),
valtype=configtypes.String(),
required_keys=['one', 'two'])
message = 'Required keys .*'
with pytest.raises(configexc.ValidationError): if ok:
expectation = testutils.nop_contextmanager()
else:
expectation = pytest.raises(configexc.ValidationError,
match=message)
with expectation:
if from_str: if from_str:
d.from_str(json.dumps(val)) d.from_str(json.dumps(val))
else: else: