From 906da2bf154b0fc2a14233cb76fa762bf77cb418 Mon Sep 17 00:00:00 2001 From: Dave Bunten Date: Tue, 12 Nov 2024 15:30:14 -0700 Subject: [PATCH] Refine CLI for use with `get_table` as `almanack ` (#136) * work towards get table cli * add tests and adjust cli * correct docstring Co-Authored-By: Gregory Way * updates to subprocess test util Co-Authored-By: Gregory Way Co-Authored-By: Faisal Alquaddoomi * serialize via json.dumps with python-fire Co-Authored-By: Faisal Alquaddoomi --------- Co-authored-by: Gregory Way Co-authored-by: Faisal Alquaddoomi --- pyproject.toml | 2 +- src/almanack/cli.py | 29 ++++++++++++++++++ src/almanack/metrics/data.py | 4 +-- src/almanack/reporting/cli.py | 24 --------------- tests/test_cli.py | 56 +++++++++++++++++++++++++++++++++++ tests/utils.py | 16 ++++++++++ 6 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 src/almanack/cli.py delete mode 100644 src/almanack/reporting/cli.py create mode 100644 tests/test_cli.py diff --git a/pyproject.toml b/pyproject.toml index deda89f7..32c8e646 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ jsonschema = "^4.23.0" sbomdiff = "^0.5.6" [tool.poetry.scripts] -almanack = "almanack.reporting.cli:trigger" +almanack = "almanack.cli:cli_get_table" [tool.poetry-dynamic-versioning] enable = true diff --git a/src/almanack/cli.py b/src/almanack/cli.py new file mode 100644 index 00000000..97f77859 --- /dev/null +++ b/src/almanack/cli.py @@ -0,0 +1,29 @@ +""" +Setup almanack CLI through python-fire +""" + +import json + +import fire + +from .metrics.data import get_table + + +def cli_get_table() -> None: + """ + Creates Fire CLI for get_table_json_wrapper. + + This enables the use of CLI such as: + `almanck ` + """ + fire.Fire(component=get_table, serialize=json.dumps) + + +if __name__ == "__main__": + """ + Setup the CLI with python-fire for the almanack package. + + This allows the function `check` to be ran through the + command line interface. + """ + cli_get_table() diff --git a/src/almanack/metrics/data.py b/src/almanack/metrics/data.py index 29bd17df..98fd09e4 100644 --- a/src/almanack/metrics/data.py +++ b/src/almanack/metrics/data.py @@ -6,7 +6,7 @@ import shutil import tempfile from datetime import datetime, timezone -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import pygit2 import yaml @@ -20,7 +20,7 @@ METRICS_TABLE = f"{pathlib.Path(__file__).parent!s}/metrics.yml" -def get_table(repo_path: str) -> Dict[str, Any]: +def get_table(repo_path: str) -> List[Dict[str, Any]]: """ Gather metrics on a repository and return the results in a structured format. diff --git a/src/almanack/reporting/cli.py b/src/almanack/reporting/cli.py deleted file mode 100644 index e8f02b58..00000000 --- a/src/almanack/reporting/cli.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Setup Entropy Report CLI through python-fire -""" - -import fire - -import almanack.metrics.entropy.processing_repositories as report - - -def trigger() -> None: - """ - Run the CLI command to process `report.py` using python-fire. - """ - fire.Fire(report) - - -if __name__ == "__main__": - """ - Setup the CLI with python-fire for the almanack package. - - This allows the function `check` to be ran through the - command line interface. - """ - trigger() diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..cf06992f --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,56 @@ +""" +Tests CLI functionality. +""" + +import json + +import yaml + +from almanack.metrics.data import METRICS_TABLE +from tests.data.almanack.repo_setup.create_repo import repo_setup + +from .utils import run_cli_command + + +def test_cli_util(): + """ + Test the run_cli_command for successful output + """ + + _, _, returncode = run_cli_command(["echo", "'hello world'"]) + + assert returncode == 0 + + +def test_cli_almanack(tmp_path): + """ + Tests running `almanack` as a CLI + """ + + # create a repo with a single file and commit + repo = repo_setup(repo_path=tmp_path, files={"example.txt": "example"}) + + # gather output and return code from running a CLI command + stdout, _, returncode = run_cli_command(command=["almanack", repo.path]) + + # make sure we return 0 on success + assert returncode == 0 + + # gather result of CLI as json + results = json.loads(stdout) + + # make sure we have a list of output + assert isinstance(results, list) + + # open the metrics table + with open(METRICS_TABLE, "r") as f: + metrics_table = yaml.safe_load(f) + + # check that all keys exist in the output from metrics table to cli str + assert all( + x == y + for x, y in zip( + sorted([result["name"] for result in results]), + sorted([metric["name"] for metric in metrics_table["metrics"]]), + ) + ) diff --git a/tests/utils.py b/tests/utils.py index 2f803010..b0b7b33e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,6 +3,7 @@ """ import subprocess +from typing import Tuple def check_subproc_run_for_nonzero(completed_proc: subprocess.CompletedProcess) -> None: @@ -17,3 +18,18 @@ def check_subproc_run_for_nonzero(completed_proc: subprocess.CompletedProcess) - except Exception as exc: # raise the exception with decoded output from linkchecker for readability raise Exception(completed_proc.stdout.decode()) from exc + + +def run_cli_command(command: str) -> Tuple[str, str, int]: + """ + Run a CLI command using subprocess and capture the output and return code. + + Args: + command (list): The command to run as a list of strings. + + Returns: + tuple: (str: stdout, str: stderr, int: returncode) + """ + + result = subprocess.run(args=command, capture_output=True, text=True, check=False) + return result.stdout, result.stderr, result.returncode