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

@ -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,43 @@ 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)
ordered = {}
for aname in cls.__dict__.get('paramOrder', ()):
accessibles = OrderedDict() # dict of accessibles
merged_properties = {} # dict of dict of merged properties
new_names = [] # list of names of new accessibles
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
elif key in accessibles:
# either a bare value overriding a parameter
# or a method overriding a command
aobj = aobj.copy()
aobj.override(value)
accessibles[key] = aobj
for aname, aobj in accessibles.items():
if aobj != getattr(cls, aname, None):
aobj = aobj.copy()
setattr(cls, aname, aobj)
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 +322,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 +352,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 +402,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))
@ -410,7 +423,7 @@ class Module(HasAccessibles):
for pname, p in self.parameters.items():
try:
p.checkProperties()
except ConfigError:
except ConfigError as e:
errors.append('%s: %s' % (pname, e))
if errors:
raise ConfigError(errors)
@ -426,7 +439,7 @@ class Module(HasAccessibles):
"""announce a changed value or readerror"""
pobj = self.parameters[pname]
timestamp = timestamp or time.time()
changed = pobj.value != value or timestamp > (pobj.timestamp or 0) + 1
changed = pobj.value != value
if value is not None:
pobj.value = value # store the value even in case of error
if err:
@ -439,8 +452,10 @@ class Module(HasAccessibles):
pobj.value = pobj.datatype(value)
except Exception as e:
err = secop_error(e)
if not changed:
return # experimental: do not update unchanged values within 1 sec
if not changed and timestamp < ((pobj.timestamp or 0)
+ self.DISPATCHER.OMIT_UNCHANGED_WITHIN):
# no change within short time -> omit
return
pobj.timestamp = timestamp
pobj.readerror = err
if pobj.export: