add optional validation to ValueType

* add optional parameter for ValueType: validator
used for checking, if a value meets a criteria (e.g. is dict)
+ InternalParameter, which is not exported and can hold any python value

Change-Id: If39a7a4a8019f2aa1a930e42cbef4fca59163b78
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30787
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
This commit is contained in:
Alexander Zaft 2023-03-24 15:22:29 +01:00 committed by Markus Zolliker
parent 7baacb0f9e
commit c101af99d3
3 changed files with 67 additions and 11 deletions

View File

@ -28,8 +28,8 @@
import sys import sys
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from frappy.errors import WrongTypeError, RangeError, \ from frappy.errors import ConfigError, DiscouragedConversion, \
ConfigError, ProgrammingError, ProtocolError, DiscouragedConversion ProgrammingError, ProtocolError, RangeError, WrongTypeError
from frappy.lib import clamp, generalConfig from frappy.lib import clamp, generalConfig
from frappy.lib.enum import Enum from frappy.lib.enum import Enum
from frappy.parse import Parser from frappy.parse import Parser
@ -1161,11 +1161,35 @@ class DataTypeType(DataType):
class ValueType(DataType): class ValueType(DataType):
"""validates any python value""" """Can take any python value.
The optional (callable) validator can be used to restrict values to a
certain type.
For example using `ValueType(dict)` would ensure only values that can be
turned into a dictionary can be used in this instance, as the conversion
`dict(value)` is called for validation.
Notes:
The validator must either accept a value by returning it or the converted value,
or raise an error.
"""
def __init__(self, validator=None):
super().__init__()
self.validator = validator
def __call__(self, value): def __call__(self, value):
"""accepts any type -> no conversion""" """accepts any type -> default is no conversion"""
if self.validator:
try:
return self.validator(value)
except Exception as e:
raise ConfigError('Validator %s raised %r for value %s' \
% (self.validator, e, value)) from e
return value return value
def copy(self):
return ValueType(self.validator)
def export_value(self, value): def export_value(self, value):
"""if needed, reformat value for transport""" """if needed, reformat value for transport"""
return value return value

View File

@ -25,13 +25,12 @@
# no fixtures needed # no fixtures needed
import pytest import pytest
from frappy.datatypes import ArrayOf, BLOBType, BoolType, \ from frappy.datatypes import ArrayOf, BLOBType, BoolType, CommandType, \
CommandType, ConfigError, DataType, EnumType, FloatRange, \ ConfigError, DataType, DiscouragedConversion, EnumType, FloatRange, \
IntRange, ProgrammingError, ScaledInteger, StatusType, \ IntRange, ProgrammingError, ScaledInteger, StatusType, StringType, \
StringType, StructOf, TextType, TupleOf, get_datatype, \ StructOf, TextType, TupleOf, ValueType, get_datatype
DiscouragedConversion from frappy.errors import BadValueError, RangeError, WrongTypeError
from frappy.lib import generalConfig from frappy.lib import generalConfig
from frappy.errors import WrongTypeError, RangeError, BadValueError
def copytest(dt): def copytest(dt):
@ -725,3 +724,36 @@ def test_main_unit(unit, dt):
assert '$' in before assert '$' in before
assert before != after assert before != after
assert before.replace('$', unit) == after assert before.replace('$', unit) == after
def ex_validator(i):
if i > 10:
raise RuntimeError('too large')
return i
@pytest.mark.parametrize('validator, value, result', [
(dict, [('a', 1)], {'a': 1}),
(ex_validator, 5, 5),
# pylint: disable=unnecessary-lambda
(lambda x: dict(x), {'a': 1}, {'a': 1}),
# pylint: disable=unnecessary-lambda
(lambda i: ex_validator(i) * 3, 3, 9),
])
def test_value_type(validator, value, result):
t = ValueType()
tv = ValueType(validator)
assert t(value) == value
assert tv(value) == result
@pytest.mark.parametrize('validator, value', [
(dict, 'strinput'),
(ex_validator, 20),
# pylint: disable=unnecessary-lambda
(lambda i: list(i), 1),
])
def test_value_type_rejecting(validator, value):
t = ValueType()
tv = ValueType(validator)
assert t(value) == value
with pytest.raises(ConfigError):
tv(value)