Browse Source

string formatting, whitespace, minor fixes throughout

The MMGen Project 4 years ago
parent
commit
5e745f2a08
7 changed files with 155 additions and 113 deletions
  1. 42 25
      mmgen/obj.py
  2. 1 1
      mmgen/protocol.py
  3. 98 69
      mmgen/tw.py
  4. 1 1
      mmgen/tx.py
  5. 11 5
      test/objtest.py
  6. 0 12
      test/objtest_py_d/ot_btc_mainnet.py
  7. 2 0
      test/test.py

+ 42 - 25
mmgen/obj.py

@@ -108,13 +108,15 @@ class InitErrors(object):
 			e2_fmt = '({}) '.format(e2.args[0]) if e2 else ''
 			e2_fmt = '({}) '.format(e2.args[0]) if e2 else ''
 			errmsg = fs.format(m,objname or cls.__name__,e2_fmt,e.args[0])
 			errmsg = fs.format(m,objname or cls.__name__,e2_fmt,e.args[0])
 
 
-		if m2: errmsg = '{!r}\n{}'.format(m2,errmsg)
+		if m2:
+			errmsg = '{!r}\n{}'.format(m2,errmsg)
 
 
 		from .util import die,msg
 		from .util import die,msg
 		if cls.on_fail == 'silent':
 		if cls.on_fail == 'silent':
 			return None # TODO: return False instead?
 			return None # TODO: return False instead?
 		elif cls.on_fail == 'return':
 		elif cls.on_fail == 'return':
-			if errmsg: msg(errmsg)
+			if errmsg:
+				msg(errmsg)
 			return None # TODO: return False instead?
 			return None # TODO: return False instead?
 		elif g.traceback or cls.on_fail == 'raise':
 		elif g.traceback or cls.on_fail == 'raise':
 			if hasattr(cls,'exc'):
 			if hasattr(cls,'exc'):
@@ -127,8 +129,9 @@ class InitErrors(object):
 	@classmethod
 	@classmethod
 	def method_not_implemented(cls):
 	def method_not_implemented(cls):
 		import traceback
 		import traceback
-		raise NotImplementedError('method {!r} not implemented for class {!r}'.format(
-				traceback.extract_stack()[-2].name, cls.__name__))
+		raise NotImplementedError(
+			'method {!r} not implemented for class {!r}'.format(
+				traceback.extract_stack()[-2].name, cls.__name__) )
 
 
 class Hilite(object):
 class Hilite(object):
 
 
@@ -231,8 +234,10 @@ class ImmutableAttr: # Descriptor
 
 
 	def __init__(self,dtype,typeconv=True,set_none_ok=False,include_proto=False):
 	def __init__(self,dtype,typeconv=True,set_none_ok=False,include_proto=False):
 		assert isinstance(dtype,self.ok_dtypes), 'ImmutableAttr_check1'
 		assert isinstance(dtype,self.ok_dtypes), 'ImmutableAttr_check1'
-		if include_proto: assert typeconv and type(dtype) == str, 'ImmutableAttr_check2'
-		if set_none_ok:   assert typeconv and type(dtype) != str, 'ImmutableAttr_check3'
+		if include_proto:
+			assert typeconv and type(dtype) == str, 'ImmutableAttr_check2'
+		if set_none_ok:
+			assert typeconv and type(dtype) != str, 'ImmutableAttr_check3'
 
 
 		if dtype is None:
 		if dtype is None:
 			'use instance-defined conversion function for this attribute'
 			'use instance-defined conversion function for this attribute'
@@ -365,14 +370,16 @@ class AddrIdxList(list,InitErrors,MMGenObject):
 					j = i.split('-')
 					j = i.split('-')
 					if len(j) == 1:
 					if len(j) == 1:
 						idx = AddrIdx(i,on_fail='raise')
 						idx = AddrIdx(i,on_fail='raise')
-						if not idx: break
+						if not idx:
+							break
 						ret.append(idx)
 						ret.append(idx)
 					elif len(j) == 2:
 					elif len(j) == 2:
 						beg = AddrIdx(j[0],on_fail='raise')
 						beg = AddrIdx(j[0],on_fail='raise')
-						if not beg: break
+						if not beg:
+							break
 						end = AddrIdx(j[1],on_fail='raise')
 						end = AddrIdx(j[1],on_fail='raise')
-						if not beg: break
-						if end < beg: break
+						if not beg or (end < beg):
+							break
 						ret.extend([AddrIdx(x,on_fail='raise') for x in range(beg,end+1)])
 						ret.extend([AddrIdx(x,on_fail='raise') for x in range(beg,end+1)])
 					else: break
 					else: break
 				else:
 				else:
@@ -391,7 +398,8 @@ class MMGenRange(tuple,InitErrors,MMGenObject):
 		try:
 		try:
 			if len(args) == 1:
 			if len(args) == 1:
 				s = args[0]
 				s = args[0]
-				if type(s) == cls: return s
+				if type(s) == cls:
+					return s
 				assert isinstance(s,str),'not a string or string subclass'
 				assert isinstance(s,str),'not a string or string subclass'
 				ss = s.split('-',1)
 				ss = s.split('-',1)
 				first = int(ss[0])
 				first = int(ss[0])
@@ -442,7 +450,8 @@ class BTCAmt(Decimal,Hilite,InitErrors):
 
 
 	# NB: 'from_decimal' rounds down to precision of 'min_coin_unit'
 	# NB: 'from_decimal' rounds down to precision of 'min_coin_unit'
 	def __new__(cls,num,from_unit=None,from_decimal=False,on_fail='die'):
 	def __new__(cls,num,from_unit=None,from_decimal=False,on_fail='die'):
