Browse Source

immutable attr improvements

philemon 7 years ago
parent
commit
aae5f122b4
5 changed files with 58 additions and 69 deletions
  1. 7 9
      mmgen/addr.py
  2. 31 39
      mmgen/obj.py
  3. 9 10
      mmgen/tw.py
  4. 5 7
      mmgen/tx.py
  5. 6 4
      test/tooltest.py

+ 7 - 9
mmgen/addr.py

@@ -91,18 +91,16 @@ class KeyGeneratorSecp256k1(KeyGenerator):
 		return PubKey(hexlify(priv2pub(unhexlify(privhex),int(privhex.compressed))),compressed=privhex.compressed)
 
 class AddrListEntry(MMGenListItem):
-	reassign_ok = 'label',
 	addr  = MMGenListItemAttr('addr','BTCAddr')
-	idx   = MMGenListItemAttr('idx','AddrIdx')
-	label = MMGenListItemAttr('label','TwComment')
-	sec   = MMGenImmutableAttr('sec',PrivKey)
+	idx   = MMGenImmutableAttr('idx','AddrIdx')
+	label = MMGenListItemAttr('label','TwComment',reassign_ok=True)
+	sec   = MMGenListItemAttr('sec',PrivKey,typeconv=False)
 
 class PasswordListEntry(MMGenListItem):
-	reassign_ok = 'label',
-	passwd = MMGenImmutableAttr('passwd',unicode) # TODO: create Password type
-	idx    = MMGenListItemAttr('idx','AddrIdx')
-	label  = MMGenListItemAttr('label','TwComment')
-	sec    = MMGenImmutableAttr('sec',PrivKey)
+	passwd = MMGenImmutableAttr('passwd',unicode,typeconv=False) # TODO: create Password type
+	idx    = MMGenImmutableAttr('idx','AddrIdx')
+	label  = MMGenListItemAttr('label','TwComment',reassign_ok=True)
+	sec    = MMGenListItemAttr('sec',PrivKey,typeconv=False)
 
 class AddrListChksum(str,Hilite):
 	color = 'pink'

+ 31 - 39
mmgen/obj.py

@@ -101,19 +101,13 @@ class MMGenObject(object):
 class MMGenList(list,MMGenObject): pass
 class MMGenDict(dict,MMGenObject): pass
 
+# for attrs that are always present in the data instance
+# reassignment and deletion forbidden
 class MMGenImmutableAttr(object): # Descriptor
 
-	typeconv = False
-	builtin_typeconv = False
-
-	def __init__(self,name,dtype,typeconv=None,builtin_typeconv=None):
-		if typeconv is not None:
-			assert typeconv in (True,False)
-			self.typeconv = typeconv
-		if builtin_typeconv is not None:
-			assert builtin_typeconv
-			self.builtin_typeconv = builtin_typeconv
-			self.typeconv = False # override
+	def __init__(self,name,dtype,typeconv=True):
+		self.typeconv = typeconv
+		assert type(dtype) in (str,type)
 		self.name = name
 		self.dtype = dtype
 
@@ -121,17 +115,16 @@ class MMGenImmutableAttr(object): # Descriptor
 		return instance.__dict__[self.name]
 
 	# forbid all reassignment
-	def chk_ok_set_attr(self,instance):
-		if hasattr(instance,self.name):
-			m = "Attribute '{}' of {} instance cannot be reassigned"
-			raise AttributeError(m.format(self.name,type(instance)))
+	def set_attr_ok(self,instance):
+		return not hasattr(instance,self.name)
 
 	def __set__(self,instance,value):
-		self.chk_ok_set_attr(instance)
+		if not self.set_attr_ok(instance):
+			m = "Attribute '{}' of {} instance cannot be reassigned"
+			raise AttributeError(m.format(self.name,type(instance)))
 		if self.typeconv:   # convert type
-			instance.__dict__[self.name] = globals()[self.dtype](value)
-		elif self.builtin_typeconv:
-			instance.__dict__[self.name] = self.dtype(value)
+			instance.__dict__[self.name] = \
+				globals()[self.dtype](value) if type(self.dtype) == str else self.dtype(value)
 		else:               # check type
 			if type(value) != self.dtype:
 				m = "Attribute '{}' of {} instance must of type {}"
