diff --git a/CHANGES.md b/CHANGES.md index d96eaead..3f09f244 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,8 @@ development source code and as such may not be routinely kept up to date. ## Improvements +* `nextstrain setup --list` now lists the available pathogens for setup. + ([#502](https://github.com/nextstrain/cli/issues/502) * Added the following to the list of "well-known" environment variables that are automatically passed thru to runtimes: diff --git a/doc/changes.md b/doc/changes.md index 0480819d..0b42c0c6 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -19,6 +19,8 @@ development source code and as such may not be routinely kept up to date. (v-next-improvements)= ### Improvements +* `nextstrain setup --list` now lists the available pathogens for setup. + ([#502](https://github.com/nextstrain/cli/issues/502) * Added the following to the list of "well-known" environment variables that are automatically passed thru to runtimes: diff --git a/doc/commands/setup.rst b/doc/commands/setup.rst index 9c32b595..03bcb46a 100644 --- a/doc/commands/setup.rst +++ b/doc/commands/setup.rst @@ -14,6 +14,7 @@ nextstrain setup usage: nextstrain setup [--dry-run] [--force] [--set-default] [@[=]] nextstrain setup [--dry-run] [--force] [--set-default] + nextstrain setup --list nextstrain setup --help @@ -78,3 +79,7 @@ options Use this pathogen version or runtime as the default if set up is successful. +.. option:: --list + + List available pathogens. + diff --git a/nextstrain/cli/command/setup.py b/nextstrain/cli/command/setup.py index 29cf051e..2fc4e248 100644 --- a/nextstrain/cli/command/setup.py +++ b/nextstrain/cli/command/setup.py @@ -24,7 +24,7 @@ from ..errors import UserError from ..util import colored, runner_module, runner_name, print_and_check_setup_tests from ..types import Options, RunnerModule, SetupTestResults -from ..pathogens import PathogenVersion, pathogen_defaults +from ..pathogens import PathogenVersion, pathogen_defaults, fetch_nextstrain_run_pathogens from ..runner import all_runners_by_name, runner_defaults @@ -33,10 +33,35 @@ failure = partial(colored, "red") +def list_available_pathogens(): + """ + Fetches and displays available pathogens with workflow compatibility details. + """ + print("Fetching available pathogens…") + print() + + pathogens = fetch_nextstrain_run_pathogens() + pathogens = sorted(pathogens, key=lambda p: p["name"].lower()) + + for pathogen in pathogens: + print(pathogen["name"]) + + compatible_workflows = [ + workflow + for workflow, workflow_info in pathogen.get("registration", {}).get("workflows", {}).items() + if isinstance(workflow_info, dict) and workflow_info.get("compatibility", {}).get("nextstrain run") + ] + if compatible_workflows: + print(" Workflows compatible with nextstrain run:") + for workflow in compatible_workflows: + print(f" - {workflow}") + + def register_parser(subparser): """ %(prog)s [--dry-run] [--force] [--set-default] [@[=]] %(prog)s [--dry-run] [--force] [--set-default] + %(prog)s --list %(prog)s --help """ parser = subparser.add_parser("setup", help = "Set up a pathogen or runtime") @@ -60,7 +85,9 @@ def register_parser(subparser): A runtime is one of {{{', '.join(all_runners_by_name)}}}. """), - metavar = "|") + metavar = "|", + nargs = "?", + default = None) parser.add_argument( "--dry-run", @@ -78,11 +105,24 @@ def register_parser(subparser): help = "Use this pathogen version or runtime as the default if set up is successful.", action = "store_true") + parser.add_argument( + "--list", + help = "List available pathogens.", + action = "store_true") + return parser @console.auto_dry_run_indicator() def run(opts: Options) -> int: + if opts.list: + list_available_pathogens() + return 0 + + if not opts.arg: + print("No pathogen or runtime specified. Run with --list to see available pathogens.") + return 1 + try: runner = runner_module(opts.arg) except ValueError as e1: diff --git a/nextstrain/cli/pathogens.py b/nextstrain/cli/pathogens.py index d1e2ed81..8d5209c6 100644 --- a/nextstrain/cli/pathogens.py +++ b/nextstrain/cli/pathogens.py @@ -1198,3 +1198,33 @@ def nextstrain_repo_zip_url(name: str, version: str) -> URL: return URL(urlquote(revision), version_url) else: return version_url + + +def fetch_nextstrain_run_pathogens() -> List[Dict]: + """ + Fetches all pathogen repos from nextstrain.org, filtered to only those + with at least one `nextstrain run` compatible workflow. + + Returns a list of dicts with "name" and optionally "registration" keys. + """ + url = URL("/pathogen-repos", NEXTSTRAIN_DOT_ORG) + + with requests.Session() as http: + response = http.get(str(url), headers={"Accept": "application/json"}) + response.raise_for_status() + return [repo for repo in response.json() if has_nextstrain_run_compatibility(repo)] + + +def has_nextstrain_run_compatibility(repo: Dict) -> bool: + """ + Returns True if the pathogen repo has at least one workflow with `nextstrain run` compatibility. + """ + workflows = repo.get("registration", {}).get("workflows", {}) + + for workflow in workflows.values(): + if isinstance(workflow, dict): + compatibility = workflow.get("compatibility", {}) + if compatibility.get("nextstrain run"): + return True + + return False