-		if type(num) == cls: return num
+		if type(num) == cls:
+			return num
 		cls.arg_chk(on_fail)
 		cls.arg_chk(on_fail)
 		try:
 		try:
 			if from_unit:
 			if from_unit:
@@ -480,7 +489,8 @@ class BTCAmt(Decimal,Hilite,InitErrors):
 		cls.method_not_implemented()
 		cls.method_not_implemented()
 
 
 	def fmt(self,fs=None,color=False,suf='',prec=1000):
 	def fmt(self,fs=None,color=False,suf='',prec=1000):
-		if fs == None: fs = self.amt_fs
+		if fs == None:
+			fs = self.amt_fs
 		s = str(int(self)) if int(self) == self else self.normalize().__format__('f')
 		s = str(int(self)) if int(self) == self else self.normalize().__format__('f')
 		if '.' in fs:
 		if '.' in fs:
 			p1,p2 = list(map(int,fs.split('.',1)))
 			p1,p2 = list(map(int,fs.split('.',1)))
@@ -576,7 +586,8 @@ class SeedID(str,Hilite,InitErrors):
 	width = 8
 	width = 8
 	trunc_ok = False
 	trunc_ok = False
 	def __new__(cls,seed=None,sid=None,on_fail='die'):
 	def __new__(cls,seed=None,sid=None,on_fail='die'):
-		if type(sid) == cls: return sid
+		if type(sid) == cls:
+			return sid
 		cls.arg_chk(on_fail)
 		cls.arg_chk(on_fail)
 		try:
 		try:
 			if seed:
 			if seed:
@@ -586,7 +597,7 @@ class SeedID(str,Hilite,InitErrors):
 				return str.__new__(cls,make_chksum_8(seed.data))
 				return str.__new__(cls,make_chksum_8(seed.data))
 			elif sid:
 			elif sid:
 				assert set(sid) <= set(hexdigits.upper()),'not uppercase hex digits'
 				assert set(sid) <= set(hexdigits.upper()),'not uppercase hex digits'
-				assert len(sid) == cls.width,'not {} characters wide'.format(cls.width)
+				assert len(sid) == cls.width, f'not {cls.width} characters wide'
 				return str.__new__(cls,sid)
 				return str.__new__(cls,sid)
 			raise ValueError('no arguments provided')
 			raise ValueError('no arguments provided')
 		except Exception as e:
 		except Exception as e:
@@ -596,7 +607,8 @@ class SubSeedIdx(str,Hilite,InitErrors):
 	color = 'red'
 	color = 'red'
 	trunc_ok = False
 	trunc_ok = False
 	def __new__(cls,s,on_fail='die'):
 	def __new__(cls,s,on_fail='die'):
-		if type(s) == cls: return s
+		if type(s) == cls:
+			return s
 		cls.arg_chk(on_fail)
 		cls.arg_chk(on_fail)
 		try:
 		try:
 			assert isinstance(s,str),'not a string or string subclass'
 			assert isinstance(s,str),'not a string or string subclass'
@@ -676,7 +688,7 @@ class TwLabel(str,InitErrors,MMGenObject):
 			ts = text.split(None,1)
 			ts = text.split(None,1)
 			mmid = TwMMGenID(proto,ts[0],on_fail='raise')
 			mmid = TwMMGenID(proto,ts[0],on_fail='raise')
 			comment = TwComment(ts[1] if len(ts) == 2 else '',on_fail='raise')
 			comment = TwComment(ts[1] if len(ts) == 2 else '',on_fail='raise')
-			me = str.__new__(cls,'{}{}'.format(mmid,' {}'.format(comment) if comment else ''))
+			me = str.__new__( cls, mmid + (' ' + comment if comment else '') )
 			me.mmid = mmid
 			me.mmid = mmid
 			me.comment = comment
 			me.comment = comment
 			me.proto = proto
 			me.proto = proto
@@ -690,16 +702,18 @@ class HexStr(str,Hilite,InitErrors):
 	hexcase = 'lower'
 	hexcase = 'lower'
 	trunc_ok = False
 	trunc_ok = False
 	def __new__(cls,s,on_fail='die',case=None):
 	def __new__(cls,s,on_fail='die',case=None):
-		if type(s) == cls: return s
+		if type(s) == cls:
+			return s
 		cls.arg_chk(on_fail)
 		cls.arg_chk(on_fail)
-		if case == None: case = cls.hexcase
+		if case == None:
+			case = cls.hexcase
 		try:
 		try:
 			assert isinstance(s,str),'not a string or string subclass'
 			assert isinstance(s,str),'not a string or string subclass'
-			assert case in ('upper','lower'),"'{}' incorrect case specifier".format(case)
-			assert set(s) <= set(getattr(hexdigits,case)()),'not {}case hexadecimal symbols'.format(case)
+			assert case in ('upper','lower'), f'{case!r} incorrect case specifier'
+			assert set(s) <= set(getattr(hexdigits,case)()), f'not {case}case hexadecimal symbols'
 			assert not len(s) % 2,'odd-length string'
 			assert not len(s) % 2,'odd-length string'
 			if cls.width:
 			if cls.width:
-				assert len(s) == cls.width,'Value is not {} characters wide'.format(cls.width)
+				assert len(s) == cls.width, f'Value is not {cls.width} characters wide'
 			return str.__new__(cls,s)
 			return str.__new__(cls,s)
 		except Exception as e:
 		except Exception as e:
 			return cls.init_fail(e,s)
 			return cls.init_fail(e,s)
