fix parameter inheritance using MRO
+ no update on unhchanged values within 1 sec Change-Id: I3e3d50bb5541e8d4da2badc3133d243dd0a3b892
This commit is contained in:
@ -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:
|
||||
|
Reference in New Issue
Block a user