Skip to content

Commit

Permalink
Merge pull request #3008 from mirpedrol/pipeline-name-validation
Browse files Browse the repository at this point in the history
Create: allow more special characters on the pipeline name for non-nf-core pipelines
  • Loading branch information
mirpedrol authored May 31, 2024
2 parents 26fb9e5 + 2b724e0 commit d7ce41b
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- update minimal textual version and snapshots ([#2998](https://github.com/nf-core/tools/pull/2998))
- return directory if base_dir is the root directory ([#3003](https://github.com/nf-core/tools/pull/3003))
- Update pre-commit hook astral-sh/ruff-pre-commit to v0.4.6 ([#3006](https://github.com/nf-core/tools/pull/3006))
- Create: allow more special characters on the pipeline name for non-nf-core pipelines ([#3008](https://github.com/nf-core/tools/pull/3008))

## [v2.14.1 - Tantalum Toad - Patch](https://github.com/nf-core/tools/releases/tag/2.14.1) - [2024-05-09]

Expand Down
22 changes: 10 additions & 12 deletions nf_core/pipelines/create/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from textual.app import App
from textual.widgets import Button

from nf_core.pipelines.create import utils
from nf_core.pipelines.create.basicdetails import BasicDetails
from nf_core.pipelines.create.custompipeline import CustomPipeline
from nf_core.pipelines.create.finaldetails import FinalDetails
Expand All @@ -14,15 +15,10 @@
from nf_core.pipelines.create.loggingscreen import LoggingScreen
from nf_core.pipelines.create.nfcorepipeline import NfcorePipeline
from nf_core.pipelines.create.pipelinetype import ChoosePipelineType
from nf_core.pipelines.create.utils import (
CreateConfig,
CustomLogHandler,
LoggingConsole,
)
from nf_core.pipelines.create.welcome import WelcomeScreen

log_handler = CustomLogHandler(
console=LoggingConsole(classes="log_console"),
log_handler = utils.CustomLogHandler(
console=utils.LoggingConsole(classes="log_console"),
rich_tracebacks=True,
show_time=False,
show_path=False,
Expand All @@ -36,7 +32,7 @@
log_handler.setLevel("INFO")


class PipelineCreateApp(App[CreateConfig]):
class PipelineCreateApp(App[utils.CreateConfig]):
"""A Textual app to manage stopwatches."""

CSS_PATH = "create.tcss"
Expand All @@ -60,10 +56,10 @@ class PipelineCreateApp(App[CreateConfig]):
}

# Initialise config as empty
TEMPLATE_CONFIG = CreateConfig()
TEMPLATE_CONFIG = utils.CreateConfig()

# Initialise pipeline type
PIPELINE_TYPE = None
NFCORE_PIPELINE = True

# Log handler
LOG_HANDLER = log_handler
Expand All @@ -78,10 +74,12 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "start":
self.push_screen("choose_type")
elif event.button.id == "type_nfcore":
self.PIPELINE_TYPE = "nfcore"
self.NFCORE_PIPELINE = True
utils.NFCORE_PIPELINE_GLOBAL = True
self.push_screen("basic_details")
elif event.button.id == "type_custom":
self.PIPELINE_TYPE = "custom"
self.NFCORE_PIPELINE = False
utils.NFCORE_PIPELINE_GLOBAL = False
self.push_screen("basic_details")
elif event.button.id == "continue":
self.push_screen("final_details")
Expand Down
8 changes: 4 additions & 4 deletions nf_core/pipelines/create/basicdetails.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def compose(self) -> ComposeResult:
"GitHub organisation",
"nf-core",
classes="column",
disabled=self.parent.PIPELINE_TYPE == "nfcore",
disabled=self.parent.NFCORE_PIPELINE,
)
yield TextInput(
"name",
Expand Down Expand Up @@ -85,7 +85,7 @@ def on_screen_resume(self):
add_hide_class(self.parent, "exist_warn")
for text_input in self.query("TextInput"):
if text_input.field_id == "org":
text_input.disabled = self.parent.PIPELINE_TYPE == "nfcore"
text_input.disabled = self.parent.NFCORE_PIPELINE

@on(Button.Pressed)
def on_button_pressed(self, event: Button.Pressed) -> None:
Expand All @@ -102,9 +102,9 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
try:
self.parent.TEMPLATE_CONFIG = CreateConfig(**config)
if event.button.id == "next":
if self.parent.PIPELINE_TYPE == "nfcore":
if self.parent.NFCORE_PIPELINE:
self.parent.push_screen("type_nfcore")
elif self.parent.PIPELINE_TYPE == "custom":
else:
self.parent.push_screen("type_custom")
except ValueError:
pass
46 changes: 39 additions & 7 deletions nf_core/pipelines/create/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import re
from contextlib import contextmanager
from contextvars import ContextVar
from logging import LogRecord
from pathlib import Path
from typing import Optional, Union
from typing import Any, Dict, Iterator, Optional, Union

from pydantic import BaseModel, ConfigDict, ValidationError, field_validator
from pydantic import BaseModel, ConfigDict, ValidationError, ValidationInfo, field_validator
from rich.logging import RichHandler
from textual import on
from textual._context import active_app
Expand All @@ -14,6 +16,22 @@
from textual.widget import Widget
from textual.widgets import Button, Input, Markdown, RichLog, Static, Switch

# Use ContextVar to define a context on the model initialization
_init_context_var: ContextVar = ContextVar("_init_context_var", default={})


@contextmanager
def init_context(value: Dict[str, Any]) -> Iterator[None]:
token = _init_context_var.set(value)
try:
yield
finally:
_init_context_var.reset(token)


# Define a global variable to store the pipeline type
NFCORE_PIPELINE_GLOBAL: bool = True


class CreateConfig(BaseModel):
"""Pydantic model for the nf-core create config."""
Expand All @@ -30,12 +48,25 @@ class CreateConfig(BaseModel):

model_config = ConfigDict(extra="allow")

def __init__(self, /, **data: Any) -> None:
"""Custom init method to allow using a context on the model initialization."""
self.__pydantic_validator__.validate_python(
data,
self_instance=self,
context=_init_context_var.get(),
)

@field_validator("name")
@classmethod
def name_nospecialchars(cls, v: str) -> str:
def name_nospecialchars(cls, v: str, info: ValidationInfo) -> str:
"""Check that the pipeline name is simple."""
if not re.match(r"^[a-z]+$", v):
raise ValueError("Must be lowercase without punctuation.")
context = info.context
if context and context["is_nfcore"]:
if not re.match(r"^[a-z]+$", v):
raise ValueError("Must be lowercase without punctuation.")
else:
if not re.match(r"^[a-zA-Z-_]+$", v):
raise ValueError("Must not contain special characters. Only '-' or '_' are allowed.")
return v

@field_validator("org", "description", "author", "version", "outdir")
Expand Down Expand Up @@ -117,8 +148,9 @@ def validate(self, value: str) -> ValidationResult:
If it fails, return the error messages."""
try:
CreateConfig(**{f"{self.key}": value})
return self.success()
with init_context({"is_nfcore": NFCORE_PIPELINE_GLOBAL}):
CreateConfig(**{f"{self.key}": value})
return self.success()
except ValidationError as e:
return self.failure(", ".join([err["msg"] for err in e.errors()]))

Expand Down

0 comments on commit d7ce41b

Please sign in to comment.