@@ -755,7 +769,8 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
 	# initialize with (priv_bin,compressed), WIF or self
 	# initialize with (priv_bin,compressed), WIF or self
 	def __new__(cls,proto,s=None,compressed=None,wif=None,pubkey_type=None,on_fail='die'):
 	def __new__(cls,proto,s=None,compressed=None,wif=None,pubkey_type=None,on_fail='die'):
 
 
-		if type(s) == cls: return s
+		if type(s) == cls:
+			return s
 		cls.arg_chk(on_fail)
 		cls.arg_chk(on_fail)
 
 
 		if wif:
 		if wif:
@@ -821,7 +836,8 @@ class MMGenLabel(str,Hilite,InitErrors):
 	max_screen_width = 0 # if != 0, overrides max_len
 	max_screen_width = 0 # if != 0, overrides max_len
 	desc = 'label'
 	desc = 'label'
 	def __new__(cls,s,on_fail='die',msg=None):
 	def __new__(cls,s,on_fail='die',msg=None):
-		if type(s) == cls: return s
+		if type(s) == cls:
+			return s
 		cls.arg_chk(on_fail)
 		cls.arg_chk(on_fail)
 		for k in cls.forbidden,cls.allowed:
 		for k in cls.forbidden,cls.allowed:
 			assert type(k) == list
 			assert type(k) == list
@@ -874,7 +890,8 @@ class MMGenPWIDString(MMGenLabel):
 class SeedSplitSpecifier(str,Hilite,InitErrors,MMGenObject):
 class SeedSplitSpecifier(str,Hilite,InitErrors,MMGenObject):
 	color = 'red'
 	color = 'red'
 	def __new__(cls,s,on_fail='raise'):
 	def __new__(cls,s,on_fail='raise'):
-		if type(s) == cls: return s
+		if type(s) == cls:
+			return s
 		cls.arg_chk(on_fail)
 		cls.arg_chk(on_fail)
 		try:
 		try:
 			arr = s.split(':')
 			arr = s.split(':')

+ 1 - 1
mmgen/protocol.py

@@ -166,7 +166,7 @@ class CoinProtocol(MMGenObject):
 			return False
 			return False
 
 
 		def coin_addr(self,addr):
 		def coin_addr(self,addr):
-			return CoinAddr(proto=self,addr=addr)
+			return CoinAddr( proto=self, addr=addr )
 
 
 		def addr_type(self,id_str,on_fail='die'):
 		def addr_type(self,id_str,on_fail='die'):
 			return MMGenAddrType(proto=self,id_str=id_str,on_fail=on_fail)
 			return MMGenAddrType(proto=self,id_str=id_str,on_fail=on_fail)

+ 98 - 69
mmgen/tw.py

@@ -32,9 +32,12 @@ CUR_HOME,ERASE_ALL = '\033[H','\033[0J'
 def CUR_RIGHT(n): return '\033[{}C'.format(n)
 def CUR_RIGHT(n): return '\033[{}C'.format(n)
 
 
 def get_tw_label(proto,s):
 def get_tw_label(proto,s):
-	try: return TwLabel(proto,s,on_fail='raise')
-	except BadTwComment: raise
-	except: return None
+	try:
+		return TwLabel(proto,s,on_fail='raise')
+	except BadTwComment:
+		raise
+	except:
+		return None
 
 
 _date_formatter = {
 _date_formatter = {
 	'days':      lambda rpc,secs: (rpc.cur_date - secs) // 86400,
 	'days':      lambda rpc,secs: (rpc.cur_date - secs) // 86400,
@@ -113,13 +116,6 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel:
 			def amt2(self,value):
 			def amt2(self,value):
 				return self.proto.coin_amt(value)
 				return self.proto.coin_amt(value)
 
 
-	wmsg = {
-	'no_spendable_outputs': """
-No spendable outputs found!  Import addresses with balances into your
-watch-only wallet using '{}-addrimport' and then re-run this program.
-""".strip().format(g.proj_name.lower())
-	}
-
 	async def __ainit__(self,proto,minconf=1,addrs=[]):
 	async def __ainit__(self,proto,minconf=1,addrs=[]):
 		self.proto        = proto
 		self.proto        = proto
 		self.unspent      = self.MMGenTwOutputList()
 		self.unspent      = self.MMGenTwOutputList()
@@ -146,7 +142,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 	@age_fmt.setter
 	@age_fmt.setter
 	def age_fmt(self,val):
 	def age_fmt(self,val):
 		if val not in self.age_fmts:
 		if val not in self.age_fmts:
-			raise BadAgeFormat("'{}': invalid age format (must be one of {!r})".format(val,self.age_fmts))
+			raise BadAgeFormat(f'{val!r}: invalid age format (must be one of {self.age_fmts!r})')
 		self._age_fmt = val
 		self._age_fmt = val
 
 
 	def get_display_precision(self):
 	def get_display_precision(self):
@@ -176,7 +172,10 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 			us_raw = await self.get_unspent_rpc()
 			us_raw = await self.get_unspent_rpc()
 
 
 		if not us_raw:
 		if not us_raw:
-			die(0,self.wmsg['no_spendable_outputs'])
+			die(0,fmt(f"""
+				No spendable outputs found!  Import addresses with balances into your
+				watch-only wallet using '{g.proj_name.lower()}-addrimport' and then re-run this program.
+			""").strip())
 
 
 		lbl_id = ('account','label')['label_api' in self.rpc.caps]
 		lbl_id = ('account','label')['label_api' in self.rpc.caps]
 
 
@@ -214,7 +213,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 		}
 		}
 		key = key or self.sort_key
 		key = key or self.sort_key
 		if key not in sort_funcs:
 		if key not in sort_funcs:
