AttrCtrl: add _default_to_none flag

This commit is contained in:
The MMGen Project 2022-01-20 12:04:51 +00:00
commit b79865dba4
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
2 changed files with 39 additions and 4 deletions

View file

@ -41,15 +41,25 @@ class AttrCtrl(metaclass=AttrCtrlMeta):
Ensure that attribute's type matches that of the instance attribute, or the class
attribute, if _use_class_attr is True. If the instance or class attribute is set
to None, no type checking is performed.
If _default_to_none is True, return None when accessing non-existent attributes
instead of raising AttributeError.
"""
_autolock = True
_lock = False
_use_class_attr = False
_default_to_none = False
_skip_type_check = ()
def lock(self):
self._lock = True
def __getattr__(self,name):
if self._lock and self._default_to_none:
return None
else:
raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
def __setattr__(self,name,value):
if self._lock:
@ -63,7 +73,7 @@ class AttrCtrl(metaclass=AttrCtrlMeta):
type(ref_val).__name__,
type(value).__name__ ) )
if not hasattr(self,name):
if not (name in self.__dict__ or hasattr(type(self),name)):
raise AttributeError(f'{type(self).__name__} object has no attribute {name!r}')
ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name)
@ -101,15 +111,15 @@ class Lockable(AttrCtrl):
super().lock()
def __setattr__(self,name,value):
if self._lock and hasattr(self,name):
if self._lock and (name in self.__dict__ or hasattr(type(self),name)):
val = getattr(self,name)
if name not in (self._set_ok + self._reset_ok):
raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
elif name not in self._reset_ok:
#print(self.__dict__)
if not (
val is None or (not (val == 0) and not val) or
( self._use_class_attr and name not in self.__dict__ ) ):
(val != 0 and not val) or
(self._use_class_attr and name not in self.__dict__) ):
raise AttributeError(
f'attribute {name!r} of {type(self).__name__} object is already set,'
+ ' and resetting is forbidden' )

View file

@ -40,6 +40,16 @@ class unit_test(object):
acc.bar = 'bar val'
acc.bar = 1 # class attribute bar is None, so can be set to any type
class MyAttrCtrlDflNone(AttrCtrl):
_default_to_none = True
foo = 'fooval'
bar = None
acdn = MyAttrCtrlDflNone()
assert acdn.foo == 'fooval', f'{acdn.foo}'
assert acdn.bar == None, f'{acdn.bar}'
assert acdn.baz == None, f'{acdn.baz}'
qmsg('OK')
qmsg_r('Testing class Lockable...')
@ -102,6 +112,14 @@ class unit_test(object):
assert lca._lock == True
assert lca.foo == True
class MyLockableAutolockDflNone(Lockable):
_default_to_none = True
foo = 0
lcdn = MyLockableAutolockDflNone()
assert lcdn.foo == 0
assert lcdn.bar == None
class MyLockableBad(Lockable):
_set_ok = ('foo','bar')
foo = 1
@ -131,15 +149,22 @@ class unit_test(object):
def bad17(): lb = MyLockableBad()
def bad18(): aca.lock()
def bad19(): acdn.baz = None
def bad20(): lcdn.foo = 1
def bad21(): lcdn.bar = None
ut.process_bad_data((
('attr (1)', 'AttributeError', 'has no attr', bad1 ),
('attr (2)', 'AttributeError', 'has no attr', bad9 ),
('attr (3)', 'AttributeError', 'has no attr', bad10 ),
('attr (4)', 'AttributeError', 'has no attr', bad19 ),
('attr (5)', 'AttributeError', 'has no attr', bad21 ),
('attr type (1)', 'AttributeError', 'type', bad2 ),
("attr type (2)", 'AttributeError', 'type', bad4 ),
("attr type (3)", 'AttributeError', 'type', bad5 ),
("attr (can't set)", 'AttributeError', 'read-only', bad6 ),
("attr (can't set)", 'AttributeError', 'read-only', bad7 ),
("attr (can't set)", 'AttributeError', 'read-only', bad20 ),
("attr (can't reset)", 'AttributeError', 'reset', bad3 ),
("attr (can't reset)", 'AttributeError', 'reset', bad8 ),
("attr (can't reset)", 'AttributeError', 'reset', bad11 ),