diff --git a/mmgen_node_tools/BlocksInfo.py b/mmgen_node_tools/BlocksInfo.py index 42ccc23..a59bf50 100755 --- a/mmgen_node_tools/BlocksInfo.py +++ b/mmgen_node_tools/BlocksInfo.py @@ -20,58 +20,57 @@ mmgen_node_tools.BlocksInfo: Display information about a block or range of blocks """ -import re,json +import re, json from collections import namedtuple -from time import strftime,gmtime +from time import strftime, gmtime from decimal import Decimal -from mmgen.util import msg,Msg,Msg_r,die,suf,secs_to_ms,secs_to_dhms,is_int +from mmgen.util import msg, Msg, Msg_r, die, suf, secs_to_ms, secs_to_dhms, is_int from mmgen.rpc.util import json_encoder class RangeParser: debug = False - def __init__(self,caller,arg): + def __init__(self, caller, arg): self.caller = caller self.arg = self.orig_arg = arg - def parse(self,target): - ret = getattr(self,'parse_'+target)() + def parse(self, target): + ret = getattr(self, 'parse_'+target)() if self.debug: msg(f'arg after parse({target}): {self.arg}') return ret def finalize(self): if self.arg: - die(1,f'{self.orig_arg!r}: invalid range specifier') + die(1, f'{self.orig_arg!r}: invalid range specifier') def parse_from_tip(self): - m = re.match(r'-([0-9]+)(.*)',self.arg) + m = re.match(r'-([0-9]+)(.*)', self.arg) if m: - res,self.arg = (m[1],m[2]) + res, self.arg = (m[1], m[2]) return self.caller.check_nblocks(int(res)) def parse_abs_range(self): - m = re.match(r'([^+-]+)(-([^+-]+)){0,1}(.*)',self.arg) + m = re.match(r'([^+-]+)(-([^+-]+)){0,1}(.*)', self.arg) if m: if self.debug: msg(f'abs_range parse: first={m[1]}, last={m[3]}') self.arg = m[4] return ( self.caller.conv_blkspec(m[1]), - self.caller.conv_blkspec(m[3]) if m[3] else None - ) - return (None,None) + self.caller.conv_blkspec(m[3]) if m[3] else None) + return (None, None) def parse_add(self): - m = re.match(r'\+([0-9*]+)(.*)',self.arg) + m = re.match(r'\+([0-9*]+)(.*)', self.arg) if m: - res,self.arg = (m[1],m[2]) + res, self.arg = (m[1], m[2]) if res.strip('*') != res: - die(1,f"'+{res}': malformed nBlocks specifier") + die(1, f"'+{res}': malformed nBlocks specifier") if len(res) > 30: - die(1,f"'+{res}': overly long nBlocks specifier") + die(1, f"'+{res}': overly long nBlocks specifier") return self.caller.check_nblocks(eval(res)) # res is only digits plus '*', so eval safe class BlocksInfo: @@ -81,33 +80,33 @@ class BlocksInfo: total_solve_time = 0 header_printed = False - bf = namedtuple('block_info_fields',['fmt_func','src','fs','hdr1','hdr2','key1','key2']) + bf = namedtuple('block_info_fields', ['fmt_func', 'src', 'fs', 'hdr1', 'hdr2', 'key1', 'key2']) # bh=getblockheader, bs=getblockstats, lo=local fields = { - 'block': bf( None, 'bh', '{:<6}', '', 'Block', 'height', None ), - 'hash': bf( None, 'bh', '{:<64}', '', 'Hash', 'hash', None ), - 'date': bf( 'da', 'bh', '{:<19}', '', 'Date', 'time', None ), - 'interval': bf( 'td', 'lo', '{:>8}', 'Solve', 'Time ', 'interval', None ), - 'subsidy': bf( 'su', 'bs', '{:<5}', 'Sub-', 'sidy', 'subsidy', None ), - 'totalfee': bf( 'tf', 'bs', '{:>10}', '', 'Total Fee', 'totalfee', None ), - 'size': bf( None, 'bs', '{:>7}', '', 'Size', 'total_size', None ), - 'weight': bf( None, 'bs', '{:>7}', '', 'Weight', 'total_weight', None ), - 'fee90': bf( 'fe', 'bs', '{:>3}', '90%', 'Fee', 'feerate_percentiles', 4 ), - 'fee75': bf( 'fe', 'bs', '{:>3}', '75%', 'Fee', 'feerate_percentiles', 3 ), - 'fee50': bf( 'fe', 'bs', '{:>3}', '50%', 'Fee', 'feerate_percentiles', 2 ), - 'fee25': bf( 'fe', 'bs', '{:>3}', '25%', 'Fee', 'feerate_percentiles', 1 ), - 'fee10': bf( 'fe', 'bs', '{:>3}', '10%', 'Fee', 'feerate_percentiles', 0 ), - 'fee_max': bf( 'fe', 'bs', '{:>5}', 'Max', 'Fee', 'maxfeerate', None ), - 'fee_avg': bf( 'fe', 'bs', '{:>3}', 'Avg', 'Fee', 'avgfeerate', None ), - 'fee_min': bf( 'fe', 'bs', '{:>3}', 'Min', 'Fee', 'minfeerate', None ), - 'nTx': bf( None, 'bh', '{:>5}', '', ' nTx ', 'nTx', None ), - 'inputs': bf( None, 'bs', '{:>5}', 'In- ', 'puts', 'ins', None ), - 'outputs': bf( None, 'bs', '{:>5}', 'Out-', 'puts', 'outs', None ), - 'utxo_inc': bf( None, 'bs', '{:>6}', ' UTXO', ' Incr', 'utxo_increase', None ), - 'version': bf( None, 'bh', '{:<8}', '', 'Version', 'versionHex', None ), - 'difficulty': bf( 'di', 'bh', '{:<8}', 'Diffi-','culty', 'difficulty', None ), - 'miner': bf( None, 'lo', '{:<5}', '', 'Miner', 'miner', None ), - } + 'block': bf(None, 'bh', '{:<6}', '', 'Block', 'height', None), + 'hash': bf(None, 'bh', '{:<64}', '', 'Hash', 'hash', None), + 'date': bf('da', 'bh', '{:<19}', '', 'Date', 'time', None), + 'interval': bf('td', 'lo', '{:>8}', 'Solve', 'Time ', 'interval', None), + 'subsidy': bf('su', 'bs', '{:<5}', 'Sub-', 'sidy', 'subsidy', None), + 'totalfee': bf('tf', 'bs', '{:>10}', '', 'Total Fee', 'totalfee', None), + 'size': bf(None, 'bs', '{:>7}', '', 'Size', 'total_size', None), + 'weight': bf(None, 'bs', '{:>7}', '', 'Weight', 'total_weight', None), + 'fee90': bf('fe', 'bs', '{:>3}', '90%', 'Fee', 'feerate_percentiles', 4), + 'fee75': bf('fe', 'bs', '{:>3}', '75%', 'Fee', 'feerate_percentiles', 3), + 'fee50': bf('fe', 'bs', '{:>3}', '50%', 'Fee', 'feerate_percentiles', 2), + 'fee25': bf('fe', 'bs', '{:>3}', '25%', 'Fee', 'feerate_percentiles', 1), + 'fee10': bf('fe', 'bs', '{:>3}', '10%', 'Fee', 'feerate_percentiles', 0), + 'fee_max': bf('fe', 'bs', '{:>5}', 'Max', 'Fee', 'maxfeerate', None), + 'fee_avg': bf('fe', 'bs', '{:>3}', 'Avg', 'Fee', 'avgfeerate', None), + 'fee_min': bf('fe', 'bs', '{:>3}', 'Min', 'Fee', 'minfeerate', None), + 'nTx': bf(None, 'bh', '{:>5}', '', ' nTx ', 'nTx', None), + 'inputs': bf(None, 'bs', '{:>5}', 'In- ', 'puts', 'ins', None), + 'outputs': bf(None, 'bs', '{:>5}', 'Out-', 'puts', 'outs', None), + 'utxo_inc': bf(None, 'bs', '{:>6}', ' UTXO', ' Incr', 'utxo_increase', None), + 'version': bf(None, 'bh', '{:<8}', '', 'Version', 'versionHex', None), + 'difficulty': bf('di', 'bh', '{:<8}', 'Diffi-','culty', 'difficulty', None), + 'miner': bf(None, 'lo', '{:<5}', '', 'Miner', 'miner', None)} + dfl_fields = ( 'block', 'date', @@ -121,8 +120,8 @@ class BlocksInfo: 'fee10', 'fee_avg', 'fee_min', - 'version', - ) + 'version') + fixed_fields = ( 'block', # until ≈ 09/01/2028 (block 1000000) 'hash', @@ -131,36 +130,34 @@ class BlocksInfo: 'weight', # until ≈ 2.5x block size increase 'version', 'subsidy', # until ≈ 01/04/2028 (increases by 1 digit per halving until 9th halving [max 10 digits]) - 'difficulty', # until 1.00e+100 (i.e. never) - ) + 'difficulty') # until 1.00e+100 (i.e. never) # column width adjustment data: - fs_lsqueeze = ('totalfee','inputs','outputs','nTx') + fs_lsqueeze = ('totalfee', 'inputs', 'outputs', 'nTx') fs_rsqueeze = () fs_groups = ( - ('fee10','fee25','fee50','fee75','fee90','fee_avg','fee_min','fee_max'), - ) + ('fee10', 'fee25', 'fee50', 'fee75', 'fee90', 'fee_avg', 'fee_min', 'fee_max')) fs_lsqueeze2 = ('interval',) - all_stats = ['col_avg','range','avg','mini_avg','total','diff'] - dfl_stats = ['range','mini_avg','diff'] + all_stats = ['col_avg', 'range', 'avg', 'mini_avg', 'total', 'diff'] + dfl_stats = ['range', 'mini_avg', 'diff'] noindent_stats = ['col_avg'] - avg_stats_skip = {'block', 'hash', 'date', 'version','miner'} + avg_stats_skip = {'block', 'hash', 'date', 'version', 'miner'} - range_data = namedtuple('parsed_range_data',['first','last','from_tip','nblocks','step']) + range_data = namedtuple('parsed_range_data', ['first', 'last', 'from_tip', 'nblocks', 'step']) - t_fmt = lambda self,t: f'{t/86400:.2f} days' if t > 172800 else f'{t/3600:.2f} hrs' + t_fmt = lambda self, t: f'{t/86400:.2f} days' if t > 172800 else f'{t/3600:.2f} hrs' @classmethod - def parse_cslist(cls,uarg,full_set,dfl_set,desc): + def parse_cslist(cls, uarg, full_set, dfl_set, desc): - def make_list(m,func): + def make_list(m, func): groups_lc = [set(e.lower() for e in gi.split(',')) for gi in m.groups()] for group in groups_lc: for e in group: if e not in full_set_lc: - die(1,f'{e!r}: unrecognized {desc}') + die(1, f'{e!r}: unrecognized {desc}') # display elements in order: return [e for e in full_set if e.lower() in func(groups_lc)] @@ -168,33 +165,31 @@ class BlocksInfo: dfl_set_lc = set(e.lower() for e in dfl_set) cspat = r'(\w+(?:,\w+)*)' - for pat,func in ( - ( rf'{cspat}$', lambda g: g[0] ), - ( rf'\+{cspat}$', lambda g: dfl_set_lc | g[0] ), - ( rf'\-{cspat}$', lambda g: dfl_set_lc - g[0] ), - ( rf'\+{cspat}\-{cspat}$', lambda g: ( dfl_set_lc | g[0] ) - g[1] ), - ( rf'\-{cspat}\+{cspat}$', lambda g: ( dfl_set_lc - g[0] ) | g[1] ), - ( rf'all\-{cspat}$', lambda g: full_set_lc - g[0] ) - ): - m = re.match(pat,uarg,re.ASCII|re.IGNORECASE) + for pat, func in ( + (rf'{cspat}$', lambda g: g[0]), + (rf'\+{cspat}$', lambda g: dfl_set_lc | g[0]), + (rf'\-{cspat}$', lambda g: dfl_set_lc - g[0]), + (rf'\+{cspat}\-{cspat}$', lambda g: (dfl_set_lc | g[0]) - g[1]), + (rf'\-{cspat}\+{cspat}$', lambda g: (dfl_set_lc - g[0]) | g[1]), + (rf'all\-{cspat}$', lambda g: full_set_lc - g[0])): + m = re.match(pat, uarg, re.ASCII|re.IGNORECASE) if m: - return make_list(m,func) + return make_list(m, func) else: - die(1,f'{uarg}: invalid parameter') + die(1, f'{uarg}: invalid parameter') - def __init__(self,cfg,cmd_args,rpc): + def __init__(self, cfg, cmd_args, rpc): - def parse_cs_uarg(uarg,full_set,dfl_set,desc): + def parse_cs_uarg(uarg, full_set, dfl_set, desc): return ( full_set if uarg == 'all' else [] if uarg == 'none' else - self.parse_cslist(uarg,full_set,dfl_set,desc) - ) + self.parse_cslist(uarg, full_set, dfl_set, desc)) def get_fields(): - return parse_cs_uarg(self.cfg.fields,list(self.fields),self.dfl_fields,'field') + return parse_cs_uarg(self.cfg.fields, list(self.fields), self.dfl_fields, 'field') def get_stats(): - return parse_cs_uarg(self.cfg.stats.lower(),self.all_stats,self.dfl_stats,'stat') + return parse_cs_uarg(self.cfg.stats.lower(), self.all_stats, self.dfl_stats, 'stat') def parse_cmd_args(): # => (block_list, first, last, step) match cmd_args: @@ -217,7 +212,7 @@ class BlocksInfo: from_satoshi = self.rpc.proto.coin_amt.satoshi to_satoshi = 1 / from_satoshi - self.block_list,self.first,self.last,self.step = parse_cmd_args() + self.block_list, self.first, self.last, self.step = parse_cmd_args() have_segwit = self.rpc.info('segwit_is_active') @@ -228,35 +223,33 @@ class BlocksInfo: self.stats_deps = { 'avg': set(self.fields) - self.avg_stats_skip, 'col_avg': set(self.fields) - self.avg_stats_skip, - 'mini_avg': {'interval','size'} | ({'weight'} if have_segwit else set()), - 'total': {'interval','subsidy','totalfee','nTx','inputs','outputs','utxo_inc'}, + 'mini_avg': {'interval', 'size'} | ({'weight'} if have_segwit else set()), + 'total': {'interval', 'subsidy', 'totalfee', 'nTx', 'inputs', 'outputs', 'utxo_inc'}, 'range': {}, - 'diff': {}, - } + 'diff': {}} self.fmt_funcs = { - 'da': lambda arg: strftime('%Y-%m-%d %X',gmtime(arg)), + 'da': lambda arg: strftime('%Y-%m-%d %X', gmtime(arg)), 'td': lambda arg: ( - '-{:02}:{:02}'.format(abs(arg)//60,abs(arg)%60) if arg < 0 else - ' {:02}:{:02}'.format(arg//60,arg%60) ), + '-{:02}:{:02}'.format(abs(arg)//60, abs(arg)%60) if arg < 0 else + ' {:02}:{:02}'.format(arg//60, arg%60)), 'tf': lambda arg: '{:.8f}'.format(arg * from_satoshi), 'su': lambda arg: str(arg * from_satoshi).rstrip('0').rstrip('.'), 'fe': lambda arg: str(arg), - 'di': lambda arg: '{:.2e}'.format(Decimal(arg)), - } + 'di': lambda arg: '{:.2e}'.format(Decimal(arg))} if self.cfg.coin == 'BCH': self.fmt_funcs.update({ 'su': lambda arg: str(arg).rstrip('0').rstrip('.'), 'fe': lambda arg: str(int(Decimal(arg) * to_satoshi)), - 'tf': lambda arg: '{:.8f}'.format(Decimal(arg)), - }) + 'tf': lambda arg: '{:.8f}'.format(Decimal(arg))}) self.fnames = tuple( - [f for f in self.fields if self.fields[f].src == 'bh' or f == 'interval'] if self.cfg.header_info else - get_fields() if self.cfg.fields else - self.dfl_fields - ) + [f for f in self.fields if self.fields[f].src == 'bh' or f == 'interval'] + if self.cfg.header_info + else get_fields() if self.cfg.fields + else self.dfl_fields) + if self.cfg.miner_info and 'miner' not in self.fnames: self.fnames += ('miner',) @@ -266,15 +259,15 @@ class BlocksInfo: if 'diff' in self.stats and not self.cfg.stats and self.last != self.tip: self.stats.remove('diff') - if {'avg','col_avg'} <= set(self.stats) and self.cfg.stats_only: + if {'avg', 'col_avg'} <= set(self.stats) and self.cfg.stats_only: self.stats.remove('col_avg') - if {'avg','mini_avg'} <= set(self.stats): + if {'avg', 'mini_avg'} <= set(self.stats): self.stats.remove('mini_avg') if self.cfg.full_stats: add_fnames = {fname for sname in self.stats for fname in self.stats_deps[sname]} - self.fnames = tuple(f for f in self.fields if f in {'block'} | set(self.fnames) | add_fnames ) + self.fnames = tuple(f for f in self.fields if f in {'block'} | set(self.fnames) | add_fnames) else: if 'col_avg' in self.stats and not self.fnames: self.stats.remove('col_avg') @@ -287,8 +280,7 @@ class BlocksInfo: self.bs_keys = set( [v.key1 for v in self.fvals if v.src == 'bs'] + ['total_size'] + - (['total_weight'] if have_segwit else []) - ) + (['total_weight'] if have_segwit else [])) if 'miner' in self.fnames: # capturing parens must contain only ASCII chars! @@ -302,17 +294,16 @@ class BlocksInfo: rb'([\x20-\x7e]{9,})', rb'[/^]([a-zA-Z0-9&. #/-]{5,})', rb'[/^]([_a-zA-Z0-9&. #/-]+)/', - rb'^\x03...\W{0,5}([\\_a-zA-Z0-9&. #/-]+)[/\\]', - )] + rb'^\x03...\W{0,5}([\\_a-zA-Z0-9&. #/-]+)[/\\]')] - self.block_data = namedtuple('block_data',self.fnames) - self.deps = { v.src for v in self.fvals } + self.block_data = namedtuple('block_data', self.fnames) + self.deps = {v.src for v in self.fvals} - def gen_fs(self,fnames,fill=[],fill_char='-',add_name=False): + def gen_fs(self, fnames, fill=[], fill_char='-', add_name=False): for i in range(len(fnames)): name = fnames[i] - ls = (' ','')[name in self.fs_lsqueeze + self.fs_lsqueeze2] - rs = (' ','')[name in self.fs_rsqueeze] + ls = (' ', '')[name in self.fs_lsqueeze + self.fs_lsqueeze2] + rs = (' ', '')[name in self.fs_rsqueeze] if i < len(fnames) - 1 and fnames[i+1] in self.fs_lsqueeze2: rs = '' if i: @@ -321,7 +312,7 @@ class BlocksInfo: ls = '' break repl = (name if add_name else '') + ':' + (fill_char if name in fill else '') - yield (ls + self.fields[name].fs.replace(':',repl) + rs) + yield (ls + self.fields[name].fs.replace(':', repl) + rs) def conv_blkspec(self, arg): match arg: @@ -347,22 +338,22 @@ class BlocksInfo: case _: return arg - def parse_rangespec(self,arg): + def parse_rangespec(self, arg): - p = RangeParser(self,arg) + p = RangeParser(self, arg) - from_tip = p.parse('from_tip') - first,last = (self.tip-from_tip,None) if from_tip else p.parse('abs_range') - add1 = p.parse('add') - add2 = p.parse('add') + from_tip = p.parse('from_tip') + first, last = (self.tip-from_tip, None) if from_tip else p.parse('abs_range') + add1 = p.parse('add') + add2 = p.parse('add') p.finalize() if add2 and last is not None: - die(1,f'{arg!r}: invalid range specifier') + die(1, f'{arg!r}: invalid range specifier') - nblocks,step = (add1,add2) if last is None else (None,add1) + nblocks, step = (add1, add2) if last is None else (None, add1) - if p.debug: msg(repr(self.range_data(first,last,from_tip,nblocks,step))) + if p.debug: msg(repr(self.range_data(first, last, from_tip, nblocks, step))) if nblocks: if first is None: @@ -373,12 +364,12 @@ class BlocksInfo: last = self.conv_blkspec(last or first) if p.debug: - msg(repr(self.range_data(first,last,from_tip,nblocks,step))) + msg(repr(self.range_data(first, last, from_tip, nblocks, step))) if first > last: - die(1,f'{first}-{last}: invalid block range') + die(1, f'{first}-{last}: invalid block range') - return self.range_data(first,last,from_tip,nblocks,step) + return self.range_data(first, last, from_tip, nblocks, step) async def process_blocks(self): @@ -388,7 +379,7 @@ class BlocksInfo: c = self.rpc - heights = self.block_list or range(self.first,self.last+1) + heights = self.block_list or range(self.first, self.last+1) self.hdrs = await get_hdrs(heights) if self.block_list: @@ -397,8 +388,7 @@ class BlocksInfo: else: self.first_prev_hdr = ( self.hdrs[0] if heights[0] == 0 else - await c.call('getblockheader',await c.call('getblockhash',heights[0]-1)) - ) + await c.call('getblockheader', await c.call('getblockhash', heights[0]-1))) self.t_cur = self.first_prev_hdr['time'] self.res = [] @@ -409,16 +399,16 @@ class BlocksInfo: ret = await self.process_block(self.hdrs[n]) self.res.append(ret) if self.fnames and not self.cfg.stats_only: - self.output_block(ret,n) + self.output_block(ret, n) - def output_block(self,data,n): + def output_block(self, data, n): def gen(): - for k,v in data._asdict().items(): + for k, v in data._asdict().items(): func = self.fields[k].fmt_func yield self.fmt_funcs[func](v) if func else v Msg(self.fs.format(*gen())) - async def process_block(self,hdr): + async def process_block(self, hdr): self.t_diff = hdr['time'] - self.t_cur self.t_cur = hdr['time'] @@ -426,14 +416,12 @@ class BlocksInfo: blk_data = { 'bh': hdr, - 'lo': { 'interval': self.t_diff } - } + 'lo': {'interval': self.t_diff}} if 'bs' in self.deps: bs = ( self.genesis_stats if hdr['height'] == 0 else - await self.rpc.call('getblockstats',hdr['hash'],list(self.bs_keys)) - ) + await self.rpc.call('getblockstats', hdr['hash'], list(self.bs_keys))) self.total_bytes += bs['total_size'] if 'total_weight' in bs: self.total_weight += bs['total_weight'] @@ -446,14 +434,13 @@ class BlocksInfo: for v in self.fvals: yield ( blk_data[v.src][v.key1] if v.key2 is None else - blk_data[v.src][v.key1][v.key2] - ) + blk_data[v.src][v.key1][v.key2]) return self.block_data(*gen()) - async def get_miner_string(self,H): - tx0 = (await self.rpc.call('getblock',H))['tx'][0] - bd = await self.rpc.call('getrawtransaction',tx0,1) + async def get_miner_string(self, H): + tx0 = (await self.rpc.call('getblock', H))['tx'][0] + bd = await self.rpc.call('getrawtransaction', tx0, 1) if type(bd) == tuple: return '---' else: @@ -464,13 +451,12 @@ class BlocksInfo: trmap_in = { '\\': ' ', '/': ' ', - ',': ' ', - } - trmap = { ord(a):b for a,b in trmap_in.items() } + ',': ' '} + trmap = {ord(a): b for a, b in trmap_in.items()} for pat in self.miner_pats: m = pat.search(cb) if m: - return re.sub( r'\s+', ' ', m[1].decode().strip('^').translate(trmap).strip() ) + return re.sub(r'\s+', ' ', m[1].decode().strip('^').translate(trmap).strip()) return '' def print_header(self): @@ -484,11 +470,11 @@ class BlocksInfo: yield self.fs.format(*hdr1) yield self.fs.format(*hdr2) - def process_stats(self,sname): - method = getattr(self,f'create_{sname}_stats',None) - return self.output_stats(method() if method else self.create_stats(sname),sname) + def process_stats(self, sname): + method = getattr(self, f'create_{sname}_stats', None) + return self.output_stats(method() if method else self.create_stats(sname), sname) - def fmt_stat_item(self,fs,s): + 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): @@ -497,7 +483,7 @@ class BlocksInfo: for d in data: match d: case [a, b]: - yield (indent + a).format(**{k:self.fmt_stat_item(*v) for k, v in b.items()}) + 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(): @@ -524,15 +510,14 @@ class BlocksInfo: 'range': ('{}', self.hdrs[-1]['height'] - self.hdrs[0]['height'] + 1), 'elapsed': (self.t_fmt, elapsed), 'nBlocks': ('{}', total_blks), - 'step': ('{}', self.step), - } - ) - if elapsed: - yield ( 'Start: {}', 'start_date', self.fmt_funcs['da'], self.hdrs[0]['time'] ) - yield ( 'End: {}', 'end_date', self.fmt_funcs['da'], self.hdrs[-1]['time'] ) - yield ( 'Avg BDI: {} min', 'avg_bdi', '{:.2f}', elapsed / nblocks / 60 ) + 'step': ('{}', self.step)}) - return ( 'range', gen() ) + if elapsed: + yield ('Start: {}', 'start_date', self.fmt_funcs['da'], self.hdrs[0]['time']) + yield ('End: {}', 'end_date', self.fmt_funcs['da'], self.hdrs[-1]['time']) + yield ('Avg BDI: {} min', 'avg_bdi', '{:.2f}', elapsed / nblocks / 60) + + return ('range', gen()) async def create_diff_stats(self): @@ -541,18 +526,17 @@ class BlocksInfo: tip_hdr = ( self.hdrs[-1] if self.hdrs[-1]['height'] == self.tip else - await c.call('getblockheader',await c.call('getblockhash',self.tip)) - ) + await c.call('getblockheader', await c.call('getblockhash', self.tip))) min_sample_blks = 432 # ≈3 days - rel_hdr = await c.call('getblockheader',await c.call('getblockhash',self.tip-rel)) + rel_hdr = await c.call('getblockheader', await c.call('getblockhash', self.tip-rel)) if rel >= min_sample_blks: sample_blks = rel - bdi = ( tip_hdr['time'] - rel_hdr['time'] ) / rel + bdi = (tip_hdr['time'] - rel_hdr['time']) / rel else: - sample_blks = min(min_sample_blks,self.tip) - start_hdr = await c.call('getblockheader',await c.call('getblockhash',self.tip-sample_blks)) + sample_blks = min(min_sample_blks, self.tip) + start_hdr = await c.call('getblockheader', await c.call('getblockhash', self.tip-sample_blks)) diff_adj = Decimal(tip_hdr['difficulty']) / Decimal(start_hdr['difficulty']) time1 = rel_hdr['time'] - start_hdr['time'] time2 = tip_hdr['time'] - rel_hdr['time'] @@ -560,7 +544,7 @@ class BlocksInfo: rem = self.rpc.proto.diff_adjust_interval - rel - return ( 'difficulty', ( + return ('difficulty', ( 'Difficulty Statistics:', ('Current height: {}', 'chain_tip', '{}', self.tip), ('Next diff adjust: {next_diff_adjust} (in {blks_remaining} block%s [{time_remaining}])' % suf(rem), @@ -593,105 +577,98 @@ class BlocksInfo: def gen(): for field in self.fnames: if field in self.avg_stats_skip: - yield ( field, ('{}','') ) + yield (field, ('{}', '')) else: ret = self.sum_field_avg(field) func = self.fields[field].fmt_func - yield ( field, ( (self.fmt_funcs[func] if func else '{}'), ret )) + yield (field, ((self.fmt_funcs[func] if func else '{}'), ret)) if not self.header_printed: self.print_header() - fs = ''.join(self.gen_fs(self.fnames,fill=self.avg_stats_skip,add_name=True)).strip() - return ('column_averages', ('Column averages:', (fs, dict(gen())) )) + fs = ''.join(self.gen_fs(self.fnames, fill=self.avg_stats_skip, add_name=True)).strip() + return ('column_averages', ('Column averages:', (fs, dict(gen())))) - def avg_stats_data(self,data,spec_conv,spec_val): + def avg_stats_data(self, data, spec_conv, spec_val): coin = self.rpc.proto.coin return data( hdr = 'Averages for processed blocks:', func = self.sum_field_avg, - spec_sufs = { 'subsidy': f' {coin}', 'totalfee': f' {coin}' }, + spec_sufs = {'subsidy': f' {coin}', 'totalfee': f' {coin}'}, spec_convs = { - 'interval': spec_conv(0, lambda arg: secs_to_ms(arg)), + 'interval': spec_conv(0, lambda arg: secs_to_ms(arg)), 'utxo_inc': spec_conv(-1, '{:<+}'), - 'mb_per_hour': spec_conv(0, '{}'), - }, + 'mb_per_hour': spec_conv(0, '{}')}, spec_vals = ( spec_val( 'mb_per_hour', 'MB/hr', 'interval', lambda values: 'bs' in self.deps, lambda values: ( '{:.4f}'.format((self.total_bytes / 10000) / (self.total_solve_time / 36)) - if self.total_solve_time else 'N/A' ), - ), - ) - ) + if self.total_solve_time else 'N/A')), + )) mini_avg_stats_data = avg_stats_data - def total_stats_data(self,data,spec_conv,spec_val): + def total_stats_data(self, data, spec_conv, spec_val): coin = self.rpc.proto.coin return data( hdr = 'Totals for processed blocks:', func = self.sum_field_total, - spec_sufs = { 'subsidy': f' {coin}', 'totalfee': f' {coin}', 'reward': f' {coin}' }, + spec_sufs = {'subsidy': f' {coin}', 'totalfee': f' {coin}', 'reward': f' {coin}'}, spec_convs = { - 'interval': spec_conv(0, lambda arg: secs_to_dhms(arg)), + 'interval': spec_conv(0, lambda arg: secs_to_dhms(arg)), 'utxo_inc': spec_conv(-1, '{:<+}'), - 'reward': spec_conv(0, self.fmt_funcs['tf']), - }, + 'reward': spec_conv(0, self.fmt_funcs['tf'])}, spec_vals = ( spec_val( 'reward', 'Reward', 'totalfee', - lambda values: {'subsidy','totalfee'} <= set(values), - lambda values: values['subsidy'] + values['totalfee'] - ), - ) - ) + lambda values: {'subsidy', 'totalfee'} <= set(values), + lambda values: values['subsidy'] + values['totalfee']), + )) - async def create_stats(self,sname): + async def create_stats(self, sname): def convert_stats_hdr(field): v = self.fields[field] - return '{} {}'.format(v.hdr1.strip(), v.hdr2.strip()).replace('- ','') if v.hdr1 else v.hdr2.strip() + return '{} {}'.format( + v.hdr1.strip(), v.hdr2.strip()).replace('- ', '') if v.hdr1 else v.hdr2.strip() - d = getattr(self,f'{sname}_stats_data')( - namedtuple('stats_data',['hdr','func','spec_sufs','spec_convs','spec_vals']), - namedtuple('spec_conv',['width_adj','conv']), - namedtuple('spec_val',['name','lbl','insert_after','condition','code']) - ) + d = getattr(self, f'{sname}_stats_data')( + namedtuple('stats_data', ['hdr', 'func', 'spec_sufs', 'spec_convs', 'spec_vals']), + namedtuple('spec_conv', ['width_adj', 'conv']), + namedtuple('spec_val', ['name', 'lbl', 'insert_after', 'condition', 'code'])) fnames = [n for n in self.fnames if n in self.stats_deps[sname]] - lbls = {n:convert_stats_hdr(n) for n in fnames} - values = {n:d.func(n) for n in fnames} - col1_w = max((len(l) for l in lbls.values()),default=0) + 2 + lbls = {n: convert_stats_hdr(n) for n in fnames} + values = {n: d.func(n) for n in fnames} + col1_w = max((len(l) for l in lbls.values()), default=0) + 2 + print(d.spec_vals) for v in d.spec_vals: + print(v) if v.condition(values): try: idx = fnames.index(v.insert_after) + 1 except: idx = 0 - fnames.insert(idx,v.name) + fnames.insert(idx, v.name) lbls[v.name] = v.lbl values[v.name] = v.code(values) def gen(): - for n,fname in enumerate(fnames): + for n, fname in enumerate(fnames): spec_conv = d.spec_convs.get(fname) yield ( '{lbl:{wid}} {{}}{suf}'.format( lbl = lbls[fname] + ':', wid = col1_w + (spec_conv.width_adj if spec_conv else 0), - suf = d.spec_sufs.get(fname) or '' - ), + suf = d.spec_sufs.get(fname) or ''), fname, spec_conv.conv if spec_conv else ( - (lambda x: self.fmt_funcs[x] if x else '{}')(self.fields[fname].fmt_func) - ), - values[fname] - ) + (lambda x: self.fmt_funcs[x] if x else '{}')(self.fields[fname].fmt_func)), + values[fname]) - return ( sname, (d.hdr,) + tuple(gen()) ) + return (sname, (d.hdr,) + tuple(gen())) - def process_stats_pre(self,i): + def process_stats_pre(self, i): if (self.fnames and not self.cfg.stats_only) or i != 0: Msg('') @@ -724,13 +701,12 @@ class BlocksInfo: 'totalfee': 0, 'txs': 1, 'utxo_increase': 1, - 'utxo_size_inc': 117 - } + 'utxo_size_inc': 117} class JSONBlocksInfo(BlocksInfo): - def __init__(self,cfg,cmd_args,rpc): - super().__init__(cfg,cmd_args,rpc) + def __init__(self, cfg, cmd_args, rpc): + super().__init__(cfg, cmd_args, rpc) if self.cfg.json_raw: self.output_block = self.output_block_raw self.fmt_stat_item = self.fmt_stat_item_raw @@ -741,22 +717,22 @@ class JSONBlocksInfo(BlocksInfo): await super().process_blocks() Msg_r(']') - def output_block_raw(self,data,n): - Msg_r( (', ','')[n==0] + json.dumps(data._asdict(),cls=json_encoder) ) + def output_block_raw(self, data, n): + Msg_r((', ', '')[n==0] + json.dumps(data._asdict(), cls=json_encoder)) - def output_block(self,data,n): + def output_block(self, data, n): def gen(): - for k,v in data._asdict().items(): + for k, v in data._asdict().items(): func = self.fields[k].fmt_func - yield ( k, (self.fmt_funcs[func](v) if func else v) ) - Msg_r( (', ','')[n==0] + json.dumps(dict(gen()),cls=json_encoder) ) + yield (k, (self.fmt_funcs[func](v) if func else v)) + Msg_r((', ', '')[n==0] + json.dumps(dict(gen()), cls=json_encoder)) def print_header(self): pass - def fmt_stat_item_raw(self,fs,s): + def fmt_stat_item_raw(self, fs, s): return s - async def output_stats(self,res,sname): + async def output_stats(self, res, sname): def gen(data): for d in data: @@ -771,10 +747,10 @@ class JSONBlocksInfo(BlocksInfo): 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) )) + varname, data = await res + Msg_r(', "{}_data": {}'.format(varname, json.dumps(dict(gen(data)), cls=json_encoder))) - def process_stats_pre(self,i): pass + def process_stats_pre(self, i): pass def finalize_output(self): Msg('}') diff --git a/mmgen_node_tools/PeerBlocks.py b/mmgen_node_tools/PeerBlocks.py index c3d7c68..3329f07 100755 --- a/mmgen_node_tools/PeerBlocks.py +++ b/mmgen_node_tools/PeerBlocks.py @@ -14,66 +14,66 @@ mmgen_node_tools.PeerBlocks: List blocks in flight, disconnect stalling nodes import asyncio from collections import namedtuple -from mmgen.util import msg,msg_r,is_int -from mmgen.term import get_term,get_terminal_size,get_char +from mmgen.util import msg, msg_r, is_int +from mmgen.term import get_term, get_terminal_size, get_char from mmgen.ui import line_input from .PollDisplay import PollDisplay -RED,RESET = ('\033[31m','\033[0m') -COLORS = ['\033[38;5;%s;1m' % c for c in list(range(247,256)) + [231]] -ERASE_ALL,CUR_HOME = ('\033[J','\033[H') -CUR_HIDE,CUR_SHOW = ('\033[?25l','\033[?25h') +RED, RESET = ('\033[31m', '\033[0m') +COLORS = ['\033[38;5;%s;1m' % c for c in list(range(247, 256)) + [231]] +ERASE_ALL, CUR_HOME = ('\033[J', '\033[H') +CUR_HIDE, CUR_SHOW = ('\033[?25l', '\033[?25h') term = None class Display(PollDisplay): poll_secs = 2 - def __init__(self,cfg): + def __init__(self, cfg): super().__init__(cfg) - global term,term_width + global term, term_width if not term: term = get_term() term.init(noecho=True) term_width = self.cfg.columns or get_terminal_size().width msg_r(CUR_HOME+ERASE_ALL+CUR_HOME) - async def get_info(self,rpc): + async def get_info(self, rpc): return await rpc.call('getpeerinfo') - def display(self,count): + def display(self, count): msg_r( CUR_HOME + (ERASE_ALL if count == 1 else '') + 'CONNECTED PEERS ({a}) {b} - poll {c}'.format( a = len(self.info), b = self.desc, - c = count ).ljust(term_width)[:term_width] + c = count).ljust(term_width)[:term_width] + '\n' + ('\n'.join(self.gen_display()) + '\n' if self.info else '') + ERASE_ALL + f"Type a peer number to disconnect, 'q' to quit, or any other key for {self.other_desc} display:" - + '\b' ) + + '\b') - async def disconnect_node(self,rpc,addr): - return await rpc.call('disconnectnode',addr) + async def disconnect_node(self, rpc, addr): + return await rpc.call('disconnectnode', addr) def get_input(self): - s = get_char(immed_chars='q0123456789',prehold_protect=False,num_bytes=1) + s = get_char(immed_chars='q0123456789', prehold_protect=False, num_bytes=1) if not is_int(s): return s with self.info_lock: msg('') term.reset() # readline required for correct operation here; without it, user must re-type first digit - ret = line_input( self.cfg, 'peer number> ', insert_txt=s, hold_protect=False ) + ret = line_input(self.cfg, 'peer number> ', insert_txt=s, hold_protect=False) term.init(noecho=True) self.enable_display = False # prevent display from updating before process_input() return ret - async def process_input(self,rpc): + async def process_input(self, rpc): ids = tuple(str(i['id']) for i in self.info) ret = False @@ -83,7 +83,7 @@ class Display(PollDisplay): from mmgen.exception import RPCFailure addr = self.info[ids.index(self.input)]['addr'] try: - await self.disconnect_node(rpc,addr) + await self.disconnect_node(rpc, addr) except RPCFailure: msg_r(f'Unable to disconnect peer {self.input} ({addr})') else: @@ -105,8 +105,8 @@ class BlocksDisplay(Display): def gen_display(self): - pd = namedtuple('peer_data',['id','blks_data','blks_width']) - bd = namedtuple('block_datum',['num','disp']) + pd = namedtuple('peer_data', ['id', 'blks_data', 'blks_width']) + bd = namedtuple('block_datum', ['num', 'disp']) def gen_block_data(): global min_height @@ -114,15 +114,15 @@ class BlocksDisplay(Display): for d in self.info: if d.get('inflight'): blocks = d['inflight'] - min_height = min(blocks) if not min_height else min(min_height,min(blocks)) - line = ' '.join(map(str,blocks))[:blks_field_width] + min_height = min(blocks) if not min_height else min(min_height, min(blocks)) + line = ' '.join(map(str, blocks))[:blks_field_width] blocks_disp = line.split() yield pd( d['id'], - [bd(blocks[i],blocks_disp[i]) for i in range(len(blocks_disp))], - len(line) ) + [bd(blocks[i], blocks_disp[i]) for i in range(len(blocks_disp))], + len(line)) else: - yield pd(d['id'],[],0) + yield pd(d['id'], [], 0) def gen_line(peer_data): for blk in peer_data.blks_data: @@ -136,7 +136,7 @@ class BlocksDisplay(Display): for peer_data in tuple(gen_block_data()): yield fs.format( peer_data.id, - ' '.join(gen_line(peer_data)) + ' ' * (blks_field_width - peer_data.blks_width) ) + ' '.join(gen_line(peer_data)) + ' ' * (blks_field_width - peer_data.blks_width)) class PeersDisplay(Display): @@ -152,5 +152,4 @@ class PeersDisplay(Display): A = id_width, b = d['addr'], B = addr_width, - c = d['subver'] - ).ljust(term_width)[:term_width] + c = d['subver']).ljust(term_width)[:term_width] diff --git a/mmgen_node_tools/PollDisplay.py b/mmgen_node_tools/PollDisplay.py index 1de8a1a..8274e40 100755 --- a/mmgen_node_tools/PollDisplay.py +++ b/mmgen_node_tools/PollDisplay.py @@ -12,7 +12,7 @@ mmgen_node_tools.PollDisplay: update and display RPC data; get input from user """ -import sys,threading +import sys, threading from mmgen.util import msg from mmgen.term import get_char @@ -22,18 +22,18 @@ class PollDisplay: input = None poll_secs = 1 - def __init__(self,cfg): + def __init__(self, cfg): self.cfg = cfg self.info_lock = threading.Lock() # self.info accessed by 2 threads self.display_kill_flag = threading.Event() def get_input(self): - return get_char(immed_chars='q',prehold_protect=False,num_bytes=1) + return get_char(immed_chars='q', prehold_protect=False, num_bytes=1) - async def process_input(self,rpc): + async def process_input(self, rpc): return True - async def run(self,rpc): + async def run(self, rpc): async def do_display(): with self.info_lock: @@ -68,7 +68,7 @@ class PollDisplay: self.display_kill_flag.set() while True: - threading.Thread(target=get_input,daemon=True).start() + threading.Thread(target=get_input, daemon=True).start() await do_display() if await process_input(): break diff --git a/mmgen_node_tools/Sound.py b/mmgen_node_tools/Sound.py index df9f8ae..557b97a 100755 --- a/mmgen_node_tools/Sound.py +++ b/mmgen_node_tools/Sound.py @@ -19,28 +19,28 @@ mmgen_node_tools.Sound: audio-related functions for MMGen node tools """ -import sys,os,time +import sys, os, time from mmgen.util import die from mmgen_node_tools.Util import do_system _alsa_config_file = '/tmp/alsa-config-' + os.path.basename(sys.argv[0]) -_dvols = { 'Master': 78, 'Speaker': 78, 'Headphone': 15, 'PCM': 190 } +_dvols = {'Master': 78, 'Speaker': 78, 'Headphone': 15, 'PCM': 190} def timespec2secs(ts): import re - mul = { 's': 1, 'm': 60, 'h': 60*60, 'd': 60*60*24 } + mul = {'s': 1, 'm': 60, 'h': 60*60, 'd': 60*60*24} pat = r'^([0-9]+)([smhd]*)$' - m = re.match(pat,ts) + m = re.match(pat, ts) if m is None: die(2,"'%s': invalid time specifier" % ts) - a,b = m.groups() + a, b = m.groups() return int(a) * (mul[b] if b else 1) def parse_repeat_spec(rs): - return [(timespec2secs(i),timespec2secs(j)) - for i,j in [a.split(':') for a in rs.split(',')]] + return [(timespec2secs(i), timespec2secs(j)) + for i, j in [a.split(':') for a in rs.split(',')]] def init_sound(): def _restore_sound(): @@ -51,33 +51,33 @@ def init_sound(): atexit.register(_restore_sound) do_system('sudo alsactl store -f ' + _alsa_config_file) -def play_sound(fn,vol,repeat_spec='',remote_host='',kill_flg=None,testing=False): +def play_sound(fn, vol, repeat_spec='', remote_host='', kill_flg=None, testing=False): if not remote_host: do_system('sudo alsactl store -f ' + _alsa_config_file) - for k in 'Master','Speaker','Headphone': - do_system(('sudo amixer -q set %s on' % k),testing) + for k in 'Master', 'Speaker', 'Headphone': + do_system(('sudo amixer -q set %s on' % k), testing) # do_system('amixer -q set Headphone off') - vols = dict([(k,int(_dvols[k] * float(vol) / 100)) for k in _dvols]) + vols = dict([(k, int(_dvols[k] * float(vol) / 100)) for k in _dvols]) for k in vols: - do_system('sudo amixer -q set %s %s' % (k,vols[k]),testing) + do_system('sudo amixer -q set %s %s' % (k, vols[k]), testing) fn = os.path.expanduser(fn) cmd = ( 'aplay -q %s' % fn, - 'ssh %s mmnode-play-sound -v%d %s' % (remote_host,vol,fn) + 'ssh %s mmnode-play-sound -v%d %s' % (remote_host, vol, fn) )[bool(remote_host)] if repeat_spec and kill_flg: - for interval,duration in parse_repeat_spec(repeat_spec): + for interval, duration in parse_repeat_spec(repeat_spec): start = time.time() while time.time() < start + duration: - do_system(cmd,testing) + do_system(cmd, testing) if kill_flg.wait(interval): if not remote_host: do_system('sudo alsactl restore -f ' + _alsa_config_file) return else: # Play once - do_system(cmd,testing) + do_system(cmd, testing) if not remote_host: do_system('sudo alsactl restore -f ' + _alsa_config_file) diff --git a/mmgen_node_tools/Ticker.py b/mmgen_node_tools/Ticker.py index 95e03f2..ebefcf3 100755 --- a/mmgen_node_tools/Ticker.py +++ b/mmgen_node_tools/Ticker.py @@ -29,10 +29,10 @@ from mmgen.util import msg, msg_r, Msg, Msg_r, die, fmt, fmt_list, fmt_dict, lis from mmgen.ui import do_pager homedir = os.getenv('HOME') -dfl_cachedir = os.path.join(homedir,'.cache','mmgen-node-tools') +dfl_cachedir = os.path.join(homedir, '.cache', 'mmgen-node-tools') cfg_fn = 'ticker-cfg.yaml' portfolio_fn = 'ticker-portfolio.yaml' -asset_tuple = namedtuple('asset_tuple',['symbol','id','source']) +asset_tuple = namedtuple('asset_tuple', ['symbol', 'id', 'source']) last_api_host = None percent_cols = { @@ -50,20 +50,19 @@ class DataSource: }, { 'fi': 'yahoospot', 'hi': 'yahoohist', - } - ] + }] @classmethod - def get_sources(cls,randomize=False): - g = random.sample(cls.source_groups,k=len(cls.source_groups)) if randomize else cls.source_groups - return {k:v for a in g for k,v in a.items()} + def get_sources(cls, randomize=False): + g = random.sample(cls.source_groups, k=len(cls.source_groups)) if randomize else cls.source_groups + return {k: v for a in g for k, v in a.items()} class base: def fetch_delay(self): global last_api_host if not gcfg.testing and last_api_host and last_api_host != self.api_host: - delay = 1 + random.randrange(1,5000) / 1000 + delay = 1 + random.randrange(1, 5000) / 1000 msg_r(f'Waiting {delay:.3f} seconds...') time.sleep(delay) msg('') @@ -72,19 +71,18 @@ class DataSource: def get_data_from_network(self): curl_cmd = list_gen( - ['curl', '--tr-encoding', '--header', 'Accept: application/json',True], + ['curl', '--tr-encoding', '--header', 'Accept: application/json', True], ['--compressed'], # adds 'Accept-Encoding: gzip' - ['--proxy', cfg.proxy, isinstance(cfg.proxy,str)], + ['--proxy', cfg.proxy, isinstance(cfg.proxy, str)], ['--silent', not cfg.verbose], - [self.api_url] - ) + [self.api_url]) if gcfg.testing: - Msg(fmt_list(curl_cmd,fmt='bare')) + Msg(fmt_list(curl_cmd, fmt='bare')) return try: - return run(curl_cmd,check=True,stdout=PIPE).stdout.decode() + return run(curl_cmd, check=True, stdout=PIPE).stdout.decode() except CalledProcessError as e: msg('') from .Misc import curl_exit_codes @@ -101,7 +99,7 @@ class DataSource: os.makedirs(cfg.cachedir) if not os.path.exists(self.json_fn): - open(self.json_fn,'w').write('{}') + open(self.json_fn, 'w').write('{}') use_cached_data = cfg.cached_data and not gcfg.download @@ -123,7 +121,7 @@ class DataSource: if gcfg.testing: return {} else: - die(1,self.rate_limit_errmsg(elapsed)) + die(1, self.rate_limit_errmsg(elapsed)) match data_type: case 'json': @@ -131,7 +129,7 @@ class DataSource: data = json.loads(data_in) except: self.json_data_error_msg(data_in) - die(2,'Retrieved data is not valid JSON, exiting') + die(2, 'Retrieved data is not valid JSON, exiting') json_text = data_in case 'python': data = data_in @@ -143,9 +141,9 @@ class DataSource: f'No cached {self.data_desc}! Run command without the --cached-data option, ' 'or use --download to retrieve data from remote host') else: - die(2,'Remote host returned no data!') + die(2, 'Remote host returned no data!') elif 'error' in data: - die(1,data['error']) + die(1, data['error']) if use_cached_data: if not cfg.quiet: @@ -162,15 +160,15 @@ class DataSource: return self.postprocess_data(data) - def json_data_error_msg(self,json_text): + def json_data_error_msg(self, json_text): pass - def postprocess_data(self,data): + def postprocess_data(self, data): return data @property def json_fn_rel(self): - return os.path.relpath(self.json_fn,start=homedir) + return os.path.relpath(self.json_fn, start=homedir) class coinpaprika(base): desc = 'CoinPaprika' @@ -185,30 +183,29 @@ class DataSource: def __init__(self): self.asset_limit = int(cfg.asset_limit or self.dfl_asset_limit) - def rate_limit_errmsg(self,elapsed): + def rate_limit_errmsg(self, elapsed): return ( f'Rate limit exceeded! Retry in {self.timeout-elapsed} seconds' + - ('' if cfg.btc_only else ', or use --cached-data or --btc') - ) + ('' if cfg.btc_only else ', or use --cached-data or --btc')) @property def api_url(self): return ( f'https://{self.api_host}/v1/tickers/btc-bitcoin' if cfg.btc_only else f'https://{self.api_host}/v1/tickers?limit={self.asset_limit}' if self.asset_limit else - f'https://{self.api_host}/v1/tickers' ) + f'https://{self.api_host}/v1/tickers') @property def json_fn(self): return os.path.join( cfg.cachedir, - 'ticker-btc.json' if cfg.btc_only else 'ticker.json' ) + 'ticker-btc.json' if cfg.btc_only else 'ticker.json') @property def timeout(self): return 0 if gcfg.test_suite else self.btc_ratelimit if cfg.btc_only else self.ratelimit - def json_data_error_msg(self,json_text): + def json_data_error_msg(self, json_text): tor_captcha_msg = f""" If you’re using Tor, the API request may have failed due to Captcha protection. A workaround for this issue is to retrieve the JSON data with a browser from @@ -223,20 +220,20 @@ class DataSource: Then invoke the program with --cached-data and without --btc """ msg(json_text[:1024] + '...') - msg(orange(fmt(tor_captcha_msg,strip_char='\t'))) + msg(orange(fmt(tor_captcha_msg, strip_char='\t'))) - def postprocess_data(self,data): + def postprocess_data(self, data): return [data] if cfg.btc_only else data @staticmethod - def parse_asset_id(s,require_label): - sym,label = (*s.split('-',1),None)[:2] + def parse_asset_id(s, require_label): + sym, label = (*s.split('-', 1), None)[:2] if require_label and not label: - die(1,f'{s!r}: asset label is missing') + die(1, f'{s!r}: asset label is missing') return asset_tuple( symbol = sym.upper(), id = (s.lower() if label else None), - source = 'cc' ) + source = 'cc') class yahoospot(base): @@ -250,12 +247,12 @@ class DataSource: json_fn_basename = 'ticker-finance.json' @staticmethod - def get_id(sym,data): + def get_id(sym, data): return sym.lower() @staticmethod - def conv_data(sym,data,btcusd): - price_usd = Decimal( data['regularMarketPrice']['raw'] ) + def conv_data(sym, data, btcusd): + price_usd = Decimal(data['regularMarketPrice']['raw']) return { 'id': sym, 'name': data['shortName'], @@ -266,15 +263,14 @@ class DataSource: 'percent_change_30d': data['pct_chg_4wks'], 'percent_change_7d': data['pct_chg_1wk'], 'percent_change_24h': data['regularMarketChangePercent']['raw'] * 100, - 'last_updated': data['regularMarketTime'], - } + 'last_updated': data['regularMarketTime']} - def rate_limit_errmsg(self,elapsed): + def rate_limit_errmsg(self, elapsed): return f'Rate limit exceeded! Retry in {self.timeout-elapsed} seconds, or use --cached-data' @property def json_fn(self): - return os.path.join( cfg.cachedir, self.json_fn_basename ) + return os.path.join(cfg.cachedir, self.json_fn_basename) @property def timeout(self): @@ -282,37 +278,36 @@ class DataSource: @property def symbols(self): - return [r.symbol for r in cfg.rows if isinstance(r,tuple) and r.source == 'fi'] + return [r.symbol for r in cfg.rows if isinstance(r, tuple) and r.source == 'fi'] def get_data_from_network(self): kwargs = { 'formatted': True, 'asynchronous': True, - 'proxies': { 'https': cfg.proxy2 }, - } + 'proxies': {'https': cfg.proxy2}} if gcfg.test_suite: - kwargs.update({ 'timeout': 1, 'retry': 0 }) + kwargs.update({'timeout': 1, 'retry': 0}) if gcfg.testing: Msg('\nyahooquery.Ticker(\n {},\n {}\n)'.format( self.symbols, - fmt_dict(kwargs,fmt='kwargs') )) + fmt_dict(kwargs, fmt='kwargs'))) return from yahooquery import Ticker - return self.process_network_data( Ticker(self.symbols,**kwargs) ) + return self.process_network_data(Ticker(self.symbols,**kwargs)) - def process_network_data(self,ticker): + def process_network_data(self, ticker): return ticker.price @staticmethod - def parse_asset_id(s,require_label): + def parse_asset_id(s, require_label): return asset_tuple( symbol = s.upper(), id = s.lower(), - source = 'fi' ) + source = 'fi') class yahoohist(yahoospot): @@ -322,17 +317,17 @@ class DataSource: period = '1y' interval = '1wk' - def process_network_data(self,ticker): + def process_network_data(self, ticker): return ticker.history( period = self.period, interval = self.interval).to_json(orient='index') - def postprocess_data(self,data): + def postprocess_data(self, data): def gen(): keys = set() d = {} for key, val in data.items(): - if m := re.match(r"\('(.*?)', datetime\.date\((.*)\)\)$",key): + if m := re.match(r"\('(.*?)', datetime\.date\((.*)\)\)$", key): date = '{}-{:>02}-{:>02}'.format(*m[2].split(', ')) if (sym := m[1]) in keys: d[date] = val @@ -343,12 +338,12 @@ class DataSource: return dict(gen()) def assets_list_gen(cfg_in): - for k,v in cfg_in.cfg['assets'].items(): + for k, v in cfg_in.cfg['assets'].items(): yield '' yield k.upper() for e in v: - out = e.split('-',1) - yield ' {:5s} {}'.format(out[0],out[1] if len(out) == 2 else '') + out = e.split('-', 1) + yield ' {:5s} {}'.format(out[0], out[1] if len(out) == 2 else '') def gen_data(data): """ @@ -367,10 +362,9 @@ def gen_data(data): f'The symbol {dup_sym!r} is shared by the following assets:\n' + '\n ' + '\n '.join(d['id'] for d in data['cc'] if d['symbol'] == dup_sym) + '\n\nPlease specify the asset by one of the full IDs listed above\n' + - f'instead of {dup_sym!r}' - ) + f'instead of {dup_sym!r}') - def check_assets_found(wants,found,keys=['symbol','id']): + def check_assets_found(wants, found, keys=['symbol', 'id']): error = False for k in keys: missing = wants[k] - found[k] @@ -378,36 +372,32 @@ def gen_data(data): msg( ('The following IDs were not found in source data:\n{}' if k == 'id' else 'The following symbols could not be resolved:\n{}').format( - fmt_list(missing,fmt='col',indent=' ') - )) + fmt_list(missing, fmt='col', indent=' '))) error = True if error: - die(1,'Missing data, exiting') + die(1, 'Missing data, exiting') rows_want = { - 'id': {r.id for r in cfg.rows if isinstance(r,tuple) and r.id} - {'usd-us-dollar'}, - 'symbol': {r.symbol for r in cfg.rows if isinstance(r,tuple) and r.id is None} - {'USD'}, - } + 'id': {r.id for r in cfg.rows if isinstance(r, tuple) and r.id} - {'usd-us-dollar'}, + 'symbol': {r.symbol for r in cfg.rows if isinstance(r, tuple) and r.id is None} - {'USD'}} usr_rate_assets = tuple(u.rate_asset for u in cfg.usr_rows + cfg.usr_columns if u.rate_asset) usr_rate_assets_want = { 'id': {a.id for a in usr_rate_assets if a.id}, - 'symbol': {a.symbol for a in usr_rate_assets if not a.id} - } + 'symbol': {a.symbol for a in usr_rate_assets if not a.id}} usr_assets = cfg.usr_rows + cfg.usr_columns + tuple(c for c in (cfg.query or ()) if c) usr_wants = { 'id': ( {a.id for a in usr_assets + usr_rate_assets if a.id} - - {a.id for a in usr_assets if a.rate and a.id} - {'usd-us-dollar'} ) + {a.id for a in usr_assets if a.rate and a.id} - {'usd-us-dollar'}) , 'symbol': ( {a.symbol for a in usr_assets + usr_rate_assets if not a.id} - - {a.symbol for a in usr_assets if a.rate} - {'USD'} ), - } + {a.symbol for a in usr_assets if a.rate} - {'USD'})} - found = { 'id': set(), 'symbol': set() } + found = {'id': set(), 'symbol': set()} rate_assets = {} - wants = {k:rows_want[k] | usr_wants[k] for k in ('id','symbol')} + wants = {k: rows_want[k] | usr_wants[k] for k in ('id', 'symbol')} for d in data['cc']: if d['id'] == 'btc-bitcoin': @@ -417,38 +407,37 @@ def gen_data(data): get_id = src_cls['fi'].get_id conv_func = src_cls['fi'].conv_data - for k,v in data['fi'].items(): - id = get_id(k,v) + for k, v in data['fi'].items(): + id = get_id(k, v) if wants['id']: if id in wants['id']: - if not isinstance(v,dict): + if not isinstance(v, dict): die(2, str(v)) if id in found['id']: - die(1,dup_sym_errmsg(id)) + die(1, dup_sym_errmsg(id)) if m := data['hi'].get(k): spot = v['regularMarketPrice']['raw'] hist = tuple(m.values()) v['pct_chg_1wk'], v['pct_chg_4wks'], v['pct_chg_1y'] = ( (spot / hist[-2]['close'] - 1) * 100, (spot / hist[-5]['close'] - 1) * 100, # 4 weeks ≈ 1 month - (spot / hist[0]['close'] - 1) * 100, - ) + (spot / hist[0]['close'] - 1) * 100) else: v['pct_chg_1wk'] = v['pct_chg_4wks'] = v['pct_chg_1y'] = None - yield ( id, conv_func(id,v,btcusd) ) + yield (id, conv_func(id, v, btcusd)) found['id'].add(id) wants['id'].remove(id) if id in usr_rate_assets_want['id']: - rate_assets[k] = conv_func(id,v,btcusd) # NB: using symbol instead of ID for key + rate_assets[k] = conv_func(id, v, btcusd) # NB: using symbol instead of ID for key else: break - for k in ('id','symbol'): + for k in ('id', 'symbol'): for d in data['cc']: if wants[k]: if d[k] in wants[k]: if d[k] in found[k]: - die(1,dup_sym_errmsg(d[k])) + die(1, dup_sym_errmsg(d[k])) if not 'price_usd' in d: d['price_usd'] = Decimal(str(d['quotes']['USD']['price'])) d['price_btc'] = Decimal(str(d['quotes']['USD']['price'])) / btcusd @@ -457,8 +446,9 @@ def gen_data(data): d['percent_change_30d'] = d['quotes']['USD']['percent_change_30d'] d['percent_change_1y'] = d['quotes']['USD']['percent_change_1y'] # .replace('Z','+00:00') -- Python 3.9 backport - d['last_updated'] = int(datetime.datetime.fromisoformat(d['last_updated'].replace('Z','+00:00')).timestamp()) - yield (d['id'],d) + d['last_updated'] = int(datetime.datetime.fromisoformat( + d['last_updated'].replace('Z', '+00:00')).timestamp()) + yield (d['id'], d) found[k].add(d[k]) wants[k].remove(d[k]) if d[k] in usr_rate_assets_want[k]: @@ -466,7 +456,7 @@ def gen_data(data): else: break - check_assets_found(usr_wants,found) + check_assets_found(usr_wants, found) for asset in (cfg.usr_rows + cfg.usr_columns): if asset.rate: @@ -475,14 +465,13 @@ def gen_data(data): """ _id = asset.id or f'{asset.symbol}-user-asset-{asset.symbol}'.lower() ra_rate = rate_assets[asset.rate_asset.symbol]['price_usd'] if asset.rate_asset else 1 - yield ( _id, { + yield (_id, { 'symbol': asset.symbol, 'id': _id, 'name': ' '.join(_id.split('-')[1:]), 'price_usd': ra_rate / asset.rate, 'price_btc': ra_rate / asset.rate / btcusd, - 'last_updated': None, - }) + 'last_updated': None}) yield ('usd-us-dollar', { 'symbol': 'USD', @@ -490,21 +479,20 @@ def gen_data(data): 'name': 'US Dollar', 'price_usd': Decimal(1), 'price_btc': Decimal(1) / btcusd, - 'last_updated': None, - }) + 'last_updated': None}) def main(): def update_sample_file(usr_cfg_file): - usr_data = files('mmgen_node_tools').joinpath('data',os.path.basename(usr_cfg_file)).read_text() + usr_data = files('mmgen_node_tools').joinpath('data', os.path.basename(usr_cfg_file)).read_text() sample_file = usr_cfg_file + '.sample' sample_data = open(sample_file).read() if os.path.exists(sample_file) else None if usr_data != sample_data: - os.makedirs(os.path.dirname(sample_file),exist_ok=True) + os.makedirs(os.path.dirname(sample_file), exist_ok=True) msg('{} {}'.format( - ('Updating','Creating')[sample_data is None], - sample_file )) - open(sample_file,'w').write(usr_data) + ('Updating', 'Creating')[sample_data is None], + sample_file)) + open(sample_file, 'w').write(usr_data) try: from importlib.resources import files # Python 3.9 @@ -515,19 +503,19 @@ def main(): update_sample_file(cfg_in.portfolio_file) if gcfg.portfolio and not cfg_in.portfolio: - die(1,'No portfolio configured!\nTo configure a portfolio, edit the file ~/{}'.format( - os.path.relpath(cfg_in.portfolio_file,start=homedir))) + die(1, 'No portfolio configured!\nTo configure a portfolio, edit the file ~/{}'.format( + os.path.relpath(cfg_in.portfolio_file, start=homedir))) if gcfg.list_ids: src_ids = ['cc'] elif gcfg.download: if not gcfg.download in DataSource.get_sources(): - die(1,f'{gcfg.download!r}: invalid data source') + die(1, f'{gcfg.download!r}: invalid data source') src_ids = [gcfg.download] else: src_ids = DataSource.get_sources(randomize=True) - src_data = { k: src_cls[k]().get_data() for k in src_ids } + src_data = {k: src_cls[k]().get_data() for k in src_ids} if gcfg.testing: return @@ -542,24 +530,24 @@ def main(): data = dict(gen_data(src_data)) (do_pager if cfg.pager else Msg_r)( - '\n'.join(getattr(Ticker,cfg.clsname)(data).gen_output()) + '\n') + '\n'.join(getattr(Ticker, cfg.clsname)(data).gen_output()) + '\n') def make_cfg(gcfg_arg): - query_tuple = namedtuple('query',['asset','to_asset']) - asset_data = namedtuple('asset_data',['symbol','id','amount','rate','rate_asset','source']) + query_tuple = namedtuple('query', ['asset', 'to_asset']) + asset_data = namedtuple('asset_data', ['symbol', 'id', 'amount', 'rate', 'rate_asset', 'source']) - def parse_asset_id(s,require_label=False): - return src_cls['fi' if re.match(fi_pat,s) else 'cc'].parse_asset_id(s,require_label) + def parse_asset_id(s, require_label=False): + return src_cls['fi' if re.match(fi_pat, s) else 'cc'].parse_asset_id(s, require_label) def get_rows_from_cfg(add_data=None): def gen(): - for n,(k,v) in enumerate(cfg_in.cfg['assets'].items()): + for n, (k, v) in enumerate(cfg_in.cfg['assets'].items()): yield k if add_data and k in add_data: v += tuple(add_data[k]) for e in v: - yield parse_asset_id(e,require_label=True) + yield parse_asset_id(e, require_label=True) return tuple(gen()) def parse_percent_cols(arg): @@ -568,17 +556,19 @@ def make_cfg(gcfg_arg): res = arg.lower().split(',') for s in res: if s not in percent_cols: - die(1,f'{arg!r}: invalid --percent-cols parameter (valid letters: {fmt_list(percent_cols)})') + die(1, '{!r}: invalid --percent-cols parameter (valid letters: {})'.format( + arg, + fmt_list(percent_cols))) return res - def parse_usr_asset_arg(key,use_cf_file=False): + def parse_usr_asset_arg(key, use_cf_file=False): """ asset_id[:rate[:rate_asset]] """ def parse_parm(s): ss = s.split(':') - assert len(ss) in (1,2,3), f'{s}: malformed argument' - asset_id,rate,rate_asset = (*ss,None,None)[:3] + assert len(ss) in (1, 2, 3), f'{s}: malformed argument' + asset_id, rate, rate_asset = (*ss, None, None)[:3] parsed_id = parse_asset_id(asset_id) return asset_data( @@ -588,19 +578,19 @@ def make_cfg(gcfg_arg): rate = ( None if rate is None else 1 / Decimal(rate[:-1]) if rate.lower().endswith('r') else - Decimal(rate) ), + Decimal(rate)), rate_asset = parse_asset_id(rate_asset) if rate_asset else None, - source = parsed_id.source ) + source = parsed_id.source) - cl_opt = getattr(gcfg,key) + cl_opt = getattr(gcfg, key) cf_opt = cfg_in.cfg.get(key,[]) if use_cf_file else [] - return tuple( parse_parm(s) for s in (cl_opt.split(',') if cl_opt else cf_opt) ) + return tuple(parse_parm(s) for s in (cl_opt.split(',') if cl_opt else cf_opt)) def parse_query_arg(s): """ asset_id:amount[:to_asset_id[:to_amount]] """ - def parse_query_asset(asset_id,amount): + def parse_query_asset(asset_id, amount): parsed_id = parse_asset_id(asset_id) return asset_data( symbol = parsed_id.symbol, @@ -608,21 +598,20 @@ def make_cfg(gcfg_arg): amount = None if amount is None else Decimal(amount), rate = None, rate_asset = None, - source = parsed_id.source ) + source = parsed_id.source) ss = s.split(':') - assert len(ss) in (2,3,4), f'{s}: malformed argument' - asset_id,amount,to_asset_id,to_amount = (*ss,None,None)[:4] + assert len(ss) in (2, 3, 4), f'{s}: malformed argument' + asset_id, amount, to_asset_id, to_amount = (*ss, None, None)[:4] return query_tuple( - asset = parse_query_asset(asset_id,amount), - to_asset = parse_query_asset(to_asset_id,to_amount) if to_asset_id else None - ) + asset = parse_query_asset(asset_id, amount), + to_asset = parse_query_asset(to_asset_id, to_amount) if to_asset_id else None) - def gen_uniq(obj_list,key,preload=None): - found = set([getattr(obj,key) for obj in preload if hasattr(obj,key)] if preload else ()) + def gen_uniq(obj_list, key, preload=None): + found = set([getattr(obj, key) for obj in preload if hasattr(obj, key)] if preload else ()) for obj in obj_list: - id = getattr(obj,key) + id = getattr(obj, key) if id not in found: yield obj found.add(id) @@ -632,39 +621,38 @@ def make_cfg(gcfg_arg): 'user_added', usr_rows + (tuple(asset for asset in query if asset) if query else ()) + - usr_columns ) + usr_columns) def get_portfolio_assets(ret=()): if cfg_in.portfolio and gcfg.portfolio: - ret = (parse_asset_id(e,require_label=True) for e in cfg_in.portfolio) - return ( 'portfolio', tuple(e for e in ret if (not gcfg.btc) or e.symbol == 'BTC') ) + ret = (parse_asset_id(e, require_label=True) for e in cfg_in.portfolio) + return ('portfolio', tuple(e for e in ret if (not gcfg.btc) or e.symbol == 'BTC')) def get_portfolio(): - return {k:Decimal(v) for k,v in cfg_in.portfolio.items() if (not gcfg.btc) or k == 'btc-bitcoin'} + return {k: Decimal(v) for k, v in cfg_in.portfolio.items() + if (not gcfg.btc) or k == 'btc-bitcoin'} def parse_add_precision(arg): if not arg: return 0 s = str(arg) if not (s.isdigit() and s.isascii()): - die(1,f'{s}: invalid parameter for --add-precision (not an integer)') + die(1, f'{s}: invalid parameter for --add-precision (not an integer)') if int(s) > 30: - die(1,f'{s}: invalid parameter for --add-precision (value >30)') + die(1, f'{s}: invalid parameter for --add-precision (value >30)') return int(s) def create_rows(): rows = ( ('trade_pair',) + query if (query and query.to_asset) else - ('bitcoin',parse_asset_id('btc-bitcoin')) if gcfg.btc else - get_rows_from_cfg( add_data={'fiat':['usd-us-dollar']} if gcfg.add_columns else None ) - ) + ('bitcoin', parse_asset_id('btc-bitcoin')) if gcfg.btc else + get_rows_from_cfg(add_data={'fiat':['usd-us-dollar']} if gcfg.add_columns else None)) - for hdr,data in ( - (get_usr_assets(),) if query else - (get_usr_assets(), get_portfolio_assets()) - ): + for hdr, data in ( + (get_usr_assets(),) if query else + (get_usr_assets(), get_portfolio_assets())): if data: - uniq_data = tuple(gen_uniq(data,'symbol',preload=rows)) + uniq_data = tuple(gen_uniq(data, 'symbol', preload=rows)) if uniq_data: rows += (hdr,) + uniq_data return rows @@ -693,26 +681,25 @@ def make_cfg(gcfg_arg): 'quiet', 'verbose']) - global gcfg,cfg_in,src_cls,cfg + global gcfg, cfg_in, src_cls, cfg gcfg = gcfg_arg - src_cls = { k: getattr(DataSource,v) for k,v in DataSource.get_sources().items() } + src_cls = {k: getattr(DataSource, v) for k, v in DataSource.get_sources().items()} fi_pat = src_cls['fi'].asset_id_pat cmd_args = gcfg._args cfg_in = get_cfg_in() usr_rows = parse_usr_asset_arg('add_rows') - usr_columns = parse_usr_asset_arg('add_columns',use_cf_file=True) + usr_columns = parse_usr_asset_arg('add_columns', use_cf_file=True) query = parse_query_arg(cmd_args[0]) if cmd_args else None def get_proxy(name): - proxy = getattr(gcfg,name) + proxy = getattr(gcfg, name) return ( '' if proxy == '' else 'none' if (proxy and proxy.lower() == 'none') - else (proxy or cfg_in.cfg.get(name)) - ) + else (proxy or cfg_in.cfg.get(name))) proxy = get_proxy('proxy') proxy = None if proxy == 'none' else proxy @@ -723,7 +710,7 @@ def make_cfg(gcfg_arg): usr_rows = usr_rows, usr_columns = usr_columns, query = query, - adjust = ( lambda x: (100 + x) / 100 if x else 1 )( Decimal(gcfg.adjust or 0) ), + adjust = (lambda x: (100 + x) / 100 if x else 1)(Decimal(gcfg.adjust or 0)), clsname = 'trading' if query else 'overview', btc_only = gcfg.btc or cfg_in.cfg.get('btc'), add_prec = parse_add_precision(gcfg.add_precision or cfg_in.cfg.get('add_precision')), @@ -745,17 +732,16 @@ def make_cfg(gcfg_arg): thousands_comma = gcfg.thousands_comma or cfg_in.cfg.get('thousands_comma'), update_time = gcfg.update_time or cfg_in.cfg.get('update_time'), quiet = gcfg.quiet or cfg_in.cfg.get('quiet'), - verbose = gcfg.verbose or cfg_in.cfg.get('verbose'), - ) + verbose = gcfg.verbose or cfg_in.cfg.get('verbose')) def get_cfg_in(): - ret = namedtuple('cfg_in_data',['cfg','portfolio','cfg_file','portfolio_file']) - cfg_file,portfolio_file = ( - [os.path.join(gcfg.data_dir_root,'node_tools',fn) for fn in (cfg_fn,portfolio_fn)] - ) - cfg_data,portfolio_data = ( - [yaml.safe_load(open(fn).read()) if os.path.exists(fn) else None for fn in (cfg_file,portfolio_file)] - ) + ret = namedtuple('cfg_in_data', ['cfg', 'portfolio', 'cfg_file', 'portfolio_file']) + cfg_file, portfolio_file = ( + [os.path.join(gcfg.data_dir_root, 'node_tools', fn) + for fn in (cfg_fn, portfolio_fn)]) + cfg_data, portfolio_data = ( + [yaml.safe_load(open(fn).read()) if os.path.exists(fn) else None + for fn in (cfg_file, portfolio_file)]) return ret( cfg = cfg_data or { 'assets': { @@ -765,14 +751,11 @@ def get_cfg_in(): # Pound Sterling, Euro, Swiss Franc 'fiat': [ 'gbpusd=x', 'eurusd=x', 'chfusd=x' ], # Dow Jones Industrials, Nasdaq 100, S&P 500 - 'index': [ '^dji', '^ixic', '^gspc' ], - }, - 'proxy': 'http://vpn-gw:8118' - }, - portfolio = portfolio_data, - cfg_file = cfg_file, - portfolio_file = portfolio_file, - ) + 'index': [ '^dji', '^ixic', '^gspc' ]}, + 'proxy': 'http://vpn-gw:8118'}, + portfolio = portfolio_data, + cfg_file = cfg_file, + portfolio_file = portfolio_file) class Ticker: @@ -781,43 +764,43 @@ class Ticker: offer = None to_asset = None - def __init__(self,data): + def __init__(self, data): self.comma = ',' if cfg.thousands_comma else '' - self.col1_wid = max(len('TOTAL'),( + self.col1_wid = max(len('TOTAL'), ( max(len(self.create_label(d['id'])) for d in data.values()) if cfg.name_labels else - max(len(d['symbol']) for d in data.values()) - )) + 1 + max(len(d['symbol']) for d in data.values()))) + 1 - self.rows = [row._replace(id=self.get_id(row)) if isinstance(row,tuple) else row for row in cfg.rows] - self.col_usd_prices = {k:self.data[k]['price_usd'] for k in self.col_ids} + self.rows = [row._replace(id=self.get_id(row)) if isinstance(row, tuple) else row + for row in cfg.rows] + self.col_usd_prices = {k: self.data[k]['price_usd'] for k in self.col_ids} - self.prices = {row.id:self.get_row_prices(row.id) - for row in self.rows if isinstance(row,tuple) and row.id in data} + self.prices = {row.id: self.get_row_prices(row.id) + for row in self.rows if isinstance(row, tuple) and row.id in data} self.prices['usd-us-dollar'] = self.get_row_prices('usd-us-dollar') - def format_last_update_col(self,cross_assets=()): + def format_last_update_col(self, cross_assets=()): if cfg.elapsed: from mmgen.util2 import format_elapsed_hr fmt_func = format_elapsed_hr else: - fmt_func = lambda t,now: time.strftime('%F %X', time.gmtime(t)) + fmt_func = lambda t, now: time.strftime('%F %X', time.gmtime(t)) d = self.data max_w = 0 if cross_assets: last_updated_x = [d[a.id]['last_updated'] for a in cross_assets] - min_t = min( (int(n) for n in last_updated_x if isinstance(n,int) ), default=None ) + min_t = min((int(n) for n in last_updated_x if isinstance(n, int)), default=None) else: min_t = None for row in self.rows: - if isinstance(row,tuple): + if isinstance(row, tuple): try: - t = int( d[row.id]['last_updated'] ) + t = int(d[row.id]['last_updated']) except TypeError as e: d[row.id]['last_updated_fmt'] = gray('--' if 'NoneType' in str(e) else str(e)) except KeyError as e: @@ -825,7 +808,7 @@ class Ticker: pass else: t_fmt = d[row.id]['last_updated_fmt'] = fmt_func( - (min(t,min_t) if min_t else t), + (min(t, min_t) if min_t else t), now = now) max_w = max(len(t_fmt), max_w) @@ -836,7 +819,7 @@ class Ticker: self.uprec = {k: max(0, v+4) + cfg.add_prec for k, v in exp} self.uwid = {k: 12 + max(0, abs(v)-6) + cfg.add_prec for k, v in exp} - def get_id(self,asset): + def get_id(self, asset): if asset.id: return asset.id else: @@ -844,11 +827,11 @@ class Ticker: if d['symbol'] == asset.symbol: return d['id'] - def create_label(self,id): + def create_label(self, id): return self.data[id]['name'].upper() def gen_output(self): - yield 'Current time: {} UTC'.format(time.strftime('%F %X',time.gmtime(now))) + yield 'Current time: {} UTC'.format(time.strftime('%F %X', time.gmtime(now))) for asset in self.usr_col_assets: if asset.symbol != 'USD': @@ -858,17 +841,16 @@ class Ticker: self.create_label(asset.id), usdprice, self.comma, - max(2, 4-usdprice.adjusted()) ) + max(2, 4-usdprice.adjusted())) - if hasattr(self,'subhdr'): + if hasattr(self, 'subhdr'): yield self.subhdr if self.show_adj: yield ( ('Offered price differs from spot' if self.offer else 'Adjusting prices') + ' by ' - + yellow('{:+.2f}%'.format( (self.adjust-1) * 100 )) - ) + + yellow('{:+.2f}%'.format((self.adjust-1) * 100))) yield '' @@ -879,7 +861,7 @@ class Ticker: yield self.table_hdr for row in self.rows: - if isinstance(row,str): + if isinstance(row, str): yield ('-' * self.hl_wid) else: try: @@ -896,21 +878,20 @@ class Ticker: yield blue('PORTFOLIO') yield self.table_hdr yield '-' * self.hl_wid - for sym,amt in cfg.portfolio.items(): + for sym, amt in cfg.portfolio.items(): try: - yield self.fmt_row(self.data[sym],amt=amt) + yield self.fmt_row(self.data[sym], amt=amt) except KeyError: yield gray(f'(no data for {sym})') yield '-' * self.hl_wid if not cfg.btc_only: yield self.fs_num.format( lbl = 'TOTAL', pc3='', pc4='', pc1='', pc2='', upd='', amt='', - **{ k.replace('-','_'): v for k,v in self.prices['total'].items() } - ) + **{k.replace('-', '_'): v for k, v in self.prices['total'].items()}) class overview(base): - def __init__(self,data): + def __init__(self, data): self.data = data self.adjust = cfg.adjust self.show_adj = self.adjust != 1 @@ -922,25 +903,26 @@ class Ticker: self.format_last_update_col() if cfg.portfolio: - self.prices['total'] = { col_id: sum(self.prices[row.id][col_id] * cfg.portfolio[row.id] - for row in self.rows if isinstance(row,tuple) and row.id in cfg.portfolio and row.id in data) - for col_id in self.col_ids } + self.prices['total'] = {col_id: sum(self.prices[row.id][col_id] * cfg.portfolio[row.id] + for row in self.rows + if isinstance(row, tuple) and row.id in cfg.portfolio and row.id in data) + for col_id in self.col_ids} self.init_prec() self.init_fs() - def get_row_prices(self,id): + def get_row_prices(self, id): if id in self.data: d = self.data[id] - return { k: ( + return {k: ( d['price_btc'] if k == 'btc-bitcoin' else d['price_usd'] / self.col_usd_prices[k] - ) * self.adjust for k in self.col_ids } + ) * self.adjust for k in self.col_ids} - def fmt_row(self,d,amt=None,amt_fmt=None): + def fmt_row(self, d, amt=None, amt_fmt=None): def fmt_pct(n): - return gray(' --') if n is 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']] @@ -957,53 +939,45 @@ class Ticker: pc4 = fmt_pct(d.get('percent_change_30d')), upd = d.get('last_updated_fmt'), amt = amt_fmt, - **{ k.replace('-','_'): v * (1 if amt is None else amt) for k,v in p.items() } - ) + **{k.replace('-', '_'): v * (1 if amt is None else amt) for k, v in p.items()}) def init_fs(self): - col_prec = {'usd-us-dollar':2+cfg.add_prec,'btc-bitcoin':8+cfg.add_prec } # | self.uprec # Python 3.9 - col_prec.update(self.uprec) - col_wid = {'usd-us-dollar':8+cfg.add_prec,'btc-bitcoin':12+cfg.add_prec } # """ - col_wid.update(self.uwid) + col_prec = {'usd-us-dollar': 2+cfg.add_prec, 'btc-bitcoin': 8+cfg.add_prec} | self.uprec max_row = max( - ( (k,v['btc-bitcoin']) for k,v in self.prices.items() ), - key = lambda a: a[1] - ) - widths = { k: len('{:{}.{}f}'.format( self.prices[max_row[0]][k], self.comma, col_prec[k] )) - for k in self.col_ids } + ((k, v['btc-bitcoin']) for k, v in self.prices.items()), + key = lambda a: a[1]) + widths = {k: len('{:{}.{}f}'.format(self.prices[max_row[0]][k], self.comma, col_prec[k])) + for k in self.col_ids} - fd = namedtuple('format_str_data',['fs_str','fs_num','wid']) + fd = namedtuple('format_str_data', ['fs_str', 'fs_num', 'wid']) col_fs_data = { - 'label': fd(f'{{lbl:{self.col1_wid}}}',f'{{lbl:{self.col1_wid}}}',self.col1_wid), + 'label': fd(f'{{lbl:{self.col1_wid}}}', f'{{lbl:{self.col1_wid}}}', self.col1_wid), 'pct1y': fd(' {pc3:7}', ' {pc3:7}', 8), 'pct1m': fd(' {pc4:7}', ' {pc4:7}', 8), 'pct1w': fd(' {pc1:7}', ' {pc1:7}', 8), 'pct1d': fd(' {pc2:7}', ' {pc2:7}', 8), - 'update_time': fd(' {upd}', ' {upd}', max((19 if cfg.portfolio else 0),self.upd_w) + 2), - 'amt': fd(' {amt}', ' {amt}', 21), - } -# } | { k: fd( # Python 3.9 - col_fs_data.update({ k: fd( - ' {{{}:>{}}}'.format( k.replace('-','_'), widths[k] ), - ' {{{}:{}{}.{}f}}'.format( k.replace('-','_'), widths[k], self.comma, col_prec[k] ), - widths[k]+2 - ) for k in self.col_ids - }) + 'update_time': fd(' {upd}', ' {upd}', + max((19 if cfg.portfolio else 0), self.upd_w) + 2), + 'amt': fd(' {amt}', ' {amt}', 21) + } | {k: fd( + ' {{{}:>{}}}'.format(k.replace('-', '_'), widths[k]), + ' {{{}:{}{}.{}f}}'.format(k.replace('-', '_'), widths[k], self.comma, col_prec[k]), + widths[k] + 2 + ) for k in self.col_ids} cols = ( - ['label','usd-us-dollar'] + + ['label', 'usd-us-dollar'] + [asset.id for asset in self.usr_col_assets] + - [a for a,b in ( - ( 'btc-bitcoin', not cfg.btc_only ), - ( 'pct1y', 'y' in cfg.percent_cols ), - ( 'pct1m', 'm' in cfg.percent_cols ), - ( 'pct1w', 'w' in cfg.percent_cols ), - ( 'pct1d', 'd' in cfg.percent_cols ), - ( 'update_time', cfg.update_time ), - ) if b] - ) + [a for a, b in ( + ('btc-bitcoin', not cfg.btc_only), + ('pct1y', 'y' in cfg.percent_cols), + ('pct1m', 'm' in cfg.percent_cols), + ('pct1w', 'w' in cfg.percent_cols), + ('pct1d', 'd' in cfg.percent_cols), + ('update_time', cfg.update_time)) + if b]) cols2 = list(cols) if cfg.update_time: cols2.pop() @@ -1029,17 +1003,16 @@ class Ticker: amt = ' AMOUNT', usd_us_dollar = 'USD', btc_bitcoin = ' BTC', - **{ a.id.replace('-','_'): a.symbol for a in self.usr_col_assets } - ) + **{a.id.replace('-', '_'): a.symbol for a in self.usr_col_assets}) class trading(base): - def __init__(self,data): + def __init__(self, data): self.data = data self.asset = cfg.query.asset._replace(id=self.get_id(cfg.query.asset)) self.to_asset = ( cfg.query.to_asset._replace(id=self.get_id(cfg.query.to_asset)) - if cfg.query.to_asset else None ) + if cfg.query.to_asset else None) self.col_ids = [self.asset.id] self.adjust = cfg.adjust if self.to_asset: @@ -1048,12 +1021,13 @@ class Ticker: real_price = ( self.asset.amount * data[self.asset.id]['price_usd'] - / data[self.to_asset.id]['price_usd'] - ) + / data[self.to_asset.id]['price_usd']) if self.adjust != 1: - die(1,'the --adjust option may not be combined with TO_AMOUNT in the trade specifier') + die(1, + 'the --adjust option may not be combined with TO_AMOUNT ' + 'in the trade specifier') self.adjust = self.offer / real_price - self.hl_ids = [self.asset.id,self.to_asset.id] + self.hl_ids = [self.asset.id, self.to_asset.id] else: self.hl_ids = [self.asset.id] @@ -1070,10 +1044,10 @@ class Ticker: self.init_prec() self.init_fs() - def get_row_prices(self,id): + def get_row_prices(self, id): if id in self.data: d = self.data[id] - return { k: self.col_usd_prices[self.asset.id] / d['price_usd'] for k in self.col_ids } + return {k: self.col_usd_prices[self.asset.id] / d['price_usd'] for k in self.col_ids} def init_fs(self): self.max_wid = max( @@ -1081,10 +1055,8 @@ class Ticker: v[self.asset.id] * self.asset.amount, 16 + cfg.add_prec, self.comma, - 8 + cfg.add_prec - )) - for v in self.prices.values() - ) + 8 + cfg.add_prec)) + for v in self.prices.values()) self.fs_str = '{lbl:%s} {p_spot}' % self.col1_wid self.hl_wid = self.col1_wid + self.max_wid + 1 if self.show_adj: @@ -1094,20 +1066,19 @@ class Ticker: self.fs_str += ' {upd}' self.hl_wid += self.upd_w + 2 - def fmt_row(self,d): + def fmt_row(self, d): id = d['id'] p = self.prices[id][self.asset.id] * self.asset.amount - p_spot = '{:{}{}.{}f}'.format( p, self.max_wid, self.comma, 8+cfg.add_prec ) + p_spot = '{:{}{}.{}f}'.format(p, self.max_wid, self.comma, 8+cfg.add_prec) p_adj = ( - '{:{}{}.{}f}'.format( p*self.adjust, self.max_wid, self.comma, 8+cfg.add_prec ) - if self.show_adj else '' ) + '{:{}{}.{}f}'.format(p*self.adjust, self.max_wid, self.comma, 8+cfg.add_prec) + if self.show_adj else '') return self.fs_str.format( lbl = self.create_label(id) if cfg.name_labels else d['symbol'], p_spot = green(p_spot) if id in self.hl_ids else p_spot, p_adj = yellow(p_adj) if id in self.hl_ids else p_adj, - upd = d.get('last_updated_fmt'), - ) + upd = d.get('last_updated_fmt')) @property def table_hdr(self): @@ -1115,12 +1086,11 @@ class Ticker: lbl = '', p_spot = '{t:>{w}}'.format( t = 'SPOT PRICE', - w = self.max_wid ), + w = self.max_wid), p_adj = '{t:>{w}}'.format( t = ('OFFERED' if self.offer else 'ADJUSTED') + ' PRICE', - w = self.max_wid ), - upd = 'UPDATED' - ) + w = self.max_wid), + upd = 'UPDATED') @property def subhdr(self): @@ -1133,9 +1103,8 @@ class Ticker: ) + ( ( ' =>' + - (' {:{}}'.format(self.offer,self.comma) if self.offer else '') + + (' {:{}}'.format(self.offer, self.comma) if self.offer else '') + ' {} ({})'.format( self.to_asset.symbol, - self.create_label(self.to_asset.id) ) - ) if self.to_asset else '' ) - ) + self.create_label(self.to_asset.id)) + ) if self.to_asset else '')) diff --git a/mmgen_node_tools/Util.py b/mmgen_node_tools/Util.py index 1d5426b..f36faad 100755 --- a/mmgen_node_tools/Util.py +++ b/mmgen_node_tools/Util.py @@ -21,53 +21,52 @@ mmgen_node_tools.Util: utility functions for MMGen node tools import time -def get_hms(t=None,utc=False,no_secs=False): +def get_hms(t=None, utc=False, no_secs=False): secs = t or time.time() - ret = (time.localtime,time.gmtime)[utc](secs) - fs,n = (('{:02}:{:02}:{:02}',6),('{:02}:{:02}',5))[no_secs] + ret = (time.localtime, time.gmtime)[utc](secs) + fs, n = (('{:02}:{:02}:{:02}', 6), ('{:02}:{:02}', 5))[no_secs] return fs.format(*ret[3:n]) -def get_day_hms(t=None,utc=False): +def get_day_hms(t=None, utc=False): secs = t or time.time() - ret = (time.localtime,time.gmtime)[utc](secs) + ret = (time.localtime, time.gmtime)[utc](secs) return '{:04}-{:02}-{:02} {:02}:{:02}:{:02}'.format(*ret[0:6]) -def do_system(cmd,testing=False,shell=False): +def do_system(cmd, testing=False, shell=False): if testing: from mmgen.util import msg msg("Would execute: '%s'" % cmd) return True else: import subprocess - return subprocess.call((cmd if shell else cmd.split()),shell,stderr=subprocess.PIPE) + return subprocess.call((cmd if shell else cmd.split()), shell, stderr=subprocess.PIPE) -def get_url(url,gzip_ok=False,proxy=None,timeout=60,verbose=False,debug=False): +def get_url(url, gzip_ok=False, proxy=None, timeout=60, verbose=False, debug=False): if debug: print('get_url():') print(' url', url) - print(' gzip_ok:',gzip_ok, 'proxy:',proxy, 'timeout:',timeout, 'verbose:',verbose) - import pycurl,io + print(' gzip_ok:', gzip_ok, 'proxy:', proxy, 'timeout:', timeout, 'verbose:', verbose) + import pycurl, io c = pycurl.Curl() c_out = io.StringIO() - c.setopt(pycurl.WRITEFUNCTION,c_out.write) - c.setopt(pycurl.TIMEOUT,timeout) - c.setopt(pycurl.FOLLOWLOCATION,True) - c.setopt(pycurl.COOKIEFILE,'') - c.setopt(pycurl.VERBOSE,verbose) + c.setopt(pycurl.WRITEFUNCTION, c_out.write) + c.setopt(pycurl.TIMEOUT, timeout) + c.setopt(pycurl.FOLLOWLOCATION, True) + c.setopt(pycurl.COOKIEFILE, '') + c.setopt(pycurl.VERBOSE, verbose) if gzip_ok: - c.setopt(pycurl.USERAGENT,'Lynx/2.8.9dev.8 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.4.9') + c.setopt(pycurl.USERAGENT, 'Lynx/2.8.9dev.8 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.4.9') c.setopt(pycurl.HTTPHEADER, [ 'Accept: text/html, text/plain, text/sgml, text/css, application/xhtml+xml, */*;q=0.01', 'Accept-Encoding: gzip', - 'Accept-Language: en'] - ) + 'Accept-Language: en']) if proxy: - c.setopt(pycurl.PROXY,proxy) - c.setopt(pycurl.URL,url) + c.setopt(pycurl.PROXY, proxy) + c.setopt(pycurl.URL, url) c.perform() text = c_out.getvalue() if text[:2] == '\x1f\x8b': # gzip magic number - c_out.seek(0,0) + c_out.seek(0, 0) import gzip with gzip.GzipFile(fileobj=c_out) as f: text = f.read() @@ -103,20 +102,19 @@ big_digits = { """ } -_bnums_c,_bpunc_c = [[l.strip('\n') + ' ' * (big_digits[m]*big_digits['n']) +_bnums_c, _bpunc_c = [[l.strip('\n') + ' ' * (big_digits[m]*big_digits['n']) for l in big_digits[k][1:].split('\n')] - for k,m in (('nums','w'),('punc','pw'))] + for k, m in (('nums', 'w'), ('punc', 'pw'))] -_bnums_n,_bpunc_n = [[[l[0+(j*w):w+(j*w)] for l in i] - for j in range(big_digits[n])] for n,w,i in - (('n',big_digits['w'],_bnums_c),('pn',big_digits['pw'],_bpunc_c))] +_bnums_n, _bpunc_n = [[[l[0+(j*w):w+(j*w)] for l in i] + for j in range(big_digits[n])] for n, w, i in + (('n', big_digits['w'], _bnums_c), ('pn', big_digits['pw'], _bpunc_c))] -def display_big_digits(s,pre='',suf=''): - s = [int((d,10,11)[(d in '.:')+(d==':')]) for d in s] +def display_big_digits(s, pre='', suf=''): + s = [int((d, 10, 11)[(d in '.:')+(d==':')]) for d in s] return pre + ('\n'+pre).join( - [''.join([(_bnums_n+_bpunc_n)[d][l] for d in s]) + suf for l in range(big_digits['h'])] - ) + [''.join([(_bnums_n+_bpunc_n)[d][l] for d in s]) + suf for l in range(big_digits['h'])]) if __name__ == '__main__': num = '2345.17' - print(display_big_digits(num,pre='+ ',suf=' +')) + print(display_big_digits(num, pre='+ ', suf=' +')) diff --git a/mmgen_node_tools/main_addrbal.py b/mmgen_node_tools/main_addrbal.py index 37f0482..4647029 100755 --- a/mmgen_node_tools/main_addrbal.py +++ b/mmgen_node_tools/main_addrbal.py @@ -16,7 +16,7 @@ import sys from mmgen.obj import CoinTxID from mmgen.cfg import Config -from mmgen.util import msg,Msg,die,suf,make_timestr,async_run +from mmgen.util import msg, Msg, die, suf, make_timestr, async_run from mmgen.color import red opts_data = { @@ -32,16 +32,16 @@ opts_data = { } } -def do_output(proto,addr_data,blk_hdrs): +def do_output(proto, addr_data, blk_hdrs): col1w = len(str(len(addr_data))) indent = ' ' * (col1w + 2) - for n,(addr,unspents) in enumerate(addr_data.items(),1): + for n, (addr, unspents) in enumerate(addr_data.items(), 1): Msg(f'\n{n:{col1w}}) Address: {addr.hl(addr.view_pref)}') if unspents: - heights = { u['height'] for u in unspents } + heights = {u['height'] for u in unspents} Msg('{}Balance: {}'.format( indent, sum(proto.coin_amt(u['amount']) for u in unspents).hl2(unit=True, fs='{:,}'))), @@ -50,22 +50,21 @@ def do_output(proto,addr_data,blk_hdrs): red(str(len(unspents))), suf(unspents), red(str(len(heights))), - suf(heights) )) + suf(heights))) blk_w = len(str(unspents[-1]['height'])) - fs = '%s{:%s} {:19} {:64} {:4} {}' % (indent,max(5,blk_w)) - Msg(fs.format('Block','Date','TxID','Vout',' Amount')) + fs = '%s{:%s} {:19} {:64} {:4} {}' % (indent, max(5, blk_w)) + Msg(fs.format('Block', 'Date', 'TxID', 'Vout', ' Amount')) for u in unspents: Msg(fs.format( u['height'], - make_timestr( blk_hdrs[u['height']]['time'] ), + make_timestr(blk_hdrs[u['height']]['time']), CoinTxID(u['txid']).hl(), red(str(u['vout']).rjust(4)), - proto.coin_amt(u['amount']).fmt(6, color=True, prec=8) - )) + proto.coin_amt(u['amount']).fmt(6, color=True, prec=8))) else: Msg(f'{indent}No balance') -def do_output_tabular(proto,addr_data,blk_hdrs): +def do_output_tabular(proto, addr_data, blk_hdrs): col1w = len(str(len(addr_data))) + 1 max_addrw = max(len(addr) for addr in addr_data) @@ -75,9 +74,9 @@ def do_output_tabular(proto,addr_data,blk_hdrs): lb_w = max(len(h) for h in lb_heights) fs = ( - ' {n:>%s} {a} {u} {b:>%s} {t:19} {B:>%s} {T:19} {A}' % (col1w,max(5,fb_w),max(4,lb_w)) + ' {n:>%s} {a} {u} {b:>%s} {t:19} {B:>%s} {T:19} {A}' % (col1w, max(5, fb_w), max(4, lb_w)) if cfg.first_block else - ' {n:>%s} {a} {u} {B:>%s} {T:19} {A}' % (col1w,max(4,lb_w)) ) + ' {n:>%s} {a} {u} {B:>%s} {T:19} {A}' % (col1w, max(4, lb_w))) Msg('\n' + fs.format( n = '', @@ -87,20 +86,19 @@ def do_output_tabular(proto,addr_data,blk_hdrs): t = 'Block', B = 'Last', T = 'Block', - A = ' Amount' )) + A = ' Amount')) - for n,(addr,unspents) in enumerate(addr_data.items(),1): + for n, (addr, unspents) in enumerate(addr_data.items(), 1): if unspents: Msg(fs.format( n = str(n) + ')', a = addr.fmt(addr.view_pref, max_addrw, color=True), u = red(str(len(unspents)).rjust(5)), b = unspents[0]['height'], - t = make_timestr( blk_hdrs[unspents[0]['height']]['time'] ), + t = make_timestr(blk_hdrs[unspents[0]['height']]['time']), B = unspents[-1]['height'], - T = make_timestr( blk_hdrs[unspents[-1]['height']]['time'] ), - A = sum(proto.coin_amt(u['amount']) for u in unspents).fmt(7, color=True, prec=8) - )) + T = make_timestr(blk_hdrs[unspents[-1]['height']]['time']), + A = sum(proto.coin_amt(u['amount']) for u in unspents).fmt(7, color=True, prec=8))) else: Msg(fs.format( n = str(n) + ')', @@ -110,61 +108,61 @@ def do_output_tabular(proto,addr_data,blk_hdrs): t = '', B = '-', T = '', - A = ' -' )) + A = ' -')) async def main(req_addrs): proto = cfg._proto from mmgen.addr import CoinAddr - addrs = [CoinAddr(proto,addr) for addr in req_addrs] + addrs = [CoinAddr(proto, addr) for addr in req_addrs] from mmgen.rpc import rpc_init - rpc = await rpc_init(cfg,ignore_wallet=True) + rpc = await rpc_init(cfg, ignore_wallet=True) height = await rpc.call('getblockcount') Msg(f'{proto.coin} {proto.network.upper()} [height {height}]') from mmgen.proto.btc.misc import scantxoutset - res = await scantxoutset( cfg, rpc, [f'addr({addr})' for addr in addrs] ) + res = await scantxoutset(cfg, rpc, [f'addr({addr})' for addr in addrs]) if not res['success']: - die(1,'UTXO scanning failed or was interrupted') + die(1, 'UTXO scanning failed or was interrupted') elif not res['unspents']: msg('Address has no balance' if len(addrs) == 1 else - 'Addresses have no balances' ) + 'Addresses have no balances') else: addr_data = {k:[] for k in addrs} if 'desc' in res['unspents'][0]: import re - for unspent in sorted(res['unspents'],key=lambda x: x['height']): - addr = re.match('addr\((.*?)\)',unspent['desc'])[1] + for unspent in sorted(res['unspents'], key=lambda x: x['height']): + addr = re.match('addr\((.*?)\)', unspent['desc'])[1] addr_data[addr].append(unspent) else: from mmgen.proto.btc.tx.base import decodeScriptPubKey - for unspent in sorted(res['unspents'],key=lambda x: x['height']): + for unspent in sorted(res['unspents'], key=lambda x: x['height']): ds = decodeScriptPubKey(proto, unspent['scriptPubKey']) addr_data[ds.addr].append(unspent) good_addrs = len([v for v in addr_data.values() if v]) Msg('Total: {} in {} address{}'.format( - proto.coin_amt(res['total_amount']).hl2(unit=True,fs='{:,}'), + proto.coin_amt(res['total_amount']).hl2(unit=True, fs='{:,}'), red(str(good_addrs)), - suf(good_addrs,'es') - )) + suf(good_addrs, 'es'))) blk_heights = {i['height'] for i in res['unspents']} blk_hashes = await rpc.batch_call('getblockhash', [(h,) for h in blk_heights]) blk_hdrs = await rpc.batch_call('getblockheader', [(H,) for H in blk_hashes]) - (do_output_tabular if cfg.tabular else do_output)( proto, addr_data, dict(zip(blk_heights,blk_hdrs)) ) + (do_output_tabular if cfg.tabular else do_output)( + proto, addr_data, dict(zip(blk_heights, blk_hdrs))) -cfg = Config( opts_data=opts_data, init_opts={'rpc_backend':'aiohttp'} ) +cfg = Config(opts_data=opts_data, init_opts={'rpc_backend': 'aiohttp'}) if len(cfg._args) < 1: - die(1,'This command requires at least one coin address argument') + die(1, 'This command requires at least one coin address argument') try: async_run(cfg, main, args=[cfg._args]) diff --git a/mmgen_node_tools/main_blocks_info.py b/mmgen_node_tools/main_blocks_info.py index 18136c4..eaed15b 100755 --- a/mmgen_node_tools/main_blocks_info.py +++ b/mmgen_node_tools/main_blocks_info.py @@ -20,9 +20,9 @@ mmnode-blocks-info: Display information about a block or range of blocks """ -from mmgen.cfg import gc,Config -from mmgen.util import async_run,fmt_list -from .BlocksInfo import BlocksInfo,JSONBlocksInfo +from mmgen.cfg import gc, Config +from mmgen.util import async_run, fmt_list +from .BlocksInfo import BlocksInfo, JSONBlocksInfo opts_data = { 'sets': [ @@ -145,14 +145,13 @@ EXAMPLES: $ {p} --rpc-backend=aio -H +1000 This program requires a txindex-enabled daemon for correct operation. -""" }, +"""}, 'code': { - 'notes': lambda cfg,proto,s: s.format( + 'notes': lambda cfg, proto, s: s.format( I = proto.diff_adjust_interval, - F = fmt_list(BlocksInfo.fields,fmt='bare'), - S = fmt_list(BlocksInfo.all_stats,fmt='bare'), - p = gc.prog_name, - ) + F = fmt_list(BlocksInfo.fields, fmt='bare'), + S = fmt_list(BlocksInfo.all_stats, fmt='bare'), + p = gc.prog_name) } } @@ -172,7 +171,7 @@ async def main(): await m.process_blocks() if m.last: - for i,sname in enumerate(m.stats): + for i, sname in enumerate(m.stats): m.process_stats_pre(i) await m.process_stats(sname) diff --git a/mmgen_node_tools/main_feeview.py b/mmgen_node_tools/main_feeview.py index 3211f7f..188f60e 100755 --- a/mmgen_node_tools/main_feeview.py +++ b/mmgen_node_tools/main_feeview.py @@ -21,10 +21,10 @@ mmnode-feeview: Visualize the fee structure of a node’s mempool """ from mmgen.cfg import Config -from mmgen.util import async_run,die,fmt,make_timestr,check_int_between -from mmgen.util2 import int2bytespec,parse_bytespec +from mmgen.util import async_run, die, fmt, make_timestr, check_int_between +from mmgen.util2 import int2bytespec, parse_bytespec -min_prec,max_prec,dfl_prec = (0,6,4) +min_prec, max_prec, dfl_prec = (0, 6, 4) fee_brackets = [ 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, @@ -42,9 +42,9 @@ fee_brackets = [ opts_data = { 'sets': [ - ('detail',True,'ranges',True), - ('detail',True,'show_mb_col',True), - ('detail',True,'precision',6), + ('detail', True, 'ranges', True), + ('detail', True, 'show_mb_col', True), + ('detail', True, 'precision', 6), ], 'text': { 'desc': 'Visualize the fee structure of a node’s mempool', @@ -83,37 +83,37 @@ cfg = Config(opts_data=opts_data) if cfg.ignore_below: if cfg.show_empty: - die(1,'Conflicting options: --ignore-below, --show-empty') + die(1, 'Conflicting options: --ignore-below, --show-empty') ignore_below = parse_bytespec(cfg.ignore_below) precision = ( check_int_between(cfg.precision, min_prec, max_prec, desc='--precision arg') - if cfg.precision else dfl_prec ) + if cfg.precision else dfl_prec) from mmgen.term import get_terminal_size width = cfg.columns or get_terminal_size().width class fee_bracket: - def __init__(self,top,bottom): + def __init__(self, top, bottom): self.top = top self.bottom = bottom self.tx_bytes = 0 self.tx_bytes_cum = 0 self.skip = False -def log(data,fn): +def log(data, fn): import json from mmgen.rpc.util import json_encoder from mmgen.fileutil import write_data_to_file write_data_to_file( cfg = cfg, outfile = fn, - data = json.dumps(data,cls=json_encoder,sort_keys=True,indent=4), + data = json.dumps(data, cls=json_encoder, sort_keys=True, indent=4), desc = 'mempool', - ask_overwrite = False ) + ask_overwrite = False) -def create_data(coin_amt,mempool): - out = [fee_bracket(fee_brackets[i],fee_brackets[i-1] if i else 0) for i in range(len(fee_brackets))] +def create_data(coin_amt, mempool): + out = [fee_bracket(fee_brackets[i], fee_brackets[i-1] if i else 0) for i in range(len(fee_brackets))] # populate fee brackets: size_key = 'size' if proto.coin == 'BCH' else 'vsize' @@ -143,7 +143,7 @@ def create_data(coin_amt,mempool): return out -def gen_header(host,mempool,blockcount): +def gen_header(host, mempool, blockcount): yield fmt(f""" Mempool Fee Structure @@ -159,27 +159,26 @@ def gen_header(host,mempool,blockcount): elif cfg.ignore_below: yield 'Ignoring fee brackets with less than {:,} bytes ({})'.format( ignore_below, - int2bytespec(ignore_below,'MB','0.6',strip=True,add_space=True), - ) + int2bytespec(ignore_below, 'MB', '0.6', strip=True, add_space=True)) if cfg.include_current: yield 'Including transactions in current fee bracket in Total MB amounts' def fmt_mb(n): - return int2bytespec(n,'MB',f'0.{precision}',print_sym=False) + return int2bytespec(n, 'MB', f'0.{precision}', print_sym=False) def gen_body(data): - tx_bytes_max = max((i.tx_bytes for i in data),default=0) - top_max = max((i.top for i in data),default=0) - bot_max = max((i.bottom for i in data),default=0) - col1_w = max(len(f'{bot_max}-{top_max}') if cfg.ranges else len(f'{top_max}'),6) + tx_bytes_max = max((i.tx_bytes for i in data), default=0) + top_max = max((i.top for i in data), default=0) + bot_max = max((i.bottom for i in data), default=0) + col1_w = max(len(f'{bot_max}-{top_max}') if cfg.ranges else len(f'{top_max}'), 6) col2_w = len(fmt_mb(tx_bytes_max)) if cfg.show_mb_col else 0 col3_w = len(fmt_mb(data[-1].tx_bytes_cum)) if data else 0 col4_w = width - col1_w - col2_w - col3_w - (4 if col2_w else 3) if cfg.show_mb_col: - fs = '{a:<%i} {b:>%i} {c:>%i} {d}' % (col1_w,col2_w,col3_w) + fs = '{a:<%i} {b:>%i} {c:>%i} {d}' % (col1_w, col2_w, col3_w) else: - fs = '{a:<%i} {c:>%i} {d}' % (col1_w,col3_w) + fs = '{a:<%i} {c:>%i} {d}' % (col1_w, col3_w) yield fs.format(a='', b='', c=f'{"Total":<{col3_w}}', d='') yield fs.format(a='sat/B', b=f'{"MB":<{col2_w}}', c=f'{"MB":<{col3_w}}', d='') @@ -188,16 +187,16 @@ def gen_body(data): if not i.skip: cum_bytes = i.tx_bytes_cum + i.tx_bytes if cfg.include_current else i.tx_bytes_cum yield fs.format( - a = '{}-{}'.format(i.bottom,i.top) if cfg.ranges else i.top, + a = '{}-{}'.format(i.bottom, i.top) if cfg.ranges else i.top, b = fmt_mb(i.tx_bytes), c = fmt_mb(cum_bytes), - d = '-' * int(col4_w * ( i.tx_bytes / tx_bytes_max )) ) + d = '-' * int(col4_w * (i.tx_bytes / tx_bytes_max))) yield fs.format( a = 'TOTAL', b = '', c = fmt_mb(data[-1].tx_bytes_cum + data[-1].tx_bytes if data else 0), - d = '' ) + d = '') async def main(): @@ -205,19 +204,19 @@ async def main(): proto = cfg._proto from mmgen.rpc import rpc_init - c = await rpc_init(cfg,ignore_wallet=True) + c = await rpc_init(cfg, ignore_wallet=True) - mempool = await c.call('getrawmempool',True) + mempool = await c.call('getrawmempool', True) if cfg.log: - log(mempool,'mempool.json') + log(mempool, 'mempool.json') - data = create_data(proto.coin_amt,mempool) + data = create_data(proto.coin_amt, mempool) cfg._util.stdout_or_pager( '\n'.join(gen_header( c.host, mempool, - await c.call('getblockcount') )) + '\n\n' + - '\n'.join(gen_body(data)) + '\n' ) + await c.call('getblockcount'))) + '\n\n' + + '\n'.join(gen_body(data)) + '\n') async_run(cfg, main) diff --git a/mmgen_node_tools/main_halving_calculator.py b/mmgen_node_tools/main_halving_calculator.py index 8f0a3a3..60c5440 100755 --- a/mmgen_node_tools/main_halving_calculator.py +++ b/mmgen_node_tools/main_halving_calculator.py @@ -28,7 +28,7 @@ from mmgen.util import async_run bdr_proj = 9.95 opts_data = { - 'sets': [('mined',True,'list',True)], + 'sets': [('mined', True, 'list', True)], 'text': { 'desc': 'Estimate date(s) of future block subsidy halving(s)', 'usage':'[opts]', @@ -41,7 +41,7 @@ opts_data = { {bdr_proj:.5f} min) -s, --sample-size=N Block range to calculate block discovery interval for next halving estimate (default: dynamically calculated) -""" } +"""} } cfg = Config(opts_data=opts_data) @@ -53,14 +53,14 @@ def date(t): return '{}-{:02}-{:02} {:02}:{:02}:{:02}'.format(*time.gmtime(t)[:6]) def dhms(t): - t,neg = (-t,'-') if t < 0 else (t,' ') + t, neg = (-t, '-') if t < 0 else (t, ' ') return f'{neg}{t//60//60//24} days, {t//60//60%24:02}:{t//60%60:02}:{t%60:02} h/m/s' def time_diff_warning(t_diff): if abs(t_diff) > 60*60: print('Warning: block tip time is {} {} clock time!'.format( dhms(abs(t_diff)), - ('behind','ahead of')[t_diff<0])) + ('behind', 'ahead of')[t_diff<0])) async def main(): @@ -72,9 +72,9 @@ async def main(): tip = await c.call('getblockcount') assert tip > 1, 'block tip must be > 1' remaining = proto.halving_interval - tip % proto.halving_interval - sample_size = int(cfg.sample_size) if cfg.sample_size else min(tip-1,max(remaining,144)) + sample_size = int(cfg.sample_size) if cfg.sample_size else min(tip-1, max(remaining, 144)) - cur,old = await c.gathered_call('getblockstats',((tip,),(tip - sample_size,))) + cur, old = await c.gathered_call('getblockstats', ((tip,), (tip - sample_size,))) clock_time = int(time.time()) time_diff_warning(clock_time - cur['time']) @@ -98,8 +98,7 @@ async def main(): f'Current block discovery interval (over last {sample_size} blocks): {bdr/60:0.2f} min\n\n' f'Current clock time (UTC): {date(clock_time)}\n' f'Est. halving date (UTC): {date(t_next)}\n' - f'Est. time until halving: {dhms(cur["time"] + t_rem - clock_time)}' - ) + f'Est. time until halving: {dhms(cur["time"] + t_rem - clock_time)}') async def print_halvings(): halving_blocknums = [i*proto.halving_interval for i in range(proto.max_halvings+1)][1:] @@ -108,13 +107,13 @@ async def main(): nhist = len(hist_halvings) nSubsidy = int(proto.start_subsidy / proto.coin_amt.satoshi) - block0_hash = await c.call('getblockhash',0) - block0_date = (await c.call('getblock',block0_hash))['time'] + block0_hash = await c.call('getblockhash', 0) + block0_date = (await c.call('getblock', block0_hash))['time'] def gen_data(): total_mined = 0 date = block0_date - for n,blk in enumerate(halving_blocknums): + for n, blk in enumerate(halving_blocknums): mined = (nSubsidy >> n) * proto.halving_interval if n == 0: mined -= nSubsidy # subtract unspendable genesis block subsidy @@ -123,13 +122,11 @@ async def main(): bdi = ( (hist_halvings[n]['time'] - date) / (proto.halving_interval * 60) if n < nhist else bdr/60 if n == nhist - else bdr_proj - ) + else bdr_proj) date = ( hist_halvings[n]['time'] if n < nhist - else t_next + int((n - nhist) * halving_secs) - ) - yield ( n, sub, blk, mined, total_mined, bdi, date ) + else t_next + int((n - nhist) * halving_secs)) + yield (n, sub, blk, mined, total_mined, bdi, date) if sub == 0: break @@ -150,8 +147,7 @@ async def main(): e = 'BDI (mins)', f = 'SUBSIDY ({proto.coin})', g = f'MINED ({proto.coin})', - h = f'TOTAL MINED ({proto.coin})' - ) + h = f'TOTAL MINED ({proto.coin})') + '\n' + fs.format( a = '-' * 7, @@ -159,22 +155,20 @@ async def main(): c = '-' * 19, d = '-' * 2, e = '-' * 10, - f = '-' * 13, + f = '-' * 17, g = '-' * 17, - h = '-' * 17 - ) + h = '-' * 17) + '\n' + '\n'.join(fs.format( - a = n + 1, - b = blk, - c = date(t), - d = ' P' if n > nhist else '' if n < nhist else ' E', - e = f'{bdr:8.5f}', - f = proto.coin_amt(sub, from_unit='satoshi').fmt(2, prec=8), - g = proto.coin_amt(mined, from_unit='satoshi').fmt(8, prec=8), - h = proto.coin_amt(total_mined, from_unit='satoshi').fmt(8, prec=8) - ) for n, sub, blk, mined, total_mined, bdr, t in gen_data()) - ) + a = n + 1, + b = blk, + c = date(t), + d = ' P' if n > nhist else '' if n < nhist else ' E', + e = f'{bdr:8.5f}', + f = proto.coin_amt(sub, from_unit='satoshi').fmt(2, prec=8), + g = proto.coin_amt(mined, from_unit='satoshi').fmt(8, prec=8), + h = proto.coin_amt(total_mined, from_unit='satoshi').fmt(8, prec=8) + ) for n, sub, blk, mined, total_mined, bdr, t in gen_data())) if cfg.list: await print_halvings() diff --git a/mmgen_node_tools/main_netrate.py b/mmgen_node_tools/main_netrate.py index b273ac6..f5cf992 100755 --- a/mmgen_node_tools/main_netrate.py +++ b/mmgen_node_tools/main_netrate.py @@ -20,7 +20,7 @@ mmnode-netrate: Bitcoin daemon network rate monitor """ -import sys,time +import sys, time from mmgen.cfg import Config from mmgen.util import async_run @@ -32,39 +32,38 @@ opts_data = { 'options': """ -h, --help Print this help message --, --longhelp Print help message for long options (common options) -""" - } +"""} } cfg = Config(opts_data=opts_data) -ERASE_LINE,CUR_UP = '\033[K','\033[1A' +ERASE_LINE, CUR_UP = '\033[K', '\033[1A' async def main(): from mmgen.rpc import rpc_init - c = await rpc_init(cfg,ignore_wallet=True) + c = await rpc_init(cfg, ignore_wallet=True) async def get_data(): d = await c.call('getnettotals') - return [float(e) for e in (d['totalbytesrecv'],d['totalbytessent'],d['timemillis'])] + return [float(e) for e in (d['totalbytesrecv'], d['totalbytessent'], d['timemillis'])] - rs,ss,ts = (None,None,None) + rs, ss, ts = (None, None, None) while True: - r,s,t = await get_data() + r, s, t = await get_data() if rs is not None: sys.stderr.write( '\rrcvd: {:9.2f} kB/s\nsent: {:9.2f} kB/s '.format( (r-rs)/(t-ts), - (s-ss)/(t-ts) )) + (s-ss)/(t-ts))) time.sleep(2) if rs is not None: - sys.stderr.write('{}{}{}'.format(ERASE_LINE,CUR_UP,ERASE_LINE)) + sys.stderr.write('{}{}{}'.format(ERASE_LINE, CUR_UP, ERASE_LINE)) - rs,ss,ts = (r,s,t) + rs, ss, ts = (r, s, t) try: async_run(cfg, main) diff --git a/mmgen_node_tools/main_peerblocks.py b/mmgen_node_tools/main_peerblocks.py index 35292ea..6a7cb38 100755 --- a/mmgen_node_tools/main_peerblocks.py +++ b/mmgen_node_tools/main_peerblocks.py @@ -27,8 +27,7 @@ opts_data = { 'options': """ -h, --help Print this help message --, --longhelp Print help message for long options (common options) -""" - } +"""} } from mmgen.cfg import Config @@ -37,9 +36,9 @@ cfg = Config(opts_data=opts_data) async def main(): from mmgen.rpc import rpc_init - rpc = await rpc_init(cfg,ignore_wallet=True) + rpc = await rpc_init(cfg, ignore_wallet=True) - from .PeerBlocks import BlocksDisplay,PeersDisplay + from .PeerBlocks import BlocksDisplay, PeersDisplay blocks = BlocksDisplay(cfg) peers = PeersDisplay(cfg) diff --git a/mmgen_node_tools/main_ticker.py b/mmgen_node_tools/main_ticker.py index 351cb1e..c36d886 100755 --- a/mmgen_node_tools/main_ticker.py +++ b/mmgen_node_tools/main_ticker.py @@ -208,29 +208,26 @@ Customize output by editing the file To add a portfolio, edit the file ~/{pf_cfg} -""" - }, +"""}, 'code': { 'options': lambda s: s.format( - dfl_cachedir = os.path.relpath(dfl_cachedir,start=homedir), - ds = fmt_dict(DataSource.get_sources(),fmt='equal_compact'), + dfl_cachedir = os.path.relpath(dfl_cachedir, start=homedir), + ds = fmt_dict(DataSource.get_sources(), fmt='equal_compact'), al = DataSource.coinpaprika.dfl_asset_limit, - pc = fmt_list(Ticker.percent_cols,fmt='bare'), - ), + pc = fmt_list(Ticker.percent_cols, fmt='bare')), 'notes': lambda s: s.format( - assets = fmt_list(assets_list_gen(cfg_in),fmt='col',indent=' '), - cfg = os.path.relpath(cfg_in.cfg_file,start=homedir), - pf_cfg = os.path.relpath(cfg_in.portfolio_file,start=homedir), + assets = fmt_list(assets_list_gen(cfg_in), fmt='col', indent=' '), + cfg = os.path.relpath(cfg_in.cfg_file, start=homedir), + pf_cfg = os.path.relpath(cfg_in.portfolio_file, start=homedir), al = DataSource.coinpaprika.dfl_asset_limit, cc = src_cls['cc'](), - fi = src_cls['fi'](), - ) + fi = src_cls['fi']()) } } import os -from mmgen.util import fmt_list,fmt_dict +from mmgen.util import fmt_list, fmt_dict from mmgen.cfg import Config from . import Ticker @@ -238,7 +235,7 @@ gcfg = Config(opts_data=opts_data, caller_post_init=True) Ticker.make_cfg(gcfg) -from .Ticker import dfl_cachedir,homedir,DataSource,assets_list_gen,cfg_in,src_cls +from .Ticker import dfl_cachedir, homedir, DataSource, assets_list_gen, cfg_in, src_cls gcfg._post_init() diff --git a/mmgen_node_tools/main_txfind.py b/mmgen_node_tools/main_txfind.py index 2411f36..0f9db5b 100755 --- a/mmgen_node_tools/main_txfind.py +++ b/mmgen_node_tools/main_txfind.py @@ -23,7 +23,7 @@ mmnode-txfind: Find a transaction in the blockchain or mempool import sys from mmgen.cfg import Config -from mmgen.util import msg,Msg,die,is_hex_str,async_run +from mmgen.util import msg, Msg, die, is_hex_str, async_run opts_data = { 'text': { @@ -48,29 +48,26 @@ msg_data = { 'normal': { 'none': 'Transaction not found in blockchain or mempool', 'block': 'Transaction is in block {b} ({c} confirmations)', - 'mem': 'Transaction is in mempool', - }, + 'mem': 'Transaction is in mempool'}, 'quiet': { 'none': 'None', 'block': '{b} {c}', - 'mem': 'mempool', - } -} + 'mem': 'mempool'}} async def main(txid): if len(txid) != 64 or not is_hex_str(txid): - die(2,f'{txid}: invalid transaction ID') + die(2, f'{txid}: invalid transaction ID') if cfg.verbose: msg(f'TxID: {txid}') from mmgen.rpc import rpc_init - c = await rpc_init(cfg,ignore_wallet=True) + c = await rpc_init(cfg, ignore_wallet=True) exitval = 0 try: tip1 = await c.call('getblockcount') - ret = await c.call('getrawtransaction',txid,True) + ret = await c.call('getrawtransaction', txid, True) tip2 = await c.call('getblockcount') except: Msg('\r' + msgs['none']) @@ -90,6 +87,6 @@ cfg = Config(opts_data=opts_data) msgs = msg_data['quiet' if cfg.quiet else 'normal'] if len(cfg._args) != 1: - die(1,'One transaction ID must be specified') + die(1, 'One transaction ID must be specified') sys.exit(async_run(cfg, main, args=[cfg._args[0]])) diff --git a/test/cmdtest_d/regtest.py b/test/cmdtest_d/regtest.py index 4792dee..a504678 100755 --- a/test/cmdtest_d/regtest.py +++ b/test/cmdtest_d/regtest.py @@ -335,19 +335,19 @@ class CmdTestRegtest(CmdTestBase): us = await r.rpc_call('listunspent',wallet='miner') tx_input = us[7] # 25 BTC in coinbase -- us[0] could have < 25 BTC fee = self.proto.coin_amt('0.001') - outputs = {p.addr:tx1_amt for p in pairs[:nTxs]} + outputs = {p.addr: tx1_amt for p in pairs[:nTxs]} outputs.update({burn_addr: self.proto.coin_amt(tx_input['amount']) - (tx1_amt*nTxs) - fee}) return await do_tx( - [{ 'txid': tx_input['txid'], 'vout': 0 }], + [{'txid': tx_input['txid'], 'vout': 0}], outputs, await r.miner_wif) async def do_tx2(tx,pairno): fee = self.proto.coin_amt(fees[pairno], from_decimal=True) - outputs = {p.addr:tx2_amt for p in pairs} + outputs = {p.addr: tx2_amt for p in pairs} outputs.update({burn_addr: tx1_amt - (tx2_amt*len(pairs)) - fee}) return await do_tx( - [{ 'txid': tx['txid'], 'vout': pairno }], + [{'txid': tx['txid'], 'vout': pairno}], outputs, pairs[pairno].wif ) diff --git a/test/overlay/fakemods/mmgen_node_tools/PeerBlocks.py b/test/overlay/fakemods/mmgen_node_tools/PeerBlocks.py index 7c2fdf3..444ae08 100644 --- a/test/overlay/fakemods/mmgen_node_tools/PeerBlocks.py +++ b/test/overlay/fakemods/mmgen_node_tools/PeerBlocks.py @@ -294,15 +294,14 @@ class fake_data: 20 7303 7307 7310 7311 7316 7322 7334 7343 7344 7350 7356 7363 7374 7377 7384 7225 21 7310 7311 7316 7322 7334 7343 7344 7350 7356 7363 7374 7377 7384 7225 7317 7386 22 7316 7322 7334 7343 7344 7350 7356 7363 7374 7377 7384 7225 7317 7386 7398 7409 - """ - } + """} def make_data(): def gen_address_data(): for line in fake_data.addresses.strip().split('\n'): data = line.split(maxsplit=2) - yield (data[0], {k:v for k,v in zip(('id','addr','subver'),data)}) + yield (data[0], {k: v for k, v in zip(('id', 'addr', 'subver'), data)}) def gen_iterations_data(): for line in fake_data.iterations.strip().split('\n'): @@ -320,8 +319,7 @@ class fake_data: 'id': int(d['id']), 'addr': d['addr'], 'subver': d['subver'], - 'inflight': [int(n)+830000 for n in blocks[iter_no]], - } + 'inflight': [int(n)+830000 for n in blocks[iter_no]]} def gen_data(): for iter_no in iterations_data: