7 Commits 5a24cbfc0a ... 1f12baac4c

Author SHA1 Message Date
  The MMGen Project 1f12baac4c update for MMGen Wallet v16.1.dev3 2 months ago
  The MMGen Project fe45fbaa23 minor cleanups 2 months ago
  MMGen@trixie 37c74e361c use match statement where practicable 2 months ago
  MMGen@trixie bfb2dd839a setup.cfg: bump requirements 2 months ago
  MMGen@trixie 4d1f4577a7 lint, whitespace 2 months ago
  MMGen@trixie 5c9d705301 test suite: lint, whitespace 2 months ago
  MMGen@trixie cab8be0167 pyproject.toml: add ruff data 2 months ago

+ 58 - 50
mmgen_node_tools/BlocksInfo.py

@@ -197,18 +197,18 @@ class BlocksInfo:
 			return parse_cs_uarg(self.cfg.stats.lower(),self.all_stats,self.dfl_stats,'stat')
 
 		def parse_cmd_args(): # => (block_list, first, last, step)
-			if not cmd_args:
-				return (None,self.tip,self.tip,None)
-			elif len(cmd_args) == 1:
-				r = self.parse_rangespec(cmd_args[0])
-				return (
-					list(range(r.first,r.last+1,r.step)) if r.step else None,
-					r.first,
-					r.last,
-					r.step
-				)
-			else:
-				return ([self.conv_blkspec(a) for a in cmd_args],None,None,None)
+			match cmd_args:
+				case [] | None:
+					return (None, self.tip, self.tip, None)
+				case [arg]:
+					r = self.parse_rangespec(arg)
+					return (
+						list(range(r.first, r.last+1, r.step)) if r.step else None,
+						r.first,
+						r.last,
+						r.step)
+				case [*args]:
+					return ([self.conv_blkspec(a) for a in args], None, None, None)
 
 		self.cfg = cfg
 		self.rpc = rpc
@@ -323,25 +323,29 @@ class BlocksInfo:
 			repl = (name if add_name else '') + ':' + (fill_char if name in fill else '')
 			yield (ls + self.fields[name].fs.replace(':',repl) + rs)
 
-	def conv_blkspec(self,arg):
-		if str(arg).lower() == 'cur':
-			return self.tip
-		elif is_int(arg):
-			if int(arg) < 0:
-				die(1,f'{arg}: block number must be non-negative')
-			elif int(arg) > self.tip:
-				die(1,f'{arg}: requested block height greater than current chain tip!')
-			else:
-				return int(arg)
-		else:
-			die(1,f'{arg}: invalid block specifier')
-
-	def check_nblocks(self,arg):
-		if arg <= 0:
-			die(1,'nBlocks must be a positive integer')
-		elif arg > self.tip:
-			die(1, f"'{arg}': nBlocks must be less than current chain height")
-		return arg
+	def conv_blkspec(self, arg):
+		match arg:
+			case str() if arg.lower() == 'cur':
+				return self.tip
+			case x if is_int(x):
+				match int(arg):
+					case x if x < 0:
+						die(1, f'{x}: block number must be non-negative')
+					case x if x > self.tip:
+						die(1, f'{x}: requested block height greater than current chain tip!')
+					case x:
+						return x
+			case _:
+				die(1, f'{arg}: invalid block specifier')
+
+	def check_nblocks(self, arg):
+		match arg:
+			case x if x <= 0:
+				die(1, 'nBlocks must be a positive integer')
+			case x if x > self.tip:
+				die(1, f'{arg}: nBlocks must be less than current chain height')
+			case _:
+				return arg
 
 	def parse_rangespec(self,arg):
 
@@ -361,7 +365,7 @@ class BlocksInfo:
 		if p.debug: msg(repr(self.range_data(first,last,from_tip,nblocks,step)))
 
 		if nblocks:
-			if first == None:
+			if first is None:
 				first = self.tip - nblocks + 1
 			last = first + nblocks - 1
 
@@ -487,20 +491,21 @@ class BlocksInfo:
 	def fmt_stat_item(self,fs,s):
 		return fs.format(s) if type(fs) == str else fs(s)
 
