fix parameter inheritance using MRO

+ no update on unhchanged values within 1 sec

Change-Id: I3e3d50bb5541e8d4da2badc3133d243dd0a3b892
This commit is contained in:
2021-10-06 08:32:47 +02:00
parent 544e42033d
commit e38cd11bfe
8 changed files with 400 additions and 164 deletions

View File

@ -71,6 +71,8 @@ class Data:
class DispatcherStub:
OMIT_UNCHANGED_WITHIN = 0
def __init__(self, updates):
self.updates = updates
@ -106,7 +108,6 @@ def test_IOHandler():
group1 = Hdl('group1', 'SIMPLE?', '%g')
group2 = Hdl('group2', 'CMD?%(channel)d', '%g,%s,%d')
class Module1(Module):
channel = Property('the channel', IntRange(), default=3)
loop = Property('the loop', IntRange(), default=2)

View File

@ -26,14 +26,16 @@ import threading
import pytest
from secop.datatypes import BoolType, FloatRange, StringType
from secop.errors import ProgrammingError
from secop.modules import Communicator, Drivable, Module
from secop.datatypes import BoolType, FloatRange, StringType, IntRange, CommandType
from secop.errors import ProgrammingError, ConfigError
from secop.modules import Communicator, Drivable, Readable, Module
from secop.params import Command, Parameter
from secop.poller import BasicPoller
class DispatcherStub:
OMIT_UNCHANGED_WITHIN = 0
def __init__(self, updates):
self.updates = updates
@ -51,6 +53,9 @@ class LoggerStub:
info = warning = exception = debug
logger = LoggerStub()
class ServerStub:
def __init__(self, updates):
self.dispatcher = DispatcherStub(updates)
@ -125,7 +130,7 @@ def test_ModuleMagic():
value = Parameter(datatype=FloatRange(unit='deg'))
a1 = Parameter(datatype=FloatRange(unit='$/s'), readonly=False)
b2 = Parameter('<b2>', datatype=BoolType(), default=True,
poll=True, readonly=False, initwrite=True)
poll=True, readonly=False, initwrite=True)
def write_a1(self, value):
self._a1_written = value
@ -142,7 +147,6 @@ def test_ModuleMagic():
sortcheck2 = ['status', 'pollinterval', 'target', 'stop',
'a1', 'a2', 'cmd2', 'param1', 'param2', 'cmd', 'value', 'b2']
logger = LoggerStub()
updates = {}
srv = ServerStub(updates)
@ -228,3 +232,103 @@ def test_ModuleMagic():
o.earlyInit()
for o in objects:
o.initModule()
def test_param_inheritance():
srv = ServerStub({})
class Base(Module):
param = Parameter()
class MissingDatatype(Base):
param = Parameter('param')
class MissingDescription(Base):
param = Parameter(datatype=FloatRange(), default=0)
# missing datatype and/or description of a parameter has to be detected
# at instantation and only then
with pytest.raises(ConfigError) as e_info:
MissingDatatype('o', logger, {'description': ''}, srv)
assert 'datatype' in repr(e_info.value)
with pytest.raises(ConfigError) as e_info:
MissingDescription('o', logger, {'description': ''}, srv)
assert 'description' in repr(e_info.value)
with pytest.raises(ConfigError) as e_info:
Base('o', logger, {'description': ''}, srv)
def test_mixin():
# srv = ServerStub({})
class Mixin: # no need to inherit from Module or HasAccessible
value = Parameter(unit='K') # missing datatype and description acceptable in mixins
param1 = Parameter('no datatype yet', fmtstr='%.5f')
param2 = Parameter('no datatype yet', default=1)
class MixedReadable(Mixin, Readable):
pass
class MixedDrivable(MixedReadable, Drivable):
value = Parameter(unit='Ohm', fmtstr='%.3f')
param1 = Parameter(datatype=FloatRange())
with pytest.raises(ProgrammingError):
class MixedModule(Mixin):
param1 = Parameter('', FloatRange(), fmtstr=0) # fmtstr must be a string
assert repr(MixedDrivable.status.datatype) == repr(Drivable.status.datatype)
assert repr(MixedReadable.status.datatype) == repr(Readable.status.datatype)
assert MixedReadable.value.datatype.unit == 'K'
assert MixedDrivable.value.datatype.unit == 'Ohm'
assert MixedDrivable.value.datatype.fmtstr == '%.3f'
# when datatype is overridden, fmtstr falls back to default:
assert MixedDrivable.param1.datatype.fmtstr == '%g'
srv = ServerStub({})
MixedDrivable('o', logger, {
'description': '',
'param1.description': 'param 1',
'param1': 0,
'param2.datatype': {"type": "double"},
}, srv)
with pytest.raises(ConfigError):
MixedReadable('o', logger, {
'description': '',
'param1.description': 'param 1',
'param1': 0,
'param2.datatype': {"type": "double"},
}, srv)
def test_command_config():
class Mod(Module):
@Command(IntRange(0, 1), result=IntRange(0, 1))
def convert(self, value):
return value
srv = ServerStub({})
mod = Mod('o', logger, {
'description': '',
'convert.argument': {'type': 'bool'},
}, srv)
assert mod.commands['convert'].datatype.export_datatype() == {
'type': 'command',
'argument': {'type': 'bool'},
'result': {'type': 'int', 'min': 0, 'max': 1},
}
mod = Mod('o', logger, {
'description': '',
'convert.datatype': {'type': 'command', 'argument': {'type': 'bool'}, 'result': {'type': 'bool'}},
}, srv)
assert mod.commands['convert'].datatype.export_datatype() == {
'type': 'command',
'argument': {'type': 'bool'},
'result': {'type': 'bool'},
}

View File

@ -88,15 +88,18 @@ def test_Override():
p1 = Parameter(default=True)
p2 = Parameter() # override without change
assert Mod.p1 != Base.p1
assert Mod.p2 != Base.p2
assert Mod.p3 == Base.p3
assert id(Mod.p2) != id(Base.p2) # must be a new object
assert repr(Mod.p2) == repr(Base.p2) # but must be a clone
assert id(Mod.p1) != id(Base.p1)
assert id(Mod.p2) != id(Base.p2)
assert id(Mod.p3) == id(Base.p3)
assert repr(Mod.p2) == repr(Base.p2) # must be a clone
assert repr(Mod.p3) == repr(Base.p3) # must be a clone
assert Mod.p1.default == True
# manipulating default makes Base.p1 and Mod.p1 match
Mod.p1.default = False
assert repr(Mod.p1) == repr(Base.p1)
def test_Export():
class Mod:
class Mod(HasAccessibles):
param = Parameter('description1', datatype=BoolType, default=False)
assert Mod.param.export == '_param'

View File

@ -159,3 +159,26 @@ def test_Property_override():
a = 's'
assert 'can not set' in str(e.value)
def test_Properties_mro():
class A(HasProperties):
p = Property('base', StringType(), 'base', export='always')
class B(A):
pass
class C(A):
p = Property('sub', FloatRange(), extname='p')
class D(C, B):
p = 1
class E(B, C):
p = 2
assert B().exportProperties() == {'_p': 'base'}
assert D().exportProperties() == {'p': 1.0}
# in an older implementation the following would fail, as B.p is constructed first
# and then B.p overrides C.p
assert E().exportProperties() == {'p': 2.0}