From b79865dba423fcc7f1d5ad600c37e45b1e04ceae Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 20 Jan 2022 12:04:51 +0000 Subject: [PATCH] AttrCtrl: add _default_to_none flag --- mmgen/base_obj.py | 18 ++++++++++++++---- test/unit_tests_d/ut_lockable.py | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/mmgen/base_obj.py b/mmgen/base_obj.py index f8d209d3..33235ac9 100755 --- a/mmgen/base_obj.py +++ b/mmgen/base_obj.py @@ -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' ) diff --git a/test/unit_tests_d/ut_lockable.py b/test/unit_tests_d/ut_lockable.py index 0d67f858..47b646c5 100755 --- a/test/unit_tests_d/ut_lockable.py +++ b/test/unit_tests_d/ut_lockable.py @@ -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 ),