as_json on FloatRange and ScaledInteger must be a property
as the unit property may be set in the config file, the datatype might change after creation. This proposed implementation changes the internal name of *_precision to its full name, as this is easier for the way it is done. IntRange is not yet modified, as anyway 'unit' (and 'fmtstr') should not be applied to it, this is not forseen in the standard. Questions for further work: - should we also allow to configure 'fmtstr'? - should it be allowed to change min, max from configuration? - if yes, what is the proper way to do it? Change-Id: I1fda7e8274109fdcca3c792c0a6e3dc6664cc45e Reviewed-on: https://forge.frm2.tum.de/review/20304 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
d702bea7a6
commit
bc33f263ec
@ -97,48 +97,40 @@ class DataType(object):
|
||||
if unit is given, use it, else use the unit of the datatype (if any)"""
|
||||
raise NotImplementedError
|
||||
|
||||
def setprop(self, key, value, default, func=lambda x:x):
|
||||
"""set a datatype property and store the default"""
|
||||
self._defaults[key] = default
|
||||
if value is None:
|
||||
value = default
|
||||
setattr(self, key, func(value))
|
||||
|
||||
class FloatRange(DataType):
|
||||
"""Restricted float type"""
|
||||
|
||||
def __init__(self, minval=None, maxval=None, unit=u'', fmtstr=u'',
|
||||
def __init__(self, minval=None, maxval=None, unit=None, fmtstr=None,
|
||||
absolute_precision=None, relative_precision=None,):
|
||||
# store hints
|
||||
self.hints = {}
|
||||
self.unit = unicode(unit)
|
||||
self.fmtstr = unicode(fmtstr or u'%g')
|
||||
self.abs_prec = float(absolute_precision or 0.0)
|
||||
self.rel_prec = float(relative_precision or 1.2e-7)
|
||||
|
||||
# store values for the validator
|
||||
self.min = float(u'-inf') if minval is None else float(minval)
|
||||
self.max = float(u'+inf') if maxval is None else float(maxval)
|
||||
self._defaults = {}
|
||||
self.setprop('min', minval, float(u'-inf'), float)
|
||||
self.setprop('max', maxval, float(u'+inf'), float)
|
||||
self.setprop('unit', unit, u'', unicode)
|
||||
self.setprop('fmtstr', fmtstr, u'%g', unicode)
|
||||
self.setprop('absolute_precision', absolute_precision, 0.0, float)
|
||||
self.setprop('relative_precision', relative_precision, 1.2e-7, float)
|
||||
|
||||
# check values
|
||||
if self.min > self.max:
|
||||
raise ValueError(u'Max must be larger then min!')
|
||||
if '%' not in self.fmtstr:
|
||||
raise ValueError(u'Invalid fmtstr!')
|
||||
if self.abs_prec < 0:
|
||||
if self.absolute_precision < 0:
|
||||
raise ValueError(u'absolute_precision MUST be >=0')
|
||||
if self.rel_prec < 0:
|
||||
if self.relative_precision < 0:
|
||||
raise ValueError(u'relative_precision MUST be >=0')
|
||||
|
||||
info = {}
|
||||
if self.min != float(u'-inf'):
|
||||
info[u'min'] = self.min
|
||||
if self.max != float(u'inf'):
|
||||
info[u'max'] = self.max
|
||||
if unit:
|
||||
self.hints[u'unit'] = self.unit
|
||||
if fmtstr:
|
||||
self.hints[u'fmtstr'] = self.fmtstr
|
||||
if absolute_precision is not None:
|
||||
self.hints[u'absolute_precision'] = self.abs_prec
|
||||
if relative_precision is not None:
|
||||
self.hints[u'relative_precision'] = self.rel_prec
|
||||
info.update(self.hints)
|
||||
self.as_json = [u'double', info]
|
||||
@property
|
||||
def as_json(self):
|
||||
return [u'double', {k: getattr(self, k) for k, v in self._defaults.items()
|
||||
if v != getattr(self, k)}]
|
||||
|
||||
def validate(self, value):
|
||||
try:
|
||||
@ -159,11 +151,7 @@ class FloatRange(DataType):
|
||||
(value, self.min, self.max))
|
||||
|
||||
def __repr__(self):
|
||||
items = [] if self.max or self.min is None else \
|
||||
[u'-inf' if self.min == float(u'-inf') else self.fmtstr % self.min,
|
||||
u'inf' if self.max == float(u'inf') else self.fmtstr % self.max]
|
||||
for k,v in self.hints.items():
|
||||
items.append(u'%s=%r' % (k,v))
|
||||
items = [u'%s=%r' % (k,v) for k,v in self.as_json[1].items()]
|
||||
return u'FloatRange(%s)' % (', '.join(items))
|
||||
|
||||
def export_value(self, value):
|
||||
@ -189,29 +177,17 @@ class FloatRange(DataType):
|
||||
class IntRange(DataType):
|
||||
"""Restricted int type"""
|
||||
|
||||
def __init__(self, minval=None, maxval=None, fmtstr=u'%d', unit=u''):
|
||||
self.hints = {}
|
||||
self.fmtstr = unicode(fmtstr)
|
||||
self.unit = unicode(unit)
|
||||
def __init__(self, minval=None, maxval=None):
|
||||
self.min = DEFAULT_MIN_INT if minval is None else int(minval)
|
||||
self.max = DEFAULT_MAX_INT if maxval is None else int(maxval)
|
||||
|
||||
# check values
|
||||
if self.min > self.max:
|
||||
raise ValueError(u'Max must be larger then min!')
|
||||
if '%' not in self.fmtstr:
|
||||
raise ValueError(u'Invalid fmtstr!')
|
||||
|
||||
info = {}
|
||||
self.hints = {}
|
||||
info[u'min'] = self.min
|
||||
info[u'max'] = self.max
|
||||
if unit:
|
||||
self.hints[u'unit'] = self.unit
|
||||
if fmtstr != u'%d':
|
||||
self.hints[u'fmtstr'] = self.fmtstr
|
||||
info.update(self.hints)
|
||||
self.as_json = [u'int', info]
|
||||
@property
|
||||
def as_json(self):
|
||||
return [u'int', {"min": self.min, "max": self.max}]
|
||||
|
||||
def validate(self, value):
|
||||
try:
|
||||
@ -227,10 +203,7 @@ class IntRange(DataType):
|
||||
raise ValueError(u'Can not validate %r to int' % value)
|
||||
|
||||
def __repr__(self):
|
||||
items = [u"%d, %d" % (self.min, self.max)]
|
||||
for k, v in self.hints.items():
|
||||
items.append(u'%s=%r' % (k, v))
|
||||
return u'IntRange(%s)' % (u', '.join(items))
|
||||
return u'IntRange(%d, %d)' % (self.min, self.max)
|
||||
|
||||
def export_value(self, value):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -245,11 +218,7 @@ class IntRange(DataType):
|
||||
return self.validate(value)
|
||||
|
||||
def format_value(self, value, unit=None):
|
||||
if unit is None:
|
||||
unit = self.unit
|
||||
if unit:
|
||||
return u' '.join([self.fmtstr % value, unit])
|
||||
return self.fmtstr % value
|
||||
return u'%d' % value
|
||||
|
||||
|
||||
class ScaledInteger(DataType):
|
||||
@ -258,18 +227,16 @@ class ScaledInteger(DataType):
|
||||
note: limits are for the scaled value (i.e. the internal value)
|
||||
the scale is only used for calculating to/from transport serialisation"""
|
||||
|
||||
def __init__(self, scale, minval=None, maxval=None, unit=u'', fmtstr=u'',
|
||||
def __init__(self, scale, minval=None, maxval=None, unit=None, fmtstr=None,
|
||||
absolute_precision=None, relative_precision=None,):
|
||||
self._defaults = {}
|
||||
self.scale = float(scale)
|
||||
if not self.scale > 0:
|
||||
raise ValueError(u'Scale MUST be positive!')
|
||||
|
||||
# store hints
|
||||
self.hints = {}
|
||||
self.unit = unicode(unit)
|
||||
self.fmtstr = unicode(fmtstr or u'%g')
|
||||
self.abs_prec = float(absolute_precision or self.scale)
|
||||
self.rel_prec = float(relative_precision or 0)
|
||||
self.setprop('unit', unit, u'', unicode)
|
||||
self.setprop('fmtstr', fmtstr, u'%g', unicode)
|
||||
self.setprop('absolute_precision', absolute_precision, self.scale, float)
|
||||
self.setprop('relative_precision', relative_precision, 1.2e-7, float)
|
||||
|
||||
self.min = DEFAULT_MIN_INT * self.scale if minval is None else float(minval)
|
||||
self.max = DEFAULT_MAX_INT * self.scale if maxval is None else float(maxval)
|
||||
@ -279,26 +246,19 @@ class ScaledInteger(DataType):
|
||||
raise ValueError(u'Max must be larger then min!')
|
||||
if '%' not in self.fmtstr:
|
||||
raise ValueError(u'Invalid fmtstr!')
|
||||
if self.abs_prec < 0:
|
||||
if self.absolute_precision < 0:
|
||||
raise ValueError(u'absolute_precision MUST be >=0')
|
||||
if self.rel_prec < 0:
|
||||
if self.relative_precision < 0:
|
||||
raise ValueError(u'relative_precision MUST be >=0')
|
||||
|
||||
info = {}
|
||||
self.hints = {}
|
||||
info[u'min'] = int(self.min // self.scale)
|
||||
info[u'max'] = int((self.max + self.scale * 0.5) // self.scale)
|
||||
info[u'scale'] = self.scale
|
||||
if unit:
|
||||
self.hints[u'unit'] = self.unit
|
||||
if fmtstr:
|
||||
self.hints[u'fmtstr'] = self.fmtstr
|
||||
if absolute_precision is not None:
|
||||
self.hints[u'absolute_precision'] = self.abs_prec
|
||||
if relative_precision is not None:
|
||||
self.hints[u'relative_precision'] = self.rel_prec
|
||||
info.update(self.hints)
|
||||
self.as_json = [u'scaled', info]
|
||||
@property
|
||||
def as_json(self):
|
||||
info = {k: getattr(self, k) for k, v in self._defaults.items()
|
||||
if v != getattr(self, k)}
|
||||
info['scale'] = self.scale
|
||||
info['min'] = int((self.min + self.scale * 0.5) // self.scale)
|
||||
info['max'] = int((self.max + self.scale * 0.5) // self.scale)
|
||||
return [u'scaled', info]
|
||||
|
||||
def validate(self, value):
|
||||
try:
|
||||
@ -316,8 +276,10 @@ class ScaledInteger(DataType):
|
||||
return value # return 'actual' value (which is more discrete than a float)
|
||||
|
||||
def __repr__(self):
|
||||
items = [self.fmtstr % self.scale, self.fmtstr % self.min, self.fmtstr % self.max]
|
||||
for k,v in self.hints.items():
|
||||
hints = self.as_json[1]
|
||||
hints.pop('scale')
|
||||
items = [self.scale]
|
||||
for k,v in hints.items():
|
||||
items.append(u'%s=%r' % (k,v))
|
||||
return u'ScaledInteger(%s)' % (', '.join(items))
|
||||
|
||||
|
@ -95,13 +95,8 @@ def test_IntRange():
|
||||
dt = IntRange()
|
||||
assert dt.as_json[0] == u'int'
|
||||
assert dt.as_json[1][u'min'] < 0 < dt.as_json[1][u'max']
|
||||
|
||||
dt = IntRange(unit=u'X', fmtstr=u'%r')
|
||||
assert dt.as_json == [u'int', {u'fmtstr': u'%r', u'max': 16777216,
|
||||
u'min': -16777216, u'unit': u'X'}]
|
||||
assert dt.format_value(42) == u'42 X'
|
||||
assert dt.format_value(42, u'') == u'42'
|
||||
assert dt.format_value(42, u'Z') == u'42 Z'
|
||||
assert dt.as_json == [u'int', {u'max': 16777216, u'min': -16777216}]
|
||||
assert dt.format_value(42) == u'42'
|
||||
|
||||
|
||||
def test_ScaledInteger():
|
||||
@ -277,11 +272,11 @@ def test_ArrayOf():
|
||||
u'members':[u'int', {u'min':-10,
|
||||
u'max':10}]}]
|
||||
|
||||
dt = ArrayOf(IntRange(-10, 10, unit=u'Z'), 1, 3)
|
||||
dt = ArrayOf(FloatRange(-10, 10, unit=u'Z'), 1, 3)
|
||||
assert dt.as_json == [u'array', {u'min':1, u'max':3,
|
||||
u'members':[u'int', {u'min':-10,
|
||||
u'max':10,
|
||||
u'unit':u'Z'}]}]
|
||||
u'members':[u'double', {u'min':-10,
|
||||
u'max':10,
|
||||
u'unit':u'Z'}]}]
|
||||
with pytest.raises(ValueError):
|
||||
dt.validate(9)
|
||||
with pytest.raises(ValueError):
|
||||
@ -302,10 +297,9 @@ def test_TupleOf():
|
||||
with pytest.raises(ValueError):
|
||||
TupleOf(2)
|
||||
|
||||
dt = TupleOf(IntRange(-10, 10, unit=u'X'), BoolType())
|
||||
dt = TupleOf(IntRange(-10, 10), BoolType())
|
||||
assert dt.as_json == [u'tuple', {u'members':[[u'int', {u'min':-10,
|
||||
u'max':10,
|
||||
u'unit':u'X'}],
|
||||
u'max':10}],
|
||||
[u'bool', {}]]}]
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
@ -318,7 +312,7 @@ def test_TupleOf():
|
||||
assert dt.export_value([1, True]) == [1, True]
|
||||
assert dt.import_value([1, True]) == [1, True]
|
||||
|
||||
assert dt.format_value([3,0]) == u"(3 X, False)"
|
||||
assert dt.format_value([3,0]) == u"(3, False)"
|
||||
|
||||
|
||||
def test_StructOf():
|
||||
@ -328,13 +322,12 @@ def test_StructOf():
|
||||
with pytest.raises(ProgrammingError):
|
||||
StructOf(IntRange=1)
|
||||
|
||||
dt = StructOf(a_string=StringType(0, 55), an_int=IntRange(0, 999, unit=u'Y'),
|
||||
dt = StructOf(a_string=StringType(0, 55), an_int=IntRange(0, 999),
|
||||
optional=[u'an_int'])
|
||||
assert dt.as_json == [u'struct', {u'members':{u'a_string':
|
||||
[u'string', {u'min':0, u'max':55}],
|
||||
u'an_int':
|
||||
[u'int', {u'min':0, u'max':999,
|
||||
u'unit':u'Y'}],},
|
||||
[u'int', {u'min':0, u'max':999}],},
|
||||
u'optional':[u'an_int'],
|
||||
}]
|
||||
|
||||
@ -352,7 +345,7 @@ def test_StructOf():
|
||||
assert dt.import_value({u'an_int': 13, u'a_string': u'WFEC'}) == {
|
||||
u'a_string': u'WFEC', u'an_int': 13}
|
||||
|
||||
assert dt.format_value({u'an_int':2, u'a_string':u'Z'}) == u"{a_string=u'Z', an_int=2 Y}"
|
||||
assert dt.format_value({u'an_int':2, u'a_string':u'Z'}) == u"{a_string=u'Z', an_int=2}"
|
||||
|
||||
|
||||
def test_get_datatype():
|
||||
|
Loading…
x
Reference in New Issue
Block a user