-			die(1,"'{}': invalid sort key.  Valid options: {}".format(key,' '.join(sort_funcs.keys())))
+			die(1,f'{key!r}: invalid sort key.  Valid options: {" ".join(sort_funcs.keys())}')
 		self.sort_key = key
 		self.sort_key = key
 		assert type(reverse) == bool
 		assert type(reverse) == bool
 		self.unspent.sort(key=sort_funcs[key],reverse=reverse or self.reverse)
 		self.unspent.sort(key=sort_funcs[key],reverse=reverse or self.reverse)
@@ -230,10 +229,11 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 		from .term import get_terminal_size
 		from .term import get_terminal_size
 		while True:
 		while True:
 			self.cols = g.terminal_width or get_terminal_size().width
 			self.cols = g.terminal_width or get_terminal_size().width
-			if self.cols >= g.min_screen_width: break
-			m1 = 'Screen too narrow to display the tracking wallet\n'
-			m2 = 'Please resize your screen to at least {} characters and hit ENTER '
-			my_raw_input((m1+m2).format(g.min_screen_width))
+			if self.cols >= g.min_screen_width:
+				break
+			my_raw_input(
+				'Screen too narrow to display the tracking wallet\n'
+				+ f'Please resize your screen to at least {g.min_screen_width} characters and hit ENTER ' )
 
 
 	def get_display_constants(self):
 	def get_display_constants(self):
 		unsp = self.unspent
 		unsp = self.unspent
