simple possiblity for overriding properties
When a class attribute is assigned with the same name as a property from an inherited class, the property default is overridden. If the type does not match, a ProgrammingError is raised. Change-Id: Ifdab5c8bbdaae008370a9b297c770f5c04db9311 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/21989 Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch> Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
This commit is contained in:
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from secop.errors import ProgrammingError, ConfigError
|
from secop.errors import ProgrammingError, ConfigError, BadValueError
|
||||||
|
|
||||||
|
|
||||||
# storage for 'properties of a property'
|
# storage for 'properties of a property'
|
||||||
@ -52,8 +52,9 @@ class Property:
|
|||||||
self.settable = settable or mandatory # settable means settable from the cfg file
|
self.settable = settable or mandatory # settable means settable from the cfg file
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Property(%r, %s, default=%r, extname=%r, export=%r, mandatory=%r)' % (
|
return 'Property(%r, %s, default=%r, extname=%r, export=%r, mandatory=%r, settable=%r)' % (
|
||||||
self.description, self.datatype, self.default, self.extname, self.export, self.mandatory)
|
self.description, self.datatype, self.default, self.extname, self.export,
|
||||||
|
self.mandatory, self.settable)
|
||||||
|
|
||||||
|
|
||||||
class Properties(OrderedDict):
|
class Properties(OrderedDict):
|
||||||
@ -100,7 +101,8 @@ 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
|
||||||
properties.update(attrs.get('properties', {}))
|
aprops = attrs.get('properties', {})
|
||||||
|
properties.update(aprops)
|
||||||
newtype.properties = properties
|
newtype.properties = properties
|
||||||
|
|
||||||
# generate getters
|
# generate getters
|
||||||
@ -108,9 +110,23 @@ class PropertyMeta(type):
|
|||||||
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:
|
if k in attrs and not isinstance(attrs[k], property):
|
||||||
if not isinstance(attrs[k], property):
|
if callable(attrs[k]):
|
||||||
raise ProgrammingError('Name collision with property %r' % k)
|
raise ProgrammingError('%r: property %r collides with method'
|
||||||
|
% (newtype, k))
|
||||||
|
if k in aprops:
|
||||||
|
# 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:
|
||||||
|
# make a copy first!
|
||||||
|
prop = Property(**newtype.properties[k].__dict__)
|
||||||
|
prop.default = prop.datatype(attrs[k])
|
||||||
|
newtype.properties[k] = prop
|
||||||
|
except BadValueError:
|
||||||
|
raise ProgrammingError('%r: property %r can not be set to %r'
|
||||||
|
% (newtype, k, attrs[k]))
|
||||||
setattr(newtype, k, property(getter))
|
setattr(newtype, k, property(getter))
|
||||||
return newtype
|
return newtype
|
||||||
|
|
||||||
|
@ -114,6 +114,36 @@ def test_Property_checks():
|
|||||||
o.checkProperties()
|
o.checkProperties()
|
||||||
o = cl()
|
o = cl()
|
||||||
o.checkProperties()
|
o.checkProperties()
|
||||||
|
# test for min/max check
|
||||||
o.setProperty('maxabc', 1)
|
o.setProperty('maxabc', 1)
|
||||||
with pytest.raises(ConfigError):
|
with pytest.raises(ConfigError):
|
||||||
o.checkProperties()
|
o.checkProperties()
|
||||||
|
|
||||||
|
def test_Property_override():
|
||||||
|
o1 = c()
|
||||||
|
class co(c):
|
||||||
|
a = 3
|
||||||
|
o2 = co()
|
||||||
|
assert o1.a == 1
|
||||||
|
assert o2.a == 3
|
||||||
|
|
||||||
|
with pytest.raises(ProgrammingError) as e:
|
||||||
|
class cx(c): # pylint: disable=unused-variable
|
||||||
|
def a(self):
|
||||||
|
pass
|
||||||
|
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:
|
||||||
|
class cz(c): # pylint: disable=unused-variable
|
||||||
|
a = 's'
|
||||||
|
|
||||||
|
assert 'can not be set to' in str(e.value)
|
||||||
|
Reference in New Issue
Block a user