allow to set exported properties in code

Actually, only property values set in the configuration can
be exported, as values equal to the default are not exported.
For this, the mechanism of overwriting properties by class attributes
has to be modified.

Change-Id: I4388d1fbb36393e863556fbbc8df800dd4800c87
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22161
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2020-01-10 10:56:56 +01:00
parent 8466a159fe
commit 2d98fe8812
3 changed files with 17 additions and 25 deletions

View File

@ -37,7 +37,7 @@ class Property:
otherwise, this is optional in which case the default value is applied. otherwise, this is optional in which case the default value is applied.
All values MUST pass the datatype. All values MUST pass the datatype.
''' '''
# note: this is inteded to be used on base classes. # note: this is intended to be used on base classes.
# the VALUES of the properties are on the instances! # the VALUES of the properties are on the instances!
def __init__(self, description, datatype, default=None, extname='', export=False, mandatory=None, settable=True): def __init__(self, description, datatype, default=None, extname='', export=False, mandatory=None, settable=True):
if not callable(datatype): if not callable(datatype):
@ -102,29 +102,24 @@ class PropertyMeta(type):
for base in reversed(bases): for base in reversed(bases):
properties.update(getattr(base, "properties", {})) properties.update(getattr(base, "properties", {}))
# update with properties from new class # update with properties from new class
aprops = attrs.get('properties', {}) properties.update(attrs.get('properties', {}))
properties.update(aprops)
newtype.properties = properties newtype.properties = properties
# generate getters # generate getters
for k in properties: for k, po in properties.items():
def getter(self, pname=k): def getter(self, pname=k):
val = self.__class__.properties[pname].default val = self.__class__.properties[pname].default
return self.properties.get(pname, val) return self.properties.get(pname, val)
if k in attrs and not isinstance(attrs[k], property): if k in attrs and not isinstance(attrs[k], property):
if callable(attrs[k]): if callable(attrs[k]):
raise ProgrammingError('%r: property %r collides with method' raise ProgrammingError('%r: property %r collides with method'
% (newtype, k)) % (newtype, k))
if k in aprops: # store the attribute value for putting on the instance later
# this property was defined in the same class
raise ProgrammingError('%r: name collision with property %r'
% (newtype, k))
# assume the attribute value should be assigned to the property
try: try:
# make a copy first! # for inheritance reasons, it seems best to store it as a renamed attribute
prop = Property(**newtype.properties[k].__dict__) setattr(newtype, '_initProp_' + k, po.datatype(attrs[k]))
prop.default = prop.datatype(attrs[k])
newtype.properties[k] = prop
except BadValueError: except BadValueError:
raise ProgrammingError('%r: property %r can not be set to %r' raise ProgrammingError('%r: property %r can not be set to %r'
% (newtype, k, attrs[k])) % (newtype, k, attrs[k]))
@ -144,7 +139,10 @@ class HasProperties(metaclass=PropertyMeta):
self.properties = {} self.properties = {}
# pre-init with properties default value (if any) # pre-init with properties default value (if any)
for pn, po in self.__class__.properties.items(): for pn, po in self.__class__.properties.items():
if not po.mandatory: value = getattr(self, '_initProp_' + pn, self)
if value is not self: # property value was given as attribute
self.properties[pn] = value
elif not po.mandatory:
self.properties[pn] = po.default self.properties[pn] = po.default
def checkProperties(self): def checkProperties(self):

View File

@ -127,8 +127,6 @@ class Main(Communicator):
class PpmsMixin(HasIodev, Module): class PpmsMixin(HasIodev, Module):
properties = { properties = {
'iodev': Attached(), 'iodev': Attached(),
'general_stop': Property('respect general stop', datatype=BoolType(),
export=True, default=True)
} }
pollerClass = Poller pollerClass = Poller
@ -439,6 +437,11 @@ class Temp(PpmsMixin, Drivable):
14: [Status.ERROR, 'can not complete'], 14: [Status.ERROR, 'can not complete'],
15: [Status.ERROR, 'general failure'], 15: [Status.ERROR, 'general failure'],
} }
properties = {
'iodev': Attached(),
'general_stop': Property('respect general stop', datatype=BoolType(),
export=True, default=True)
}
channel = 'temp' channel = 'temp'
_stopped = False _stopped = False

View File

@ -133,15 +133,6 @@ def test_Property_override():
pass pass
assert 'collides with method' in str(e.value) assert 'collides with method' in str(e.value)
with pytest.raises(ProgrammingError) as e:
class cy(c): # pylint: disable=unused-variable
properties = {
'b' : Property('', FloatRange(), 3.14),
}
b = 1.5
assert 'name collision with property' in str(e.value)
with pytest.raises(ProgrammingError) as e: with pytest.raises(ProgrammingError) as e:
class cz(c): # pylint: disable=unused-variable class cz(c): # pylint: disable=unused-variable
a = 's' a = 's'