From 6ccf29fb21f966c899fb7d51464e41d05ed7acf1 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Wed, 1 Oct 2025 15:30:56 +0000 Subject: [PATCH] util.py: new `isAsync()` function --- mmgen/main_tool.py | 8 +++----- mmgen/proto/btc/regtest.py | 6 +++--- mmgen/rpc/local.py | 12 ++++++------ mmgen/tw/view.py | 14 ++++++++------ mmgen/util.py | 3 +++ test/include/unit_test.py | 14 +++++++------- test/tooltest2.py | 6 ++---- 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 72fcfa4c..e9fb79f4 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -23,7 +23,7 @@ mmgen-tool: Perform various MMGen- and cryptocoin-related operations. import sys, os, importlib from .cfg import gc, Config -from .util import msg, Msg, die, capfirst, suf, async_run +from .util import msg, Msg, die, capfirst, suf, async_run, isAsync opts_data = { 'filter_codes': ['-'], @@ -391,10 +391,8 @@ if gc.prog_name.endswith('-tool'): args, kwargs = process_args(cmd, args, cls) - ret = getattr(cls(cfg, cmdname=cmd), cmd)(*args, **kwargs) - - if type(ret).__name__ == 'coroutine': - ret = async_run(ret) + func = getattr(cls(cfg, cmdname=cmd), cmd) + ret = async_run(func(*args, **kwargs)) if isAsync(func) else func(*args, **kwargs) process_result( ret, diff --git a/mmgen/proto/btc/regtest.py b/mmgen/proto/btc/regtest.py index 2fb7b8f1..fcffc6e2 100755 --- a/mmgen/proto/btc/regtest.py +++ b/mmgen/proto/btc/regtest.py @@ -21,7 +21,7 @@ proto.btc.regtest: Coin daemon regression test mode setup and operations """ import os, shutil, json -from ...util import msg, gmsg, die, capfirst, suf +from ...util import msg, gmsg, die, capfirst, suf, isAsync from ...util2 import cliargs_convert from ...protocol import init_proto from ...rpc import rpc_init @@ -256,8 +256,8 @@ class MMGenRegtest(MMGenObject): print(ret if isinstance(ret, str) else json.dumps(ret, cls=json_encoder, indent=4)) async def cmd(self, args): - ret = getattr(self, args[0])(*args[1:]) - return (await ret) if type(ret).__name__ == 'coroutine' else ret + func = getattr(self, args[0]) + return await func(*args[1:]) if isAsync(func) else func(*args[1:]) async def fork(self, coin): # currently disabled diff --git a/mmgen/rpc/local.py b/mmgen/rpc/local.py index 99e3cdd0..c80db98e 100755 --- a/mmgen/rpc/local.py +++ b/mmgen/rpc/local.py @@ -14,7 +14,7 @@ rpc.local: local RPC client class for the MMGen Project import sys, json, asyncio, importlib -from ..util import msg, die, fmt, oneshot_warning +from ..util import msg, die, fmt, oneshot_warning, isAsync from . import util @@ -55,7 +55,7 @@ class RPCClient: self.timeout = self.cfg.http_timeout or 60 self.auth = None - def _get_backend(self, backend): + def _get_backend_cls(self, backend): dfl_backends = { 'linux': 'httplib', 'darwin': 'httplib', @@ -63,14 +63,14 @@ class RPCClient: def get_cls(backend_id): return getattr(importlib.import_module(f'mmgen.rpc.backends.{backend_id}'), backend_id) backend_id = backend or self.cfg.rpc_backend - return get_cls(dfl_backends[sys.platform] if backend_id == 'auto' else backend_id)(self) + return get_cls(dfl_backends[sys.platform] if backend_id == 'auto' else backend_id) def set_backend(self, backend=None): - self.backend = self._get_backend(backend) + self.backend = self._get_backend_cls(backend)(self) async def set_backend_async(self, backend=None): - ret = self._get_backend(backend) - self.backend = (await ret) if type(ret).__name__ == 'coroutine' else ret + cls = self._get_backend_cls(backend) + self.backend = await cls(self) if isAsync(cls.__init__) else cls(self) # Call family of methods - direct-to-daemon RPC call: # - positional params are passed to the daemon, 'timeout' and 'wallet' kwargs to the backend diff --git a/mmgen/tw/view.py b/mmgen/tw/view.py index c249d5e8..03dad975 100755 --- a/mmgen/tw/view.py +++ b/mmgen/tw/view.py @@ -27,7 +27,7 @@ from ..cfg import gv from ..objmethods import MMGenObject from ..obj import get_obj, MMGenIdx, MMGenList from ..color import nocolor, yellow, orange, green, red, blue -from ..util import msg, msg_r, fmt, die, capfirst, suf, make_timestr +from ..util import msg, msg_r, fmt, die, capfirst, suf, make_timestr, isAsync from ..rpc import rpc_init from ..base_obj import AsyncInit @@ -288,8 +288,10 @@ class TwView(MMGenObject, metaclass=AsyncInit): lbl_id = ('account', 'label')['label_api' in self.rpc.caps] - res = self.gen_data(rpc_data, lbl_id) - self.data = MMGenList(await res if type(res).__name__ == 'coroutine' else res) + self.data = MMGenList( + await self.gen_data(rpc_data, lbl_id) if isAsync(self.gen_data) else + self.gen_data(rpc_data, lbl_id)) + self.disp_data = list(self.filter_data()) if not self.data: @@ -607,9 +609,9 @@ class TwView(MMGenObject, metaclass=AsyncInit): match reply: case ch if ch in key_mappings: - ret = action_classes[ch].run(self, action_methods[ch]) - if type(ret).__name__ == 'coroutine': - await ret + func = action_classes[ch].run + arg = action_methods[ch] + await func(self, arg) if isAsync(func) else func(self, arg) case 'q': msg('') if self.scroll: diff --git a/mmgen/util.py b/mmgen/util.py index 8109d8b2..a54af3c0 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -487,3 +487,6 @@ def cached_property(orig_func): setattr(self, attr_name, orig_func(self)) return getattr(self, attr_name) return new_func + +def isAsync(func): + return bool(func.__code__.co_flags & 128) diff --git a/test/include/unit_test.py b/test/include/unit_test.py index ef528a66..ed9ac99b 100755 --- a/test/include/unit_test.py +++ b/test/include/unit_test.py @@ -32,7 +32,7 @@ if not os.getenv('MMGEN_DEVTOOLS'): from mmgen.cfg import Config, gc, gv from mmgen.color import gray, brown, orange, yellow, red -from mmgen.util import msg, msg_r, gmsg, ymsg, Msg +from mmgen.util import msg, msg_r, gmsg, ymsg, Msg, isAsync from test.include.common import set_globals, end_msg @@ -151,9 +151,7 @@ class UnitTestHelpers: for (desc, exc_chk, emsg_chk, func) in data: try: cfg._util.vmsg_r(' {}{:{w}}'.format(pfx, desc+':', w=desc_w+1)) - ret = func() - if type(ret).__name__ == 'coroutine': - asyncio.run(ret) + asyncio.run(func()) if isAsync(func) else func() except Exception as e: exc = type(e).__name__ emsg = e.args[0] if e.args else '(unspecified error)' @@ -187,9 +185,11 @@ def run_test(test, subtest=None): elif not cfg.quiet: msg_r(f'Testing {func.__defaults__[0]}...') - ret = func(test, UnitTestHelpers(subtest)) - if type(ret).__name__ == 'coroutine': - ret = asyncio.run(ret) + if isAsync(func): + ret = asyncio.run(func(test, UnitTestHelpers(subtest))) + else: + ret = func(test, UnitTestHelpers(subtest)) + if do_desc and not cfg.quiet: msg('OK\n' if cfg.verbose else 'OK') except: diff --git a/test/tooltest2.py b/test/tooltest2.py index c88d7b69..c7cce471 100755 --- a/test/tooltest2.py +++ b/test/tooltest2.py @@ -36,7 +36,7 @@ from test.include.common import set_globals, end_msg, init_coverage from mmgen import main_tool from mmgen.cfg import Config from mmgen.color import green, blue, purple, cyan, gray -from mmgen.util import msg, msg_r, Msg, die +from mmgen.util import msg, msg_r, Msg, die, isAsync skipped_tests = ['mn2hex_interactive'] coin_dependent_groups = ('Coin', 'File') @@ -134,9 +134,7 @@ def call_method(cls, method, cmd_name, args, mmtype, stdin_input): vmsg(f'Input: {stdin_input!r}') sys.exit(0) else: - ret = method(*aargs, **kwargs) - if type(ret).__name__ == 'coroutine': - ret = asyncio.run(ret) + ret = asyncio.run(method(*aargs, **kwargs)) if isAsync(method) else method(*aargs, **kwargs) cfg._set_quiet(oq_save) return ret