@@ -360,20 +360,29 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 			max_lbl_len = max([len(i.label) for i in self.unspent if i.label] or [2])
 			max_lbl_len = max([len(i.label) for i in self.unspent if i.label] or [2])
 			for n,i in enumerate(self.unspent):
 			for n,i in enumerate(self.unspent):
 				yield fs.format(
 				yield fs.format(
-					n  = str(n+1)+')',
-					t  = '{},{}'.format('|'+'.'*63 if i.skip == 'txid' and self.group else i.txid,i.vout),
+					n  = str(n+1) + ')',
+					t  = '{},{}'.format(
+							('|'+'.'*63 if i.skip == 'txid' and self.group else i.txid),
+							i.vout ),
 					a  = (
 					a  = (
 						'|'+'.' * addr_w if i.skip == 'addr' and self.group else
 						'|'+'.' * addr_w if i.skip == 'addr' and self.group else
 						i.addr.fmt(color=color,width=addr_w) ),
 						i.addr.fmt(color=color,width=addr_w) ),
-					m  = MMGenID.fmtc(i.twmmid if i.twmmid.type=='mmgen' else
-						'Non-{}'.format(g.proj_name),width = mmid_w,color=color),
+					m  = MMGenID.fmtc(
+							(i.twmmid if i.twmmid.type == 'mmgen' else f'Non-{g.proj_name}'),
+							width = mmid_w,
+							color = color ),
 					A  = i.amt.fmt(color=color),
 					A  = i.amt.fmt(color=color),
 					A2 = ( i.amt2.fmt(color=color) if i.amt2 is not None else '' ),
 					A2 = ( i.amt2.fmt(color=color) if i.amt2 is not None else '' ),
 					c  = i.confs,
 					c  = i.confs,
 					b  = self.rpc.blockcount - (i.confs - 1),
 					b  = self.rpc.blockcount - (i.confs - 1),
 					D  = self.age_disp(i,'date_time'),
 					D  = self.age_disp(i,'date_time'),
 					l  = i.label.hl(color=color) if i.label else
 					l  = i.label.hl(color=color) if i.label else
-						TwComment.fmtc('',color = color,nullrepl='-',width=max_lbl_len) ).rstrip()
+						TwComment.fmtc(
+							s        = '',
+							color    = color,
+							nullrepl = '-',
+							width    = max_lbl_len )
+					).rstrip()
 
 
 		fs2 = '{} (block #{}, {} UTC)\n{}Sort order: {}\n{}\n\nTotal {}: {}\n'
 		fs2 = '{} (block #{}, {} UTC)\n{}Sort order: {}\n{}\n\nTotal {}: {}\n'
 		self.fmt_print = fs2.format(
 		self.fmt_print = fs2.format(
@@ -399,11 +408,11 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 	def get_idx_from_user(self,action):
 	def get_idx_from_user(self,action):
 		msg('')
 		msg('')
 		while True:
 		while True:
-			ret = my_raw_input('Enter {} number (or RETURN to return to main menu): '.format(self.item_desc))
+			ret = my_raw_input(f'Enter {self.item_desc} number (or RETURN to return to main menu): ')
 			if ret == '': return (None,None) if action == 'a_lbl_add' else None
 			if ret == '': return (None,None) if action == 'a_lbl_add' else None
 			n = AddrIdx(ret,on_fail='silent')
 			n = AddrIdx(ret,on_fail='silent')
 			if not n or n < 1 or n > len(self.unspent):
 			if not n or n < 1 or n > len(self.unspent):
-				msg('Choice must be a single number between 1 and {}'.format(len(self.unspent)))
+				msg(f'Choice must be a single number between 1 and {len(self.unspent)}')
 			else:
 			else:
 				if action == 'a_lbl_add':
 				if action == 'a_lbl_add':
 					while True:
 					while True:
@@ -411,17 +420,17 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 						if s == 'q':
 						if s == 'q':
 							return None,None
 							return None,None
 						elif s == '':
 						elif s == '':
-							fs = "Removing label for {} #{}.  Is this what you want?"
-							if keypress_confirm(fs.format(self.item_desc,n)):
+							if keypress_confirm(
+									f'Removing label for {self.item_desc} #{n}.  Is this what you want?'):
 								return n,s
 								return n,s
 						elif s:
 						elif s:
 							if TwComment(s,on_fail='return'):
 							if TwComment(s,on_fail='return'):
 								return n,s
 								return n,s
 				else:
 				else:
 					if action == 'a_addr_delete':
 					if action == 'a_addr_delete':
-						fs = "Removing {} #{} from tracking wallet.  Is this what you want?"
+						fs = 'Removing {} #{} from tracking wallet.  Is this what you want?'
 					elif action == 'a_balance_refresh':
 					elif action == 'a_balance_refresh':
-						fs = "Refreshing tracking wallet {} #{}.  Is this what you want?"
+						fs = 'Refreshing tracking wallet {} #{}.  Is this what you want?'
 					if keypress_confirm(fs.format(self.item_desc,n)):
 					if keypress_confirm(fs.format(self.item_desc,n)):
 						return n
 						return n
 
 
@@ -468,7 +477,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 					e = self.unspent[idx-1]
 					e = self.unspent[idx-1]
 					bal = await self.wallet.get_balance(e.addr,force_rpc=True)
 					bal = await self.wallet.get_balance(e.addr,force_rpc=True)
 					await self.get_unspent_data()
 					await self.get_unspent_data()
-					oneshot_msg = yellow('{} balance for account #{} refreshed\n\n'.format(self.proto.dcoin,idx))
+					oneshot_msg = yellow(f'{self.proto.dcoin} balance for account #{idx} refreshed\n\n')
 				self.display_constants = self.get_display_constants()
 				self.display_constants = self.get_display_constants()
 			elif action == 'a_lbl_add':
 			elif action == 'a_lbl_add':
 				idx,lbl = self.get_idx_from_user(action)
 				idx,lbl = self.get_idx_from_user(action)
@@ -476,8 +485,10 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 					e = self.unspent[idx-1]
 					e = self.unspent[idx-1]
 					if await self.wallet.add_label(e.twmmid,lbl,addr=e.addr):
 					if await self.wallet.add_label(e.twmmid,lbl,addr=e.addr):
 						await self.get_unspent_data()
 						await self.get_unspent_data()
-						a = 'added to' if lbl else 'removed from'
-						oneshot_msg = yellow("Label {} {} #{}\n\n".format(a,self.item_desc,idx))
+						oneshot_msg = yellow('Label {} {} #{}\n\n'.format(
+							('added to' if lbl else 'removed from'),
+							self.item_desc,
+							idx ))
 					else:
 					else:
 						oneshot_msg = red('Label could not be added\n\n')
 						oneshot_msg = red('Label could not be added\n\n')
 				self.display_constants = self.get_display_constants()
 				self.display_constants = self.get_display_constants()
@@ -487,22 +498,29 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 					e = self.unspent[idx-1]
 					e = self.unspent[idx-1]
 					if await self.wallet.remove_address(e.addr):
 					if await self.wallet.remove_address(e.addr):
 						await self.get_unspent_data()
 						await self.get_unspent_data()
-						oneshot_msg = yellow("{} #{} removed\n\n".format(capfirst(self.item_desc),idx))
+						oneshot_msg = yellow(f'{capfirst(self.item_desc)} #{idx} removed\n\n')
 					else:
 					else:
 						oneshot_msg = red('Address could not be removed\n\n')
 						oneshot_msg = red('Address could not be removed\n\n')
 				self.display_constants = self.get_display_constants()
 				self.display_constants = self.get_display_constants()
 			elif action == 'a_print':
 			elif action == 'a_print':
-				of = '{}-{}[{}].out'.format(self.dump_fn_pfx,self.proto.dcoin,
-										','.join(self.sort_info(include_group=False)).lower())
+				of = '{}-{}[{}].out'.format(
+					self.dump_fn_pfx,
+					self.proto.dcoin,
+					','.join(self.sort_info(include_group=False)).lower() )
 				msg('')
 				msg('')
 				try:
 				try:
-					write_data_to_file(of,await self.format_for_printing(),desc='{} listing'.format(self.desc))
+					write_data_to_file(
+						of,
+						await self.format_for_printing(),
+						desc = f'{self.desc} listing' )
 				except UserNonConfirmation as e:
 				except UserNonConfirmation as e:
-					oneshot_msg = red("File '{}' not overwritten by user request\n\n".format(of))
+					oneshot_msg = red(f'File {of!r} not overwritten by user request\n\n')
 				else:
 				else:
-					oneshot_msg = yellow("Data written to '{}'\n\n".format(of))
+					oneshot_msg = yellow(f'Data written to {of!r}\n\n')
 			elif action in ('a_view','a_view_wide'):
 			elif action in ('a_view','a_view_wide'):
-				do_pager(self.fmt_display if action == 'a_view' else await self.format_for_printing(color=True))
+				do_pager(
+					self.fmt_display if action == 'a_view' else
+					await self.format_for_printing(color=True) )
 				if g.platform == 'linux' and oneshot_msg == None:
 				if g.platform == 'linux' and oneshot_msg == None:
 					msg_r(CUR_RIGHT(len(prompt.split('\n')[-1])-2))
 					msg_r(CUR_RIGHT(len(prompt.split('\n')[-1])-2))
 					no_output = True
 					no_output = True
@@ -533,7 +551,7 @@ class TwAddrList(MMGenDict,metaclass=aInitMeta):
 			for mmid in sorted(a.mmid for a in acct_labels if a):
 			for mmid in sorted(a.mmid for a in acct_labels if a):
 				if mmid == mmid_prev:
 				if mmid == mmid_prev:
 					err = True
 					err = True
-					msg('Duplicate MMGen ID ({}) discovered in tracking wallet!\n'.format(mmid))
+					msg(f'Duplicate MMGen ID ({mmid}) discovered in tracking wallet!\n')
 				mmid_prev = mmid
 				mmid_prev = mmid
 			if err: rdie(3,'Tracking wallet is corrupted!')
 			if err: rdie(3,'Tracking wallet is corrupted!')
 
 
@@ -567,14 +585,15 @@ class TwAddrList(MMGenDict,metaclass=aInitMeta):
 						die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format(
 						die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format(
 							proto.coin,
 							proto.coin,
 							d['address'],
 							d['address'],
-							self[lm]['addr']) )
+							self[lm]['addr'] ))
 				else:
 				else:
 					lm.confs = d['confirmations']
 					lm.confs = d['confirmations']
 					lm.txid = d['txid']
 					lm.txid = d['txid']
 					lm.date = None
 					lm.date = None