-	async def output_stats(self,res,sname):
+	async def output_stats(self, res, sname):
 
 		def gen(data):
 			for d in data:
-				if len(d) == 2:
-					yield (indent+d[0]).format(**{k:self.fmt_stat_item(*v) for k,v in d[1].items()})
-				elif len(d) == 4:
-					yield (indent+d[0]).format(self.fmt_stat_item(d[2],d[3]))
-				elif type(d) == str:
-					yield d
-				else:
-					assert False, f'{d}: invalid stats data'
-
-		foo,data = await res
+				match d:
+					case [a, b]:
+						yield (indent + a).format(**{k:self.fmt_stat_item(*v) for k, v in b.items()})
+					case [a, _, b, c]:
+						yield (indent + a).format(self.fmt_stat_item(b, c))
+					case str():
+						yield d
+					case _:
+						assert False, f'{d}: invalid stats data'
+
+		foo, data = await res
 		indent = '' if sname in self.noindent_stats else '  '
 		Msg('\n'.join(gen(data)))
 
@@ -755,13 +760,16 @@ class JSONBlocksInfo(BlocksInfo):
 
 		def gen(data):
 			for d in data:
-				if len(d) == 2:
-					for k,v in d[1].items():
-						yield (k,self.fmt_stat_item(*v))
-				elif len(d) == 4:
-					yield (d[1],self.fmt_stat_item(d[2],d[3]))
-				elif type(d) != str:
-					assert False, f'{d}: invalid stats data'
+				match d:
+					case [_, a]:
+						for k, v in a.items():
+							yield (k, self.fmt_stat_item(*v))
+					case [_, a, b, c]:
+						yield (a, self.fmt_stat_item(b, c))
+					case str():
+						pass
+					case _:
+						assert False, f'{d}: invalid stats data'
 
 		varname,data = await res
 		Msg_r(', "{}_data": {}'.format( varname, json.dumps(dict(gen(data)),cls=json_encoder) ))

+ 1 - 1
mmgen_node_tools/PollDisplay.py

@@ -52,7 +52,7 @@ class PollDisplay:
 				count += 1
 
 		async def process_input():
-			if self.input == None:
+			if self.input is None:
 				sys.exit(1)
 			elif self.input == 'q':
 				msg('')

+ 1 - 1
mmgen_node_tools/Sound.py

@@ -33,7 +33,7 @@ def timespec2secs(ts):
 	mul = { 's': 1, 'm': 60, 'h': 60*60, 'd': 60*60*24 }
 	pat = r'^([0-9]+)([smhd]*)$'
 	m = re.match(pat,ts)
-	if m == None:
+	if m is None:
 		die(2,"'%s': invalid time specifier" % ts)
 	a,b = m.groups()
 	return int(a) * (mul[b] if b else 1)

+ 24 - 20
mmgen_node_tools/Ticker.py

@@ -19,13 +19,13 @@ mmgen_node_tools.Ticker: Display price information for cryptocurrency and other
 # Possible alternatives:
 # - https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC,LTC&tsyms=USD,EUR
 
-import sys,os,re,time,datetime,json,yaml,random
-from subprocess import run,PIPE,CalledProcessError
+import sys, os, re, time, datetime, json, yaml, random
+from subprocess import run, PIPE, CalledProcessError
 from decimal import Decimal
 from collections import namedtuple
 
-from mmgen.color import red,yellow,green,blue,orange,gray
-from mmgen.util import msg,msg_r,Msg,Msg_r,die,Die,suf,fmt,fmt_list,fmt_dict,list_gen
+from mmgen.color import red, yellow, green, blue, orange, gray
+from mmgen.util import msg, msg_r, Msg, Msg_r, die, fmt, fmt_list, fmt_dict, list_gen
 from mmgen.ui import do_pager
 
 homedir = os.getenv('HOME')
@@ -89,9 +89,11 @@ class DataSource:
 				msg('')
 				from .Misc import curl_exit_codes
 				msg(red(curl_exit_codes[e.returncode]))
-				msg(red('Command line:\n  {}'.format( ' '.join((repr(i) if ' ' in i else i) for i in e.cmd) )))
+				msg(red('Command line:\n  {}'.format(
+					' '.join((repr(i) if ' ' in i else i) for i in e.cmd))))
 				from mmgen.exception import MMGenCalledProcessError
