Initial work on new configdata

This commit is contained in:
Florian Bruhin 2017-06-12 15:12:32 +02:00
parent c2e75bf2fd
commit 7e52eb7b0e

View File

@ -134,11 +134,14 @@ class BaseType:
"""Get the type's valid values for documentation."""
return self.valid_values
def _basic_validation(self, value):
def _basic_validation(self, value, pytype=None):
"""Do some basic validation for the value (empty, non-printable chars).
Also does a Python typecheck on the value (if coming from YAML/Python).
Arguments:
value: The value to check.
pytype: If given, a Python type to check the value against.
"""
if not value:
if self.none_ok:
@ -146,29 +149,17 @@ class BaseType:
else:
raise configexc.ValidationError(value, "may not be empty!")
if any(ord(c) < 32 or ord(c) == 0x7f for c in value):
raise configexc.ValidationError(value, "may not contain "
"unprintable chars!")
if isinstance(value, str):
if any(ord(c) < 32 or ord(c) == 0x7f for c in value):
raise configexc.ValidationError(value, "may not contain "
"unprintable chars!")
def transform(self, value):
"""Transform the setting value.
if pytype is not None and not isinstance(value, pytype):
raise configexc.ValidationError(
value, "expected a value of type {} but got {}".format(
pytype, type(value)))
This method can assume the value is indeed a valid value.
The default implementation returns the original value.
Args:
value: The original string value.
Return:
The transformed value.
"""
if not value:
return None
else:
return value
def validate(self, value):
def _validate_valid_values(self, value):
"""Validate value against possible values.
The default implementation checks the value against self.valid_values
@ -177,7 +168,7 @@ class BaseType:
Args:
value: The value to validate.
"""
self._basic_validation(value)
# FIXME:conf still needed?
if not value:
return
if self.valid_values is not None:
@ -189,6 +180,45 @@ class BaseType:
raise NotImplementedError("{} does not implement validate.".format(
self.__class__.__name__))
def from_str(self, value):
"""Get the setting value from a string.
Args:
value: The original string value.
Return:
The transformed value.
"""
raise NotImplementedError
def from_py(self, value):
"""Get the setting value from a Python value.
Args:
value: The value we got from Python/YAML.
Return:
The transformed value.
Raise:
configexc.ValidationError if the value was invalid.
"""
raise NotImplementedError
def to_str(self, value):
"""Get a string from the setting value.
The resulting string should be parseable again by from_str.
"""
raise NotImplementedError
# def to_py(self, value):
# """Get a Python/YAML value from the setting value.
# The resulting value should be parseable again by from_py.
# """
# raise NotImplementedError
def complete(self):
"""Return a list of possible values for completion.
@ -223,19 +253,25 @@ class MappingType(BaseType):
MAPPING = {}
def __init__(self, none_ok=False,
valid_values=None):
def __init__(self, none_ok=False, valid_values=None):
super().__init__(none_ok)
self.valid_values = valid_values
def validate(self, value):
super().validate(value.lower())
def transform(self, value):
def from_py(self, value):
self._basic_validation(value, pytype=str)
if not value:
return None
self._validate_valid_values(value.lower())
return self.MAPPING[value.lower()]
def from_str(self, value):
return self.from_py(value)
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):
@ -265,16 +301,11 @@ class String(BaseType):
self.forbidden = forbidden
self._completions = completions
def validate(self, value):
self._basic_validation(value)
def from_py(self, value):
self._basic_validation(value, pytype=str)
if not value:
return
if self.valid_values is not None:
if value not in self.valid_values:
raise configexc.ValidationError(
value,
"valid values: {}".format(', '.join(self.valid_values)))
return None
self._validate_valid_values(value)
if self.forbidden is not None and any(c in value
for c in self.forbidden):
@ -287,6 +318,11 @@ class String(BaseType):
raise configexc.ValidationError(value, "must be at most {} chars "
"long!".format(self.maxlen))
return value
def from_str(self, value):
return self.from_py(value)
def complete(self):
if self._completions is not None:
return self._completions
@ -298,16 +334,18 @@ class UniqueCharString(String):
"""A string which may not contain duplicate chars."""
def validate(self, value):
super().validate(value)
def from_py(self, value):
value = super().from_py(value)
if not value:
return
return None
# Check for duplicate values
if len(set(value)) != len(value):
raise configexc.ValidationError(
value, "String contains duplicate values!")
return value
class List(BaseType):
@ -329,23 +367,23 @@ class List(BaseType):
def get_valid_values(self):
return self.valtype.get_valid_values()
def transform(self, value):
def from_str(self, value):
try:
json_val = json.loads(value)
except ValueError as e:
raise configexc.ValidationError(value, str(e))
return self.from_py(json_val)
def from_py(self, value):
self._basic_validation(value, pytype=list)
if not value:
return None
else:
return [self.valtype.transform(v.strip())
for v in value.split(',')]
def validate(self, value):
self._basic_validation(value)
if not value:
return
vals = value.split(',')
if self.length is not None and len(vals) != self.length:
if self.length is not None and len(value) != self.length:
raise configexc.ValidationError(value, "Exactly {} values need to "
"be set!".format(self.length))
for val in vals:
self.valtype.validate(val.strip())
return [self.valtype.from_py(v) for v in value]
class FlagList(List):
@ -364,19 +402,13 @@ class FlagList(List):
super().__init__(BaseType(), none_ok)
self.valtype.valid_values = valid_values
def validate(self, value):
if self.valtype.valid_values is not None:
super().validate(value)
else:
self._basic_validation(value)
if not value:
return
vals = super().transform(value)
def from_py(self, value):
vals = super().from_py(value)
# Check for duplicate values
if len(set(vals)) != len(vals):
raise configexc.ValidationError(
value, "List contains duplicate values!")
return vals
def complete(self):
valid_values = self.valtype.valid_values
@ -407,17 +439,18 @@ class Bool(BaseType):
super().__init__(none_ok)
self.valid_values = ValidValues('true', 'false')
def transform(self, value):
if not value:
return None
else:
return BOOLEAN_STATES[value.lower()]
def from_py(self, value):
self._basic_validation(value, pytype=bool)
return value
def validate(self, value):
def from_str(self, value):
self._basic_validation(value)
if not value:
return
elif value.lower() not in BOOLEAN_STATES:
return None
try:
return BOOLEAN_STATES[value.lower()]
except KeyError:
raise configexc.ValidationError(value, "must be a boolean!")
@ -429,17 +462,15 @@ class BoolAsk(Bool):
super().__init__(none_ok)
self.valid_values = ValidValues('true', 'false', 'ask')
def transform(self, value):
if value.lower() == 'ask':
def from_py(self, value):
# basic validation unneeded if it's == 'ask' and done by Bool if we call
# super().from_py
if isinstance(value, str) and value.lower() == 'ask':
return 'ask'
else:
return super().transform(value)
return super().from_py(value)
def validate(self, value):
if value.lower() == 'ask':
return
else:
super().validate(value)
def from_str(self, value):
return self.from_py(value)
class Int(BaseType):
@ -470,26 +501,24 @@ class Int(BaseType):
assert isinstance(value, int), value
return value
def transform(self, value):
if not value:
return None
else:
return int(value)
def validate(self, value):
self._basic_validation(value)
if not value:
return
def from_str(self, value):
try:
intval = int(value)
except ValueError:
raise configexc.ValidationError(value, "must be an integer!")
if self.minval is not None and intval < self.minval:
return self.from_py(intval)
def from_py(self, value):
self._basic_validation(value, pytype=int)
if not value:
return value
if self.minval is not None and value < self.minval:
raise configexc.ValidationError(value, "must be {} or "
"bigger!".format(self.minval))
if self.maxval is not None and intval > self.maxval:
if self.maxval is not None and value > self.maxval:
raise configexc.ValidationError(value, "must be {} or "
"smaller!".format(self.maxval))
return value
class Float(BaseType):
@ -501,34 +530,46 @@ class Float(BaseType):
maxval: Maximum value (inclusive).
"""
# FIXME:conf inherit Int/Float
def __init__(self, minval=None, maxval=None, none_ok=False):
super().__init__(none_ok)
if maxval is not None and minval is not None and maxval < minval:
raise ValueError("minval ({}) needs to be <= maxval ({})!".format(
minval, maxval))
self.minval = minval
self.maxval = maxval
self.minval = self._parse_limit(minval)
self.maxval = self._parse_limit(maxval)
if self.maxval is not None and self.minval is not None:
if self.maxval < self.minval:
raise ValueError("minval ({}) needs to be <= maxval ({})!"
.format(self.minval, self.maxval))
def transform(self, value):
if not value:
return None
def _parse_limit(self, value):
if value == 'maxint':
return qtutils.MAXVALS['int']
elif value == 'maxint64':
return qtutils.MAXVALS['int64']
else:
return float(value)
if value is not None:
assert isinstance(value, int), value
return value
def validate(self, value):
self._basic_validation(value)
if not value:
return
def from_str(self, value):
try:
floatval = float(value)
intval = int(value)
except ValueError:
raise configexc.ValidationError(value, "must be a float!")
if self.minval is not None and floatval < self.minval:
raise configexc.ValidationError(value, "must be an integer!")
return self.from_py(intval)
def from_py(self, value):
self._basic_validation(value, pytype=int)
if not value:
return value
if self.minval is not None and value < self.minval:
raise configexc.ValidationError(value, "must be {} or "
"bigger!".format(self.minval))
if self.maxval is not None and floatval > self.maxval:
if self.maxval is not None and value > self.maxval:
raise configexc.ValidationError(value, "must be {} or "
"smaller!".format(self.maxval))
return value
class Perc(BaseType):