Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polish looper init tutorial and looper init_piface #508

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions looper/cli_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@
read_yaml_file,
inspect_looper_config_file,
is_PEP_file_type,
looper_config_tutorial,
)

from typing import List, Tuple
from rich.console import Console


def opt_attr_pair(name: str) -> Tuple[str, str]:
Expand Down Expand Up @@ -122,17 +124,34 @@ def run_looper(args: TopLevelParser, parser: ArgumentParser, test_args=None):
sys.exit(1)

if subcommand_name == "init":
return int(
not initiate_looper_config(
dotfile_path(),
subcommand_args.pep_config,
subcommand_args.output_dir,
subcommand_args.sample_pipeline_interfaces,
subcommand_args.project_pipeline_interfaces,
subcommand_args.force_yes,
)

console = Console()
console.clear()
console.rule(f"\n[magenta]Looper initialization[/magenta]")
console.print(
"[bold]Would you like to follow a guided tutorial?[/bold] [green]Y[/green] / [red]n[/red]..."
)

selection = None
while selection not in ["y", "n"]:
selection = console.input("\nSelection: ").lower().strip()

if selection == "n":
console.clear()
return int(
not initiate_looper_config(
dotfile_path(),
subcommand_args.pep_config,
subcommand_args.output_dir,
subcommand_args.sample_pipeline_interfaces,
subcommand_args.project_pipeline_interfaces,
subcommand_args.force_yes,
)
)
else:
console.clear()
return int(looper_config_tutorial())

if subcommand_name == "init_piface":
sys.exit(int(not init_generic_pipeline()))

Expand Down
186 changes: 169 additions & 17 deletions looper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from .const import *
from .command_models.commands import SUPPORTED_COMMANDS
from .exceptions import MisconfigurationException, PipelineInterfaceConfigError
from rich.console import Console
from rich.pretty import pprint

_LOGGER = getLogger(__name__)

Expand Down Expand Up @@ -406,6 +408,8 @@ def init_generic_pipeline():
"""
Create generic pipeline interface
"""
console = Console()

try:
os.makedirs("pipeline")
except FileExistsError:
Expand All @@ -417,21 +421,26 @@ def init_generic_pipeline():
# Create Generic Pipeline Interface
generic_pipeline_dict = {
"pipeline_name": "default_pipeline_name",
"pipeline_type": "sample",
"output_schema": "output_schema.yaml",
"var_templates": {"pipeline": "{looper.piface_dir}/pipeline.sh"},
"command_template": "{pipeline.var_templates.pipeline} {sample.file} "
"--output-parent {looper.sample_output_folder}",
"var_templates": {"pipeline": "{looper.piface_dir}/count_lines.sh"},
"sample_interface": {
"command_template": "{pipeline.var_templates.pipeline} {sample.file} "
"--output-parent {looper.sample_output_folder}"
},
}

console.rule(f"\n[magenta]Pipeline Interface[/magenta]")
# Write file
if not os.path.exists(dest_file):
pprint(generic_pipeline_dict, expand_all=True)
with open(dest_file, "w") as file:
yaml.dump(generic_pipeline_dict, file)
print(f"Pipeline interface successfully created at: {dest_file}")
console.print(
f"Pipeline interface successfully created at: [yellow]{dest_file}[/yellow]"
)
else:
print(
f"Pipeline interface file already exists `{dest_file}`. Skipping creation.."
console.print(
f"Pipeline interface file already exists [yellow]`{dest_file}`[/yellow]. Skipping creation.."
)

# Create Generic Output Schema
Expand All @@ -445,14 +454,22 @@ def init_generic_pipeline():
}
},
}

console.rule(f"\n[magenta]Output Schema[/magenta]")
# Write file
if not os.path.exists(dest_file):
pprint(generic_output_schema_dict, expand_all=True)
with open(dest_file, "w") as file:
yaml.dump(generic_output_schema_dict, file)
print(f"Output schema successfully created at: {dest_file}")
console.print(
f"Output schema successfully created at: [yellow]{dest_file}[/yellow]"
)
else:
print(f"Output schema file already exists `{dest_file}`. Skipping creation..")
console.print(
f"Output schema file already exists [yellow]`{dest_file}`[/yellow]. Skipping creation.."
)

console.rule(f"\n[magenta]Example Pipeline Shell Script[/magenta]")
# Create Generic countlines.sh
dest_file = os.path.join(os.getcwd(), "pipeline", LOOPER_GENERIC_COUNT_LINES)
shell_code = """#!/bin/bash
Expand All @@ -461,11 +478,16 @@ def init_generic_pipeline():
echo "Number of lines: $linecount"
"""
if not os.path.exists(dest_file):
console.print(shell_code)
with open(dest_file, "w") as file:
file.write(shell_code)
print(f"count_lines.sh successfully created at: {dest_file}")
console.print(
f"count_lines.sh successfully created at: [yellow]{dest_file}[/yellow]"
)
else:
print(f"count_lines.sh file already exists `{dest_file}`. Skipping creation..")
console.print(
f"count_lines.sh file already exists [yellow]`{dest_file}`[/yellow]. Skipping creation.."
)

return True