-				raise MMGenCalledProcessError(f'Subprocess returned non-zero exit status {e.returncode}')
+				raise MMGenCalledProcessError(
+					f'Subprocess returned non-zero exit status {e.returncode}')
 
 		def get_data(self):
 
@@ -123,16 +125,17 @@ class DataSource:
 				else:
 					die(1,self.rate_limit_errmsg(elapsed))
 
-			if data_type == 'json':
-				try:
-					data = json.loads(data_in)
-				except:
-					self.json_data_error_msg(data_in)
-					die(2,'Retrieved data is not valid JSON, exiting')
-				json_text = data_in
-			elif data_type == 'python':
-				data = data_in
-				json_text = json.dumps(data_in)
+			match data_type:
+				case 'json':
+					try:
+						data = json.loads(data_in)
+					except:
+						self.json_data_error_msg(data_in)
+						die(2,'Retrieved data is not valid JSON, exiting')
+					json_text = data_in
+				case 'python':
+					data = data_in
+					json_text = json.dumps(data_in)
 
 			if not data:
 				if use_cached_data:
@@ -327,15 +330,16 @@ class DataSource:
 		def postprocess_data(self,data):
 			def gen():
 				keys = set()
-				for key,val in data.items():
+				d = {}
+				for key, val in data.items():
 					if m := re.match(r"\('(.*?)', datetime\.date\((.*)\)\)$",key):
 						date = '{}-{:>02}-{:>02}'.format(*m[2].split(', '))
 						if (sym := m[1]) in keys:
 							d[date] = val
 						else:
 							keys.add(sym)
-							d = {date:val}
-							yield (sym,d)
+							d = {date: val}
+							yield (sym, d)
 			return dict(gen())
 
 def assets_list_gen(cfg_in):
@@ -936,7 +940,7 @@ class Ticker:
 		def fmt_row(self,d,amt=None,amt_fmt=None):
 
 			def fmt_pct(n):
-				return gray('     --') if n == None else (red,green)[n>=0](f'{n:+7.2f}')
+				return gray('     --') if n is None else (red,green)[n>=0](f'{n:+7.2f}')
 
 			p = self.prices[d['id']]
 

+ 0 - 1
mmgen_node_tools/Util.py

@@ -20,7 +20,6 @@ mmgen_node_tools.Util: utility functions for MMGen node tools
 """
 
 import time
-from mmgen.util import suf
 
 def get_hms(t=None,utc=False,no_secs=False):
 	secs = t or time.time()

+ 1 - 1
mmgen_node_tools/data/version

@@ -1 +1 @@
-3.5.0
+3.6.dev0

+ 2 - 2
mmgen_node_tools/main_addrbal.py

@@ -14,7 +14,7 @@ mmnode-addrbal: Get balances for arbitrary addresses in the blockchain
 
 import sys
 
-from mmgen.obj import CoinTxID,Int
+from mmgen.obj import CoinTxID
 from mmgen.cfg import Config
 from mmgen.util import msg,Msg,die,suf,make_timestr,async_run
 from mmgen.color import red
@@ -167,6 +167,6 @@ if len(cfg._args) < 1:
 	die(1,'This command requires at least one coin address argument')
 
 try:
-	async_run(main(cfg._args))
+	async_run(cfg, main, args=[cfg._args])
 except KeyboardInterrupt:
 	sys.stderr.write('\n')

+ 2 - 2
mmgen_node_tools/main_blocks_info.py

@@ -164,7 +164,7 @@ async def main():
 
 	cls = JSONBlocksInfo if cfg.json else BlocksInfo
 
-	m = cls( cfg, cfg._args, await rpc_init(cfg,ignore_wallet=True) )
+	m = cls(cfg, cfg._args, await rpc_init(cfg, ignore_wallet=True))
 
 	if m.fnames and not cfg.no_header:
 		m.print_header()
@@ -178,4 +178,4 @@ async def main():
 
 	m.finalize_output()
 
-async_run(main())
+async_run(cfg, main)

+ 1 - 1
mmgen_node_tools/main_feeview.py

@@ -220,4 +220,4 @@ async def main():
 			await c.call('getblockcount') )) + '\n\n' +
 		'\n'.join(gen_body(data)) + '\n' )
 
-async_run(main())
+async_run(cfg, main)

+ 4 - 5
mmgen_node_tools/main_halving_calculator.py

@@ -21,7 +21,6 @@ mmnode-halving-calculator: Estimate date(s) of future block subsidy halving(s)
 """
 
 import time
