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
 	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
 	attribute, if _use_class_attr is True.  If the instance or class attribute is set
 	to None, no type checking is performed.
 	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
 	_autolock = True
 	_lock = False
 	_lock = False
 	_use_class_attr = False
 	_use_class_attr = False
+	_default_to_none = False
 	_skip_type_check = ()
 	_skip_type_check = ()
 
 
 	def lock(self):
 	def lock(self):
 		self._lock = True
 		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):
 	def __setattr__(self,name,value):
 
 
 		if self._lock:
 		if self._lock:
@@ -63,7 +73,7 @@ class AttrCtrl(metaclass=AttrCtrlMeta):
 						type(ref_val).__name__,
 						type(ref_val).__name__,
 						type(value).__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}')
 				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)
 			ref_val = getattr(type(self),name) if self._use_class_attr else getattr(self,name)
@@ -101,15 +111,15 @@ class Lockable(AttrCtrl):
 		super().lock()
 		super().lock()
 
 
 	def __setattr__(self,name,value):
 	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)
 			val = getattr(self,name)
 			if name not in (self._set_ok + self._reset_ok):
 			if name not in (self._set_ok + self._reset_ok):
 				raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
 				raise AttributeError(f'attribute {name!r} of {type(self).__name__} object is read-only')
 			elif name not in self._reset_ok:
 			elif name not in self._reset_ok:
 				#print(self.__dict__)
 				#print(self.__dict__)
 				if not (
 				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(
 					raise AttributeError(
 						f'attribute {name!r} of {type(self).__name__} object is already set,'
 						f'attribute {name!r} of {type(self).__name__} object is already set,'
 						+ ' and resetting is forbidden' )
 						+ ' 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 = 'bar val'
 		acc.bar = 1 # class attribute bar is None, so can be set to any type
 		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('OK')
 		qmsg_r('Testing class Lockable...')
 		qmsg_r('Testing class Lockable...')
 
 
@@ -102,6 +112,14 @@ class unit_test(object):
 		assert lca._lock == True
 		assert lca._lock == True
 		assert lca.foo == 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):
 		class MyLockableBad(Lockable):
 			_set_ok = ('foo','bar')
 			_set_ok = ('foo','bar')
 			foo = 1
 			foo = 1
@@ -131,15 +149,22 @@ class unit_test(object):
 		def bad17(): lb = MyLockableBad()
 		def bad17(): lb = MyLockableBad()
 		def bad18(): aca.lock()
 		def bad18(): aca.lock()
 
 
+		def bad19(): acdn.baz = None
+		def bad20(): lcdn.foo = 1
+		def bad21(): lcdn.bar = None
+
 		ut.process_bad_data((
 		ut.process_bad_data((
 			('attr (1)',           'AttributeError', 'has no attr', bad1 ),
 			('attr (1)',           'AttributeError', 'has no attr', bad1 ),
 			('attr (2)',           'AttributeError', 'has no attr', bad9 ),
 			('attr (2)',           'AttributeError', 'has no attr', bad9 ),
 			('attr (3)',           'AttributeError', 'has no attr', bad10 ),
 			('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 (1)',      'AttributeError', 'type',        bad2 ),
 			("attr type (2)",      'AttributeError', 'type',        bad4 ),
 			("attr type (2)",      'AttributeError', 'type',        bad4 ),
 			("attr type (3)",      'AttributeError', 'type',        bad5 ),
 			("attr type (3)",      'AttributeError', 'type',        bad5 ),
 			("attr (can't set)",   'AttributeError', 'read-only',   bad6 ),
 			("attr (can't set)",   'AttributeError', 'read-only',   bad6 ),
 			("attr (can't set)",   'AttributeError', 'read-only',   bad7 ),
 			("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',       bad3 ),
 			("attr (can't reset)", 'AttributeError', 'reset',       bad8 ),
 			("attr (can't reset)", 'AttributeError', 'reset',       bad8 ),
 			("attr (can't reset)", 'AttributeError', 'reset',       bad11 ),
 			("attr (can't reset)", 'AttributeError', 'reset',       bad11 ),