-					self[lm] = {'amt': proto.coin_amt('0'),
-								'lbl': label,
-								'addr': CoinAddr(proto,d['address'])}
+					self[lm] = {
+						'amt': proto.coin_amt('0'),
+						'lbl': label,
+						'addr': CoinAddr(proto,d['address']) }
 				self[lm]['amt'] += d['amount']
 				self[lm]['amt'] += d['amount']
 				self.total += d['amount']
 				self.total += d['amount']
 
 
@@ -614,7 +633,7 @@ class TwAddrList(MMGenDict,metaclass=aInitMeta):
 		if not self.has_age:
 		if not self.has_age:
 			show_age = False
 			show_age = False
 		if age_fmt not in self.age_fmts:
 		if age_fmt not in self.age_fmts:
-			raise BadAgeFormat("'{}': invalid age format (must be one of {!r})".format(age_fmt,self.age_fmts))
+			raise BadAgeFormat(f'{age_fmt!r}: invalid age format (must be one of {self.age_fmts!r})')
 		fs = '{mid}' + ('',' {addr}')[showbtcaddrs] + ' {cmt} {amt}' + ('',' {age}')[show_age]
 		fs = '{mid}' + ('',' {addr}')[showbtcaddrs] + ' {cmt} {amt}' + ('',' {age}')[show_age]
 		mmaddrs = [k for k in self.keys() if k.type == 'mmgen']
 		mmaddrs = [k for k in self.keys() if k.type == 'mmgen']
 		max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
 		max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
@@ -671,7 +690,9 @@ class TwAddrList(MMGenDict,metaclass=aInitMeta):
 					age=self.age_disp(mmid,age_fmt) if show_age and hasattr(mmid,'confs') else '-'
 					age=self.age_disp(mmid,age_fmt) if show_age and hasattr(mmid,'confs') else '-'
 					).rstrip()
 					).rstrip()
 
 
-			yield '\nTOTAL: {} {}'.format(self.total.hl(color=True),self.proto.dcoin)
+			yield '\nTOTAL: {} {}'.format(
+				self.total.hl(color=True),
+				self.proto.dcoin )
 
 
 		return '\n'.join(gen_output())
 		return '\n'.join(gen_output())
 
 
@@ -694,7 +715,7 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 			mode = 'w'
 			mode = 'w'
 
 
 		if g.debug:
 		if g.debug:
-			print_stack_trace('TW INIT {!r} {!r}'.format(mode,self))
+			print_stack_trace(f'TW INIT {mode!r} {self!r}')
 
 
 		self.rpc = await rpc_init(proto) # TODO: create on demand - only certain ops require RPC
 		self.rpc = await rpc_init(proto) # TODO: create on demand - only certain ops require RPC
 		self.proto = proto
 		self.proto = proto
@@ -707,8 +728,10 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 			self.init_empty()
 			self.init_empty()
 
 
 		if self.data['coin'] != self.proto.coin: # TODO remove?
 		if self.data['coin'] != self.proto.coin: # TODO remove?
-			m = 'Tracking wallet coin ({}) does not match current coin ({})!'
-			raise WalletFileError(m.format(self.data['coin'],self.proto.coin))
+			raise WalletFileError(
+				'Tracking wallet coin ({}) does not match current coin ({})!'.format(
+					self.data['coin'],
+					self.proto.coin ))
 
 
 		self.conv_types(self.data[self.data_key])
 		self.conv_types(self.data[self.data_key])
 		self.cur_balances = {} # cache balances to prevent repeated lookups per program invocation
 		self.cur_balances = {} # cache balances to prevent repeated lookups per program invocation
@@ -739,8 +762,7 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 				self.init_empty()
 				self.init_empty()
 				self.force_write()
 				self.force_write()
 			else:
 			else:
-				m = "File '{}' exists but does not contain valid json data"
-				raise WalletFileError(m.format(self.tw_fn))
+				raise WalletFileError(f'File {self.tw_fn!r} exists but does not contain valid json data')
 		else:
 		else:
 			self.upgrade_wallet_maybe()
 			self.upgrade_wallet_maybe()
 
 
@@ -748,7 +770,7 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 		if self.mode == 'w':
 		if self.mode == 'w':
 			import atexit
 			import atexit
 			def del_tw(tw):
 			def del_tw(tw):
-				dmsg('Running exit handler del_tw() for {!r}'.format(tw))
+				dmsg(f'Running exit handler del_tw() for {tw!r}')
 				del tw
 				del tw
 			atexit.register(del_tw,self)
 			atexit.register(del_tw,self)
 
 
@@ -766,7 +788,7 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 		Since no exceptions are raised, errors will not be caught by the test suite.
 		Since no exceptions are raised, errors will not be caught by the test suite.
 		"""
 		"""
 		if g.debug:
 		if g.debug:
-			print_stack_trace('TW DEL {!r}'.format(self))
+			print_stack_trace(f'TW DEL {self!r}')
 
 
 		if self.mode == 'w':
 		if self.mode == 'w':
 			self.write()
 			self.write()
@@ -846,17 +868,22 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 	@write_mode
 	@write_mode
 	def write_changed(self,data):
 	def write_changed(self,data):
 		write_data_to_file(
 		write_data_to_file(
-			self.tw_fn,data,
-			desc='{} data'.format(self.base_desc),
-			ask_overwrite=False,ignore_opt_outdir=True,quiet=True,
-			check_data=True,cmp_data=self.orig_data)
+			self.tw_fn,
+			data,
+			desc              = f'{self.base_desc} data',
+			ask_overwrite     = False,
+			ignore_opt_outdir = True,
+			quiet             = True,
+			check_data        = True,
+			cmp_data          = self.orig_data )
+
 		self.orig_data = data
 		self.orig_data = data
 
 
 	def write(self): # use 'check_data' to check wallet hasn't been altered by another program
 	def write(self): # use 'check_data' to check wallet hasn't been altered by another program
 		if not self.use_tw_file:
 		if not self.use_tw_file:
 			dmsg("'use_tw_file' is False, doing nothing")
 			dmsg("'use_tw_file' is False, doing nothing")
 			return
 			return
-		dmsg('write(): checking if {} data has changed'.format(self.desc))
+		dmsg(f'write(): checking if {self.desc} data has changed')
 		wdata = json.dumps(self.data)
 		wdata = json.dumps(self.data)
 
 
 		if self.orig_data != wdata:
 		if self.orig_data != wdata:
@@ -903,11 +930,11 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 
 
 		try:
 		try:
 			if not is_mmgen_id(self.proto,arg1):
 			if not is_mmgen_id(self.proto,arg1):
-				assert coinaddr,"Invalid coin address for this chain: {}".format(arg1)
-			assert coinaddr,"{pn} address '{ma}' not found in tracking wallet"
-			assert await self.is_in_wallet(coinaddr),"Address '{ca}' not found in tracking wallet"
+				assert coinaddr, f'Invalid coin address for this chain: {arg1}'
+			assert coinaddr, f'{g.proj_name} address {mmaddr!r} not found in tracking wallet'
+			assert await self.is_in_wallet(coinaddr), f'Address {coinaddr!r} not found in tracking wallet'
 		except Exception as e:
 		except Exception as e:
-			msg(e.args[0].format(pn=g.proj_name,ma=mmaddr,ca=coinaddr))
+			msg(str(e))
 			return False
 			return False
 
 
 		# Allow for the possibility that BTC addr of MMGen addr was entered.
 		# Allow for the possibility that BTC addr of MMGen addr was entered.
@@ -917,7 +944,7 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 			mmaddr = (await TwAddrData(proto=self.proto)).coinaddr2mmaddr(coinaddr)
 			mmaddr = (await TwAddrData(proto=self.proto)).coinaddr2mmaddr(coinaddr)
 
 
 		if not mmaddr:
 		if not mmaddr:
-			mmaddr = '{}:{}'.format(self.proto.base_coin.lower(),coinaddr)
+			mmaddr = f'{self.proto.base_coin.lower()}:{coinaddr}'
 
 
 		mmaddr = TwMMGenID(self.proto,mmaddr)
 		mmaddr = TwMMGenID(self.proto,mmaddr)
 
 
@@ -929,14 +956,16 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 
 
 		if await self.set_label(coinaddr,lbl) == False:
 		if await self.set_label(coinaddr,lbl) == False:
 			if not silent:
 			if not silent:
-				msg('Label could not be {}'.format(('removed','added')[bool(label)]))
+				msg( 'Label could not be {}'.format('added' if label else 'removed') )
 			return False
 			return False
 		else:
 		else:
-			m = mmaddr.type.replace('mmg','MMG')
-			a = mmaddr.replace(self.proto.base_coin.lower()+':','')
-			s = '{} address {} in tracking wallet'.format(m,a)
-			if label: msg("Added label '{}' to {}".format(label,s))
-			else:     msg('Removed label from {}'.format(s))
+			desc = '{} address {} in tracking wallet'.format(
+				mmaddr.type.replace('mmg','MMG'),
+				mmaddr.replace(self.proto.base_coin.lower()+':','') )
+			if label:
+				msg(f'Added label {label!r} to {desc}')
+			else:
+				msg(f'Removed label from {desc}')
 			return True
 			return True
 
 
 	@write_mode
 	@write_mode
@@ -945,7 +974,7 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta):
 
 
 	@write_mode
 	@write_mode
 	async def remove_address(self,addr):
 	async def remove_address(self,addr):
