fix parameter inheritance

Correct inheritance has to follow the MRO, not only consider
the direct base classes.

Patchset 3: changed only tests, indicating that we need to change the code

Following patchsets include a major change in params.py and
modules.py. The parameter properties for inheritance, corresponding
mainly to the constructor arguments have to be stored separately
from the property values including inherited stuff.

Change-Id: Ibcbccb6abcc22e7e2d91df8f70ef64226684d8cc
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26805
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
This commit is contained in:
2021-10-05 16:04:50 +02:00
parent 41489a4a24
commit 3b7cc33f64
6 changed files with 350 additions and 138 deletions

View File

@ -25,9 +25,10 @@
import sys
import time
from collections import OrderedDict
from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
IntRange, StatusType, StringType, TextType, TupleOf, get_datatype
IntRange, StatusType, StringType, TextType, TupleOf
from secop.errors import BadValueError, ConfigError, InternalError, \
ProgrammingError, SECoPError, SilentError, secop_error
from secop.lib import formatException, mkthread
@ -40,7 +41,7 @@ Done = object() #: a special return value for a read/write function indicating
class HasAccessibles(HasProperties):
"""base class of module
"""base class of Module
joining the class's properties, parameters and commands dicts with
those of base classes.
@ -52,40 +53,46 @@ class HasAccessibles(HasProperties):
super().__init_subclass__()
# merge accessibles from all sub-classes, treat overrides
# for now, allow to use also the old syntax (parameters/commands dict)
accessibles = {}
for base in reversed(cls.__bases__):
accessibles.update(getattr(base, 'accessibles', {}))
newaccessibles = {k: v for k, v in cls.__dict__.items() if isinstance(v, Accessible)}
for aname, aobj in list(accessibles.items()):
value = getattr(cls, aname, None)
if not isinstance(value, Accessible): # else override is already done in __set_name__
if value is None:
accessibles.pop(aname)
else:
# this is either a method overwriting a command
# or a value overwriting a property value or parameter default
anew = aobj.override(value)
newaccessibles[aname] = anew
setattr(cls, aname, anew)
anew.__set_name__(cls, aname)
accessibles = OrderedDict() # dict of accessibles
merged_properties = {} # dict of dict of merged properties
new_names = [] # list of names of new accessibles
override_values = {} # bare values overriding a parameter and methods overriding a command
ordered = {}
for aname in cls.__dict__.get('paramOrder', ()):
for base in reversed(cls.__mro__):
for key, value in base.__dict__.items():
if isinstance(value, Accessible):
value.updateProperties(merged_properties.setdefault(key, {}))
if base == cls and key not in accessibles:
new_names.append(key)
accessibles[key] = value
override_values.pop(key, None)
elif key in accessibles:
override_values[key] = value
for aname, aobj in accessibles.items():
if aname in override_values:
aobj = aobj.copy()
aobj.merge(merged_properties[aname])
aobj.override(override_values[aname])
# replace the bare value by the created accessible
setattr(cls, aname, aobj)
else:
aobj.merge(merged_properties[aname])
accessibles[aname] = aobj
# rebuild order: (1) inherited items, (2) items from paramOrder, (3) new accessibles
# move (2) to the end
for aname in list(cls.__dict__.get('paramOrder', ())):
if aname in accessibles:
ordered[aname] = accessibles.pop(aname)
elif aname in newaccessibles:
ordered[aname] = newaccessibles.pop(aname)
# ignore unknown names
# starting from old accessibles not mentioned, append items from 'order'
accessibles.update(ordered)
# then new accessibles not mentioned
accessibles.update(newaccessibles)
accessibles.move_to_end(aname)
# ignore unknown names
# move (3) to the end
for aname in new_names:
accessibles.move_to_end(aname)
# note: for python < 3.6 the order of inherited items is not ensured between
# declarations within the same class
cls.accessibles = accessibles
# Correct naming of EnumTypes
for k, v in accessibles.items():
if isinstance(v, Parameter) and isinstance(v.datatype, EnumType):
v.datatype.set_name(k)
# moved to Parameter.__set_name__
# check validity of Parameter entries
for pname, pobj in accessibles.items():
@ -318,8 +325,9 @@ class Module(HasAccessibles):
paramobj = self.accessibles.get(paramname, None)
# paramobj might also be a command (not sure if this is needed)
if paramobj:
if propname == 'datatype':
propvalue = get_datatype(propvalue, k)
# no longer needed, this conversion is done by DataTypeType.__call__:
# if propname == 'datatype':
# propvalue = get_datatype(propvalue, k)
try:
paramobj.setProperty(propname, propvalue)
except KeyError:
@ -347,6 +355,10 @@ class Module(HasAccessibles):
self.valueCallbacks[pname] = []
self.errorCallbacks[pname] = []
if not pobj.hasDatatype():
errors.append('%s needs a datatype' % pname)
continue
if pname in cfgdict:
if not pobj.readonly and pobj.initwrite is not False:
# parameters given in cfgdict have to call write_<pname>
@ -393,11 +405,15 @@ class Module(HasAccessibles):
cfgdict.pop(k)
except (ValueError, TypeError) as e:
# self.log.exception(formatExtendedStack())
errors.append('module %s, parameter %s: %s' % (self.name, k, e))
errors.append('parameter %s: %s' % (k, e))
# ensure consistency
for aobj in self.accessibles.values():
aobj.finish()
# Modify units AFTER applying the cfgdict
for k, v in self.parameters.items():
dt = v.datatype
for pname, pobj in self.parameters.items():
dt = pobj.datatype
if '$' in dt.unit:
dt.setProperty('unit', dt.unit.replace('$', self.parameters['value'].datatype.unit))