chainlib

Generic blockchain access library and tooling
Log | Files | Refs | README | LICENSE

commit 45575c3c2991c5e07a0407fab090483ebc2bfdef
parent b8ee1ed97d14f8aec8daa161614335aef4c3e899
Author: lash <dev@holbrook.no>
Date:   Thu, 13 Oct 2022 13:02:57 +0000

Merge tag 'v0.3.1'

Diffstat:
MCHANGELOG | 17+++++++++++++++++
Mchainlib/block.py | 50++++++++++++++++++++------------------------------
Mchainlib/cli/arg.py | 318+++++++++++++++++++++++++++++--------------------------------------------------
Mchainlib/cli/config.py | 343++++++++++++++++++++-----------------------------------------------------------
Mchainlib/cli/man.py | 58+++++++++++++++++++++++++++++++---------------------------
Mchainlib/cli/rpc.py | 2+-
Mchainlib/data/config/config.ini | 9+++++++++
Mchainlib/error.py | 7++++++-
Mchainlib/settings.py | 37++++++++++++++++++++++++++++++-------
Mchainlib/status.py | 1+
Mchainlib/tx.py | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mrequirements.txt | 5+++--
Mscripts/chainlib-man.py | 17++++++++++-------
Msetup.cfg | 2+-
Mtests/test_cli.py | 101+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
15 files changed, 471 insertions(+), 576 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,3 +1,20 @@ +- 0.3.1 + * Update man page generation script to use new cli flag builder +- 0.3.0 + * Implement arg handling from aiee + * Implement value argument handling from aiee +- 0.2.1 + * Fix bug in wire format generation for tx +- 0.2.0 + * Consolidate genertic blcok, tx and tx result objects +- 0.1.3 + * Remove superfluous arguments for chain settings instantiation +- 0.1.2 + * Upgrade hexathon dep +- 0.1.1 + * Add settings object +- 0.1.0 + * Upgrade deps - 0.0.23 * Configuration variable descriptions * Arg flags to names listing method diff --git a/chainlib/block.py b/chainlib/block.py @@ -2,7 +2,8 @@ import enum # local imports -from chainlib.tx import Tx +from .tx import Tx +from .src import Src class BlockSpec(enum.IntEnum): @@ -12,23 +13,28 @@ class BlockSpec(enum.IntEnum): LATEST = 0 -class Block: +class Block(Src): """Base class to extend for implementation specific block object. """ tx_generator = Tx + def __init__(self, src=None): + self.number = None + self.txs = [] + self.author = None - def src(self): - """Return implementation specific block representation. + self.get_tx = self.tx_index_by_hash + self.tx = self.tx_by_index - :rtype: dict - :returns: Block representation - """ - return self.block_src + self.fee_limit = 0 + self.fee_cost = 0 + self.parent_hash = None + + super(Block, self).__init__(src=src) - def tx(self, idx): + def tx_by_index(self, idx): """Return transaction object for transaction data at given index. :param idx: Transaction index @@ -39,28 +45,12 @@ class Block: return self.tx_generator(self.txs[idx], self) - def tx_src(self, idx): - """Return implementation specific transaction representation for transaction data at given index - - :param idx: Transaction index - :type idx: int - :rtype: chainlib.tx.Tx - :returns: Transaction representation - """ - return self.txs[idx] + def tx_index_by_hash(self, hsh): + for tx in self.tx: + if tx == hsh: + return tx + return -1 def __str__(self): return 'block {} {} ({} txs)'.format(self.number, self.hash, len(self.txs)) - - - @classmethod - def from_src(cls, src): - """Instantiate an implementation specific block object from the given block representation. - - :param src: Block representation - :type src: dict - :rtype: chainlib.block.Block - :returns: Block object - """ - return cls(src) diff --git a/chainlib/cli/arg.py b/chainlib/cli/arg.py @@ -1,227 +1,143 @@ # standard imports import logging import argparse -import enum -import os -import select +#import enum +#import os +#import select import sys -import re +#import re -# local imports -from .base import ( - default_config_dir, - Flag, - argflag_std_target, +# external imports +from aiee.arg import ( + ArgFlag as BaseArgFlag, + Arg as BaseArg, + process_args, ) logg = logging.getLogger(__name__) -def stdin_arg(): - """Retreive input arguments from stdin if they exist. +#def stdin_arg(): +# """Retreive input arguments from stdin if they exist. +# +# Method does not block, and expects arguments to be ready on stdin before being called. +# +# :rtype: str +# :returns: Input arguments string +# """ +# h = select.select([sys.stdin], [], [], 0) +# if len(h[0]) > 0: +# v = h[0][0].read() +# return v.rstrip() +# return None - Method does not block, and expects arguments to be ready on stdin before being called. - - :rtype: str - :returns: Input arguments string - """ - h = select.select([sys.stdin], [], [], 0) - if len(h[0]) > 0: - v = h[0][0].read() - return v.rstrip() - return None +class ArgumentParser(argparse.ArgumentParser): -_default_long_args = { - '-a': '--recipient', - '-e': '--executable-address', - '-s': '--send', - '-y': '--key-file', - } + def parse_args(self, argv=sys.argv[1:]): + if '--dumpconfig' in argv: + argv = [argv[0], '--dumpconfig'] + return super(ArgumentParser, self).parse_args(args=argv) + + +class ArgFlag(BaseArgFlag): + + def __init__(self): + super(ArgFlag, self).__init__() + + self.add('verbose') + self.add('config') + self.add('raw') + self.add('env') + self.add('provider') + self.add('chain_spec') + self.add('target') + self.add('unsafe') + self.add('seq') + self.add('key_file') + self.add('fee') + self.add('nonce') + self.add('no_target') + self.add('exec') + self.add('wallet') + self.add('wait') + self.add('wait_all') + self.add('send') + self.add('rpc_auth') + self.add('fmt_human') + self.add('fmt_wire') + self.add('fmt_rpc') + self.add('veryverbose') + self.add('path') + self.add('backend') + + self.alias('sign', 'key_file', 'send') + self.alias('std_base', 'verbose', 'config', 'raw', 'env', 'target') + self.alias('std_base_read', 'verbose', 'config', 'raw', 'env', 'provider', 'chain_spec', 'seq') + self.alias('std_read', 'std_base', 'provider', 'chain_spec', 'unsafe', 'seq', 'sign', 'fee', 'target') + self.alias('std_write', 'verbose', 'config', 'raw', 'env', 'provider', 'chain_spec', 'unsafe', 'seq', 'key_file', 'sign', 'target', 'wait', 'wait_all', 'send', 'rpc_auth', 'nonce', 'fee') + self.alias('std_target', 'no_target', 'exec', 'wallet') + self.alias('state', 'backend', 'path') + +class Arg(BaseArg): + + def __init__(self, flags): + super(Arg, self).__init__(flags) + + self.add_long('no-logs', 'verbose', typ=bool, help='Turn off all logging') + self.add('v', 'verbose', typ=bool, help='Be verbose') + self.add('vv', 'verbose', check=False, typ=bool, help='Be more verbose') + self.add('vvv', 'veryverbose', check=False, typ=bool, help='Be morse verbose with custom tracing') -_default_dest = { - '-a': 'recipient', - '-e': 'executable_address', - } + self.add('n', 'config', help='Configuration namespace') + self.set_long('n', 'namespace', dest='namespace') + self.add('c', 'config', dest='config', help='Configuration directory') + self.set_long('c', 'config') + self.add_long('dumpconfig', 'config', help='Output configuration and quit. Use with --raw to omit values and output schema only.') + self.add('a', 'wallet', dest='recipient', help='Recipient address') + self.set_long('a', 'recipient') -_default_fmt = 'human' + self.add('e', 'exec', dest='executable_address', help='Recipient address') + self.set_long('e', 'executable') + self.add('w', 'wait', typ=bool, help='Wait for the last transaction to be confirmed') + self.add('ww', 'wait', check=False, typ=bool, help='Wait for every transaction to be confirmed') -class ArgumentParser(argparse.ArgumentParser): - """Extends the standard library argument parser to construct arguments based on configuration flags. - - The extended class is set up to facilitate piping of single positional arguments via stdin. For this reason, positional arguments should be added using the locally defined add_positional method instead of add_argument. - - Long flag aliases for short flags are editable using the arg_long argument. Editing a non-existent short flag will produce no error and have no effect. Adding a long flag for a short flag that does not have an alias will also not have effect. - - Calls chainlib.cli.args.ArgumentParser.process_flags with arg_flags and env arguments, see the method's documentation for further details. - - :param arg_flags: Argument flag bit vector to generate configuration values for. - :type arg_flags: chainlib.cli.Flag - :param arg_long: Change long flag alias for given short flags. Example value: {'-a': '--addr', '-e': '--contract'} - :type arg_long: dict - :param env: Environment variables - :type env: dict - :param usage: Usage string, passed to parent - :type usage: str - :param description: Description string, passed to parent - :type description: str - :param epilog: Epilog string, passed to parent - :type epilog: str - """ - - def __init__(self, arg_flags=0x0f, arg_long={}, env=os.environ, usage=None, description=None, epilog=None, default_format=_default_fmt, *args, **kwargs): - super(ArgumentParser, self).__init__(usage=usage, description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter, *args, **kwargs) - - self.pos_args = [] - self.long_args = _default_long_args - self.arg_dest = _default_dest - self.default_format = default_format + self.add_long('env-prefix', 'env', help='environment prefix for variables to overwrite configuration') + + self.add('p', 'provider', help='RPC HTTP(S) provider url') + self.set_long('p', 'provider') + self.add_long('rpc-dialect', 'provider', help='RPC HTTP(S) backend dialect') + self.add_long('rpc-timeout', 'provider', help='RPC autentication credential values') + self.add_long('rpc-proxy', 'provider', help='RPC autentication credential values') - re_long = r'^--[a-z\-]+$' - for k in arg_long.keys(): - if re.match(re_long, arg_long[k]) == None: - raise ValueError('invalid long arg {}'.format(arg_long[k])) - self.long_args[k] = arg_long[k] - dest = arg_long[k][2:] - dest = dest.replace('-', '_') - self.arg_dest[k] = dest + self.add_long('height', 'target', default='latest', help='Block height to execute against') + + self.add_long('rpc-auth', 'rpc_auth', help='RPC autentication scheme') + self.add_long('rpc-credentials', 'rpc_auth', help='RPC autentication credential values') - self.process_flags(arg_flags, env) + self.add('i', 'chain_spec', help='Chain specification string') + self.set_long('i', 'chain-spec') + self.add('u', 'unsafe', typ=bool, help='Do not verify address checksums') + self.set_long('u', 'unsafe') - def add_positional(self, name, type=str, help=None, append=False, required=True): - """Add a positional argument. + self.add_long('seq', 'seq', typ=bool, help='Use sequential rpc ids') - Stdin piping will only be possible in the event a single positional argument is defined. + self.add('y', 'key_file', help='Keystore file to use for signing or address') + self.set_long('y', 'key_file') + self.add_long('passphrase-file', 'key_file', help='Keystore file to use for signing or address') - If the "required" is set, the resulting parsed arguments must have provided a value either from stdin or excplicitly on the command line. + self.add('s', 'send', typ=bool, help='Send to network') + self.set_long('s', 'send') - :param name: Attribute name of argument - :type name: str - :param type: Argument type - :type type: str - :param help: Help string - :type help: str - :param required: If true, argument will be set to required - :type required: bool - """ - self.pos_args.append((name, type, help, required, append,)) + self.add_long('raw', 'raw', typ=bool, help='Do not decode output') + self.add('0', 'raw', typ=bool, help='Omit newline to output') + self.add_long('nonce', 'nonce', typ=int, help='override nonce') + self.add_long('fee-price', 'fee', typ=int, help='override fee price') + self.add_long('fee-limit', 'fee', typ=int, help='override fee limit') - def parse_args(self, argv=sys.argv[1:]): - """Overrides the argparse.ArgumentParser.parse_args method. - - Implements reading arguments from stdin if a single positional argument is defined (and not set to required). - - If the "required" was set for the single positional argument, the resulting parsed arguments must have provided a value either from stdin or excplicitly on the command line. - - :param argv: Argument vector to process - :type argv: list - """ - if len(self.pos_args) == 1: - arg = self.pos_args[0] - if arg[4]: - self.add_argument(arg[0], nargs='*', type=arg[1], default=stdin_arg(), help=arg[2]) - else: - self.add_argument(arg[0], nargs='?', type=arg[1], default=stdin_arg(), help=arg[2]) - else: - for arg in self.pos_args: - if arg[3]: - if arg[4]: - self.add_argument(arg[0], nargs='+', type=arg[1], help=arg[2]) - else: - self.add_argument(arg[0], type=arg[1], help=arg[2]) - else: - if arg[4]: - self.add_argument(arg[0], nargs='*', type=arg[1], help=arg[2]) - else: - self.add_argument(arg[0], type=arg[1], help=arg[2]) - args = super(ArgumentParser, self).parse_args(args=argv) - - if getattr(args, 'dumpconfig', None) != None: - return args - - if len(self.pos_args) == 1: - arg = self.pos_args[0] - argname = arg[0] - required = arg[3] - if getattr(args, arg[0], None) == None: - argp = stdin_arg() - if argp == None and required: - self.error('need first positional argument or value from stdin') - setattr(args, arg[0], argp) - - return args - - - def process_flags(self, arg_flags, env): - """Configures the arguments of the parser using the provided flags. - - Environment variables are used for default values for: - - CONFINI_DIR: -c, --config - CONFINI_ENV_PREFIX: --env-prefix - - This method is called by the constructor, and is not intended to be called directly. - - :param arg_flags: Argument flag bit vector to generate configuration values for. - :type arg_flags: chainlib.cli.Flag - :param env: Environment variables - :type env: dict - """ - if arg_flags & Flag.VERBOSE: - self.add_argument('--no-logs', dest='no_logs',action='store_true', help='Turn off all logging') - self.add_argument('-v', action='store_true', help='Be verbose') - self.add_argument('-vv', action='store_true', help='Be more verbose') - if arg_flags & Flag.CONFIG: - self.add_argument('-c', '--config', type=str, default=env.get('CONFINI_DIR'), help='Configuration directory') - self.add_argument('-n', '--namespace', type=str, help='Configuration namespace') - self.add_argument('--dumpconfig', type=str, choices=['env', 'ini'], help='Output configuration and quit. Use with --raw to omit values and output schema only.') - if arg_flags & Flag.WAIT: - self.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed') - self.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed') - if arg_flags & Flag.ENV_PREFIX: - self.add_argument('--env-prefix', default=env.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration') - if arg_flags & Flag.PROVIDER: - self.add_argument('-p', '--rpc-provider', dest='p', type=str, help='RPC HTTP(S) provider url') - self.add_argument('--rpc-dialect', dest='rpc_dialect', type=str, help='RPC HTTP(S) backend dialect') - if arg_flags & Flag.NO_TARGET == 0: - self.add_argument('--height', default='latest', help='Block height to execute against') - if arg_flags & Flag.RPC_AUTH: - self.add_argument('--rpc-auth', dest='rpc_auth', type=str, help='RPC autentication scheme') - self.add_argument('--rpc-credentials', dest='rpc_credentials', type=str, help='RPC autentication credential values') - if arg_flags & Flag.CHAIN_SPEC: - self.add_argument('-i', '--chain-spec', dest='i', type=str, help='Chain specification string') - if arg_flags & Flag.UNSAFE: - self.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Do not verify address checksums') - if arg_flags & Flag.SEQ: - self.add_argument('--seq', action='store_true', help='Use sequential rpc ids') - if arg_flags & Flag.KEY_FILE: - self.add_argument('-y', self.long_args['-y'], dest='y', type=str, help='Keystore file to use for signing or address') - self.add_argument('--passphrase-file', dest='passphrase_file', type=str, help='File containing passphrase for keystore') - if arg_flags & Flag.SEND: - self.add_argument('-s', self.long_args['-s'], dest='s', action='store_true', help='Send to network') - if arg_flags & Flag.RAW: - self.add_argument('--raw', action='store_true', help='Do not decode output') - if arg_flags & (Flag.SIGN | Flag.NONCE): - self.add_argument('--nonce', type=int, help='override nonce') - if arg_flags & (Flag.SIGN | Flag.FEE): - self.add_argument('--fee-price', dest='fee_price', type=int, help='override fee price') - self.add_argument('--fee-limit', dest='fee_limit', type=int, help='override fee limit') - # wtf? - #if arg_flags & argflag_std_target == 0: - # arg_flags |= Flag.WALLET - if arg_flags & Flag.EXEC: - self.add_argument('-e', self.long_args['-e'], dest=self.arg_dest['-e'], type=str, help='contract address') - if arg_flags & Flag.WALLET: - self.add_argument('-a', self.long_args['-a'], dest=self.arg_dest['-a'], type=str, help='recipient address') - if arg_flags & (Flag.FMT_HUMAN | Flag.FMT_WIRE | Flag.FMT_RPC): - format_choices = [] - if arg_flags & Flag.FMT_HUMAN: - format_choices.append('human') - if arg_flags & Flag.FMT_WIRE: - format_choices.append('bin') - if arg_flags & Flag.FMT_RPC: - format_choices.append('rpc') - self.add_argument('-f', '--format', type=str, choices=format_choices, help='output formatting (default: {})'.format(self.default_format)) + self.add_long('state-path', 'path', help='Path to store state data under') + self.add_long('runtime-path', 'path', help='Path to store volatile data under') + self.add_long('backend', 'backend', help='Backend to use for data storage') diff --git a/chainlib/cli/config.py b/chainlib/cli/config.py @@ -36,277 +36,102 @@ class Config(confini.Config): default_base_config_dir = default_parent_config_dir default_fee_limit = 0 + def __init__(self, config_dir=None, namespace=None): + self.namespace = namespace + if config_dir == None: + config_dir = self.default_base_config_dir + if self.namespace != None: + config_dir = os.path.join(config_dir, namespace) + super(Config, self).__init__(config_dir) - @staticmethod - def override_defaults(base_dir=None, default_fee_limit=None): - if base_dir != None: - Config.default_base_config_dir = os.path.realpath(base_dir) - if default_fee_limit != None: - Config.default_fee_limit = int(default_fee_limit) + def add_user_dir(self, v): + if self.namespace != None: + v = os.path.join(v, self.namespace) + return super(Config, self).add_override_dir(v) - @classmethod - def from_args(cls, args, arg_flags=0x0f, env=os.environ, extra_args={}, base_config_dir=None, default_config_dir=None, user_config_dir=None, default_fee_limit=None, logger=None, load_callback=None, dump_writer=sys.stdout): - """Parses arguments in argparse.ArgumentParser instance, then match and override configuration values that match them. - The method processes all known argument flags from chainlib.cli.Flag passed in the "args" argument. +def process_config(config, arg, args, flags): - All entries in extra_args may be used to associate arguments not defined in the argument flags with configuration variables, in the following manner: + if arg.match('env', flags): + config.set_env_prefix(getattr(args, 'env_prefix')) - - The value of argparser.ArgumentParser instance attribute with the dictionary key string is looked up. - - If the value is None (defined but empty), any existing value for the configuration directive will be kept. - - If the value of the extra_args dictionary entry is None, then the value will be stored in the configuration under the upper-case value of the key string, prefixed with "_" ("foo_bar" becomes "_FOO_BAR") - - If the value of the extra_args dictionary entries is a string, then the value will be stored in the configuration under that literal string. + config.process() + + args_override = {} + + if arg.match('raw', flags): + config.add(getattr(args, 'raw', None), '_RAW') - Missing attributes defined by both the "args" and "extra_args" arguments will both raise an AttributeError. - - The python package "confini" is used to process and render the configuration. - - The confini config schema is determined in the following manner: - - - If nothing is set, only the config folder in chainlib.data.config will be used as schema. - - If base_config_dir is a string or list, the config directives from the path(s) will be added to the schema. - - The global override config directories are determined in the following manner: - - - If no default_config_dir is defined, the environment variable CONFINI_DIR will be used. - - If default_config_dir is a string or list, values from the config directives from the path(s) will override those defined in the schema(s). - - The user override config directories work the same way as the global ones, but the namespace - if defined - are dependent on them. They are only applied if the CONFIG arg flag is set. User override config directories are determined in the following manner: - - - If --config argument is not defined and the pyxdg module is present, the first available xdg basedir is used. - - If --config argument is defined, the directory defined by its value will be used. - - The namespace, if defined, will be stored under the CONFIG_USER_NAMESPACE configuration key. - - :param args: Argument parser object - :type args: argparse.ArgumentParser - :param arg_flags: Argument flags defining which arguments to process into configuration. - :type arg_flags: confini.cli.args.ArgumentParser - :param env: Environment variables selection - :type env: dict - :param extra_args: Extra arguments to process and override. - :type extra_args: dict - :param base_config_dir: Path(s) to one or more directories extending the base chainlib config schema. - :type base_config_dir: list or str - :param default_config_dir: Path(s) to one or more directories overriding the defaults defined in the schema config directories. - :type default_config_dir: list or str - :param user_config_dir: User xdg config basedir, with namespace - :type user_config_dir: str - :param default_fee_limit: Default value for fee limit argument - :type default_fee_limit: int - :param logger: Logger instance to use during argument processing (will use package namespace logger if None) - :type logger: logging.Logger - :param load_callback: Callback receiving config instance as argument after config processing and load completes. - :type load_callback: function - :raises AttributeError: Attribute defined in flag not found in parsed arguments - :rtype: confini.Config - :return: Processed configuation - """ - env_prefix = getattr(args, 'env_prefix', None) - env_prefix_str = env_prefix - if env_prefix_str == None: - env_prefix_str = '' - else: - env_prefix_str += '_' - - env_loglevel_key_str = env_prefix_str + 'LOGLEVEL' - env_loglevel = os.environ.get(env_loglevel_key_str) - - if logger == None: - logger = logging.getLogger() - - if env_loglevel != None: - env_loglevel = env_loglevel.lower() - if env_loglevel == '0' or env_loglevel == 'no' or env_loglevel == 'none' or env_loglevel == 'disable' or env_loglevel == 'disabled' or env_loglevel == 'off': - logging.disable() - elif env_loglevel == '1' or env_loglevel == 'err' or env_loglevel == 'error': - logger.setLevel(logging.ERROR) - elif env_loglevel == '2' or env_loglevel == 'warning' or env_loglevel == 'warn': - logger.setLevel(logging.WARNING) - elif env_loglevel == '3' or env_loglevel == 'info': - logger.setLevel(logging.INFO) - else: - valid_level = False - try: - num_loglevel = int(env_loglevel) - valid_level = True - except: - if env_loglevel == 'debug': - valid_level = True - - if not valid_level: - raise ValueError('unknown loglevel {} set in environment variable {}'.format(env_loglevel, env_loglevel_key_str)) - - logger.setLevel(logging.DEBUG) - - - if arg_flags & Flag.VERBOSE: - if args.vv: - logger.setLevel(logging.DEBUG) - elif args.v: - logger.setLevel(logging.INFO) - if args.no_logs: - logging.disable() - - override_config_dirs = [] - config_dir = [cls.default_base_config_dir] - - if user_config_dir == None: - try: - import xdg.BaseDirectory - user_config_dir = xdg.BaseDirectory.load_first_config('chainlib/eth') - except ModuleNotFoundError: - pass - - # if one or more additional base dirs are defined, add these after the default base dir - # the consecutive dirs cannot include duplicate sections - if base_config_dir != None: - logg.debug('have explicit base config addition {}'.format(base_config_dir)) - if isinstance(base_config_dir, str): - base_config_dir = [base_config_dir] - for d in base_config_dir: - config_dir.append(d) - logg.debug('processing config dir {}'.format(config_dir)) - - # confini dir env var will be used for override configs only in this case - if default_config_dir == None: - default_config_dir = env.get('CONFINI_DIR') - if default_config_dir != None: - if isinstance(default_config_dir, str): - default_config_dir = [default_config_dir] - for d in default_config_dir: - override_config_dirs.append(d) - - # process config command line arguments - if arg_flags & Flag.CONFIG: - effective_user_config_dir = getattr(args, 'config', None) - if effective_user_config_dir == None: - effective_user_config_dir = user_config_dir - if effective_user_config_dir != None: - if getattr(args, 'namespace', None) != None: - effective_user_config_dir = os.path.join(effective_user_config_dir, args.namespace) - #if config_dir == None: - # config_dir = [cls.default_base_config_dir, effective_user_config_dir] - # logg.debug('using config arg as base config addition {}'.format(effective_user_config_dir)) - #else: - override_config_dirs.append(effective_user_config_dir) - logg.debug('using config arg as config override {}'.format(effective_user_config_dir)) - - #if config_dir == None: - # if default_config_dir == None: - # default_config_dir = default_parent_config_dir - # config_dir = default_config_dir - # override_config_dirs = [] - - config = confini.Config(config_dir, env_prefix=env_prefix, override_dirs=override_config_dirs) - config.process() - - config.add(getattr(args, 'raw'), '_RAW') - - args_override = {} - - if arg_flags & Flag.PROVIDER: - args_override['RPC_PROVIDER'] = getattr(args, 'p') - args_override['RPC_DIALECT'] = getattr(args, 'rpc_dialect') - if arg_flags & Flag.CHAIN_SPEC: - args_override['CHAIN_SPEC'] = getattr(args, 'i') - if arg_flags & Flag.KEY_FILE: - args_override['WALLET_KEY_FILE'] = getattr(args, 'y') - fp = getattr(args, 'passphrase_file') - if fp != None: - st = os.stat(fp) - if stat.S_IMODE(st.st_mode) & (stat.S_IRWXO | stat.S_IRWXG) > 0: - logg.warning('others than owner have access on password file') - f = open(fp, 'r') - args_override['WALLET_PASSPHRASE'] = f.read() - f.close() - config.censor('PASSPHRASE', 'WALLET') - config.dict_override(args_override, 'cli args', allow_empty=True) - - if arg_flags & (Flag.PROVIDER | Flag.NO_TARGET) == Flag.PROVIDER: + if arg.match('provider', flags): + args_override['RPC_PROVIDER'] = getattr(args, 'p') + args_override['RPC_DIALECT'] = getattr(args, 'rpc_dialect') + + if arg.match('chain_spec', flags): + args_override['CHAIN_SPEC'] = getattr(args, 'i') + + if arg.match('config', flags): + config.add(getattr(args, 'namespace', None), 'CONFIG_USER_NAMESPACE') + + if arg.match('key_file', flags): + args_override['WALLET_KEY_FILE'] = getattr(args, 'y') + fp = getattr(args, 'passphrase_file') + if fp != None: + st = os.stat(fp) + if stat.S_IMODE(st.st_mode) & (stat.S_IRWXO | stat.S_IRWXG) > 0: + logg.warning('others than owner have access on password file') + f = open(fp, 'r') + args_override['WALLET_PASSPHRASE'] = f.read() + f.close() + config.censor('PASSPHRASE', 'WALLET') + + if arg.match('backend', flags): + args_override['STATE_BACKEND'] = getattr(args, 'backend') + + if arg.match('path', flags): + args_override['STATE_PATH'] = getattr(args, 'state_path') + + config.dict_override(args_override, 'cli args', allow_empty=True) + + if arg.match('provider', flags): + if arg.match('target', flags): config.add(getattr(args, 'height'), '_HEIGHT') - if arg_flags & Flag.UNSAFE: - config.add(getattr(args, 'u'), '_UNSAFE') - if arg_flags & (Flag.SIGN | Flag.FEE): + + if arg.match('unsafe', flags): + config.add(getattr(args, 'u'), '_UNSAFE') + + if arg.match('sign', flags): + config.add(getattr(args, 's'), '_RPC_SEND') + + if arg.match('fee', flags): config.add(getattr(args, 'fee_price'), '_FEE_PRICE') fee_limit = getattr(args, 'fee_limit') if fee_limit == None: - fee_limit = default_fee_limit - if fee_limit == None: - fee_limit = cls.default_fee_limit + fee_limit = int(config.get('CHAIN_MIN_FEE')) config.add(fee_limit, '_FEE_LIMIT') - if arg_flags & (Flag.SIGN | Flag.NONCE): - config.add(getattr(args, 'nonce'), '_NONCE') - if arg_flags & Flag.SIGN: - config.add(getattr(args, 's'), '_RPC_SEND') + if arg.match('nonce', flags): + config.add(getattr(args, 'nonce'), '_NONCE') - # handle wait - wait = 0 - if args.w: - wait |= Flag.WAIT + if arg.match('wait', flags): if args.ww: - wait |= Flag.WAIT_ALL - wait_last = wait & (Flag.WAIT | Flag.WAIT_ALL) - config.add(bool(wait_last), '_WAIT') - wait_all = wait & Flag.WAIT_ALL - config.add(bool(wait_all), '_WAIT_ALL') - - - if arg_flags & Flag.SEQ: - config.add(getattr(args, 'seq'), '_SEQ') - if arg_flags & Flag.WALLET: - config.add(getattr(args, 'recipient'), '_RECIPIENT') - if arg_flags & Flag.EXEC: - config.add(getattr(args, 'executable_address'), '_EXEC_ADDRESS') - - if arg_flags & Flag.CONFIG: - config.add(getattr(args, 'namespace'), 'CONFIG_USER_NAMESPACE') - - if arg_flags & Flag.RPC_AUTH: - config.add(getattr(args, 'rpc_auth'), 'RPC_AUTH') - config.add(getattr(args, 'rpc_credentials'), 'RPC_CREDENTIALS') - - for k in extra_args.keys(): - logg.debug('extra_agrs {}'.format(k)) - v = extra_args[k] - if v == None: - v = '_' + k.upper() - r = getattr(args, k) - existing_r = None - try: - existing_r = config.get(v) - except KeyError: - pass - if existing_r == None or r != None: - config.add(r, v, exists_ok=True) - logg.debug('added {} to {}'.format(r, v)) - - if getattr(args, 'dumpconfig', None): - if args.dumpconfig == 'ini': - from confini.export import ConfigExporter - exporter = ConfigExporter(config, target=sys.stdout, doc=False) - exporter.export(exclude_sections=['config']) - elif args.dumpconfig == 'env': - from confini.env import export_env - export_env(config) - -# config_keys = config.all() -# with_values = not config.get('_RAW') -# for k in config_keys: -# if k[0] == '_': -# continue -# s = k + '=' -# if with_values: -# v = config.get(k) -# if v != None: -# s += str(v) -# s += '\n' -# dump_writer.write(s) - sys.exit(0) - - if load_callback != None: - load_callback(config) - - return config + config.add(True, '_WAIT_ALL') + config.add(True, '_WAIT') + elif args.w: + config.add(True, '_WAIT') + + if arg.match('seq', flags): + config.add(getattr(args, 'seq'), '_SEQ') + + if arg.match('wallet', flags): + config.add(getattr(args, 'recipient'), '_RECIPIENT') + + if arg.match('exec', flags): + config.add(getattr(args, 'executable_address'), '_EXEC_ADDRESS') + + if arg.match('rpc_auth', flags): + config.add(getattr(args, 'rpc_auth'), 'RPC_AUTH') + config.add(getattr(args, 'rpc_credentials'), 'RPC_CREDENTIALS') + + + return config diff --git a/chainlib/cli/man.py b/chainlib/cli/man.py @@ -5,10 +5,11 @@ import os import confini # local imports -from .base import ( - Flag, - argflag_std_target, - ) +#from .base import ( +# Flag, +# argflag_std_target, +# ) +from chainlib.cli.arg import ArgFlag script_dir = os.path.dirname(os.path.realpath(__file__)) data_dir = os.path.join(script_dir, '..', 'data') @@ -92,6 +93,7 @@ class DocGenerator: self.arg_flags = arg_flags self.docs = {} self.envs = {} + self.__argflag_list = ArgFlag() # self.envs = {} @@ -146,7 +148,7 @@ class DocGenerator: def process_arg(self): - if self.arg_flags & Flag.VERBOSE: + if self.arg_flags & self.__argflag_list.VERBOSE: o = DocEntry('--no-logs') o.set_groff('Turn of logging completely. Negates \\fB-v\\fP and \\fB-vv\\fP') self.docs['nologs'] = o @@ -159,7 +161,7 @@ class DocGenerator: o.set_groff('Very verbose. Show logs with debugging information.') self.docs['vv'] = o - if self.arg_flags & Flag.CONFIG: + if self.arg_flags & self.__argflag_list.CONFIG: o = DocEntry('-c', '--config') o.set_groff('Load configuration files from given directory. All files with an .ini extension will be loaded, of which all must contain valid ini file data.') o.set_groff_argvalue('config_dir') @@ -176,7 +178,7 @@ class DocGenerator: self.docs['dumpconfig'] = o - if self.arg_flags & Flag.WAIT: + if self.arg_flags & self.__argflag_list.WAIT: o = DocEntry('-w') o.set_groff('Wait for the last transaction to be confirmed on the network. Will generate an error if the EVM execution fails.') self.docs['w'] = o @@ -186,13 +188,13 @@ class DocGenerator: self.docs['ww'] = o - if self.arg_flags & Flag.ENV_PREFIX: + if self.arg_flags & self.__argflag_list.ENV: o = DocEntry('--env-prefix') o.set_groff('Environment prefix for variables to overwrite configuration. Example: If \\fB--env-prefix\\fP is set to \\fBFOO\\fP then configuration variable \\fBBAR_BAZ\\fP would be set by environment variable \\fBFOO_BAZ_BAR\\fP. Also see \\fBENVIRONMENT\\fP.') self.docs['envprefix'] = o - if self.arg_flags & Flag.PROVIDER: + if self.arg_flags & self.__argflag_list.PROVIDER: o = DocEntry('-p', '--rpc-provider') o.set_groff('Fully-qualified URL of RPC provider.') self.docs['p'] = o @@ -203,12 +205,12 @@ class DocGenerator: self.docs['rpcdialect'] = o self.envs['rpcdialect'] = 'RPC_DIALECT' - if self.arg_flags & Flag.NO_TARGET == 0: + if self.arg_flags & self.__argflag_list.NO_TARGET == 0: o = DocEntry('--height') o.set_groff('Block height at which to query state for. Does not apply to transactions.') self.docs['height'] = o - if self.arg_flags & Flag.RPC_AUTH: + if self.arg_flags & self.__argflag_list.RPC_AUTH: o = DocEntry('--rpc-auth') o.set_groff('RPC endpoint authentication method, e.g. how to handle a HTTP WWW-Authenticate header.') self.docs['rpcauth'] = o @@ -220,7 +222,7 @@ class DocGenerator: self.envs['rpccredentials'] = 'RPC_CREDENTIALS' - if self.arg_flags & Flag.CHAIN_SPEC: + if self.arg_flags & self.__argflag_list.CHAIN_SPEC: o = DocEntry('-i', '--chain-spec') o.set_groff('Chain specification string, in the format <engine>:<fork>:<chain_id>:<common_name>. Example: "evm:london:1:ethereum".') o.set_groff_argvalue('chain_spec') @@ -228,19 +230,19 @@ class DocGenerator: self.envs['i'] = 'RPC_CREDENTIALS' - if self.arg_flags & Flag.UNSAFE: + if self.arg_flags & self.__argflag_list.UNSAFE: o = DocEntry('-u', '--unsafe') o.set_groff('Allow addresses that do not pass checksum.') self.docs['u'] = o - if self.arg_flags & Flag.SEQ: + if self.arg_flags & self.__argflag_list.SEQ: o = DocEntry('--seq') o.set_groff('Use numeric sequencial jsonrpc query ids. Useful for buggy server implementations who expects such.') self.docs['seq'] = o - if self.arg_flags & Flag.KEY_FILE: + if self.arg_flags & self.__argflag_list.KEY_FILE: o = DocEntry('-y', '--key-path') o.set_groff('Path to signing key.') o.set_groff_argvalue('path') @@ -253,24 +255,24 @@ class DocGenerator: self.docs['passphrasefile'] = o - if self.arg_flags & Flag.SEND: + if self.arg_flags & self.__argflag_list.SEND: o = DocEntry('-s') o.set_groff('Send to network. If set, network state may change. This means tokens may be spent and so on. Use with care. Only applies to transactions.') self.docs['s'] = o - if self.arg_flags & Flag.RAW: + if self.arg_flags & self.__argflag_list.RAW: o = DocEntry('--raw') o.set_groff('Produce output most optimized for machines.') self.docs['raw'] = o - if self.arg_flags & (Flag.SIGN | Flag.NONCE): + if self.arg_flags & (self.__argflag_list.SIGN | self.__argflag_list.NONCE): o = DocEntry('--nonce') o.set_groff('Explicitly set nonce to use for transaction.') self.docs['nonce'] = o - if self.arg_flags & (Flag.SIGN | Flag.FEE): + if self.arg_flags & (self.__argflag_list.SIGN | self.__argflag_list.FEE): o = DocEntry('--fee-price') o.set_groff('Set fee unit price to offer for the transaction. If used with \\fB-s\\fP this may incur actual network token cost.') self.docs['feeprice'] = o @@ -282,20 +284,21 @@ class DocGenerator: # # TODO: this manipulation should be DRYd # if self.arg_flags & argflag_std_target == 0: -# self.arg_flags |= Flag.WALLET +# self.arg_flags |= self.__argflag_list.WALLET - if self.arg_flags & Flag.EXEC: + if self.arg_flags & self.__argflag_list.EXEC: o = DocEntry('-e', '--executable-address') o.set_groff('Address of an executable code point on the network.') self.docs['e'] = o - if self.arg_flags & Flag.WALLET: + if self.arg_flags & self.__argflag_list.WALLET: o = DocEntry('-a', '--recipient-address') o.set_groff('Network wallet address to operate on. For read calls, this will be the wallet address for which the query is anchored. For transaction calls, it will be the wallet address for which state will be changed.') self.docs['a'] = o - + + print("docs {}".format(self.docs.keys())) def process(self): self.process_arg() @@ -307,6 +310,7 @@ class EnvDocGenerator: def __init__(self, arg_flags, override=None): self.arg_flags = arg_flags self.envs = {} + self.__argflag_list = ArgFlag() env_dir = os.path.join(data_dir, 'env') self.config = confini.Config(env_dir, override_dirs=override) self.config.process() @@ -320,21 +324,21 @@ class EnvDocGenerator: def process(self): ks = [] - if self.arg_flags & Flag.PROVIDER: + if self.arg_flags & self.__argflag_list.PROVIDER: ks += [ 'RPC_PROVIDER', 'RPC_DIALECT', ] - if self.arg_flags & Flag.RPC_AUTH: + if self.arg_flags & self.__argflag_list.RPC_AUTH: ks += [ 'RPC_AUTH', 'RPC_CREDENTIALS', ] - if self.arg_flags & Flag.CHAIN_SPEC: + if self.arg_flags & self.__argflag_list.CHAIN_SPEC: ks.append('CHAIN_SPEC') - if self.arg_flags & Flag.KEY_FILE: + if self.arg_flags & self.__argflag_list.KEY_FILE: ks += [ 'WALLET_KEY_FILE', 'WALLET_PASSPHRASE', diff --git a/chainlib/cli/rpc.py b/chainlib/cli/rpc.py @@ -61,7 +61,7 @@ class Rpc: self.id_generator = IntSequenceGenerator() self.chain_spec = config.get('CHAIN_SPEC') - self.conn = self.constructor(url=config.get('RPC_PROVIDER'), chain_spec=self.chain_spec, auth=auth, verify_identity=config.true('RPC_VERIFY')) + self.conn = self.constructor(url=config.get('RPC_PROVIDER'), chain_spec=self.chain_spec, auth=auth, verify_identity=config.true('RPC_VERIFY'), timeout=float(config.get('RPC_TIMEOUT'))) return self.conn diff --git a/chainlib/data/config/config.ini b/chainlib/data/config/config.ini @@ -5,10 +5,19 @@ credentials = dialect = default scheme = http verify = 1 +timeout = 10.0 +proxy = [chain] spec = +min_fee = 0 +max_fee = 0 [wallet] key_file = passphrase = + +[state] +path = +runtime_path = +backend = diff --git a/chainlib/error.py b/chainlib/error.py @@ -11,13 +11,18 @@ class JSONRPCException(RPCException): pass +class InitializationError(Exception): + """Base error for errors occurring while processing settings + """ + pass + class ExecutionError(Exception): """Base error for transaction execution failures """ pass -class SignerMissingException(Exception): +class SignerMissingException(InitializationError): """Raised when attempting to retrieve a signer when none has been added """ diff --git a/chainlib/settings.py b/chainlib/settings.py @@ -1,20 +1,19 @@ +# external imports +from aiee.numbers import postfix_to_int + # local imports from .chain import ChainSpec class ChainSettings: - def __init__(self, include_sync=False, include_queue=False): + def __init__(self): self.o = {} self.get = self.o.get - def process_common(self, config): - self.o['CHAIN_SPEC'] = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) - - - def process(self, config): - self.process_common(config) + def set(self, k, v): + self.o[k] = v def __str__(self): @@ -24,3 +23,27 @@ class ChainSettings: for k in ks: s += '{}: {}\n'.format(k, self.o.get(k)) return s + + +def process_settings_common(settings, config): + chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) + settings.set('CHAIN_SPEC', chain_spec) + return settings + + +def process_settings_value(settings, config): + value = None + try: + value = config.get('_VALUE') + except KeyError: + return settings + + value = postfix_to_int(config.get('_VALUE')) + settings.set('VALUE', value) + return settings + + +def process_settings(settings, config): + settings = process_settings_common(settings, config) + settings = process_settings_value(settings, config) + return settings diff --git a/chainlib/status.py b/chainlib/status.py @@ -4,6 +4,7 @@ import enum class Status(enum.Enum): """Representation of transaction status in network. """ + UNKNOWN = -1 PENDING = 0 SUCCESS = 1 ERROR = 2 diff --git a/chainlib/tx.py b/chainlib/tx.py @@ -1,4 +1,9 @@ -class Tx: +# local imports +from .status import Status +from .src import Src + + +class Tx(Src): """Base class to extend for implementation specific transaction objects. :param src: Transaction representation source @@ -7,8 +12,73 @@ class Tx: :type block: chainlib.block.Block """ - def __init__(self, src, block=None): - self.src = src + def __init__(self, src=None, block=None, result=None, strict=False): + self.block = block + self.index = -1 + + self.fee_limit = None + self.fee_price = None + + self.nonce = None + self.value = 0 + + self.outputs = [] + self.inputs = [] + self.payload = None + + self.result = None + + super(Tx, self).__init__(src) + + if block != None: + self.apply_block(block) + + if result != None: + self.apply_result(result) + + + def apply_result(self, result): + self.result = result + + + def apply_block(self, block): self.block = block - self.block_src = None - self.index = None + + + @property + def status(self): + if self.result == None: + return None + return self.result.status + + + @property + def status_name(self): + if self.result == None: + return None + return self.result.status.name + + + def generate_wire(self, chain_spec): + pass + + + def as_dict(self): + raise NotImplementedError() + + + def __str__(self): + if self.block != None: + return 'tx {} status {} block {} index {}'.format(self.display_hash(), self.status_name(), self.block.number, self.index) + else: + return 'tx {} status {}'.format(self.display_hash(), self.hash, self.status_name()) + + +class TxResult(Src): + + def __init__(self, src=None): + self.status = Status.UNKNOWN + self.tx_index = None + self.block_hash = None + self.fee_cost = 0 + super(TxResult, self).__init__(src=src) diff --git a/requirements.txt b/requirements.txt @@ -1,4 +1,5 @@ funga~=0.5.2 pysha3==1.0.2 -hexathon~=0.1.5 -confini~=0.6.0 +hexathon~=0.1.7 +confini~=0.6.1 +aiee~=0.3.1 diff --git a/scripts/chainlib-man.py b/scripts/chainlib-man.py @@ -14,10 +14,11 @@ from chainlib.cli.man import ( DocGenerator, apply_groff, ) -from chainlib.cli.base import ( - argflag_std_base, - flag_names, - ) +from chainlib.cli.arg import ArgFlag +#from chainlib.cli.base import ( +# argflag_std_base, +# flag_names, +# ) from chainlib.cli.arg import ArgumentParser as ChainlibArgumentParser from chainlib.cli.config import Config @@ -76,8 +77,10 @@ https://git.defalsify.org """ +argflag = ArgFlag() + argparser = argparse.ArgumentParser() -argparser.add_argument('-b', default=add_0x(hex(argflag_std_base)), help='argument flag bitmask') +argparser.add_argument('-b', default=add_0x(hex(argflag.get('std_base'))), help='argument flag bitmask') argparser.add_argument('-c', help='config override directory') argparser.add_argument('-n', required=True, help='tool name to use for man filename') argparser.add_argument('-d', default='.', help='output directory') @@ -90,11 +93,11 @@ args = argparser.parse_args(sys.argv[1:]) if args.v: logg.setLevel(logging.DEBUG) - + b = bytes.fromhex(strip_0x(args.b)) flags = int.from_bytes(b, byteorder='big') +flags_debug = argflag.names(flags) -flags_debug= flag_names(flags) logg.debug('apply arg flags {}: {}'.format(flags, ', '.join(flags_debug))) g = DocGenerator(flags) diff --git a/setup.cfg b/setup.cfg @@ -3,7 +3,7 @@ name=chainlib license=WTFPL2 author_email=dev@holbrook.no description=Generic blockchain access library and tooling -version=0.1.1 +version=0.3.1 url=https://gitlab.com/chaintools/chainlib author=Louis Holbrook diff --git a/tests/test_cli.py b/tests/test_cli.py @@ -3,10 +3,20 @@ import unittest import os import logging -# local imports -import chainlib.cli -from chainlib.cli.base import argflag_std_base +# external imports +from aiee.arg import process_args +# local imports +#from chainlib.cli.base import argflag_std_base +from chainlib.cli.arg import ( + ArgFlag, + Arg, + ArgumentParser, + ) +from chainlib.cli.config import ( + Config, + process_config, + ) script_dir = os.path.dirname(os.path.realpath(__file__)) data_dir = os.path.join(script_dir, 'testdata') config_dir = os.path.join(data_dir, 'config') @@ -15,28 +25,45 @@ logging.basicConfig(level=logging.DEBUG) class TestCli(unittest.TestCase): - + + def setUp(self): + self.flags = ArgFlag() + self.arg = Arg(self.flags) + + def test_args_process_single(self): - ap = chainlib.cli.arg.ArgumentParser() + ap = ArgumentParser() + flags = self.flags.VERBOSE | self.flags.CONFIG + process_args(ap, self.arg, flags) + argv = [ '-vv', '-n', 'foo', ] args = ap.parse_args(argv) - config = chainlib.cli.config.Config.from_args(args) + config = Config(config_dir) + config = process_config(config, self.arg, args, flags) self.assertEqual(config.get('CONFIG_USER_NAMESPACE'), 'foo') def test_args_process_schema_override(self): - ap = chainlib.cli.arg.ArgumentParser() + ap = ArgumentParser() + flags = self.flags.VERBOSE | self.flags.CONFIG + process_args(ap, self.arg, flags) + args = ap.parse_args([]) - config = chainlib.cli.config.Config.from_args(args, base_config_dir=config_dir) + + config = Config(config_dir) + config = process_config(config, self.arg, args, flags) self.assertEqual(config.get('FOO_BAR'), 'baz') def test_args_process_arg_override(self): - ap = chainlib.cli.arg.ArgumentParser() + ap = ArgumentParser() + flags = self.flags.VERBOSE | self.flags.CONFIG + process_args(ap, self.arg, flags) + argv = [ '-c', config_dir, @@ -44,53 +71,57 @@ class TestCli(unittest.TestCase): 'foo', ] args = ap.parse_args(argv) - config = chainlib.cli.config.Config.from_args(args, base_config_dir=config_dir) + config = Config(config_dir, namespace=args.namespace) + config = process_config(config, self.arg, args, flags) self.assertEqual(config.get('FOO_BAR'), 'bazbazbaz') def test_args_process_internal_override(self): - ap = chainlib.cli.arg.ArgumentParser() + ap = ArgumentParser() + flags = self.flags.VERBOSE | self.flags.CONFIG | self.flags.CHAIN_SPEC + process_args(ap, self.arg, flags) + args = ap.parse_args() default_config_dir = os.path.join(config_dir, 'default') - config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir) + + config = Config(default_config_dir) + config = process_config(config, self.arg, args, flags) self.assertEqual(config.get('CHAIN_SPEC'), 'baz:bar:13:foo') user_config_dir = os.path.join(default_config_dir, 'user') - config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir, user_config_dir=user_config_dir) + config = Config(default_config_dir) + config.add_override_dir(user_config_dir) + config = process_config(config, self.arg, args, flags) self.assertEqual(config.get('CHAIN_SPEC'), 'foo:foo:666:foo') - config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir, user_config_dir=default_config_dir) + config = Config(default_config_dir) + config = process_config(config, self.arg, args, flags) self.assertEqual(config.get('CHAIN_SPEC'), 'baz:bar:13:foo') - ap = chainlib.cli.arg.ArgumentParser() + ap = ArgumentParser() + process_args(ap, self.arg, flags) argv = [ '-n', 'user', ] args = ap.parse_args(argv) - config = chainlib.cli.config.Config.from_args(args, default_config_dir=default_config_dir, user_config_dir=default_config_dir) + config = Config(default_config_dir, namespace=args.namespace) + config = process_config(config, self.arg, args, flags) self.assertEqual(config.get('CHAIN_SPEC'), 'foo:foo:666:foo') - def test_args_process_extra(self): - ap = chainlib.cli.arg.ArgumentParser() - ap.add_argument('--foo', type=str) - argv = [ - '--foo', - 'bar', - ] - args = ap.parse_args(argv) - extra_args = { - 'foo': None, - } - config = chainlib.cli.config.Config.from_args(args, extra_args=extra_args) - self.assertEqual(config.get('_FOO'), 'bar') - - extra_args = { - 'foo': 'FOOFOO', - } - config = chainlib.cli.config.Config.from_args(args, extra_args=extra_args) - self.assertEqual(config.get('FOOFOO'), 'bar') + def test_all_args(self): + ap = ArgumentParser() + flags = self.flags.all + process_args(ap, self.arg, flags) + + args = ap.parse_args([ + '-y', 'foo', + '-i', 'foo:bar:42:baz', + ]) + + config = Config() + config = process_config(config, self.arg, args, flags) if __name__ == '__main__':