Skip to content
Open
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
2 changes: 2 additions & 0 deletions nf_core/configs/create/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from nf_core.configs.create.hpcquestion import ChooseHpc
from nf_core.configs.create.nfcorequestion import ChooseNfcoreConfig
from nf_core.configs.create.welcome import WelcomeScreen
from nf_core.configs.create.defaultprocessres import DefaultProcess

## General utilities
from nf_core.utils import LoggingConsole
Expand Down Expand Up @@ -59,6 +60,7 @@ class ConfigsCreateApp(App[utils.ConfigsCreateConfig]):
"final": FinalScreen,
"hpc_question": ChooseHpc,
"hpc_customisation": HpcCustomisation,
"default_process_resources": DefaultProcess,
"final_infra_details": FinalInfraDetails,
}

Expand Down
6 changes: 4 additions & 2 deletions nf_core/configs/create/basicdetails.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from nf_core.configs.create.utils import (
ConfigsCreateConfig,
TextInput,
init_context
) ## TODO Move somewhere common?
from nf_core.utils import add_hide_class, remove_hide_class

Expand Down Expand Up @@ -101,12 +102,13 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
else:
text_input.query_one(".validation_msg").update("")
try:
self.parent.TEMPLATE_CONFIG = ConfigsCreateConfig(**config)
with init_context({"is_nfcore": self.parent.NFCORE_CONFIG, "is_infrastructure": self.parent.CONFIG_TYPE == "infrastructure"}):
self.parent.TEMPLATE_CONFIG = ConfigsCreateConfig(**config)
if event.button.id == "next":
if self.parent.CONFIG_TYPE == "infrastructure":
self.parent.push_screen("hpc_question")
elif self.parent.CONFIG_TYPE == "pipeline":
self.parent.push_screen("final")
self.parent.push_screen("default_process_resources")
except ValueError:
pass

Expand Down
4 changes: 3 additions & 1 deletion nf_core/configs/create/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from nf_core.configs.create.utils import ConfigsCreateConfig, generate_config_entry
from re import sub


class ConfigCreate:
Expand Down Expand Up @@ -31,7 +32,8 @@ def construct_params(self, contact, handle, description, url):

def write_to_file(self):
## File name option
filename = "_".join(self.template_config.general_config_name) + ".conf"
config_name = str(self.template_config.general_config_name).strip()
filename = sub(r'\s+', '_', config_name) + ".conf"