@@ -139,35 +132,34 @@ class MMGenImmutableAttr(object): # Descriptor
 			instance.__dict__[self.name] = value
 
 	def __delete__(self,instance):
-		if self.name in instance.delete_ok:
-			if self.name in instance.__dict__:
-				del instance.__dict__[self.name]
-		else:
-			m = "Atribute '{}' of {} instance cannot be deleted"
-			raise AttributeError(m.format(self.name,type(instance)))
+		m = "Atribute '{}' of {} instance cannot be deleted"
+		raise AttributeError(m.format(self.name,type(instance)))
 
+# for attrs that might not be present in the data instance
+# reassignment or deletion allowed if specified
 class MMGenListItemAttr(MMGenImmutableAttr):
 
-	typeconv = True
-	builtin_typeconv = False
+	def __init__(self,name,dtype,typeconv=True,reassign_ok=False,delete_ok=False):
+		self.reassign_ok = reassign_ok
+		self.delete_ok = delete_ok
+		MMGenImmutableAttr.__init__(self,name,dtype,typeconv=typeconv)
 
 	# return None if attribute doesn't exist
 	def __get__(self,instance,owner):
 		try: return instance.__dict__[self.name]
 		except: return None
 
-	# allow reassignment if value is None or attr in reassign_ok list
-	def chk_ok_set_attr(self,instance):
-		if hasattr(instance,self.name) and not (
-			getattr(instance,self.name) == None or self.name in instance.reassign_ok
-		):
-			m = "Attribute '{}' of {} instance cannot be reassigned"
-			raise AttributeError(m.format(self.name,type(instance)))
+	def set_attr_ok(self,instance):
+		return getattr(instance,self.name) == None or self.reassign_ok
 
-class MMGenListItem(MMGenObject):
+	def __delete__(self,instance):
+		if self.delete_ok:
+			if self.name in instance.__dict__:
+				del instance.__dict__[self.name]
+		else:
+			MMGenImmutableAttr.__delete__(self,instance)
 
-	reassign_ok = ()
-	delete_ok = ()
+class MMGenListItem(MMGenObject):
 
 	def __init__(self,*args,**kwargs):
 		if args:
@@ -576,8 +568,8 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
 	width = 64
 	trunc_ok = False
 
-	compressed = MMGenImmutableAttr('compressed',bool)
-	wif        = MMGenImmutableAttr('wif',WifKey)
+	compressed = MMGenImmutableAttr('compressed',bool,typeconv=False)
+	wif        = MMGenImmutableAttr('wif',WifKey,typeconv=False)
 
 	def __new__(*args,**kwargs): # initialize with (priv_bin,compressed), WIF or self
 		cls = args[0]

+ 9 - 10
mmgen/tw.py

@@ -40,17 +40,16 @@ class MMGenTrackingWallet(MMGenObject):
 
 	class MMGenTwUnspentOutput(MMGenListItem):
 	#	attrs = 'txid','vout','amt','label','twmmid','addr','confs','scriptPubKey','days','skip'
-		reassign_ok = 'label','skip'
-		txid     = MMGenListItemAttr('txid','BitcoinTxID')
-		vout     = MMGenListItemAttr('vout',int,typeconv=False),
-		amt      = MMGenListItemAttr('amt','BTCAmt'),
-		label    = MMGenListItemAttr('label','TwComment'),
-		twmmid = MMGenListItemAttr('twmmid','TwMMGenID')
-		addr     = MMGenListItemAttr('addr','BTCAddr'),
-		confs    = MMGenListItemAttr('confs',int,typeconv=False),
-		scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr')
+		txid     = MMGenImmutableAttr('txid','BitcoinTxID')
+		vout     = MMGenImmutableAttr('vout',int,typeconv=False),
+		amt      = MMGenImmutableAttr('amt','BTCAmt'),
+		label    = MMGenListItemAttr('label','TwComment',reassign_ok=True),
+		twmmid   = MMGenImmutableAttr('twmmid','TwMMGenID')
+		addr     = MMGenImmutableAttr('addr','BTCAddr'),
+		confs    = MMGenImmutableAttr('confs',int,typeconv=False),
+		scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexStr')
 		days    = MMGenListItemAttr('days',int,typeconv=False),
