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

Create: allow more special characters on the pipeline name for non-nf-core pipelines #3008

Merged
merged 4 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
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
16 changes: 7 additions & 9 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,7 +56,7 @@ class PipelineCreateApp(App[CreateConfig]):
}

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

# Initialise pipeline type
PIPELINE_TYPE = None
Expand All @@ -79,9 +75,11 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
self.push_screen("choose_type")
elif event.button.id == "type_nfcore":
self.PIPELINE_TYPE = "nfcore"
utils.PIPELINE_TYPE_GLOBAL = "nfcore"
self.push_screen("basic_details")
elif event.button.id == "type_custom":
self.PIPELINE_TYPE = "custom"
utils.PIPELINE_TYPE_GLOBAL = "custom"
self.push_screen("basic_details")
elif event.button.id == "continue":
self.push_screen("final_details")
Expand Down
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
PIPELINE_TYPE_GLOBAL: Union[str, None] = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making this an enum?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your comment made me think that it's actually better to use a boolean, because we only have nf-core or not nf-core, so there's no need to do complicated things :)



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": PIPELINE_TYPE_GLOBAL == "nfcore"}):
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
Loading