Initial implementation of to_str for configtypes
This commit is contained in:
parent
0c1f480fc1
commit
8ea3d92697
@ -29,6 +29,7 @@ import warnings
|
|||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import operator
|
import operator
|
||||||
|
import json
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from PyQt5.QtCore import QUrl, Qt
|
from PyQt5.QtCore import QUrl, Qt
|
||||||
@ -199,19 +200,15 @@ class BaseType:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
# def to_str(self, value):
|
def to_str(self, value):
|
||||||
# """Get a string from the setting value.
|
"""Get a string from the setting value.
|
||||||
|
|
||||||
# The resulting string should be parseable again by from_str.
|
The resulting string should be parseable again by from_str.
|
||||||
# """
|
"""
|
||||||
# raise NotImplementedError
|
if value is None:
|
||||||
|
return ''
|
||||||
# def to_py(self, value):
|
assert isinstance(value, str)
|
||||||
# """Get a Python/YAML value from the setting value.
|
return value
|
||||||
|
|
||||||
# The resulting value should be parseable again by from_py.
|
|
||||||
# """
|
|
||||||
# raise NotImplementedError
|
|
||||||
|
|
||||||
def complete(self):
|
def complete(self):
|
||||||
"""Return a list of possible values for completion.
|
"""Return a list of possible values for completion.
|
||||||
@ -258,11 +255,6 @@ class MappingType(BaseType):
|
|||||||
self._validate_valid_values(value.lower())
|
self._validate_valid_values(value.lower())
|
||||||
return self.MAPPING[value.lower()]
|
return self.MAPPING[value.lower()]
|
||||||
|
|
||||||
# def to_str(self, value):
|
|
||||||
# reverse_mapping = {v: k for k, v in self.MAPPING.items()}
|
|
||||||
# assert len(self.MAPPING) == len(reverse_mapping)
|
|
||||||
# return reverse_mapping[value]
|
|
||||||
|
|
||||||
|
|
||||||
class String(BaseType):
|
class String(BaseType):
|
||||||
|
|
||||||
@ -401,6 +393,11 @@ class List(BaseType):
|
|||||||
"be set!".format(self.length))
|
"be set!".format(self.length))
|
||||||
return [self.valtype.from_py(v) for v in value]
|
return [self.valtype.from_py(v) for v in value]
|
||||||
|
|
||||||
|
def to_str(self, value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
return json.dumps(value)
|
||||||
|
|
||||||
|
|
||||||
class FlagList(List):
|
class FlagList(List):
|
||||||
|
|
||||||
@ -472,6 +469,14 @@ class Bool(BaseType):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise configexc.ValidationError(value, "must be a boolean!")
|
raise configexc.ValidationError(value, "must be a boolean!")
|
||||||
|
|
||||||
|
def to_str(self, value):
|
||||||
|
mapping = {
|
||||||
|
None: '',
|
||||||
|
True: 'true',
|
||||||
|
False: 'false',
|
||||||
|
}
|
||||||
|
return mapping[value]
|
||||||
|
|
||||||
|
|
||||||
class BoolAsk(Bool):
|
class BoolAsk(Bool):
|
||||||
|
|
||||||
@ -495,6 +500,15 @@ class BoolAsk(Bool):
|
|||||||
return 'ask'
|
return 'ask'
|
||||||
return super().from_str(value)
|
return super().from_str(value)
|
||||||
|
|
||||||
|
def to_str(self, value):
|
||||||
|
mapping = {
|
||||||
|
None: '',
|
||||||
|
True: 'true',
|
||||||
|
False: 'false',
|
||||||
|
'ask': 'ask',
|
||||||
|
}
|
||||||
|
return mapping[value]
|
||||||
|
|
||||||
|
|
||||||
class _Numeric(BaseType): # pylint: disable=abstract-method
|
class _Numeric(BaseType): # pylint: disable=abstract-method
|
||||||
|
|
||||||
@ -536,6 +550,11 @@ class _Numeric(BaseType): # pylint: disable=abstract-method
|
|||||||
raise configexc.ValidationError(
|
raise configexc.ValidationError(
|
||||||
value, "must be {}{} or smaller!".format(self.maxval, suffix))
|
value, "must be {}{} or smaller!".format(self.maxval, suffix))
|
||||||
|
|
||||||
|
def to_str(self, value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
class Int(_Numeric):
|
class Int(_Numeric):
|
||||||
|
|
||||||
@ -597,6 +616,11 @@ class Perc(_Numeric):
|
|||||||
self._validate_bounds(floatval, suffix='%')
|
self._validate_bounds(floatval, suffix='%')
|
||||||
return floatval
|
return floatval
|
||||||
|
|
||||||
|
def to_str(self, value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class PercOrInt(_Numeric):
|
class PercOrInt(_Numeric):
|
||||||
|
|
||||||
@ -637,8 +661,8 @@ class PercOrInt(_Numeric):
|
|||||||
def from_py(self, value):
|
def from_py(self, value):
|
||||||
"""Expect a value like '42%' as string, or 23 as int."""
|
"""Expect a value like '42%' as string, or 23 as int."""
|
||||||
self._basic_py_validation(value, (int, str))
|
self._basic_py_validation(value, (int, str))
|
||||||
if not value:
|
if value is None:
|
||||||
return
|
return None
|
||||||
|
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
if not value.endswith('%'):
|
if not value.endswith('%'):
|
||||||
@ -913,6 +937,14 @@ class Regex(BaseType):
|
|||||||
# FIXME:conf is it okay if we ignore flags here?
|
# FIXME:conf is it okay if we ignore flags here?
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def to_str(self, value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
elif isinstance(value, self._regex_type):
|
||||||
|
return value.pattern
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Dict(BaseType):
|
class Dict(BaseType):
|
||||||
|
|
||||||
@ -954,6 +986,11 @@ class Dict(BaseType):
|
|||||||
return {self.keytype.from_py(key): self.valtype.from_py(val)
|
return {self.keytype.from_py(key): self.valtype.from_py(val)
|
||||||
for key, val in value.items()}
|
for key, val in value.items()}
|
||||||
|
|
||||||
|
def to_str(self, value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
return json.dumps(value)
|
||||||
|
|
||||||
|
|
||||||
class File(BaseType):
|
class File(BaseType):
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import re
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
import warnings
|
import warnings
|
||||||
@ -213,10 +214,31 @@ class TestAll:
|
|||||||
@hypothesis.given(strategies.text())
|
@hypothesis.given(strategies.text())
|
||||||
@hypothesis.example('\x00')
|
@hypothesis.example('\x00')
|
||||||
def test_from_str_hypothesis(self, klass, s):
|
def test_from_str_hypothesis(self, klass, s):
|
||||||
|
typ = klass()
|
||||||
try:
|
try:
|
||||||
klass().from_str(s)
|
val = typ.from_str(s)
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
pass
|
return
|
||||||
|
|
||||||
|
# For some types, we don't actually get the internal (YAML-like) value
|
||||||
|
# back from from_str(), so we can't convert it back.
|
||||||
|
if klass in [configtypes.FuzzyUrl, configtypes.QtFont,
|
||||||
|
configtypes.ShellCommand, configtypes.Url]:
|
||||||
|
return
|
||||||
|
|
||||||
|
converted = typ.to_str(val)
|
||||||
|
# For those we only check that to_str doesn't crash, but we can't be
|
||||||
|
# sure we get the 100% same value back.
|
||||||
|
if klass in [
|
||||||
|
configtypes.Bool, # on -> true
|
||||||
|
configtypes.BoolAsk, # ditto
|
||||||
|
configtypes.Float, # 1.0 -> 1
|
||||||
|
configtypes.Int, # 00 -> 0
|
||||||
|
configtypes.PercOrInt, # ditto
|
||||||
|
]:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert converted == s
|
||||||
|
|
||||||
def test_none_ok_true(self, klass):
|
def test_none_ok_true(self, klass):
|
||||||
"""Test None and empty string values with none_ok=True."""
|
"""Test None and empty string values with none_ok=True."""
|
||||||
@ -235,6 +257,9 @@ class TestAll:
|
|||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
meth(value)
|
meth(value)
|
||||||
|
|
||||||
|
def test_to_str_none(self, klass):
|
||||||
|
assert klass().to_str(None) == ''
|
||||||
|
|
||||||
def test_invalid_python_type(self, klass):
|
def test_invalid_python_type(self, klass):
|
||||||
"""Make sure every type fails when passing an invalid Python type."""
|
"""Make sure every type fails when passing an invalid Python type."""
|
||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
@ -349,6 +374,9 @@ class TestMappingType:
|
|||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
klass().from_py(val)
|
klass().from_py(val)
|
||||||
|
|
||||||
|
def test_to_str(self, klass):
|
||||||
|
assert klass().to_str('one') == 'one'
|
||||||
|
|
||||||
@pytest.mark.parametrize('typ', [configtypes.ColorSystem(),
|
@pytest.mark.parametrize('typ', [configtypes.ColorSystem(),
|
||||||
configtypes.Position(),
|
configtypes.Position(),
|
||||||
configtypes.SelectOnRemove()])
|
configtypes.SelectOnRemove()])
|
||||||
@ -523,20 +551,29 @@ class TestList:
|
|||||||
expected = configtypes.ValidValues('foo', 'bar', 'baz')
|
expected = configtypes.ValidValues('foo', 'bar', 'baz')
|
||||||
assert klass().get_valid_values() == expected
|
assert klass().get_valid_values() == expected
|
||||||
|
|
||||||
|
def test_to_str(self, klass):
|
||||||
|
assert klass().to_str(["a", True] == '["a", true]')
|
||||||
|
|
||||||
@hypothesis.given(val=strategies.lists(strategies.just('foo')))
|
@hypothesis.given(val=strategies.lists(strategies.just('foo')))
|
||||||
def test_hypothesis(self, klass, val):
|
def test_hypothesis(self, klass, val):
|
||||||
|
typ = klass(none_ok_outer=True)
|
||||||
try:
|
try:
|
||||||
klass().from_py(val)
|
converted = typ.from_py(val)
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
assert typ.from_str(typ.to_str(converted)) == converted
|
||||||
|
|
||||||
@hypothesis.given(val=strategies.lists(strategies.just('foo')))
|
@hypothesis.given(val=strategies.lists(strategies.just('foo')))
|
||||||
def test_hypothesis_text(self, klass, val):
|
def test_hypothesis_text(self, klass, val):
|
||||||
|
typ = klass()
|
||||||
text = json.dumps(val)
|
text = json.dumps(val)
|
||||||
try:
|
try:
|
||||||
klass().from_str(text)
|
converted = typ.from_str(text)
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
assert typ.to_str(converted) == text
|
||||||
|
|
||||||
|
|
||||||
class TestFlagList:
|
class TestFlagList:
|
||||||
@ -622,6 +659,13 @@ class TestBool:
|
|||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
klass().from_py(42)
|
klass().from_py(42)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('val, expected', [
|
||||||
|
(True, 'true'),
|
||||||
|
(False, 'false'),
|
||||||
|
])
|
||||||
|
def test_to_str(self, klass, val, expected):
|
||||||
|
assert klass().to_str(val) == expected
|
||||||
|
|
||||||
|
|
||||||
class TestBoolAsk:
|
class TestBoolAsk:
|
||||||
|
|
||||||
@ -654,6 +698,14 @@ class TestBoolAsk:
|
|||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
klass().from_py(42)
|
klass().from_py(42)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('val, expected', [
|
||||||
|
(True, 'true'),
|
||||||
|
(False, 'false'),
|
||||||
|
('ask', 'ask'),
|
||||||
|
])
|
||||||
|
def test_to_str(self, klass, val, expected):
|
||||||
|
assert klass().to_str(val) == expected
|
||||||
|
|
||||||
|
|
||||||
class TestNumeric:
|
class TestNumeric:
|
||||||
|
|
||||||
@ -744,12 +796,16 @@ class TestInt:
|
|||||||
|
|
||||||
@hypothesis.given(val=strategies.integers())
|
@hypothesis.given(val=strategies.integers())
|
||||||
def test_hypothesis(self, klass, val):
|
def test_hypothesis(self, klass, val):
|
||||||
klass().from_py(val)
|
typ = klass()
|
||||||
|
converted = typ.from_py(val)
|
||||||
|
assert typ.from_str(typ.to_str(converted)) == converted
|
||||||
|
|
||||||
@hypothesis.given(val=strategies.integers())
|
@hypothesis.given(val=strategies.integers())
|
||||||
def test_hypothesis_text(self, klass, val):
|
def test_hypothesis_text(self, klass, val):
|
||||||
text = json.dumps(val)
|
text = json.dumps(val)
|
||||||
klass().from_str(text)
|
typ = klass()
|
||||||
|
converted = typ.from_str(text)
|
||||||
|
assert typ.to_str(converted) == text
|
||||||
|
|
||||||
|
|
||||||
class TestFloat:
|
class TestFloat:
|
||||||
@ -794,13 +850,21 @@ class TestFloat:
|
|||||||
@hypothesis.given(val=strategies.one_of(strategies.floats(),
|
@hypothesis.given(val=strategies.one_of(strategies.floats(),
|
||||||
strategies.integers()))
|
strategies.integers()))
|
||||||
def test_hypothesis(self, klass, val):
|
def test_hypothesis(self, klass, val):
|
||||||
klass().from_py(val)
|
typ = klass()
|
||||||
|
converted = typ.from_py(val)
|
||||||
|
converted_2 = typ.from_str(typ.to_str(converted))
|
||||||
|
if math.isnan(converted):
|
||||||
|
assert math.isnan(converted_2)
|
||||||
|
else:
|
||||||
|
assert converted == converted_2
|
||||||
|
|
||||||
@hypothesis.given(val=strategies.one_of(strategies.floats(),
|
@hypothesis.given(val=strategies.one_of(strategies.floats(),
|
||||||
strategies.integers()))
|
strategies.integers()))
|
||||||
def test_hypothesis_text(self, klass, val):
|
def test_hypothesis_text(self, klass, val):
|
||||||
text = json.dumps(val)
|
text = json.dumps(val)
|
||||||
klass().from_str(text)
|
klass().from_str(text)
|
||||||
|
# Can't check for string equality with to_str here, as we can get 1.0
|
||||||
|
# for 1.
|
||||||
|
|
||||||
|
|
||||||
class TestPerc:
|
class TestPerc:
|
||||||
@ -847,6 +911,9 @@ class TestPerc:
|
|||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
klass(**kwargs).from_py(val)
|
klass(**kwargs).from_py(val)
|
||||||
|
|
||||||
|
def test_to_str(self, klass):
|
||||||
|
assert klass().to_str('42%') == '42%'
|
||||||
|
|
||||||
|
|
||||||
class TestPercOrInt:
|
class TestPercOrInt:
|
||||||
|
|
||||||
@ -909,13 +976,30 @@ class TestPercOrInt:
|
|||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
klass().from_py(val)
|
klass().from_py(val)
|
||||||
|
|
||||||
@hypothesis.given(val=strategies.one_of(strategies.integers(),
|
@hypothesis.given(val=strategies.one_of(
|
||||||
|
strategies.integers(),
|
||||||
|
strategies.integers().map(lambda n: str(n) + '%'),
|
||||||
strategies.text()))
|
strategies.text()))
|
||||||
def test_hypothesis(self, klass, val):
|
def test_hypothesis(self, klass, val):
|
||||||
|
typ = klass(none_ok=True)
|
||||||
try:
|
try:
|
||||||
klass().from_py(val)
|
converted = typ.from_py(val)
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
assert typ.from_str(typ.to_str(converted)) == converted
|
||||||
|
|
||||||
|
@hypothesis.given(text=strategies.one_of(
|
||||||
|
strategies.integers().map(str),
|
||||||
|
strategies.integers().map(lambda n: str(n) + '%')))
|
||||||
|
def test_hypothesis_text(self, klass, text):
|
||||||
|
typ = klass()
|
||||||
|
try:
|
||||||
|
converted = typ.from_str(text)
|
||||||
|
except configexc.ValidationError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert typ.to_str(converted) == text
|
||||||
|
|
||||||
|
|
||||||
class TestCommand:
|
class TestCommand:
|
||||||
@ -1248,6 +1332,10 @@ class TestRegex:
|
|||||||
typ = klass(flags=flags)
|
typ = klass(flags=flags)
|
||||||
assert typ.flags == expected
|
assert typ.flags == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('value', [r'foobar', re.compile(r'foobar')])
|
||||||
|
def test_to_str(self, klass, value):
|
||||||
|
assert klass().to_str(value) == 'foobar'
|
||||||
|
|
||||||
|
|
||||||
class TestDict:
|
class TestDict:
|
||||||
|
|
||||||
@ -1304,8 +1392,10 @@ class TestDict:
|
|||||||
strategies.booleans()))
|
strategies.booleans()))
|
||||||
def test_hypothesis(self, klass, val):
|
def test_hypothesis(self, klass, val):
|
||||||
d = klass(keytype=configtypes.Bool(),
|
d = klass(keytype=configtypes.Bool(),
|
||||||
valtype=configtypes.Bool())
|
valtype=configtypes.Bool(),
|
||||||
d.from_py(val)
|
none_ok=True)
|
||||||
|
converted = d.from_py(val)
|
||||||
|
assert d.from_str(d.to_str(converted)) == converted
|
||||||
|
|
||||||
@hypothesis.given(val=strategies.dictionaries(strategies.booleans(),
|
@hypothesis.given(val=strategies.dictionaries(strategies.booleans(),
|
||||||
strategies.booleans()))
|
strategies.booleans()))
|
||||||
@ -1313,9 +1403,11 @@ class TestDict:
|
|||||||
text = json.dumps(val)
|
text = json.dumps(val)
|
||||||
d = klass(keytype=configtypes.Bool(), valtype=configtypes.Bool())
|
d = klass(keytype=configtypes.Bool(), valtype=configtypes.Bool())
|
||||||
try:
|
try:
|
||||||
d.from_str(text)
|
converted = d.from_str(text)
|
||||||
except configexc.ValidationError:
|
except configexc.ValidationError:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
assert d.to_str(converted) == text
|
||||||
|
|
||||||
|
|
||||||
def unrequired_class(**kwargs):
|
def unrequired_class(**kwargs):
|
||||||
|
Loading…
Reference in New Issue
Block a user