diff --git a/CHANGES.md b/CHANGES.md index 99aa7700..a57392c6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,9 +27,10 @@ supported Python version is always bundled with `nextstrain`. ## Improvements * `nextstrain setup ` and `nextstrain version --pathogens` now list - the available workflows for a pathogen if the pathogen lists the workflows - in the top-level `nextstrain-pathogen.yaml` file. - ([#461](https://github.com/nextstrain/cli/pull/461)) + the available workflows (e.g. `ingest`, `phylogenetic`) for a pathogen if the + workflows are registered as compatible with `nextstrain run` in the + pathogen's `nextstrain-pathogen.yaml` file. + ([#461](https://github.com/nextstrain/cli/pull/461), [#472](https://github.com/nextstrain/cli/pull/472)) * Snakemake's storage support downloaded files (stored in `.snakemake/storage/`) are now downloaded from AWS Batch builds by default. diff --git a/doc/changes.md b/doc/changes.md index 26892fb1..1a25096b 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -31,9 +31,10 @@ supported Python version is always bundled with `nextstrain`. ### Improvements * `nextstrain setup ` and `nextstrain version --pathogens` now list - the available workflows for a pathogen if the pathogen lists the workflows - in the top-level `nextstrain-pathogen.yaml` file. - ([#461](https://github.com/nextstrain/cli/pull/461)) + the available workflows (e.g. `ingest`, `phylogenetic`) for a pathogen if the + workflows are registered as compatible with `nextstrain run` in the + pathogen's `nextstrain-pathogen.yaml` file. + ([#461](https://github.com/nextstrain/cli/pull/461), [#472](https://github.com/nextstrain/cli/pull/472)) * Snakemake's storage support downloaded files (stored in `.snakemake/storage/`) are now downloaded from AWS Batch builds by default. diff --git a/nextstrain/cli/command/run.py b/nextstrain/cli/command/run.py index 4ba3a515..6a5520c9 100644 --- a/nextstrain/cli/command/run.py +++ b/nextstrain/cli/command/run.py @@ -33,7 +33,7 @@ from ..errors import UserError from ..pathogens import PathogenVersion, UnmanagedPathogen from ..runner import aws_batch, docker, singularity -from ..util import byte_quantity, split_image_name +from ..util import byte_quantity, split_image_name, warn from ..volume import NamedVolume from . import build @@ -241,7 +241,21 @@ def run(opts): debug(f"Treating {opts.pathogen!r} as unmanaged pathogen directory") if opts.workflow not in pathogen.registered_workflows(): - print(f"The {opts.workflow!r} workflow is not registered as a compatible workflow, but trying to run anyways.") + warn(cleandoc(f""" + The {opts.workflow!r} workflow is not registered by pathogen {opts.pathogen!r}! + + Trying to run it anyways (but it likely won't work)… + """)) + warn() + + elif opts.workflow not in pathogen.compatible_workflows("nextstrain run"): + warn(cleandoc(f""" + The {opts.workflow!r} workflow is registered by pathogen {opts.pathogen!r} + but not marked as compatible with `nextstrain run`! + + Trying to run it anyways (but it likely won't work)… + """)) + warn() workflow_directory = pathogen.workflow_path(opts.workflow) diff --git a/nextstrain/cli/command/version.py b/nextstrain/cli/command/version.py index 4ab8a925..a1e58395 100644 --- a/nextstrain/cli/command/version.py +++ b/nextstrain/cli/command/version.py @@ -68,15 +68,11 @@ def run(opts): print(" " + name) for version in versions.values(): is_default = version == defaults.get(name) - print(" " + str(version) + (f"={version.url or ''}" if opts.verbose else ""), "(default)" if is_default else "") + compatible_workflows = version.compatible_workflows("nextstrain run") + print(" " + str(version) + (f"={version.url or ''}" if opts.verbose else "") + + (" (default)" if is_default else "") + + (f" {{{', '.join(compatible_workflows)}}}" if compatible_workflows else "")) if opts.verbose: print(" " + str(version.path)) - - if registered_workflows := version.registered_workflows(): - print(" " + "Available workflows:") - for workflow in registered_workflows: - print(" " + workflow) - else: - print(" " + "No workflows listed, please refer to pathogen docs.") else: print(" (none)") diff --git a/nextstrain/cli/pathogens.py b/nextstrain/cli/pathogens.py index 5281c3d4..9a204e89 100644 --- a/nextstrain/cli/pathogens.py +++ b/nextstrain/cli/pathogens.py @@ -308,21 +308,33 @@ def __init__(self, name_version_url: str, new_setup: bool = False): def registered_workflows(self) -> Dict[str, Dict]: """ - Parses :attr:`.registration` to return a dict of registered - compatible workflows, where the keys are workflow names. + Parses :attr:`.registration` to return a dict of registered workflows, + where the keys are workflow names. """ if self.registration is None: debug("pathogen does not have a registration") return {} - workflows = self.registration.get("compatibility", {}).get("nextstrain run") + workflows = self.registration.get("workflows") if not isinstance(workflows, dict): - debug(f"pathogen registration.compatibility['nextstrain runs'] is not a dict (got a {type(workflows).__name__})") + debug(f"pathogen registration.workflows is not a dict (got a {type(workflows).__name__})") return {} return workflows + def compatible_workflows(self, feature: str) -> Dict[str, Dict]: + """ + Filters registered workflows to return a subset of workflows that are + compatible with the provided *feature*. + """ + return { + name: info + for name, info in self.registered_workflows().items() + if isinstance(info, dict) and info.get("compatibility", {}).get(feature) + } + + def workflow_path(self, workflow: str) -> Path: return self.path / workflow @@ -498,20 +510,13 @@ def test_compatibility() -> SetupTestResult: if self.registration is None: return msg + "\n(couldn't read registration)", False - try: - compatibility = self.registration["compatibility"]["nextstrain run"] - except (KeyError, IndexError, TypeError): - if DEBUGGING: - traceback.print_exc() - return msg + "\n(couldn't find 'compatibility: nextstrain run: …' field)", False - - if compatibility: - if workflows := self.registered_workflows(): - msg += f"\nAvailable workflows: {list(workflows.keys())}" - else: - msg += f"\nNo workflows listed, please refer to pathogen docs." + if not self.registered_workflows(): + return msg + "\n(no workflows registered)", False - return msg, bool(compatibility) + if not self.compatible_workflows("nextstrain run"): + return msg + "\n(no workflows registered as compatible)", False + + return msg, True return [ ('downloaded', @@ -521,6 +526,9 @@ def test_compatibility() -> SetupTestResult: self.registration_path.is_file()), test_compatibility(), + + *((f'`nextstrain run` workflow {name!r} exists', self.workflow_path(name).is_dir()) + for name in self.compatible_workflows("nextstrain run")), ] @@ -690,6 +698,7 @@ def __init__(self, path: str): self.registration = read_pathogen_registration(self.registration_path) registered_workflows = PathogenVersion.registered_workflows + compatible_workflows = PathogenVersion.compatible_workflows workflow_path = PathogenVersion.workflow_path def __str__(self) -> str: