From c101af99d3030df73745f352cfd4c7762f273ac2 Mon Sep 17 00:00:00 2001 From: Alexander Zaft Date: Fri, 24 Mar 2023 15:22:29 +0100 Subject: [PATCH] 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 Reviewed-by: Alexander Zaft --- frappy/datatypes.py | 32 ++++++++++++++++++++++++++---- frappy/params.py | 2 +- test/test_datatypes.py | 44 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/frappy/datatypes.py b/frappy/datatypes.py index 86cf008..1651ac6 100644 --- a/frappy/datatypes.py +++ b/frappy/datatypes.py @@ -28,8 +28,8 @@ import sys from base64 import b64decode, b64encode -from frappy.errors import WrongTypeError, RangeError, \ - ConfigError, ProgrammingError, ProtocolError, DiscouragedConversion +from frappy.errors import ConfigError, DiscouragedConversion, \ + ProgrammingError, ProtocolError, RangeError, WrongTypeError from frappy.lib import clamp, generalConfig from frappy.lib.enum import Enum from frappy.parse import Parser @@ -1161,11 +1161,35 @@ class DataTypeType(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): - """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 + def copy(self): + return ValueType(self.validator) + def export_value(self, value): """if needed, reformat value for transport""" return value diff --git a/frappy/params.py b/frappy/params.py index 8310212..d27c163 100644 --- a/frappy/params.py +++ b/frappy/params.py @@ -163,7 +163,7 @@ class Parameter(Accessible): export=False, default=False) update_unchanged = Property( '''[internal] updates of unchanged values - + - one of the values 'always', 'never', 'default' or the minimum time between updates of equal values [sec]''', OrType(FloatRange(0), EnumType(always=0, never=999999999, default=-1)), diff --git a/test/test_datatypes.py b/test/test_datatypes.py index 82cda7c..d17741a 100644 --- a/test/test_datatypes.py +++ b/test/test_datatypes.py @@ -25,13 +25,12 @@ # no fixtures needed import pytest -from frappy.datatypes import ArrayOf, BLOBType, BoolType, \ - CommandType, ConfigError, DataType, EnumType, FloatRange, \ - IntRange, ProgrammingError, ScaledInteger, StatusType, \ - StringType, StructOf, TextType, TupleOf, get_datatype, \ - DiscouragedConversion +from frappy.datatypes import ArrayOf, BLOBType, BoolType, CommandType, \ + ConfigError, DataType, DiscouragedConversion, EnumType, FloatRange, \ + IntRange, ProgrammingError, ScaledInteger, StatusType, StringType, \ + StructOf, TextType, TupleOf, ValueType, get_datatype +from frappy.errors import BadValueError, RangeError, WrongTypeError from frappy.lib import generalConfig -from frappy.errors import WrongTypeError, RangeError, BadValueError def copytest(dt): @@ -725,3 +724,36 @@ def test_main_unit(unit, dt): assert '$' in before assert before != 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)