Add basic validators

to be used by the properties and datatypes.
(more patches coming!)

Change-Id: I6adc8be2a3215e8f6ec2b4c28e16c5b509a28cbf
Reviewed-on: https://forge.frm2.tum.de/review/20406
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
Enrico Faulhaber 2019-04-16 16:16:02 +02:00
parent cafc7a1409
commit 155dd8e4c6
2 changed files with 232 additions and 0 deletions

148
secop/basic_validators.py Normal file
View File

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""basic validators (for properties)"""
from __future__ import division, print_function
import re
from secop.errors import ProgrammingError
try:
# py2
unicode
except NameError:
# py3
unicode = str # pylint: disable=redefined-builtin
def FloatProperty(value):
return float(value)
def PositiveFloatProperty(value):
value = float(value)
if value > 0:
return value
raise ValueError(u'Value must be >0 !')
def NonNegativeFloatProperty(value):
value = float(value)
if value >= 0:
return value
raise ValueError(u'Value must be >=0 !')
def IntProperty(value):
if int(value) == float(value):
return int(value)
raise ValueError(u'Can\'t convert %r to int!' % value)
def PositiveIntProperty(value):
value = IntProperty(value)
if value > 0:
return value
raise ValueError(u'Value must be >0 !')
def NonNegativeIntProperty(value):
value = IntProperty(value)
if value >= 0:
return value
raise ValueError(u'Value must be >=0 !')
def BoolProperty(value):
try:
if value.lower() in [u'0', u'false', u'no', u'off',]:
return False
if value.lower() in [u'1', u'true', u'yes', u'on', ]:
return True
except AttributeError: # was no string
if bool(value) == value:
return value
raise ValueError(u'%r is no valid boolean: try one of True, False, "on", "off",...' % value)
def StringProperty(value):
return unicode(value)
def UnitProperty(value):
# probably too simple!
for s in unicode(value):
if s.lower() not in u'°abcdefghijklmnopqrstuvwxyz':
raise ValueError(u'%r is not a valid unit!')
def FmtStrProperty(value, regexp=re.compile(r'^%\.?\d+[efg]$')):
value=unicode(value)
if regexp.match(value):
return value
raise ValueError(u'%r is not a valid fmtstr!' % value)
def OneOfProperty(*args):
# literally oneof!
if not args:
raise ProgrammingError(u'OneOfProperty needs some argumets to check against!')
def OneOfChecker(value):
if value not in args:
raise ValueError(u'Value must be one of %r' % list(args))
return value
return OneOfChecker
def NoneOr(checker):
if not callable(checker):
raise ProgrammingError(u'NoneOr needs a basic validator as Argument!')
def NoneOrChecker(value):
if value is None:
return None
return checker(value)
return NoneOrChecker
def EnumProperty(**kwds):
if not kwds:
raise ProgrammingError(u'EnumProperty needs a mapping!')
def EnumChecker(value):
if value in kwds:
return kwds[value]
if value in kwds.values():
return value
raise ValueError(u'Value must be one of %r' % list(kwds))
return EnumChecker
def TupleProperty(*checkers):
if not checkers:
checkers = [None]
for c in checkers:
if not callable(c):
raise ProgrammingError(u'TupleProperty needs basic validators as Arguments!')
def TupleChecker(values):
if len(values)==len(checkers):
return tuple(c(v) for c, v in zip(checkers, values))
raise ValueError(u'Value needs %d elements!' % len(checkers))
return TupleChecker

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""test basic validators."""
from __future__ import division, print_function
# no fixtures needed
import pytest
from secop.basic_validators import FloatProperty, PositiveFloatProperty, \
NonNegativeFloatProperty, IntProperty, PositiveIntProperty, \
NonNegativeIntProperty, BoolProperty, StringProperty, UnitProperty, \
FmtStrProperty, OneOfProperty, NoneOr, EnumProperty, TupleProperty
class unprintable(object):
def __str__(self):
raise NotImplementedError
@pytest.mark.parametrize('validators_args', [
[FloatProperty, [None, 'a'], [1, 1.23, '1.23', '9e-12']],
[PositiveFloatProperty, ['x', -9, '-9', 0], [1, 1.23, '1.23', '9e-12']],
[NonNegativeFloatProperty, ['x', -9, '-9'], [0, 1.23, '1.23', '9e-12']],
[IntProperty, [None, 'a', 1.2, '1.2'], [1, '-1']],
[PositiveIntProperty, ['x', 1.9, '-9', '1e-4'], [1, '1']],
[NonNegativeIntProperty, ['x', 1.9, '-9', '1e-6'], [0, '1']],
[BoolProperty, ['x', 3], ['on', 'off', True, False]],
[StringProperty, [unprintable()], [u'1', 1.2, [{}]]],
[UnitProperty, [unprintable(), '3', 9], [u'mm', 'Gbarn', 'acre']],
[FmtStrProperty, [1, None, 'a', '%f'], [u'%.0e', u'%.3f','%.1g']],
])
def test_validators(validators_args):
v, fails, oks = validators_args
for value in fails:
with pytest.raises(Exception):
v(value)
for value in oks:
v(value)
@pytest.mark.parametrize('checker_inits', [
[OneOfProperty, lambda: OneOfProperty(a=3),], # pylint: disable=unexpected-keyword-arg
[NoneOr, lambda: NoneOr(None),],
[EnumProperty, lambda: EnumProperty(1),], # pylint: disable=too-many-function-args
[TupleProperty, lambda: TupleProperty(1,2,3),],
])
def test_checker_fails(checker_inits):
empty, badargs = checker_inits
with pytest.raises(Exception):
empty()
with pytest.raises(Exception):
badargs()
@pytest.mark.parametrize('checker_args', [
[OneOfProperty(1,2,3), ['x', None, 4], [1, 2, 3]],
[NoneOr(IntProperty), ['a', 1.2, '1.2'], [None, 1, '-1', '999999999999999']],
[EnumProperty(a=1, b=2), ['x', None, 3], [u'a', 'b', 1, 2]],
[TupleProperty(IntProperty, StringProperty), [1, 'a', ('x', 2)], [(1,'x')]],
])
def test_checkers(checker_args):
v, fails, oks = checker_args
for value in fails:
with pytest.raises(Exception):
v(value)
for value in oks:
v(value)