From e168f1b29a96f95b20794e85f6182b7bdbd8e39c Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Mon, 20 Feb 2023 01:46:45 +0100 Subject: [PATCH] Add --detailed and --summary for controlling output verbosity This changes -v/--verbose and -q/--quiet back to _only_ controlling the log level. --- fawltydeps/main.py | 7 ++++-- fawltydeps/settings.py | 43 ++++++++++++++++++++++++------------ tests/test_cmdline.py | 50 +++++++++++++++++++++++------------------- tests/test_settings.py | 2 +- 4 files changed, 62 insertions(+), 40 deletions(-) diff --git a/fawltydeps/main.py b/fawltydeps/main.py index be29757e..8b4a3c89 100644 --- a/fawltydeps/main.py +++ b/fawltydeps/main.py @@ -180,8 +180,11 @@ def main() -> int: if settings.output_format == OutputFormat.JSON: analysis.print_json(sys.stdout) else: - analysis.print_human_readable(sys.stdout, details=settings.verbosity > 0) - if settings.verbosity <= 0: + analysis.print_human_readable( + sys.stdout, + details=settings.output_format == OutputFormat.HUMAN_DETAILED, + ) + if settings.output_format == OutputFormat.HUMAN_SUMMARY: print(f"\n{VERBOSE_PROMPT}\n") # Exit codes: diff --git a/fawltydeps/settings.py b/fawltydeps/settings.py index 7431a47f..e053f300 100644 --- a/fawltydeps/settings.py +++ b/fawltydeps/settings.py @@ -70,7 +70,8 @@ class Action(OrderedEnum): class OutputFormat(OrderedEnum): """Output formats provided by the FawltyDeps application.""" - HUMAN_READABLE = "human_readable" + HUMAN_SUMMARY = "human_summary" + HUMAN_DETAILED = "human_detailed" JSON = "json" @@ -98,7 +99,7 @@ class Settings(BaseSettings): # type: ignore actions: Set[Action] = {Action.REPORT_UNDECLARED, Action.REPORT_UNUSED} code: PathOrSpecial = Path(".") deps: Path = Path(".") - output_format: OutputFormat = OutputFormat.HUMAN_READABLE + output_format: OutputFormat = OutputFormat.HUMAN_SUMMARY ignore_undeclared: Set[str] = set() ignore_unused: Set[str] = set() verbosity: int = 0 @@ -221,13 +222,6 @@ def populate_parser_options(cls, parser: argparse._ActionsContainer) -> None: " to looking for supported files in the current directory)" ), ) - parser.add_argument( - "--json", - dest="output_format", - action="store_const", - const="json", - help="Generate JSON output instead of a human-readable report", - ) parser.add_argument( "--ignore-undeclared", nargs="+", @@ -246,17 +240,38 @@ def populate_parser_options(cls, parser: argparse._ActionsContainer) -> None: " dependencies, e.g. --ignore-unused pylint black" ), ) + + # Provide three mutually exclusive options to select output format. + output_format = parser.add_mutually_exclusive_group() + output_format.add_argument( + "--summary", + dest="output_format", + action="store_const", + const="human_summary", + help="Generate human-readable summary report", + ) + output_format.add_argument( + "--detailed", + dest="output_format", + action="store_const", + const="human_detailed", + help="Generate human-readable detailed report", + ) + output_format.add_argument( + "--json", + dest="output_format", + action="store_const", + const="json", + help="Generate JSON output instead of a human-readable report", + ) + # The following two do not correspond directly to a Settings member, # but the latter is subtracted from the former to make .verbosity. parser.add_argument( "-v", "--verbose", action="count", - help=( - "Increase log level (WARNING by default, -v: INFO, -vv: DEBUG)" - " and verbosity of the output (without location details by default," - " -v, -vv: with location details)" - ), + help="Increase log level (WARNING by default, -v: INFO, -vv: DEBUG)", ) parser.add_argument( "-q", diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 2d5e5bd4..535f07a1 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -60,7 +60,7 @@ def run_fawltydeps( return proc.stdout.strip(), proc.stderr.strip(), proc.returncode -def test_list_imports_verbose__from_dash__prints_imports_from_stdin(): +def test_list_imports_detailed__from_dash__prints_imports_from_stdin(): code = dedent( """\ from pathlib import Path @@ -79,14 +79,14 @@ def test_list_imports_verbose__from_dash__prints_imports_from_stdin(): "INFO:fawltydeps.extract_imports:Parsing Python code from standard input" ) output, errors, returncode = run_fawltydeps( - "--list-imports", "-v", "--code=-", to_stdin=code + "--list-imports", "--detailed", "-v", "--code=-", to_stdin=code ) assert output.splitlines() == expect assert errors == expect_logs assert returncode == 0 -def test_list_imports_quiet__from_dash__prints_imports_from_stdin(): +def test_list_imports_summary__from_dash__prints_imports_from_stdin(): code = dedent( """\ from pathlib import Path @@ -100,7 +100,7 @@ def test_list_imports_quiet__from_dash__prints_imports_from_stdin(): expect = ["foo", "numpy", "requests"] # alphabetically sorted output, errors, returncode = run_fawltydeps( - "--list-imports", "--code=-", to_stdin=code + "--list-imports", "--summary", "--code=-", to_stdin=code ) assert output.splitlines()[:-2] == expect assert errors == "" @@ -129,7 +129,7 @@ def test_list_imports__from_py_file__prints_imports_from_file(write_tmp_files): f"INFO:fawltydeps.extract_imports:Parsing Python file {tmp_path}/myfile.py" ) output, errors, returncode = run_fawltydeps( - "--list-imports", "-v", f"--code={tmp_path}/myfile.py" + "--list-imports", "--detailed", "-v", f"--code={tmp_path}/myfile.py" ) assert output.splitlines() == expect assert errors == expect_logs @@ -191,7 +191,7 @@ def test_list_imports__from_ipynb_file__prints_imports_from_file(write_tmp_files f"INFO:fawltydeps.extract_imports:Parsing Notebook file {tmp_path}/myfile.ipynb" ) output, errors, returncode = run_fawltydeps( - "--list-imports", "-v", f"--code={tmp_path}/myfile.ipynb" + "--list-imports", "--detailed", "-v", f"--code={tmp_path}/myfile.ipynb" ) assert output.splitlines() == expect assert errors == expect_logs @@ -225,7 +225,7 @@ def test_list_imports__from_dir__prints_imports_from_py_and_ipynb_files_only( f"INFO:fawltydeps.extract_imports:Parsing Python files under {tmp_path}" ) output, errors, returncode = run_fawltydeps( - "--list-imports", "-v", f"--code={tmp_path}" + "--list-imports", "--detailed", "-v", f"--code={tmp_path}" ) assert output.splitlines() == expect assert errors == expect_logs @@ -255,7 +255,7 @@ def test_list_imports__from_missing_file__fails_with_exit_code_2(tmp_path): def test_list_imports__from_empty_dir__logs_but_extracts_nothing(tmp_path): # Enable log level INFO with -v output, errors, returncode = run_fawltydeps( - "--list-imports", f"--code={tmp_path}", "-v" + "--list-imports", f"--code={tmp_path}", "--detailed", "-v" ) assert output == "" assert f"Parsing Python files under {tmp_path}" in errors @@ -275,7 +275,7 @@ def test_list_deps__dir__prints_deps_from_requirements_txt( f"{tmp_path}/requirements.txt: requests", ] output, errors, returncode = run_fawltydeps( - "--list-deps", "-v", f"--deps={tmp_path}" + "--list-deps", "--detailed", "-v", f"--deps={tmp_path}" ) assert output.splitlines() == expect assert errors == "" @@ -360,7 +360,7 @@ def test_list_deps__missing_path__fails_with_exit_code_2(tmp_path): def test_list_deps__empty_dir__verbosely_logs_but_extracts_nothing(tmp_path): # Enable log level INFO with -v output, errors, returncode = run_fawltydeps( - "--list-deps", f"--deps={tmp_path}", "-v" + "--list-deps", f"--deps={tmp_path}", "--detailed", "-v" ) assert output == "" assert errors == "" @@ -401,7 +401,7 @@ def test_check__simple_project_with_missing_deps__reports_undeclared( f"INFO:fawltydeps.extract_imports:Parsing Python files under {tmp_path}" ) output, errors, returncode = run_fawltydeps( - "--check", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" + "--check", "--detailed", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" ) assert output.splitlines() == expect assert errors == expect_logs @@ -425,7 +425,7 @@ def test_check__simple_project_with_extra_deps__reports_unused( f"INFO:fawltydeps.extract_imports:Parsing Python files under {tmp_path}" ) output, errors, returncode = run_fawltydeps( - "--check", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" + "--check", "--detailed", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" ) assert output.splitlines() == expect assert errors == expect_logs @@ -452,7 +452,7 @@ def test_check__simple_project__can_report_both_undeclared_and_unused( f"INFO:fawltydeps.extract_imports:Parsing Python files under {tmp_path}" ) output, errors, returncode = run_fawltydeps( - "--check", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" + "--check", "--detailed", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" ) assert output.splitlines() == expect assert errors == expect_logs @@ -540,7 +540,11 @@ def test_check_undeclared__simple_project__reports_only_undeclared( f"INFO:fawltydeps.extract_imports:Parsing Python files under {tmp_path}" ) output, errors, returncode = run_fawltydeps( - "--check-undeclared", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" + "--check-undeclared", + "--detailed", + "-v", + f"--code={tmp_path}", + f"--deps={tmp_path}", ) assert output.splitlines() == expect assert errors == expect_logs @@ -564,7 +568,7 @@ def test_check_unused__simple_project__reports_only_unused( f"INFO:fawltydeps.extract_imports:Parsing Python files under {tmp_path}" ) output, errors, returncode = run_fawltydeps( - "--check-unused", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" + "--check-unused", "--detailed", "-v", f"--code={tmp_path}", f"--deps={tmp_path}" ) assert output.splitlines() == expect assert errors == expect_logs @@ -591,7 +595,7 @@ def test__no_action__defaults_to_check_action( f"INFO:fawltydeps.extract_imports:Parsing Python files under {tmp_path}" ) output, errors, returncode = run_fawltydeps( - f"--code={tmp_path}", "-v", f"--deps={tmp_path}" + f"--code={tmp_path}", "--detailed", "-v", f"--deps={tmp_path}" ) assert output.splitlines() == expect assert errors == expect_logs @@ -615,7 +619,7 @@ def test__no_options__defaults_to_check_action_in_current_dir( " requirements.txt", ] expect_logs = "INFO:fawltydeps.extract_imports:Parsing Python files under ." - output, errors, returncode = run_fawltydeps("-v", cwd=tmp_path) + output, errors, returncode = run_fawltydeps("--detailed", "-v", cwd=tmp_path) assert output.splitlines() == expect assert errors == expect_logs assert returncode == 3 @@ -750,25 +754,25 @@ def test_cmdline_on_ignored_undeclared_option( ), pytest.param( {"actions": ["list_imports"]}, - ["--verbose"], + ["--detailed"], ["code.py:1: requests"], - id="combine_actions_in_config_with_verbose_on_command_line", + id="combine_actions_in_config_with_detailed_on_command_line", ), pytest.param( - {"actions": ["list_imports"], "verbosity": 3}, + {"actions": ["list_imports"], "output_format": "human_detailed"}, ["--list-deps"], ["requirements.txt: pandas"], id="override_some_config_directives_on_command_line", ), pytest.param( - {"actions": ["list_imports"], "verbosity": 3}, - ["--quiet"], + {"actions": ["list_imports"], "output_format": "human_detailed"}, + ["--summary"], [ "requests", "", VERBOSE_PROMPT, ], - id="override_verbosity_from_config_with_quiet_on_command_line", + id="override_output_format_from_config_with_command_line_option", ), ], ) diff --git a/tests/test_settings.py b/tests/test_settings.py index ddf3a722..5c05d4ce 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -19,7 +19,7 @@ actions={Action.REPORT_UNDECLARED, Action.REPORT_UNUSED}, code=Path("."), deps=Path("."), - output_format=OutputFormat.HUMAN_READABLE, + output_format=OutputFormat.HUMAN_SUMMARY, ignore_undeclared=set(), ignore_unused=set(), verbosity=0,