Skip to content

Commit 854fdc9

Browse files
authored
Merge pull request #4130 from ewels/modules-install-no-tty
2 parents caa7834 + c9716ff commit 854fdc9

26 files changed

Lines changed: 341 additions & 41 deletions

nf_core/__main__.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -456,12 +456,13 @@ def command_pipelines_download(
456456
default=False,
457457
help="Show hidden params which don't normally need changing",
458458
)
459+
@click.option("-n", "--no-prompts", is_flag=True, default=False, help="Run without prompting for user input")
459460
@click.pass_context
460-
def command_pipelines_create_params_file(ctx, pipeline, revision, output, force, show_hidden):
461+
def command_pipelines_create_params_file(ctx, pipeline, revision, output, force, show_hidden, no_prompts):
461462
"""
462463
Build a parameter file for a pipeline.
463464
"""
464-
pipelines_create_params_file(ctx, pipeline, revision, output, force, show_hidden)
465+
pipelines_create_params_file(ctx, pipeline, revision, output, force, show_hidden, no_prompts)
465466

466467

467468
# nf-core pipelines launch
@@ -515,6 +516,7 @@ def command_pipelines_create_params_file(ctx, pipeline, revision, output, force,
515516
default="https://nf-co.re/launch",
516517
help="Customise the builder URL (for development work)",
517518
)
519+
@click.option("-n", "--no-prompts", is_flag=True, default=False, help="Run without prompting for user input")
518520
@click.pass_context
519521
def command_pipelines_launch(
520522
ctx,
@@ -527,11 +529,14 @@ def command_pipelines_launch(
527529
save_all,
528530
show_hidden,
529531
url,
532+
no_prompts,
530533
):
531534
"""
532535
Launch a pipeline using a web GUI or command line prompts.
533536
"""
534-
pipelines_launch(ctx, pipeline, id, revision, command_only, params_in, params_out, save_all, show_hidden, url)
537+
pipelines_launch(
538+
ctx, pipeline, id, revision, command_only, params_in, params_out, save_all, show_hidden, url, no_prompts
539+
)
535540

536541

537542
# nf-core pipelines list
@@ -626,14 +631,33 @@ def rocrate(
626631
@click.option("-u", "--username", type=str, help="GitHub PR: auth username.")
627632
@click.option("-t", "--template-yaml", help="Pass a YAML file to customize the template")
628633
@click.option("-b", "--blog-post", type=str, help="Link to the blog post")
634+
@click.option("-n", "--no-prompts", is_flag=True, default=False, help="Run without prompting for user input")
629635
def command_pipelines_sync(
630-
ctx, directory, from_branch, pull_request, github_repository, username, template_yaml, force_pr, blog_post
636+
ctx,
637+
directory,
638+
from_branch,
639+
pull_request,
640+
github_repository,
641+
username,
642+
template_yaml,
643+
force_pr,
644+
blog_post,
645+
no_prompts,
631646
):
632647
"""
633648
Sync a pipeline [cyan i]TEMPLATE[/] branch with the nf-core template.
634649
"""
635650
pipelines_sync(
636-
ctx, directory, from_branch, pull_request, github_repository, username, template_yaml, force_pr, blog_post
651+
ctx,
652+
directory,
653+
from_branch,
654+
pull_request,
655+
github_repository,
656+
username,
657+
template_yaml,
658+
force_pr,
659+
blog_post,
660+
no_prompts,
637661
)
638662

639663

@@ -2142,14 +2166,27 @@ def command_create_logo(logo_text, directory, name, theme, width, format, force)
21422166
@click.option("-g", "--github-repository", type=str, help="GitHub PR: target repository.")
21432167
@click.option("-u", "--username", type=str, help="GitHub PR: auth username.")
21442168
@click.option("-t", "--template-yaml", help="Pass a YAML file to customize the template")
2145-
def command_sync(ctx, directory, from_branch, pull_request, github_repository, username, template_yaml, force_pr):
2169+
@click.option("-n", "--no-prompts", is_flag=True, default=False, help="Run without prompting for user input")
2170+
def command_sync(
2171+
ctx, directory, from_branch, pull_request, github_repository, username, template_yaml, force_pr, no_prompts
2172+
):
21462173
"""
21472174
Use `nf-core pipelines sync` instead.
21482175
"""
21492176
log.warning(
21502177
"The `[magenta]nf-core sync[/]` command is deprecated. Use `[magenta]nf-core pipelines sync[/]` instead."
21512178
)
2152-
pipelines_sync(ctx, directory, from_branch, pull_request, github_repository, username, template_yaml, force_pr)
2179+
pipelines_sync(
2180+
ctx,
2181+
directory,
2182+
from_branch,
2183+
pull_request,
2184+
github_repository,
2185+
username,
2186+
template_yaml,
2187+
force_pr,
2188+
no_prompts=no_prompts,
2189+
)
21532190

21542191

21552192
# nf-core bump-version (deprecated)
@@ -2250,6 +2287,7 @@ def command_list(ctx, keywords, sort, json, show_archived):
22502287
default="https://nf-co.re/launch",
22512288
help="Customise the builder URL (for development work)",
22522289
)
2290+
@click.option("-n", "--no-prompts", is_flag=True, default=False, help="Run without prompting for user input")
22532291
@click.pass_context
22542292
def command_launch(
22552293
ctx,
@@ -2262,14 +2300,17 @@ def command_launch(
22622300
save_all,
22632301
show_hidden,
22642302
url,
2303+
no_prompts,
22652304
):
22662305
"""
22672306
Use `nf-core pipelines launch` instead.
22682307
"""
22692308
log.warning(
22702309
"The `[magenta]nf-core launch[/]` command is deprecated. Use `[magenta]nf-core pipelines launch[/]` instead."
22712310
)
2272-
pipelines_launch(ctx, pipeline, id, revision, command_only, params_in, params_out, save_all, show_hidden, url)
2311+
pipelines_launch(
2312+
ctx, pipeline, id, revision, command_only, params_in, params_out, save_all, show_hidden, url, no_prompts
2313+
)
22732314

22742315

22752316
# nf-core create-params-file (deprecated)
@@ -2292,14 +2333,15 @@ def command_launch(
22922333
default=False,
22932334
help="Show hidden params which don't normally need changing",
22942335
)
2295-
def command_create_params_file(pipeline, revision, output, force, show_hidden):
2336+
@click.option("-n", "--no-prompts", is_flag=True, default=False, help="Run without prompting for user input")
2337+
def command_create_params_file(pipeline, revision, output, force, show_hidden, no_prompts):
22962338
"""
22972339
Use `nf-core pipelines create-params-file` instead.
22982340
"""
22992341
log.warning(
23002342
"The `[magenta]nf-core create-params-file[/]` command is deprecated. Use `[magenta]nf-core pipelines create-params-file[/]` instead."
23012343
)
2302-
pipelines_create_params_file(pipeline, revision, output, force, show_hidden)
2344+
pipelines_create_params_file(pipeline, revision, output, force, show_hidden, no_prompts)
23032345

23042346

23052347
# nf-core download (deprecated)

nf_core/commands_pipelines.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import rich
77

8+
import nf_core.utils
89
from nf_core.pipelines.params_file import ParamsFileBuilder
910
from nf_core.utils import rich_force_colors
1011

@@ -53,6 +54,12 @@ def pipelines_create(ctx, name, description, author, version, force, outdir, tem
5354
)
5455
sys.exit(1)
5556
else:
57+
if not nf_core.utils.is_interactive():
58+
log.error(
59+
"No pipeline arguments provided and session is not interactive (no TTY detected). "
60+
"Please provide at least --name, --description, and --author to run non-interactively."
61+
)
62+
sys.exit(1)
5663
log.info("Launching interactive nf-core pipeline creation tool.")
5764
app = PipelineCreateApp()
5865
app.run()
@@ -206,7 +213,7 @@ def pipelines_download(
206213

207214

208215
# nf-core pipelines create-params-file
209-
def pipelines_create_params_file(ctx, pipeline, revision, output, force, show_hidden):
216+
def pipelines_create_params_file(ctx, pipeline, revision, output, force, show_hidden, no_prompts=False):
210217
"""
211218
Build a parameter file for a pipeline.
212219
@@ -218,7 +225,7 @@ def pipelines_create_params_file(ctx, pipeline, revision, output, force, show_hi
218225
Run using a remote pipeline name (such as GitHub `user/repo` or a URL),
219226
a local pipeline directory.
220227
"""
221-
builder = ParamsFileBuilder(pipeline, revision)
228+
builder = ParamsFileBuilder(pipeline, revision, no_prompts)
222229

223230
if not builder.write_params_file(Path(output), show_hidden=show_hidden, force=force):
224231
sys.exit(1)
@@ -236,6 +243,7 @@ def pipelines_launch(
236243
save_all,
237244
show_hidden,
238245
url,
246+
no_prompts=False,
239247
):
240248
"""
241249
Launch a pipeline using a web GUI or command line prompts.
@@ -262,6 +270,7 @@ def pipelines_launch(
262270
show_hidden,
263271
url,
264272
id,
273+
no_prompts,
265274
)
266275
if not launcher.launch_pipeline():
267276
sys.exit(1)
@@ -309,7 +318,16 @@ def pipelines_rocrate(
309318

310319
# nf-core pipelines sync
311320
def pipelines_sync(
312-
ctx, directory, from_branch, pull_request, github_repository, username, template_yaml, force_pr, blog_post
321+
ctx,
322+
directory,
323+
from_branch,
324+
pull_request,
325+
github_repository,
326+
username,
327+
template_yaml,
328+
force_pr,
329+
blog_post,
330+
no_prompts=False,
313331
):
314332
"""
315333
Sync a pipeline [cyan i]TEMPLATE[/] branch with the nf-core template.
@@ -331,7 +349,15 @@ def pipelines_sync(
331349
is_pipeline_directory(directory)
332350
# Sync the given pipeline dir
333351
sync_obj = PipelineSync(
334-
directory, from_branch, pull_request, github_repository, username, template_yaml, force_pr, blog_post
352+
directory,
353+
from_branch,
354+
pull_request,
355+
github_repository,
356+
username,
357+
template_yaml,
358+
force_pr,
359+
blog_post,
360+
no_prompts,
335361
)
336362
sync_obj.sync()
337363
except (SyncExceptionError, PullRequestExceptionError) as e:

nf_core/components/components_command.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,16 @@ def __init__(
3535
self.directory: Path = Path(directory)
3636
self.modules_repo = ModulesRepo(remote_url, branch, no_pull, hide_progress)
3737
self.hide_progress: bool = hide_progress
38-
self.no_prompts: bool = no_prompts
38+
self.no_prompts: bool = no_prompts or not nf_core.utils.is_interactive()
3939
self.repo_type: str | None = None
4040
self.org: str = ""
4141
self._configure_repo_and_paths()
4242

43+
def require_prompts(self, msg: str) -> None:
44+
"""Raise UserWarning if prompts are disabled (via --no-prompts or non-interactive session)."""
45+
if self.no_prompts:
46+
raise UserWarning(f"{msg} and prompts are disabled.")
47+
4348
def _configure_repo_and_paths(self, nf_dir_req: bool = True) -> None:
4449
"""
4550
Determine the repo type and set some default paths.

nf_core/components/components_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def check_inputs(self) -> None:
122122
except LookupError:
123123
raise
124124

125+
assert self.component_name is not None # Set above by user input, prompt, or guard
125126
self.component_dir = Path(self.component_type, self.modules_repo.repo_path, *self.component_name.split("/"))
126127

127128
# First, sanity check that the module directory exists
@@ -170,7 +171,6 @@ def display_nftest_output(self, nftest_out: bytes, nftest_err: bytes) -> None:
170171
print("Displaying nf-test error")
171172
if "Different Snapshot:" in nftest_err.decode():
172173
log.error("nf-test failed due to differences in the snapshots")
173-
# prompt to update snapshot
174174
if self.no_prompts:
175175
log.info("Updating snapshot")
176176
self.update = True

nf_core/components/components_utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def get_repo_info(directory: Path, use_prompt: bool | None = True) -> tuple[Path
7070
# Check for org if modules repo
7171
if repo_type == "modules":
7272
org = getattr(tools_config, "org_path", "") or ""
73-
if org == "":
73+
if org == "" and use_prompt:
7474
log.warning("Organisation path not defined in %s [key: org_path]", config_fn.name)
7575
org = questionary.text(
7676
"What is the organisation path under which modules and subworkflows are stored?",
@@ -107,6 +107,8 @@ def prompt_component_version_sha(
107107
Returns:
108108
git_sha (str): The selected version of the module/subworkflow
109109
"""
110+
if not nf_core.utils.is_interactive():
111+
raise UserWarning("Cannot interactively select a version and session is not interactive (no TTY detected).")
110112
older_commits_choice = questionary.Choice(
111113
title=[("fg:ansiyellow", "older commits"), ("class:choice-default", "")], value=""
112114
)

nf_core/components/create.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ def _get_bioconda_tool(self):
231231
log.warning(
232232
f"Could not find Conda dependency using the Anaconda API: '{self.tool_conda_name if self.tool_conda_name else self.component}'"
233233
)
234+
if self.no_prompts:
235+
log.warning(
236+
f"{e}\nBioconda package not found and prompts are disabled. "
237+
"Building module without tool software and meta."
238+
)
239+
break
234240
if rich.prompt.Confirm.ask("[violet]Do you want to enter a different Bioconda package name?"):
235241
self.tool_conda_name = rich.prompt.Prompt.ask("[violet]Name of Bioconda package").strip()
236242
continue
@@ -272,6 +278,7 @@ def _get_module_structure_components(self):
272278
"For example: {}".format(", ".join(process_label_defaults))
273279
)
274280
while self.process_label is None:
281+
self.require_prompts("Process label not provided.\nPlease provide the `--process-label` option")
275282
self.process_label = questionary.autocomplete(
276283
"Process resource label:",
277284
choices=process_label_defaults,
@@ -287,6 +294,9 @@ def _get_module_structure_components(self):
287294
"[link=https://github.com/nf-core/modules/blob/master/modules/nf-core/bwa/index/main.nf]indexing reference genome files[/link]."
288295
)
289296
while self.has_meta is None:
297+
self.require_prompts(
298+
"Meta map requirement not specified.\nPlease provide the `--has-meta` or `--no-meta` option"
299+
)
290300
self.has_meta = rich.prompt.Confirm.ask(
291301
"[violet]Will the module require a meta map of sample information?",
292302
default=True,
@@ -346,7 +356,7 @@ def _collect_name_prompt(self):
346356
elif self.component_type == "subworkflows":
347357
log.warning("Subworkflow name must be lower-case letters only, with no punctuation")
348358
name_clean = re.sub(r"[^a-z\d/]", "", self.component.lower())
349-
if rich.prompt.Confirm.ask(f"[violet]Change '{self.component}' to '{name_clean}'?"):
359+
if self.no_prompts or rich.prompt.Confirm.ask(f"[violet]Change '{self.component}' to '{name_clean}'?"):
350360
self.component = name_clean
351361
else:
352362
self.component = ""
@@ -363,6 +373,10 @@ def _collect_name_prompt(self):
363373

364374
# Prompt for new entry if we reset
365375
if self.component == "":
376+
self.require_prompts(
377+
f"No {self.component_type[:-1]} name provided.\n"
378+
f"Please provide the {self.component_type[:-1]} name as a command-line argument"
379+
)
366380
if self.component_type == "modules":
367381
self.component = rich.prompt.Prompt.ask("[violet]Name of tool/subtool").strip()
368382
elif self.component_type == "subworkflows":
@@ -435,6 +449,7 @@ def _get_username(self):
435449
while self.author is None or not github_username_regex.match(self.author):
436450
if self.author is not None and not github_username_regex.match(self.author):
437451
log.warning("Does not look like a valid GitHub username (must start with an '@')!")
452+
self.require_prompts("GitHub username not provided.\nPlease provide the `--author` option")
438453
self.author = rich.prompt.Prompt.ask(
439454
f"[violet]GitHub Username:[/]{' (@author)' if author_default is None else ''}",
440455
default=author_default,
@@ -486,10 +501,15 @@ def _copy_old_files(self, component_old_path):
486501
def _print_and_delete_pytest_files(self):
487502
"""Prompt if pytest files should be deleted and printed to stdout"""
488503
pytest_dir = Path(self.directory, "tests", self.component_type, self.org, self.component_dir)
489-
if rich.prompt.Confirm.ask(
490-
"[violet]Do you want to delete the pytest files?[/]\nPytest file 'main.nf' will be printed to standard output to allow migrating the tests manually to 'main.nf.test'.",
491-
default=False,
492-
):
504+
if self.no_prompts:
505+
log.info("Prompts disabled: skipping pytest file deletion prompt.")
506+
delete_pytest = False
507+
else:
508+
delete_pytest = rich.prompt.Confirm.ask(
509+
"[violet]Do you want to delete the pytest files?[/]\nPytest file 'main.nf' will be printed to standard output to allow migrating the tests manually to 'main.nf.test'.",
510+
default=False,
511+
)
512+
if delete_pytest:
493513
with open(pytest_dir / "main.nf") as fh:
494514
log.info(fh.read())
495515
if pytest_dir.is_symlink():

nf_core/components/info.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ def init_mod_name(self, component: str | None) -> str:
9797
module: str: Module name to check
9898
"""
9999
if component is None:
100+
self.require_prompts(
101+
f"No {self.component_type[:-1]} name provided.\n"
102+
f"Please provide the {self.component_type[:-1]} name as a command-line argument"
103+
)
100104
self.local = questionary.confirm(
101105
f"Is the {self.component_type[:-1]} locally installed?", style=nf_core.utils.nfcore_question_style
102106
).unsafe_ask()

0 commit comments

Comments
 (0)