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:
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
Reference in New Issue
Block a user