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