use Properties from secop.properties for datatypes

this change is triggered by the fact, that assigining a unit
in the config file did no longer work.

this change has several implications:
1) secop.properties must not import secop.datatypes:
   - as ValueType can not be imported, the default behaviour with
     'mandatory' and 'default' arguments was slightly changed
   - instead of checking for DataType when exporting, a try/except
     was used
2) the datatype of datatype properties is sometimes not yet defined.
   a stub is used in this cases instead, which is later replaced by
   the proper datatype. The number of stubs may be reduced, but this
   should be done in a later change, as the diff will be much less
   readable.
3) in config files, datatype properties can be changed like parameter
   properties. HasProperties.setProperties/checkProperties/getProperties
   are overridden for this.

the config editor seems still to work, an issue (probably py3) had
to be fixed there

Change-Id: I1efddf51f2c760510e913dbcaa099e8a89c9cab5
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/21399
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:
2019-10-11 15:11:04 +02:00
parent 7688abfc8d
commit f4d572966c
10 changed files with 356 additions and 173 deletions

View File

@ -26,7 +26,7 @@
import pytest
from secop.datatypes import ArrayOf, BLOBType, BoolType, \
DataType, EnumType, FloatRange, IntRange, ProgrammingError, \
DataType, EnumType, FloatRange, IntRange, ProgrammingError, ConfigError, \
ScaledInteger, StringType, TextType, StructOf, TupleOf, get_datatype, CommandType
@ -63,11 +63,14 @@ def test_FloatRange():
dt(13.14 - 10) # raises an error, if resolution is not handled correctly
assert dt.export_value(-2.718) == -2.718
assert dt.import_value(-2.718) == -2.718
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
FloatRange('x', 'Y')
# check that unit can be changed
dt.unit = 'K'
dt.setProperty('unit', 'K')
assert dt.export_datatype() == {'type': 'double', 'min':-3.14, 'max':3.14, 'unit': 'K'}
with pytest.raises(KeyError):
dt.setProperty('visibility', 0)
dt.setProperty('absolute_resolution', 0)
dt = FloatRange()
copytest(dt)
@ -84,6 +87,14 @@ def test_FloatRange():
assert dt.format_value(3.14, '') == '3.14'
assert dt.format_value(3.14, '#') == '3.14 #'
dt.setProperty('min', 1)
dt.setProperty('max', 0)
with pytest.raises(ConfigError):
dt.checkProperties()
with pytest.raises(ProgrammingError):
FloatRange(resolution=1)
def test_IntRange():
dt = IntRange(-3, 3)
@ -100,7 +111,7 @@ def test_IntRange():
dt([19, 'X'])
dt(1)
dt(0)
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
IntRange('xc', 'Yx')
dt = IntRange()
@ -110,6 +121,11 @@ def test_IntRange():
assert dt.export_datatype() == {'type': 'int', 'max': 16777216,'min': -16777216}
assert dt.format_value(42) == '42'
dt.setProperty('min', 1)
dt.setProperty('max', 0)
with pytest.raises(ConfigError):
dt.checkProperties()
def test_ScaledInteger():
dt = ScaledInteger(0.01, -3, 3)
copytest(dt)
@ -128,18 +144,24 @@ def test_ScaledInteger():
dt(0)
with pytest.raises(ValueError):
ScaledInteger('xc', 'Yx')
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
ScaledInteger(scale=0, minval=1, maxval=2)
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
ScaledInteger(scale=-10, minval=1, maxval=2)
# check that unit can be changed
dt.unit = 'A'
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300, 'unit': 'A'}
dt.setProperty('unit', 'A')
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300,
'unit': 'A'}
assert dt.export_value(0.0001) == int(0)
assert dt.export_value(2.71819) == int(272)
assert dt.import_value(272) == 2.72
dt.setProperty('scale', 0.1)
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.1, 'min':-30, 'max':30,
'unit':'A'}
assert dt.absolute_resolution == dt.scale
dt = ScaledInteger(0.003, 0, 1, unit='X', fmtstr='%.1f',
absolute_resolution=0.001, relative_resolution=1e-5)
copytest(dt)
@ -155,6 +177,14 @@ def test_ScaledInteger():
with pytest.raises(ValueError):
dt(1.004)
dt.setProperty('min', 1)
dt.setProperty('max', 0)
with pytest.raises(ConfigError):
dt.checkProperties()
with pytest.raises(ValueError):
dt.setProperty('scale', None)
def test_EnumType():
# test constructor catching illegal arguments
@ -219,6 +249,11 @@ def test_BLOBType():
dt('abcd')
assert dt(b'abcd') == b'abcd'
dt.setProperty('minbytes', 1)
dt.setProperty('maxbytes', 0)
with pytest.raises(ConfigError):
dt.checkProperties()
assert dt.export_value(b'abcd') == 'YWJjZA=='
assert dt.export_value(b'abcd') == 'YWJjZA=='
# assert dt.export_value('abcd') == 'YWJjZA=='
@ -260,6 +295,11 @@ def test_StringType():
assert dt.format_value('abcd') == "'abcd'"
dt.setProperty('minchars', 1)
dt.setProperty('maxchars', 0)
with pytest.raises(ConfigError):
dt.checkProperties()
def test_TextType():
# test constructor catching illegal arguments
@ -309,6 +349,9 @@ def test_BoolType():
assert dt.format_value(0) == "False"
assert dt.format_value(True) == "True"
with pytest.raises(TypeError):
# pylint: disable=unexpected-keyword-arg
BoolType(unit='K')
def test_ArrayOf():
# test constructor catching illegal arguments
@ -341,6 +384,17 @@ def test_ArrayOf():
assert dt.format_value([1,2,3], '') == '[1, 2, 3]'
assert dt.format_value([1,2,3], 'Q') == '[1, 2, 3] Q'
dt = ArrayOf(FloatRange(unit='K'))
assert dt.members.unit == 'K'
dt.setProperty('unit', 'mm')
with pytest.raises(TypeError):
# pylint: disable=unexpected-keyword-arg
ArrayOf(BoolType(), unit='K')
dt.setProperty('minlen', 1)
dt.setProperty('maxlen', 0)
with pytest.raises(ConfigError):
dt.checkProperties()
def test_TupleOf():
# test constructor catching illegal arguments

