In the context of subparsers, it can be useful to use simple functions to share options, or configure options (e.g., --log-level).
Here’s an example:
def validate_path(sx: str) -> Path:
p = Path(sx).expanduser().absolute()
if p.exists():
return p
raise ArgumentTypeError(f"Path '{sx}' does not exist")
def add_core(p: ArgumentParser) -> ArgumentParser:
p.add_argument('-s', '--src', help="Source file", type=validate_path)
return p
def add_logging(p: ArgumentParser, default = logging.INFO) -> ArgumentParser:
p.add_argument('--log-level', help="Log file", default=default)
return p
def p_alpha(p: ArgumentParser) -> ArgumentParser:
add_core(p)
p.add_argument('-o', '--output', help="dest file", type=Path)
return add_logging(p, logging.DEBUG)
def p_beta(p: ArgumentParser) -> ArgumentParser:
add_core(p)
p.add_argument('-m', '--max-records', help="Max records to process", type=int)
return add_logging(p, logging.ERROR)
Also, argparse is a bit clumsy with subparser creation. Depending on how big your CLI tool is, It can be useful to create a shim layer that wires things together.
Note, the big issue with refactoring is at the argparse Namespace component. You have to keep this in sync with the each subparser definition. Static analyzing tools can’t really help you here.
def run_alpha(src: Path, output: Path) -> int:
logger.info(f"Processing '{src}'")
return 0
def run_beta(src: Path, max_records: int) -> int:
logger.info(f"Beta Processing '{src}' with {max_records} records")
return 0
def run_gamma(src:Path) -> int:
logger.info(f"Gamma Processing '{src}'")
return 0
def get_parser() -> ArgumentParser:
p = ArgumentParser(description="Example Subparser")
sp = p.add_subparsers(dest='commands')
def _build(
name: str,
help_: str,
add_opts: F[[ArgumentParser], ArgumentParser],
func: F[[Any], int],
) -> ArgumentParser:
px = sp.add_parser(
name,
formatter_class=ArgumentDefaultsHelpFormatter,
help=help_,
description=help_,
)
px = add_opts(px)
px.set_defaults(func=func)
return px
_build('alpha', "Run alpha", p_alpha, lambda ns: run_alpha(ns.src, ns.output))
_build('beta', "Run Beta", p_beta, lambda ns: run_alpha(ns.src, ns.max_records))
_build("gamma", "Run gamma", p_gamma, lambda ns: run_gamma(ns.src))
p.add_argument("--version", action='version', version="0.1.0")
return p
def main(argv: list[str]) -> int:
pa = get_parser().parse_args(argv)
logging.basicConfig(level=pa.log_level)
return pa.func(pa)
Complete example is here