Browse Source

MMGenListItem: require all immutable attrs to be set in constructor

MMGen 5 years ago
parent
commit
514278d616
5 changed files with 54 additions and 8 deletions
  1. 1 1
      mmgen/addr.py
  2. 13 1
      mmgen/altcoins/eth/tw.py
  3. 19 0
      mmgen/devtools.py
  4. 16 3
      mmgen/obj.py
  5. 5 3
      mmgen/tx.py

+ 1 - 1
mmgen/addr.py

@@ -300,7 +300,7 @@ class AddrListEntry(MMGenListItem):
 	wallet_passwd  = MMGenListItemAttr('wallet_passwd','WalletPassword')
 
 class PasswordListEntry(MMGenListItem):
-	passwd = MMGenImmutableAttr('passwd',str,typeconv=False) # TODO: create Password type
+	passwd = MMGenListItemAttr('passwd',str,typeconv=False) # TODO: create Password type
 	idx    = MMGenImmutableAttr('idx','AddrIdx')
 	label  = MMGenListItemAttr('label','TwComment',reassign_ok=True)
 	sec    = MMGenListItemAttr('sec',PrivKey,typeconv=False)

+ 13 - 1
mmgen/altcoins/eth/tw.py

@@ -21,7 +21,7 @@ altcoins.eth.tw: Ethereum tracking wallet and related classes for the MMGen suit
 """
 
 from mmgen.common import *
-from mmgen.obj import ETHAmt,TwLabel,is_coin_addr,is_mmgen_id
+from mmgen.obj import ETHAmt,TwLabel,is_coin_addr,is_mmgen_id,MMGenListItem,MMGenListItemAttr,MMGenImmutableAttr
 from mmgen.tw import TrackingWallet,TwAddrList,TwUnspentOutputs
 from mmgen.addr import AddrData
 from .contract import Token
@@ -295,6 +295,18 @@ Actions:         [q]uit view, [p]rint to file, pager [v]iew, [w]ide view,
 				'confirmations': 0, # TODO
 				} for d in wl]
 
+	class MMGenTwUnspentOutput(MMGenListItem):
+		txid   = MMGenListItemAttr('txid','CoinTxID')
+		vout   = MMGenListItemAttr('vout',int,typeconv=False)
+		amt    = MMGenImmutableAttr('amt',g.proto.coin_amt.__name__)
+		amt2   = MMGenListItemAttr('amt2',g.proto.coin_amt.__name__)
+		label  = MMGenListItemAttr('label','TwComment',reassign_ok=True)
+		twmmid = MMGenImmutableAttr('twmmid','TwMMGenID')
+		addr   = MMGenImmutableAttr('addr','CoinAddr')
+		confs  = MMGenImmutableAttr('confs',int,typeconv=False)
+		days   = MMGenListItemAttr('days',int,typeconv=False)
+		skip   = MMGenListItemAttr('skip',str,typeconv=False,reassign_ok=True)
+
 class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs):
 
 	disp_type = 'token'

+ 19 - 0
mmgen/devtools.py

@@ -2,6 +2,7 @@
 
 class MMGenObject(object):
 	'placeholder - overridden when testing'
+	def immutable_attr_init_check(self): pass
 
 import os
 if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN_TRACEBACK'):
@@ -107,6 +108,24 @@ if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN
 			import re
 			return re.sub('\n+','\n',''.join(out))
 
+		# Check that all immutables have been initialized.  Expensive, so do only when testing.
+		def immutable_attr_init_check(self):
+			from mmgen.globalvars import g
+			if g.test_suite:
+				from mmgen.util import rdie
+				cls = type(self)
+				for attrname in sorted({a for a in self.valid_attrs if a[0] != '_'}):
+					for o in (cls,cls.__bases__[0]): # assume there's only one base class
+						if attrname in o.__dict__:
+							attr = o.__dict__[attrname]
+							break
+					else:
+						rdie(3,'unable to find descriptor {}.{}'.format(cls.__name__,attrname))
+					if type(attr).__name__ == 'MMGenImmutableAttr':
+						if attrname not in self.__dict__:
+							fs = 'attribute {!r} of {} has not been initialized in constructor!'
+							rdie(3,fs.format(attrname,cls.__name__))
+
 	def print_diff(a,b,from_json=True):
 		if from_json:
 			a = json.dumps(json.loads(a),indent=4).split('\n') if a else []

+ 16 - 3
mmgen/obj.py

@@ -251,18 +251,31 @@ class MMGenListItem(MMGenObject):
 
 	valid_attrs = None
 	valid_attrs_extra = set()
+	invalid_attrs = {
+		'pfmt',
+		'pmsg',
+		'pdie',
+		'valid_attrs',
+		'valid_attrs_extra',
+		'invalid_attrs',
+		'immutable_attr_init_check',
+	}
 
 	def __init__(self,*args,**kwargs):
 		if self.valid_attrs == None:
 			type(self).valid_attrs = (
-				( {e for e in dir(self) if e[:2] != '__'} | self.valid_attrs_extra ) -
-				{'pfmt','pmsg','pdie','valid_attrs','valid_attrs_extra'} )
+				( {e for e in dir(self) if e[:2] != '__'} | self.valid_attrs_extra ) - self.invalid_attrs )
+
 		if args:
-			raise ValueError('Non-keyword args not allowed')
+			raise ValueError('Non-keyword args not allowed in {!r} constructor'.format(type(self).__name__))
+
 		for k in kwargs:
 			if kwargs[k] != None:
 				setattr(self,k,kwargs[k])
 
+		# Require all immutables to be initialized.  Check performed only when testing.
+		self.immutable_attr_init_check()
+
 	# allow only valid attributes to be set
 	def __setattr__(self,name,value):
 		if name not in self.valid_attrs:

+ 5 - 3
mmgen/tx.py

@@ -247,6 +247,8 @@ class MMGenTxIO(MMGenListItem):
 class MMGenTxInput(MMGenTxIO):
 	scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr')
 	sequence     = MMGenListItemAttr('sequence',int,typeconv=False)
+	# required by copy_inputs_from_tw()
+	copy_attrs = { 'scriptPubKey','vout','amt','label','mmid','addr','confs','txid' }
 
 class MMGenTxOutput(MMGenTxIO):
 	is_chg = MMGenListItemAttr('is_chg',bool,typeconv=False)
@@ -660,10 +662,10 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 	# inputs methods
 	def copy_inputs_from_tw(self,tw_unspent_data):
 		self.inputs = MMGenTxInputList()
-		MMGenTxInput() # throwaway instance to initialize cls.valid_attrs
 		for d in tw_unspent_data:
-			t = MMGenTxInput(**{attr:getattr(d,attr) for attr in d.__dict__ if attr in MMGenTxInput.valid_attrs})
-			if d.twmmid.type == 'mmgen': t.mmid = d.twmmid # twmmid -> mmid
+			t = MMGenTxInput(**{attr:getattr(d,attr) for attr in d.__dict__ if attr in MMGenTxInput.copy_attrs})
+			if d.twmmid.type == 'mmgen':
+				t.mmid = d.twmmid # twmmid -> mmid
 			self.inputs.append(t)
 
 	def get_input_sids(self):