Expand Down Expand Up @@ -500,8 +522,14 @@ def initiate_looper_config(
:param bool force: whether the existing file should be overwritten
:return bool: whether the file was initialized
"""
console = Console()
console.clear()
console.rule(f"\n[magenta]Looper initialization[/magenta]")

if os.path.exists(looper_config_path) and not force:
print(f"Can't initialize, file exists: {looper_config_path}")
console.print(
f"[red]Can't initialize, file exists:[/red] [yellow]{looper_config_path}[/yellow]"
)
return False

if pep_path:
Expand All @@ -521,18 +549,142 @@ def initiate_looper_config(
if not output_dir:
output_dir = "."

if sample_pipeline_interfaces is None or sample_pipeline_interfaces == []:
sample_pipeline_interfaces = "pipeline_interface1.yaml"

if project_pipeline_interfaces is None or project_pipeline_interfaces == []:
project_pipeline_interfaces = "pipeline_interface2.yaml"

looper_config_dict = {
"pep_config": os.path.relpath(pep_path),
"output_dir": output_dir,
"pipeline_interfaces": {
"sample": sample_pipeline_interfaces,
"project": project_pipeline_interfaces,
},
"pipeline_interfaces": [
sample_pipeline_interfaces,
project_pipeline_interfaces,
],
}

pprint(looper_config_dict, expand_all=True)

with open(looper_config_path, "w") as dotfile:
yaml.dump(looper_config_dict, dotfile)
print(f"Initialized looper config file: {looper_config_path}")
console.print(
f"Initialized looper config file: [yellow]{looper_config_path}[/yellow]"
)

return True


def looper_config_tutorial():
"""
Prompt a user through configuring a .looper.yaml file for a new project.

:return bool: whether the file was initialized
"""

console = Console()
console.clear()
console.rule(f"\n[magenta]Looper initialization[/magenta]")

looper_cfg_path = ".looper.yaml" # not changeable

if os.path.exists(looper_cfg_path):
console.print(
f"[bold red]File exists at '{looper_cfg_path}'. Delete it to re-initialize. \n[/bold red]"
)
raise SystemExit

cfg = {}

console.print(
"This utility will walk you through creating a [yellow].looper.yaml[/yellow] file."
)
console.print("See [yellow]`looper init --help`[/yellow] for details.")
console.print("Use [yellow]`looper run`[/yellow] afterwards to run the pipeline.")
console.print("Press [yellow]^C[/yellow] at any time to quit.\n")

console.input("> ... ")

DEFAULTS = { # What you get if you just press enter
"pep_config": "databio/example",
"output_dir": "results",
"piface_path": "pipeline/pipeline_interface.yaml",
"project_name": os.path.basename(os.getcwd()),
}

creating = True

while creating:
cfg["project_name"] = (
console.input(
f"Project name: [yellow]({DEFAULTS['project_name']})[/yellow] >"
)
or DEFAULTS["project_name"]
)

cfg["pep_config"] = (
console.input(
f"Registry path or file path to PEP: [yellow]({DEFAULTS['pep_config']})[/yellow] >"
)
or DEFAULTS["pep_config"]
)

if not os.path.exists(cfg["pep_config"]):
console.print(
f"Warning: PEP file does not exist at [yellow]'{cfg['pep_config']}[/yellow]'"
)

cfg["output_dir"] = (
console.input(
f"Path to output directory: [yellow]({DEFAULTS['output_dir']})[/yellow] >"
)
or DEFAULTS["output_dir"]
)

piface_path = (
console.input(
"Path to sample pipeline interface: [yellow](pipeline_interface.yaml)[/yellow] >"
)
or DEFAULTS["piface_path"]
)
console.print("\n")

console.print(
f"""\
[yellow]pep_config:[/yellow] {cfg['pep_config']}
[yellow]output_dir:[/yellow] {cfg['output_dir']}
[yellow]pipeline_interfaces:[/yellow]
- {piface_path}
"""
)

console.print(
"[bold]Does this look good?[/bold] [bold green]Y[/bold green]/[red]n[/red]..."
)
selection = None
while selection not in ["y", "n"]:
selection = console.input("\nSelection: ").lower().strip()
if selection == "n":
console.print("Starting over...")
pass
if selection == "y":
creating = False

if not os.path.exists(piface_path):
console.print(
f"[bold red]Warning:[/bold red] File does not exist at [yellow]{piface_path}[/yellow]\nUse command [yellow]`looper init_piface`[/yellow] to create a generic pipeline interface."
)

console.print(f"Writing config file to [yellow]{looper_cfg_path}[/yellow]")

looper_config_dict = {}
looper_config_dict["pep_config"] = cfg["pep_config"]
looper_config_dict["output_dir"] = cfg["output_dir"]
looper_config_dict["pipeline_interfaces"] = [piface_path]

with open(looper_cfg_path, "w") as fp:
yaml.dump(looper_config_dict, fp)

return True


Expand Down
68 changes: 0 additions & 68 deletions looper_init.py

This file was deleted.

3 changes: 3 additions & 0 deletions tests/smoketests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,9 @@ def test_cli_compute_overwrites_yaml_settings_spec(self, prep_temp_pep, cmd):
assert_content_not_in_any_files(subs_list, "testin_mem")


@pytest.mark.skip(
reason="This functionality requires input from the user. Causing pytest to error if run without -s flag"
)
class TestLooperConfig:

def test_init_config_file(self, prep_temp_pep):
Expand Down
Loading