improve parameter initialisation

- make 'value' a Parameter property instead of an attribute
- use 'value' instead of 'default' property for setting
  the initial value in the config file
- removal of initwrite parameter property

this change is the basis of a better implementation
for change 30041 (PersistentParam property 'override_cfg')

Change-Id: I2b82bdd54c2dacb87dcd2b3472004d2f0a730cf0
This commit is contained in:
2023-01-19 11:03:04 +01:00
parent 82957c287d
commit f4e974f46c
9 changed files with 82 additions and 102 deletions

View File

@@ -47,9 +47,9 @@ class Node(dict):
)
class Param(dict):
def __init__(self, default=Undef, **kwds):
if default is not Undef:
kwds['default'] = default
def __init__(self, value=Undef, **kwds):
if value is not Undef:
kwds['value'] = value
super().__init__(**kwds)
class Group(tuple):
@@ -71,7 +71,7 @@ class Mod(dict):
elif isinstance(val, Group):
groups[key] = val
else:
# shortcut to only set default
# shortcut to only set value
self[key] = Param(val)
for group, members in groups.items():
for member in members:

View File

@@ -63,7 +63,7 @@ class HasLimits(Feature):
"""
abslimits = Property('abs limits (raw values)', default=(-9e99, 9e99), extname='abslimits', export=True,
datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg')))
limits = PersistentParam('user limits', readonly=False, default=(-9e99, 9e99), initwrite=True,
limits = PersistentParam('user limits', readonly=False, default=(-9e99, 9e99),
datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg')))
_limits = None

View File

@@ -328,7 +328,7 @@ class Module(HasAccessibles):
if value is not None:
try:
if isinstance(value, dict):
self.setProperty(key, value['default'])
self.setProperty(key, value['value'])
else:
self.setProperty(key, value)
except BadValueError:
@@ -372,14 +372,12 @@ class Module(HasAccessibles):
self.commands = {k: v for k, v in accessibles.items() if isinstance(v, Command)}
# 2) check and apply parameter_properties
for aname in list(cfgdict): # keep list() as dict may change during iter
bad = []
for aname, cfg in cfgdict.items():
aobj = self.accessibles.get(aname, None)
if aobj:
try:
for propname, propvalue in cfgdict[aname].items():
# defaults are applied later
if propname == 'default':
continue
for propname, propvalue in cfg.items():
aobj.setProperty(propname, propvalue)
except KeyError:
errors.append("'%s' has no property '%s'" %
@@ -388,23 +386,17 @@ class Module(HasAccessibles):
errors.append('%s.%s: %s' %
(aname, propname, str(e)))
else:
errors.append('%r not found' % aname)
# 3) commands do not need a default, remove them from cfgdict:
for aname in list(cfgdict):
if aname in self.commands:
cfgdict.pop(aname)
bad.append(aname)
# 3) check config for problems:
# only accept remaining config items specified in parameters
bad = [k for k in cfgdict if k not in self.parameters]
# 3) complain about names not found as accessible or property names
if bad:
errors.append(
'%s does not exist (use one of %s)' %
(', '.join(bad), ', '.join(list(self.parameters) +
list(self.propertyDict))))
# 4) complain if a Parameter entry has no default value and
# is not specified in cfgdict and deal with parameters to be written.
(', '.join(bad), ', '.join(list(self.accessibles) +
list(self.propertyDict))))
# 4) register value for writing, if given
# apply default when no value is given (in cfg or as Parameter argument)
# or complain, when cfg is needed
self.writeDict = {} # values of parameters to be written
for pname, pobj in self.parameters.items():
self.valueCallbacks[pname] = []
@@ -414,54 +406,28 @@ class Module(HasAccessibles):
errors.append('%s needs a datatype' % pname)
continue
if pname in cfgdict and 'default' in cfgdict[pname]:
if pobj.initwrite is not False and hasattr(self, 'write_' + pname):
# parameters given in cfgdict have to call write_<pname>
try:
pobj.value = pobj.datatype(cfgdict[pname]['default'])
self.writeDict[pname] = pobj.value
except BadValueError as e:
errors.append('%s: %s' % (pname, e))
else:
if pobj.value is None:
if pobj.needscfg:
errors.append('%r has no default '
'value and was not given in config!' % pname)
if pobj.default is None:
if pobj.needscfg:
errors.append('%r has no default '
'value and was not given in config!' % pname)
# we do not want to call the setter for this parameter for now,
# this should happen on the first read
pobj.readerror = ConfigError('parameter %r not initialized' % pname)
# above error will be triggered on activate after startup,
# when not all hardware parameters are read because of startup timeout
pobj.value = pobj.datatype(pobj.datatype.default)
else:
try:
value = pobj.datatype(pobj.default)
except BadValueError as e:
# this should not happen, as the default is already checked in Parameter
raise ProgrammingError('bad default for %s:%s: %s' % (name, pname, e)) from None
if pobj.initwrite and hasattr(self, 'write_' + pname):
# we will need to call write_<pname>
pobj.value = value
self.writeDict[pname] = value
else:
# dict to fit in with parameters coming from config
cfgdict[pname] = { 'default' : value }
pobj.default = pobj.datatype.default
pobj.value = pobj.default
else:
# value given explicitly, either by cfg or as Parameter argument
if hasattr(self, 'write_' + pname):
self.writeDict[pname] = pobj.value
if pobj.default is None:
pobj.default = pobj.value
# this checks again for datatype and sets the timestamp
setattr(self, pname, pobj.value)
# 5) 'apply' config:
# pass values through the datatypes and store as attributes
for k, v in list(cfgdict.items()):
try:
# this checks also for the proper datatype
# note: this will NOT call write_* methods!
if k in self.parameters or k in self.propertyDict:
if 'default' in v:
setattr(self, k, v['default'])
cfgdict.pop(k)
except (ValueError, TypeError) as e:
# self.log.exception(formatExtendedStack())
errors.append('parameter %s: %s' % (k, e))
# ensure consistency
# 5) ensure consistency
for aobj in self.accessibles.values():
aobj.finish()

View File

@@ -102,6 +102,16 @@ class Parameter(Accessible):
:param datatype: the datatype
:param inherit: whether properties not given should be inherited
:param kwds: optional properties
Usage of 'value' and 'default':
- if a value is given for a parameter in the config file, and if the write_<paramname>
method is given, it is called on startup with this value as argument
- if a value should be written to the HW on startup, even when not given in the config
add the value argument to the Parameter definition
- for parameters which are not polling the HW, either a default should be given
as a Parameter argument, or, when needscfg is set to True, a configured value is required
- when default instead of value is given in the cfg file, it is assigne to the parameter
but not written to the HW
"""
# storage for Parameter settings + value + qualifiers
@@ -128,6 +138,11 @@ class Parameter(Accessible):
if it can not be read from the hardware''', ValueType(),
export=False, default=None)
value = Property(
'''[internal] configured value of this parameter
if given, write to the hardware''', ValueType(),
export=False, default=None)
export = Property(
'''[internal] export settings
@@ -141,14 +156,9 @@ class Parameter(Accessible):
optional = Property(
'[internal] is this parameter optional?', BoolType(),
export=False, settable=False, default=False)
initwrite = Property(
'''[internal] write this parameter on initialization
default None: write if given in config''', NoneOr(BoolType()),
export=False, default=None, settable=False)
# used on the instance copy only
value = None
# value = None
timestamp = 0
readerror = None
@@ -171,6 +181,8 @@ class Parameter(Accessible):
self.datatype = datatype
if 'default' in kwds:
self.default = datatype(kwds['default'])
if 'value' in kwds:
self.value = datatype(kwds['value'])
if description is not None:
kwds['description'] = inspect.cleandoc(description)
@@ -228,7 +240,7 @@ class Parameter(Accessible):
def override(self, value):
"""override default"""
self.default = self.datatype(value)
self.value = self.datatype(value)
def merge(self, merged_properties):
"""merge with inherited properties
@@ -251,13 +263,15 @@ class Parameter(Accessible):
# serialised version of the constant, or unset
self.constant = self.datatype.export_value(constant)
self.readonly = True
if 'default' in self.propertyValues:
# fixes in case datatype has changed
try:
self.default = self.datatype(self.default)
except BadValueError:
# clear default, if it does not match datatype
self.propertyValues.pop('default')
for propname in 'default', 'value':
if propname in self.propertyValues:
value = self.propertyValues.pop(propname)
# fixes in case datatype has changed
try:
self.propertyValues[propname] = self.datatype(value)
except BadValueError:
# clear, if it does not match datatype
pass
def export_value(self):
return self.datatype.export_value(self.value)

View File

@@ -198,9 +198,6 @@ class Server:
else:
try:
modobj = cls(modname, self.log.getChild(modname), opts, self)
# all used args should be popped from opts!
if opts:
errors.append(self.unknown_options(cls, opts))
self.modules[modname] = modobj
except ConfigError as e:
errors.append('error creating module %s:' % modname)