From 396a1122ff9660b36d50f2216feeddb6d87e6820 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Fri, 7 Feb 2025 10:44:25 -0600 Subject: [PATCH 1/2] bf: show ls results when no positional args given Previous work removed globbing in favor of letting the shell do that work. However in the case where we have no positional args, we still need to glob for the files that match DUCT_OUTPUT_PREFIX. Fixes https://github.com/con/duct/issues/238 --- src/con_duct/suite/ls.py | 8 +++++++- src/con_duct/suite/main.py | 7 +++---- test/test_suite.py | 28 +++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/con_duct/suite/ls.py b/src/con_duct/suite/ls.py index f3c9e03..90a2f10 100644 --- a/src/con_duct/suite/ls.py +++ b/src/con_duct/suite/ls.py @@ -1,5 +1,6 @@ import argparse from collections import OrderedDict +import glob import json import logging from typing import Any, Dict, List, Optional @@ -10,7 +11,7 @@ except ImportError: pyout = None import yaml -from con_duct.__main__ import SummaryFormatter +from con_duct.__main__ import DUCT_OUTPUT_PREFIX, SummaryFormatter lgr = logging.getLogger(__name__) @@ -141,6 +142,11 @@ def pyout_ls(run_data_list: List[OrderedDict[str, Any]]) -> None: def ls(args: argparse.Namespace) -> int: + + if not args.paths: + pattern = f"{DUCT_OUTPUT_PREFIX[:DUCT_OUTPUT_PREFIX.index('{')]}*" + args.paths = [p for p in glob.glob(pattern)] + info_files = [path for path in args.paths if path.endswith("info.json")] run_data_raw = load_duct_runs(info_files) formatter = SummaryFormatter(enable_colors=args.colors) diff --git a/src/con_duct/suite/main.py b/src/con_duct/suite/main.py index 4906513..3b62cee 100644 --- a/src/con_duct/suite/main.py +++ b/src/con_duct/suite/main.py @@ -2,7 +2,6 @@ import os import sys from typing import List, Optional -from con_duct.__main__ import DUCT_OUTPUT_PREFIX from con_duct.suite.ls import LS_FIELD_CHOICES, ls from con_duct.suite.plot import matplotlib_plot from con_duct.suite.pprint_json import pprint_json @@ -51,7 +50,7 @@ def main(argv: Optional[List[str]] = None) -> None: parser_ls = subparsers.add_parser( "ls", - help="Print execution information for all runs matching DUCT_OUTPUT_PREFIX.", + help="Print execution information for all matching runs.", ) parser_ls.add_argument( "-f", @@ -85,8 +84,8 @@ def main(argv: Optional[List[str]] = None) -> None: parser_ls.add_argument( "paths", nargs="*", - default=[f"{DUCT_OUTPUT_PREFIX[:DUCT_OUTPUT_PREFIX.index('{')]}*"], - help="Path to duct report files, only `info.json` would be considered.", + help="Path to duct report files, only `info.json` would be considered. " + "If not provided, the program will glob for files that match DUCT_OUTPUT_PREFIX.", ) parser_ls.set_defaults(func=ls) diff --git a/test/test_suite.py b/test/test_suite.py index 673a977..1ae3589 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -149,6 +149,8 @@ class TestLS(unittest.TestCase): def setUp(self) -> None: """Create a temporary directory and test files.""" self.temp_dir = tempfile.TemporaryDirectory() + self.old_cwd = os.getcwd() + os.chdir(self.temp_dir.name) self.files = { "file1_info.json": { "schema_version": MINIMUM_SCHEMA_VERSION, @@ -163,13 +165,20 @@ def setUp(self) -> None: "schema_version": MINIMUM_SCHEMA_VERSION, "prefix": "no_match", }, + ".duct/logs/default_logpath_info.json": { + "schema_version": MINIMUM_SCHEMA_VERSION, + "prefix": "default_file1", + }, } for filename, content in self.files.items(): - with open(os.path.join(self.temp_dir.name, filename), "w") as f: + full_path = os.path.join(self.temp_dir.name, filename) + os.makedirs(os.path.dirname(full_path), exist_ok=True) + with open(full_path, "w") as f: json.dump(content, f) def tearDown(self) -> None: """Clean up the temporary directory.""" + os.chdir(self.old_cwd) self.temp_dir.cleanup() def _run_ls(self, paths: list[str], fmt: str) -> str: @@ -201,6 +210,23 @@ def test_ls_sanity(self) -> None: assert len(prefixes) == 1 assert any("file1" in p for p in prefixes) + def test_ls_no_pos_args(self) -> None: + result = self._run_ls([], "summaries") + + assert "Prefix:" in result + prefixes = [ + line.split(":", 1)[1].strip() + for line in result.splitlines() + if line.startswith("Prefix:") + ] + assert len(prefixes) == 1 + assert any("default_logpath" in p for p in prefixes) + + assert "file1" not in result + assert "file2" not in result + assert "file3" not in result + assert "not_matching.json" not in result + def test_ls_multiple_paths(self) -> None: """Basic sanity test to ensure ls() runs without crashing.""" files_1_and_2 = ["file1_info.json", "file2_info.json"] From 5806e5d94655ed3df5ef0cc7c6cbf4cadf6208b9 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Fri, 7 Feb 2025 10:53:15 -0600 Subject: [PATCH 2/2] [DATALAD RUNCMD] Autoupdate readme === Do not change lines below === { "chain": [], "cmd": "./.update-readme-help.py", "exit": 0, "extra_inputs": [], "inputs": [], "outputs": [], "pwd": "." } ^^^ Do not change lines above ^^^ --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index f21cce0..b1d9870 100644 --- a/README.md +++ b/README.md @@ -149,8 +149,7 @@ positional arguments: {pp,plot,ls} Available subcommands pp Pretty print a JSON log. plot Plot resource usage for an execution. - ls Print execution information for all runs matching - DUCT_OUTPUT_PREFIX. + ls Print execution information for all matching runs. options: -h, --help show this help message and exit