## Collect all config entries per scope, for later checking scope needs to be written
validparams = self.construct_params(
Expand Down
96 changes: 96 additions & 0 deletions nf_core/configs/create/defaultprocessres.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Get information about which process/label the user wants to configure."""

from textwrap import dedent

from textual import on
from textual.app import ComposeResult
from textual.containers import Center, Horizontal
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Input, Markdown

from nf_core.configs.create.utils import (
ConfigsCreateConfig,
TextInput,
init_context
) ## TODO Move somewhere common?
from nf_core.utils import add_hide_class, remove_hide_class


class DefaultProcess(Screen):
"""Get default process resource requirements."""

def compose(self) -> ComposeResult:
yield Header()
yield Footer()
yield Markdown(
dedent(
"""
# Default process resources
"""
)
)
yield TextInput(
"default_process_ncpus",
"2",
"Number of CPUs to use by default for all processes.",
"2",
classes="column",
)
yield TextInput(
"default_process_memgb",
"8",
"Amount of memory in GB to use by default for all processes.",
"8",
classes="column",
)
yield Markdown("The walltime required by default for all processes.")
with Horizontal():
yield TextInput(
"default_process_hours",
"1",
"Hours:",
"1",
classes="column",
)
yield TextInput(
"default_process_minutes",
"0",
"Minutes:",
"0",
classes="column",
)
yield TextInput(
"default_process_seconds",
"0",
"Seconds:",
"0",
classes="column",
)
yield Center(
Button("Back", id="back", variant="default"),
Button("Next", id="next", variant="success"),
classes="cta",
)

# Updates the __init__ initialised TEMPLATE_CONFIG object (which is built from the ConfigsCreateConfig class) with the values from the text inputs
@on(Button.Pressed)
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Save fields to the config."""
new_config = {}
for text_input in self.query("TextInput"):
this_input = text_input.query_one(Input)
validation_result = this_input.validate(this_input.value)
new_config[text_input.field_id] = this_input.value
if not validation_result.is_valid:
text_input.query_one(".validation_msg").update("\n".join(validation_result.failure_descriptions))
else:
text_input.query_one(".validation_msg").update("")
try:
config = self.parent.TEMPLATE_CONFIG.__dict__
config.update(new_config)
with init_context({"is_nfcore": self.parent.NFCORE_CONFIG, "is_infrastructure": self.parent.CONFIG_TYPE == "infrastructure"}):
self.parent.TEMPLATE_CONFIG = ConfigsCreateConfig(**config)
if event.button.id == "next":
self.parent.push_screen("final")
except ValueError:
pass
73 changes: 66 additions & 7 deletions nf_core/configs/create/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ class ConfigsCreateConfig(BaseModel):
""" Config description """
config_profile_url: Optional[str] = None
""" Config institution URL """
default_process_ncpus: Optional[str] = None
""" Default number of CPUs """
default_process_memgb: Optional[str] = None
""" Default amount of memory """
default_process_hours: Optional[str] = None
""" Default walltime - hours """
default_process_minutes: Optional[str] = None
""" Default walltime - minutes """
default_process_seconds: Optional[str] = None
""" Default walltime - seconds """
is_nfcore: Optional[bool] = None
""" Whether the config is part of the nf-core organisation """

Expand All @@ -78,19 +88,37 @@ def notempty(cls, v: str) -> str:
def path_valid(cls, v: str, info: ValidationInfo) -> str:
"""Check that a path is valid."""
context = info.context
if context and not context["is_infrastructure"]:
if context and (not context["is_infrastructure"] and not context["is_nfcore"]):
if v.strip() == "":
raise ValueError("Cannot be left empty.")
if not Path(v).is_dir():
raise ValueError("Must be a valid path.")
return v

@field_validator("config_profile_contact", "config_profile_description", "config_pipeline_name")
@field_validator("config_pipeline_name")
@classmethod
def notempty_nfcore(cls, v: str, info: ValidationInfo) -> str:
"""Check that string values are not empty when the config is nf-core."""
def nfcore_name_valid(cls, v: str, info: ValidationInfo) -> str:
"""Check that an nf-core pipeline name is valid."""
context = info.context
if context and context["is_nfcore"]:
if context and (not context["is_infrastructure"] and context["is_nfcore"]):
if v.strip() == "":
raise ValueError("Cannot be left empty.")
return v

@field_validator("config_profile_description")
@classmethod
def notempty_description(cls, v: str) -> str:
"""Check that description is not empty when."""
if v.strip() == "":
raise ValueError("Cannot be left empty.")
return v

@field_validator("config_profile_contact")
@classmethod
def notempty_contact(cls, v: str, info: ValidationInfo) -> str:
"""Check that contact values are not empty when the config is infrastructure."""
context = info.context
if context and context["is_infrastructure"]:
if v.strip() == "":
raise ValueError("Cannot be left empty.")
return v
Expand All @@ -103,7 +131,7 @@ def handle_prefix(cls, v: str, info: ValidationInfo) -> str:
"""Check that GitHub handles start with '@'.
Make providing a handle mandatory for nf-core configs"""
context = info.context
if context and context["is_nfcore"]:
if context and context["is_infrastructure"]:
if v.strip() == "":
raise ValueError("Cannot be left empty.")
elif not re.match(
Expand All @@ -122,7 +150,7 @@ def handle_prefix(cls, v: str, info: ValidationInfo) -> str:
def url_prefix(cls, v: str, info: ValidationInfo) -> str:
"""Check that institutional web links start with valid URL prefix."""
context = info.context
if context and context["is_nfcore"]:
if context and context["is_infrastructure"]:
if v.strip() == "":
raise ValueError("Cannot be left empty.")
elif not re.match(
Expand All @@ -142,6 +170,37 @@ def url_prefix(cls, v: str, info: ValidationInfo) -> str:
)
return v

@field_validator("default_process_ncpus", "default_process_memgb")
@classmethod
def pos_integer_valid(cls, v: str, info: ValidationInfo) -> str:
"""Check that integer values are non-empty and positive."""
context = info.context
if context and not context["is_infrastructure"]:
if v.strip() == "":
raise ValueError("Cannot be left empty.")
try:
v_int = int(v.strip())
except ValueError:
raise ValueError("Must be an integer.")
if not v_int > 0:
raise ValueError("Must be a positive integer.")
return v

@field_validator("default_process_hours", "default_process_minutes", "default_process_seconds")
@classmethod
def non_neg_integer_valid(cls, v: str, info: ValidationInfo) -> str:
"""Check that integer values are non-empty and non-negative."""
context = info.context
if context and not context["is_infrastructure"]:
if v.strip() == "":
raise ValueError("Cannot be left empty.")
try:
v_int = int(v.strip())
except ValueError:
raise ValueError("Must be an integer.")
if not v_int >= 0:
raise ValueError("Must be a non-negative integer.")
return v

## TODO Duplicated from pipelines utils - move to common location if possible (validation seems to be context specific so possibly not)
class TextInput(Static):
Expand Down
Loading