-		skip    = MMGenListItemAttr('skip',bool,typeconv=False),
+		skip    = MMGenListItemAttr('skip',bool,typeconv=False,reassign_ok=True),
 
 	wmsg = {
 	'no_spendable_outputs': """

+ 5 - 7
mmgen/tx.py

@@ -114,16 +114,14 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
 		return OrderedDict.__init__(self, ((k,d[k]) for k in keys))
 
 txio_attrs = {
-	'reassign_ok': ('label',),
-	'delete_ok':   ('have_wif',),
 	'vout':  MMGenListItemAttr('vout',int,typeconv=False),
-	'amt':   MMGenListItemAttr('amt','BTCAmt'),
-	'label': MMGenListItemAttr('label','TwComment'),
+	'amt':   MMGenImmutableAttr('amt','BTCAmt'),
+	'label': MMGenListItemAttr('label','TwComment',reassign_ok=True),
 	'mmid':  MMGenListItemAttr('mmid','MMGenID'),
-	'addr':  MMGenListItemAttr('addr','BTCAddr'),
-	'confs': MMGenListItemAttr('confs',int,builtin_typeconv=True), # long confs found in the wild, so convert
+	'addr':  MMGenImmutableAttr('addr','BTCAddr'),
+	'confs': MMGenListItemAttr('confs',int,typeconv=True), # long confs exist in the wild, so convert
 	'txid':  MMGenListItemAttr('txid','BitcoinTxID'),
-	'have_wif': MMGenListItemAttr('have_wif',bool,typeconv=False)
+	'have_wif': MMGenListItemAttr('have_wif',bool,typeconv=False,delete_ok=True)
 }
 
 class MMGenTX(MMGenObject):

+ 6 - 4
test/tooltest.py

@@ -235,13 +235,15 @@ class MMGenToolTestSuite(object):
 			die(1,red('Called process returned with an error (retcode %s)' % retcode))
 		return (a,a.rstrip())[bool(strip)]
 
-	def run_cmd_chk(self,name,f1,f2,kwargs='',extra_msg=''):
+	def run_cmd_chk(self,name,f1,f2,kwargs='',extra_msg='',strip_hex=False):
 		idata = read_from_file(f1).rstrip()
 		odata = read_from_file(f2).rstrip()
 		ret = self.run_cmd(name,[odata],kwargs=kwargs,extra_msg=extra_msg)
 		vmsg('In:   ' + repr(odata))
 		vmsg('Out:  ' + repr(ret))
-		if ret == idata: ok()
+		def cmp_equal(a,b):
+			return (a.lstrip('0') == b.lstrip('0')) if strip_hex else (a == b)
+		if cmp_equal(ret,idata): ok()
 		else:
 			die(3,red(
 	"Error: values don't match:\nIn:  %s\nOut: %s" % (repr(idata),repr(ret))))
@@ -284,12 +286,12 @@ class MMGenToolTestSuite(object):
 	def Strtob58(self,name):       self.run_cmd_out(name,getrandstr(16))
 	def B58tostr(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
 	def Hextob58(self,name):       self.run_cmd_out(name,getrandhex(32))
-	def B58tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
+	def B58tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2,strip_hex=True)
 	def B58randenc(self,name):
 		ret = self.run_cmd_out(name,Return=True)
 		ok_or_die(ret,is_b58_str,'base 58 string')
 	def Hextob32(self,name):       self.run_cmd_out(name,getrandhex(24))
-	def B32tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
+	def B32tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2,strip_hex=True)
 	def Randhex(self,name):
 		ret = self.run_cmd_out(name,Return=True)
 		ok_or_die(ret,binascii.unhexlify,'hex string')