View File

@ -31,7 +31,6 @@ from secop.modules import Communicator, Drivable, Module
from secop.params import Command, Override, Parameter
def test_Communicator():
logger = type('LoggerStub', (object,), dict(
debug = lambda self, *a: print(*a),
@ -54,11 +53,12 @@ def test_Communicator():
assert event.is_set() # event should be set immediately
def test_ModuleMeta():
# pylint: disable=too-many-function-args
newclass1 = ModuleMeta.__new__(ModuleMeta, 'TestDrivable', (Drivable,), {
"parameters" : {
'pollinterval': Override(reorder=True),
'param1' : Parameter('param1', datatype=BoolType(), default=False),
'param2': Parameter('param2', datatype=BoolType(), default=True),
'param2': Parameter('param2', datatype=FloatRange(unit='Ohm'), default=True),
"cmd": Command('stuff', argument=BoolType(), result=BoolType())
},
"commands": {
@ -82,10 +82,12 @@ def test_ModuleMeta():
sortcheck1 = ['value', 'status', 'target', 'pollinterval',
'param1', 'param2', 'cmd', 'a1', 'a2', 'cmd2']
# pylint: disable=too-many-function-args
newclass2 = ModuleMeta.__new__(ModuleMeta, 'UpperClass', (newclass1,), {
"parameters": {
'cmd2': Override('another stuff'),
'a1': Override(datatype=FloatRange(), reorder=True),
'value': Override(datatype=FloatRange(unit='deg'), reorder=True),
'a1': Override(datatype=FloatRange(unit='$/s'), reorder=True),
'b2': Parameter('a2', datatype=BoolType(), default=True),
},
})
@ -125,6 +127,25 @@ def test_ModuleMeta():
# HACK: atm. disabled to fix all other problems first.
assert check_order + sorted(check_order)
o1 = newclass1('o1', logger, {'.description':''}, srv)
o2 = newclass2('o2', logger, {'.description':''}, srv)
assert o2.parameters['a1'].datatype.unit == 'deg/s'
o2 = newclass2('o2', logger, {'.description':'', 'value.unit':'mm', 'param2.unit':'mm'}, srv)
# check datatype is not shared
assert o1.parameters['param2'].datatype.unit == 'Ohm'
assert o2.parameters['param2'].datatype.unit == 'mm'
# check '$' in unit works properly
assert o2.parameters['a1'].datatype.unit == 'mm/s'
cfg = newclass2.configurables
assert set(cfg.keys()) == {'export', 'group', 'description',
'meaning', 'visibility', 'implementation', 'interface_class', 'target', 'stop',
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'b2', 'cmd2', 'value',
'a1'}
assert set(cfg['value'].keys()) == {'group', 'export', 'relative_resolution',
'visibility', 'unit', 'default', 'optional', 'datatype', 'fmtstr',
'absolute_resolution', 'poll', 'max', 'min', 'readonly', 'constant',
'description'}
# check on the level of classes
# this checks newclass1 too, as it is inherited by newclass2
for baseclass in newclass2.__mro__:

View File

@ -24,10 +24,10 @@
import pytest
from secop.datatypes import IntRange, StringType, FloatRange, ValueType
from secop.errors import ProgrammingError
from secop.errors import ProgrammingError, ConfigError
from secop.properties import Property, Properties, HasProperties
# args are: datatype, default, extname, export, mandatory, settable
V_test_Property = [
[(StringType(), 'default', 'extname', False, False),
dict(default='default', extname='extname', export=True, mandatory=False)],
@ -45,16 +45,16 @@ V_test_Property = [
dict(default=0, extname='', export=False, mandatory=True)],
[(IntRange(), 0, '', False, False),
dict(default=0, extname='', export=False, mandatory=False)],
[(IntRange(), None, '', False, False),
dict(default=0, extname='', export=False, mandatory=True)], # 'normal types + no default -> mandatory
[(ValueType(), None, '', False, False),
dict(default=None, extname='', export=False, mandatory=False)], # 'special type + no default -> NOT mandatory
[(IntRange(), None, '', None),
dict(default=0, extname='', export=False, mandatory=True)], # mandatory not given, no default -> mandatory
[(ValueType(), 1, '', False),
dict(default=1, extname='', export=False, mandatory=False)], # mandatory not given, default given -> NOT mandatory
]
@pytest.mark.parametrize('args, check', V_test_Property)
def test_Property(args, check):
p = Property('', *args)
for k,v in check.items():
assert getattr(p, k) == v
result = {k: getattr(p, k) for k in check}
assert result == check
def test_Property_basic():
with pytest.raises(TypeError):
@ -96,9 +96,24 @@ class cl(c):
properties = {
'a' : Property('', IntRange(), 3),
'b' : Property('', FloatRange(), 3.14),
'minabc': Property('', IntRange(), 8),
'maxabc': Property('', IntRange(), 9),
'minx': Property('', IntRange(), 2),
'maxy': Property('', IntRange(), 1),
}
def test_HasProperties():
o = c()
assert o.properties['a'] == 1
o = cl()
assert o.properties['a'] == 3
assert o.properties['b'] == 3.14
def test_Property_checks():
o = c()
o.checkProperties()
o = cl()
o.checkProperties()
o.setProperty('maxabc', 1)
with pytest.raises(ConfigError):
o.checkProperties()