Implement ScaledInteger type
Change-Id: I14d55d2427017e1e07f947504348804c3e66848e Reviewed-on: https://forge.frm2.tum.de/review/20225 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
parent
7d63643b64
commit
58bae697c9
@ -181,6 +181,52 @@ class IntRange(DataType):
|
||||
return self.validate(value)
|
||||
|
||||
|
||||
class ScaledInteger(DataType):
|
||||
"""Scaled integer int type
|
||||
|
||||
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=-16777216, maxval=16777216):
|
||||
self.min = int(minval)
|
||||
self.max = int(maxval)
|
||||
self.scale = float(scale)
|
||||
if self.min > self.max:
|
||||
raise ValueError(u'Max must be larger then min!')
|
||||
if not self.scale > 0:
|
||||
raise ValueError(u'Scale MUST be positive!')
|
||||
self.as_json = [u'scaled', dict(min=int(round(minval/scale)), max=int(round(maxval/scale)), scale=scale)]
|
||||
|
||||
def validate(self, value):
|
||||
try:
|
||||
value = int(value)
|
||||
if value < self.min:
|
||||
raise ValueError(u'%r should be an int between %d and %d' %
|
||||
(value, self.min, self.max))
|
||||
if value > self.max:
|
||||
raise ValueError(u'%r should be an int between %d and %d' %
|
||||
(value, self.min, self.max))
|
||||
return value
|
||||
except Exception:
|
||||
raise ValueError(u'Can not validate %r to int' % value)
|
||||
|
||||
def __repr__(self):
|
||||
return u'ScaledInteger(%f, %d, %d)' % (self.scale, self.min, self.max)
|
||||
|
||||
def export_value(self, value):
|
||||
"""returns a python object fit for serialisation"""
|
||||
# XXX: rounds toward even !!! (i.e. 12.5 -> 12, 13.5 -> 14)
|
||||
return round(value / self.scale)
|
||||
|
||||
def import_value(self, value):
|
||||
"""returns a python object from serialisation"""
|
||||
return self.scale * int(value)
|
||||
|
||||
def from_string(self, text):
|
||||
value = int(text)
|
||||
return self.validate(value)
|
||||
|
||||
|
||||
class EnumType(DataType):
|
||||
def __init__(self, enum_or_name='', **kwds):
|
||||
if 'members' in kwds:
|
||||
@ -591,6 +637,7 @@ class Status(TupleOf):
|
||||
DATATYPES = dict(
|
||||
bool =BoolType,
|
||||
int =lambda min, max: IntRange(minval=min,maxval=max),
|
||||
scaled =lambda scale, min, max: ScaledInteger(scale=scale,minval=min*scale,maxval=max*scale),
|
||||
double =lambda min=None, max=None: FloatRange(minval=min, maxval=max),
|
||||
blob =lambda min=0, max=None: BLOBType(minsize=min, maxsize=max),
|
||||
string =lambda min=0, max=None: StringType(minsize=min, maxsize=max),
|
||||
|
@ -28,7 +28,7 @@ import pytest
|
||||
|
||||
from secop.datatypes import ArrayOf, BLOBType, BoolType, \
|
||||
DataType, EnumType, FloatRange, IntRange, ProgrammingError, \
|
||||
StringType, StructOf, TupleOf, get_datatype
|
||||
StringType, StructOf, TupleOf, get_datatype, ScaledInteger
|
||||
|
||||
|
||||
def test_DataType():
|
||||
@ -87,6 +87,33 @@ def test_IntRange():
|
||||
assert dt.as_json[1]['min'] < 0 < dt.as_json[1]['max']
|
||||
|
||||
|
||||
def test_ScaledInteger():
|
||||
dt = ScaledInteger(0.01, -3, 3)
|
||||
# serialisation of datatype contains limits on the 'integer' value
|
||||
assert dt.as_json == ['scaled', {'scale':0.01, 'min':-300, 'max':300}]
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
dt.validate(9)
|
||||
with pytest.raises(ValueError):
|
||||
dt.validate(-9)
|
||||
with pytest.raises(ValueError):
|
||||
dt.validate('XX')
|
||||
with pytest.raises(ValueError):
|
||||
dt.validate([19, 'X'])
|
||||
dt.validate(1)
|
||||
dt.validate(0)
|
||||
with pytest.raises(ValueError):
|
||||
ScaledInteger('xc', 'Yx')
|
||||
with pytest.raises(ValueError):
|
||||
ScaledInteger(scale=0, minval=1, maxval=2)
|
||||
with pytest.raises(ValueError):
|
||||
ScaledInteger(scale=-10, minval=1, maxval=2)
|
||||
|
||||
assert dt.export_value(0.0001) == int(0)
|
||||
assert dt.export_value(2.71819) == int(272)
|
||||
assert dt.import_value(272) == 2.72
|
||||
|
||||
|
||||
def test_EnumType():
|
||||
# test constructor catching illegal arguments
|
||||
with pytest.raises(TypeError):
|
||||
@ -311,6 +338,25 @@ def test_get_datatype():
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['double', 1, 2])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['scaled', {'scale':0.01,'min':-2.718}])
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['scaled', {'scale':0.02,'max':3.14}])
|
||||
assert isinstance(get_datatype(['scaled', {'scale':0.03,'min':-99, 'max':111}]), ScaledInteger)
|
||||
|
||||
dt = ScaledInteger(scale=0.03, minval=0, maxval=9.9)
|
||||
assert dt.as_json == ['scaled', {'max':330, 'min':0, 'scale':0.03}]
|
||||
assert get_datatype(dt.as_json).as_json == dt.as_json
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['scaled']) # dict missing
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['scaled', {'min':-10, 'max':10}]) # no scale
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['scaled', {'min':10, 'max':-10}]) # limits reversed
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['scaled', {}, 1, 2]) # trailing data
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
get_datatype(['enum'])
|
||||
with pytest.raises(ValueError):
|
||||
|
Loading…
x
Reference in New Issue
Block a user