-from decimal import Decimal
 
 from mmgen.cfg import Config
 from mmgen.util import async_run
@@ -68,7 +67,7 @@ async def main():
 	proto = cfg._proto
 
 	from mmgen.rpc import rpc_init
-	c = await rpc_init( cfg, proto, ignore_wallet=True )
+	c = await rpc_init(cfg, proto, ignore_wallet=True)
 
 	tip = await c.call('getblockcount')
 	assert tip > 1, 'block tip must be > 1'
@@ -148,8 +147,8 @@ async def main():
 				b = 'BLOCK',
 				c = 'DATE',
 				d = '',
-				e = f'BDI (mins)',
-				f = f'SUBSIDY ({proto.coin})',
+				e = 'BDI (mins)',
+				f = 'SUBSIDY ({proto.coin})',
 				g = f'MINED ({proto.coin})',
 				h = f'TOTAL MINED ({proto.coin})'
 			)
@@ -182,4 +181,4 @@ async def main():
 	else:
 		print_current_stats()
 
-async_run(main())
+async_run(cfg, main)

+ 1 - 1
mmgen_node_tools/main_netrate.py

@@ -67,6 +67,6 @@ async def main():
 		rs,ss,ts = (r,s,t)
 
 try:
-	async_run(main())
+	async_run(cfg, main)
 except KeyboardInterrupt:
 	sys.stderr.write('\n')

+ 4 - 4
mmgen_node_tools/main_peerblocks.py

@@ -31,10 +31,10 @@ opts_data = {
 	}
 }
 
-async def main():
+from mmgen.cfg import Config
+cfg = Config(opts_data=opts_data)
 
-	from mmgen.cfg import Config
-	cfg = Config(opts_data=opts_data)
+async def main():
 
 	from mmgen.rpc import rpc_init
 	rpc = await rpc_init(cfg,ignore_wallet=True)
@@ -48,4 +48,4 @@ async def main():
 		await peers.run(rpc)
 
 from mmgen.util import async_run
-async_run(main())
+async_run(cfg, main)

+ 1 - 1
mmgen_node_tools/main_txfind.py

@@ -92,4 +92,4 @@ msgs = msg_data['quiet' if cfg.quiet else 'normal']
 if len(cfg._args) != 1:
 	die(1,'One transaction ID must be specified')
 
-sys.exit(async_run(main(cfg._args[0])))
+sys.exit(async_run(cfg, main, args=[cfg._args[0]]))

+ 35 - 0
pyproject.toml

@@ -5,6 +5,41 @@ requires = [
 ]
 build-backend = "setuptools.build_meta"
 
+[tool.ruff]
+line-length = 106
+indent-width = 4
+
+[tool.ruff.format]
+quote-style = "single"
+indent-style = "tab"
+
+[tool.ruff.lint]
+ignore = [
+	"E401", # multiple imports per line
+	"E701", # multiple statements per line
+	"E721", # use isinstance()
+	"E731", # lambda instead of def
+	"E402", # module import not top of file
+	"E722", # bare except
+	"E713", # membership 'not in'
+	"E741", # ambiguous variable name
+]
+
+[tool.ruff.lint.per-file-ignores]
+"test/include/common.py"        = [ "F821" ]         # undefined name 'cfg'
+"test/misc/input_func.py"       = [ "F401" ]         # imported but unused
+"test/modtest_d/cashaddr.py"    = [ "F841" ]         # assigned to but never used
+"test/modtest_d/dep.py"         = [ "F401" ]         # imported but unused
+"test/modtest_d/testdep.py"     = [ "F401" ]         # imported but unused
+"test/modtest_d/obj.py"         = [ "F841" ]         # assigned to but never used
+"test/objtest_d/*"              = [ "F401" ]         # imported but unused
+"test/objattrtest_d/*"          = [ "F401" ]         # imported but unused
+"test/overlay/fakemods/*"       = [ "F403", "F405" ] # `import *` used
+"test/*.py"                     = [ "F401" ]         # imported but unused
+"test/colortest.py"             = [ "F403", "F405" ] # `import *` used
+"test/tooltest2.py"             = [ "F403", "F405" ] # `import *` used
+"test/overlay/tree/*"           = [ "ALL" ]
+
 [tool.pylint.format]
 indent-string = "\t"
 indent-after-paren = 2

