From a8adef0be54c752c10a8f34d594ed3faaa23de0c Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 12 Oct 2025 10:01:51 +0000 Subject: [PATCH] mmgen-ticker: various fixes and cleanups --- mmgen_node_tools/Ticker.py | 39 +++++++++++++++++++--------------- test/cmdtest_d/httpd/ticker.py | 2 +- test/cmdtest_d/misc.py | 36 +++++++++++++++---------------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/mmgen_node_tools/Ticker.py b/mmgen_node_tools/Ticker.py index bfe899b..9aee86c 100755 --- a/mmgen_node_tools/Ticker.py +++ b/mmgen_node_tools/Ticker.py @@ -25,7 +25,7 @@ from decimal import Decimal from collections import namedtuple from mmgen.color import red, yellow, green, blue, orange, gray -from mmgen.util import msg, msg_r, Msg, Msg_r, die, fmt, fmt_list, fmt_dict, list_gen +from mmgen.util import msg, msg_r, Msg, Msg_r, die, fmt, fmt_list, fmt_dict, list_gen, suf from mmgen.ui import do_pager homedir = os.getenv('HOME') @@ -99,18 +99,21 @@ class DataSource: if not os.path.exists(cfg.cachedir): os.makedirs(cfg.cachedir) - if not os.path.exists(self.json_fn): - open(self.json_fn, 'w').write('{}') - use_cached_data = cfg.cached_data and not gcfg.download if use_cached_data: data_type = 'json' - data_in = open(self.json_fn).read() + try: + data_in = open(self.json_fn).read() + except FileNotFoundError: + die(1, f'Cannot use cached data, because {self.json_fn_disp} does not exist') else: data_type = self.net_data_type - elapsed = int(time.time() - os.stat(self.json_fn).st_mtime) - if elapsed >= self.timeout or gcfg.testing: + try: + mtime = os.stat(self.json_fn).st_mtime + except FileNotFoundError: + mtime = 0 + if (elapsed := int(time.time() - mtime)) >= self.timeout or gcfg.testing: if gcfg.testing: msg('') self.fetch_delay() @@ -148,14 +151,14 @@ class DataSource: if use_cached_data: if not cfg.quiet: - msg(f'Using cached data from ~/{self.json_fn_rel}') + msg(f'Using cached data from {self.json_fn_disp}') else: if os.path.exists(self.json_fn): os.rename(self.json_fn, self.json_fn + '.bak') with open(self.json_fn, 'w') as fh: fh.write(json_text) if not cfg.quiet: - msg(f'JSON data cached to ~/{self.json_fn_rel}') + msg(f'JSON data cached to {self.json_fn_disp}') if gcfg.download: sys.exit(0) @@ -168,8 +171,8 @@ class DataSource: return data @property - def json_fn_rel(self): - return os.path.relpath(self.json_fn, start=homedir) + def json_fn_disp(self): + return '~/' + os.path.relpath(self.json_fn, start=homedir) class coinpaprika(base): desc = 'CoinPaprika' @@ -186,8 +189,9 @@ class DataSource: self.asset_limit = int(cfg.asset_limit or self.dfl_asset_limit) def rate_limit_errmsg(self, elapsed): + rem = self.timeout - elapsed return ( - f'Rate limit exceeded! Retry in {self.timeout-elapsed} seconds' + + f'Rate limit exceeded! Retry in {rem} second{suf(rem)}' + ('' if cfg.btc_only else ', or use --cached-data or --btc')) @property @@ -270,7 +274,8 @@ class DataSource: 'last_updated': data['regularMarketTime']} def rate_limit_errmsg(self, elapsed): - return f'Rate limit exceeded! Retry in {self.timeout-elapsed} seconds, or use --cached-data' + rem = self.timeout - elapsed + return f'Rate limit exceeded! Retry in {rem} second{suf(rem)}, or use --cached-data' @property def json_fn(self): @@ -364,10 +369,10 @@ def gen_data(data): checking for duplicates. """ - def dup_sym_errmsg(dup_sym): + def dup_sym_errmsg(data_type, dup_sym): return ( 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 ' + '\n '.join(d['id'] for d in data[data_type].data 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}') @@ -421,7 +426,7 @@ def gen_data(data): 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('fi', id)) if m := data['hi'].get(k): spot = v['regularMarketPrice']['raw'] hist = tuple(m.values()) @@ -444,7 +449,7 @@ def gen_data(data): 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('cc', 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 diff --git a/test/cmdtest_d/httpd/ticker.py b/test/cmdtest_d/httpd/ticker.py index 2d946ae..6131875 100755 --- a/test/cmdtest_d/httpd/ticker.py +++ b/test/cmdtest_d/httpd/ticker.py @@ -21,7 +21,7 @@ class TickerServer(HTTPD): def make_response_body(self, method, environ): - with open(f'test/ref/ticker/ticker.json') as fh: + with open('test/ref/ticker/ticker.json') as fh: text = fh.read() return text.encode() diff --git a/test/cmdtest_d/misc.py b/test/cmdtest_d/misc.py index e9d4c32..1cde41e 100755 --- a/test/cmdtest_d/misc.py +++ b/test/cmdtest_d/misc.py @@ -61,19 +61,16 @@ class CmdTestScripts(CmdTestBase): color = True cmd_group_in = ( - ('subgroup.ticker_setup', []), - ('subgroup.ticker', ['ticker_setup']), + ('subgroup.ticker', []), ) cmd_subgroups = { - 'ticker_setup': ( - "setup for 'ticker' subgroup", - ('ticker_setup', 'ticker setup'), - ), 'ticker': ( "'mmnode-ticker' script", ('ticker1', 'ticker [--help]'), + ('copy_files', 'copying JSON files to cache'), + ('ticker1a', 'ticker [--download=cc] (early caching)'), + ('ticker1b', 'ticker [--download=cc] (late caching)'), ('ticker2', 'ticker (bad proxy)'), - ('ticker2a', 'ticker [--download=cc]'), ('ticker3', 'ticker [--cached-data]'), ('ticker4', 'ticker [--cached-data --wide]'), ('ticker5', 'ticker [--cached-data --wide --adjust=-0.766] (usr cfg file)'), @@ -103,7 +100,7 @@ class CmdTestScripts(CmdTestBase): def nt_datadir(self): return os.path.join( cfg.data_dir_root, 'node_tools' ) - def ticker_setup(self): + def copy_files(self): self.spawn('',msg_only=True) shutil.copy2(os.path.join(refdir,'ticker-finance.json'),self.tmpdir) shutil.copy2(os.path.join(refdir,'ticker-finance-history.json'),self.tmpdir) @@ -135,20 +132,23 @@ class CmdTestScripts(CmdTestBase): t.expect('USAGE:') return t - def ticker2(self): - t = self.ticker(cached_data=False) - if not cfg.skipping_deps: - t.expect('Creating') - t.expect('Creating') - ret = t.expect(['proxy host could not be resolved', 'unexpected keyword']) - t.exit_val = 1 if ret else 3 - return t - - def ticker2a(self): + def ticker1a(self, first_run=True): t = self.ticker( add_opts = ['--proxy', '', '--download=cc'], cached_data = False, use_proxy = False) + if first_run and not cfg.skipping_deps: + t.expect('Creating') + t.expect('Creating') + return t + + def ticker1b(self): + return self.ticker1a(first_run=False) + + def ticker2(self): + t = self.ticker(cached_data=False) + ret = t.expect(['proxy host could not be resolved', 'unexpected keyword']) + t.exit_val = 1 if ret else 3 return t def ticker3(self):