-		raise NotImplementedError('address removal not implemented for coin {}'.format(self.proto.coin))
+		raise NotImplementedError(f'address removal not implemented for coin {self.proto.coin}')
 
 
 class TwGetBalance(MMGenObject,metaclass=aInitMeta):
 class TwGetBalance(MMGenObject,metaclass=aInitMeta):
 
 

+ 1 - 1
mmgen/tx.py

@@ -737,7 +737,7 @@ class MMGenTX:
 					if selected:
 					if selected:
 						if selected[-1] <= len(unspent):
 						if selected[-1] <= len(unspent):
 							return selected
 							return selected
-						msg('Unspent output number must be <= {}'.format(len(unspent)))
+						msg(f'Unspent output number must be <= {len(unspent)}')
 
 
 		def select_unspent_cmdline(self,unspent):
 		def select_unspent_cmdline(self,unspent):
 
 

+ 11 - 5
test/objtest.py

@@ -100,6 +100,7 @@ def run_test(test,arg,input_data):
 			raise UserWarning("Non-'None' return value {} with bad input data".format(repr(ret)))
 			raise UserWarning("Non-'None' return value {} with bad input data".format(repr(ret)))
 		if opt.silent and input_data=='good' and ret==bad_ret:
 		if opt.silent and input_data=='good' and ret==bad_ret:
 			raise UserWarning("'None' returned with good input data")
 			raise UserWarning("'None' returned with good input data")
+
 		if input_data=='good':
 		if input_data=='good':
 			if ret_idx:
 			if ret_idx:
 				ret_chk = arg[list(arg.keys())[ret_idx]].encode()
 				ret_chk = arg[list(arg.keys())[ret_idx]].encode()
@@ -108,7 +109,8 @@ def run_test(test,arg,input_data):
 		if not opt.super_silent:
 		if not opt.super_silent:
 			try: ret_disp = ret.decode()
 			try: ret_disp = ret.decode()
 			except: ret_disp = ret
 			except: ret_disp = ret
-			msg('==> {!r}'.format(ret_disp))
+			msg(f'==> {ret_disp!r}')
+
 		if opt.verbose and issubclass(cls,MMGenObject):
 		if opt.verbose and issubclass(cls,MMGenObject):
 			ret.pmsg() if hasattr(ret,'pmsg') else pmsg(ret)
 			ret.pmsg() if hasattr(ret,'pmsg') else pmsg(ret)
 	except Exception as e:
 	except Exception as e:
@@ -121,10 +123,10 @@ def run_test(test,arg,input_data):
 		if input_data == 'good':
 		if input_data == 'good':
 			raise ValueError('Error on good input data')
 			raise ValueError('Error on good input data')
 		if opt.verbose:
 		if opt.verbose:
-			msg('exitval: {}'.format(e.code))
+			msg(f'exitval: {e.code}')
 	except UserWarning as e:
 	except UserWarning as e:
-		msg('==> {!r}'.format(ret))
-		die(2,red('{}'.format(e.args[0])))
+		msg(f'==> {ret!r}')
+		die(2,red(str(e)))
 
 
 def do_loop():
 def do_loop():
 	import importlib
 	import importlib
@@ -143,7 +145,11 @@ def do_loop():
 			if not opt.silent:
 			if not opt.silent:
 				msg(purple(capfirst(k)+' input:'))
 				msg(purple(capfirst(k)+' input:'))
 			for arg in test_data[test][k]:
 			for arg in test_data[test][k]:
-				run_test(test,arg,input_data=k)
+				run_test(
+					test,
+					arg,
+					input_data = k,
+				)
 
 
 from mmgen.protocol import init_proto_from_opts
 from mmgen.protocol import init_proto_from_opts
 proto = init_proto_from_opts()
 proto = init_proto_from_opts()

+ 0 - 12
test/objtest_py_d/ot_btc_mainnet.py

@@ -149,18 +149,6 @@ tests = {
 			{'id_str':'F00BAA12:S:9999999', 'proto':proto},
 			{'id_str':'F00BAA12:S:9999999', 'proto':proto},
 		),
 		),
 	},
 	},
-	'TwLabel': {
-		'bad':  ('x x','x я','я:я',1,'f00f00f','a:b','x:L:3','F00BAA12:0 x',
-				'F00BAA12:Z:99',tw_pfx+' x',tw_pfx+'я x',
-				'F00BAA12:S:1 '+ utf8_ctrl[:40],
-				{'s':'F00BAA12:S:1 '+ utf8_ctrl[:40],'on_fail':'raise','ExcType':'BadTwComment'},
-				),
-		'good': (
-			('F00BAA12:99 a comment','F00BAA12:L:99 a comment'),
-			'F00BAA12:L:99 comment (UTF-8) α',
-			'F00BAA12:S:9999999 comment',
-			tw_pfx+'x comment')
-	},
 	'TwLabel': {
 	'TwLabel': {
 		'bad':  (
 		'bad':  (
 			{'text':'x x',           'proto':proto},
 			{'text':'x x',           'proto':proto},

+ 2 - 0
test/test.py

@@ -167,6 +167,8 @@ def add_cmdline_opts():
 
 
 # add_cmdline_opts()
 # add_cmdline_opts()
 
 
+opts.UserOpts._reset_ok += ('skip_deps',)
+
 # step 2: opts.init will create new data_dir in ./test (if not 'resume' or 'skip_deps'):
 # step 2: opts.init will create new data_dir in ./test (if not 'resume' or 'skip_deps'):
 usr_args = opts.init(opts_data)
 usr_args = opts.init(opts_data)