From 8f6f145856c227396471844d04deba9674ac14c2 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Wed, 8 Jan 2025 10:54:55 -0800 Subject: [PATCH] Add linting test to ensure use of ExtendOverwriteDefault for nargs=*/+ --- LICENSE.nextstrain-cli | 13 ++++++++----- augur/argparse_.py | 30 ++++++++++++++++++++++++++++-- tests/test_argparse_linting.py | 26 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 tests/test_argparse_linting.py diff --git a/LICENSE.nextstrain-cli b/LICENSE.nextstrain-cli index 6a5c63890..c8b87f3e8 100644 --- a/LICENSE.nextstrain-cli +++ b/LICENSE.nextstrain-cli @@ -1,8 +1,11 @@ -This license applies to the original copy of resource functions from the -Nextstrain CLI project into this project, incorporated in -"augur/data/__init__.py". Any subsequent modifications to this project's copy of -those functions are licensed under the license of this project, not of -Nextstrain CLI. +This license applies to the original copy of functions from the Nextstrain CLI +project into this project, namely + +- resource functions incorporated as "augur/data/__init__.py" +- walk_commands() function incorporated into "augur/argparse_.py" + +Any subsequent modifications to this project's copy of these functions are +licensed under the license of this project, not of Nextstrain CLI. MIT License diff --git a/augur/argparse_.py b/augur/argparse_.py index 291023172..7fe04116d 100644 --- a/augur/argparse_.py +++ b/augur/argparse_.py @@ -1,8 +1,9 @@ """ Custom helpers for the argparse standard library. """ -from argparse import Action, ArgumentParser, _ArgumentGroup, HelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE, _ExtendAction -from typing import Union +from argparse import Action, ArgumentParser, _ArgumentGroup, HelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE, _ExtendAction, _SubParsersAction +from itertools import chain +from typing import Iterable, Optional, Tuple, Union from .types import ValidationMode @@ -148,3 +149,28 @@ def add_validation_arguments(parser: Union[ArgumentParser, _ArgumentGroup]): action="store_const", const=ValidationMode.SKIP, help="Skip validation of input/output files, equivalent to --validation-mode=skip. Use at your own risk!") + + +# Originally copied from nextstrain/cli/argparse.py in the Nextstrain CLI project¹. +# +# ¹ +def walk_commands(parser: ArgumentParser, command: Optional[Tuple[str, ...]] = None) -> Iterable[Tuple[Tuple[str, ...], ArgumentParser]]: + if command is None: + command = (parser.prog,) + + yield command, parser + + subparsers = chain.from_iterable( + action.choices.items() + for action in parser._actions + if isinstance(action, _SubParsersAction)) + + visited = set() + + for subname, subparser in subparsers: + if subparser in visited: + continue + + visited.add(subparser) + + yield from walk_commands(subparser, (*command, subname)) diff --git a/tests/test_argparse_linting.py b/tests/test_argparse_linting.py new file mode 100644 index 000000000..d8aa02b2c --- /dev/null +++ b/tests/test_argparse_linting.py @@ -0,0 +1,26 @@ +# Originally based on tests/help.py from the Nextstrain CLI project.¹ +# +# ¹ +import pytest +from argparse import ArgumentParser +from typing import List, Tuple + +from augur import make_parser +from augur.argparse_ import walk_commands, ExtendOverwriteDefault + + +# Walking commands is slow, so do it only once and use it for all tests in this +# file (though currently n=1). +commands = list(walk_commands(make_parser())) + + +# Ensure we always use ExtendOverwriteDefault for options that take a variable +# number of arguments. See . +@pytest.mark.parametrize("action", [ + pytest.param(action, id = " ".join(command) + " " + "/".join(action.option_strings)) + for command, parser in commands + for action in parser._actions + if action.nargs in {"+", "*"} +]) +def test_ExtendOverwriteDefault(action): + assert isinstance(action, ExtendOverwriteDefault)