+ 2 - 2
setup.cfg

@@ -33,11 +33,11 @@ classifiers  =
 	Development Status :: 5 - Production/Stable
 
 [options]
-python_requires = >=3.9
+python_requires = >=3.11
 include_package_data = True
 
 install_requires =
-	mmgen-wallet==16.0.0
+	mmgen-wallet>=16.1.dev3
 	pyyaml
 	yahooquery
 

+ 1 - 1
test/cmdtest_d/main.py

@@ -41,7 +41,7 @@ class CmdTestMain(CmdTestBase):
 
 	def peerblocks(self,args,expect_list=None,pexpect_spawn=False):
 		t = self.spawn(
-			f'mmnode-peerblocks',
+			'mmnode-peerblocks',
 			args,
 			pexpect_spawn = pexpect_spawn )
 		if cfg.exact_output: # disable echoing of input

+ 2 - 2
test/cmdtest_d/misc.py

@@ -32,7 +32,7 @@ class CmdTestHelp(CmdTestBase):
 	color = True
 
 	def version(self):
-		t = self.spawn(f'mmnode-netrate',['--version'])
+		t = self.spawn('mmnode-netrate', ['--version'])
 		t.expect('MMNODE-NETRATE version')
 		return t
 
@@ -108,7 +108,7 @@ class CmdTestScripts(CmdTestBase):
 
 	def ticker(self, args=[], expect_list=None, cached=True, exit_val=None):
 		t = self.spawn(
-			f'mmnode-ticker',
+			'mmnode-ticker',
 			(['--cached-data'] if cached else []) + self.ticker_args + args,
 			exit_val = exit_val)
 		if expect_list:

+ 5 - 5
test/cmdtest_d/regtest.py

@@ -107,7 +107,7 @@ class CmdTestRegtest(CmdTestBase):
 
 	def __init__(self, cfg, trunner, cfgs, spawn):
 		CmdTestBase.__init__(self, cfg, trunner, cfgs, spawn)
-		if trunner == None:
+		if trunner is None:
 			return
 		if cfg._proto.testnet:
 			die(2,'--testnet and --regtest options incompatible with regtest test suite')
@@ -328,7 +328,7 @@ class CmdTestRegtest(CmdTestBase):
 		async def do_tx(inputs,outputs,wif):
 			tx_hex = await r.rpc_call( 'createrawtransaction', inputs, outputs )
 			tx = await r.rpc_call( 'signrawtransactionwithkey', tx_hex, [wif], [], self.proto.sighash_type )
-			assert tx['complete'] == True
+			assert tx['complete']
 			return tx['hex']
 
 		async def do_tx1():
@@ -371,13 +371,13 @@ class CmdTestRegtest(CmdTestBase):
 		imsg(f'Creating funding transaction with {nTxs} outputs of value {tx1_amt} {self.proto.coin}')
 		tx1_hex = await do_tx1()
 
-		imsg(f'Relaying funding transaction')
+		imsg('Relaying funding transaction')
 		await r.rpc_call('sendrawtransaction',tx1_hex)
 
-		imsg(f'Mining a block')
+		imsg('Mining a block')
 		await r.generate(1,silent=True)
 
-		imsg(f'Generating fees for mempool transactions')
+		imsg('Generating fees for mempool transactions')
 		fees = list(gen_fees(nTxs,2,120))
 
 		imsg(f'Creating and relaying {nTxs} mempool transactions with {nPairs} outputs each')

+ 7 - 0
test/modtest_d/__init__.py

@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+"""
+test.modtest_d: shared data for module tests for the MMGen Node Tools suite
+"""
+
+altcoin_tests = []