Browse Source

AttrCtrl: add _default_to_none flag

The MMGen Project 3 years ago
parent
commit
b79865dba4
2 changed files with 39 additions and 4 deletions
  1. 14 4
      mmgen/base_obj.py
  2. 25 0
      test/unit_tests_d/ut_lockable.py

+ 14 - 4
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' )

+ 25 - 0
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 ),