Skip to content

Commit

Permalink
Merge pull request #3428 from nf-core/dev
Browse files Browse the repository at this point in the history
dev -> main for 3.2.0 release
  • Loading branch information
mirpedrol authored Jan 27, 2025
2 parents 34bc338 + 0355435 commit 52e8109
Show file tree
Hide file tree
Showing 46 changed files with 434 additions and 209 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
rev: v0.9.3
hooks:
- id: ruff # linter
args: [--fix, --exit-non-zero-on-fix] # sort imports and fix
Expand All @@ -13,7 +13,7 @@ repos:
- [email protected]

- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: "3.0.3"
rev: "3.1.2"
hooks:
- id: editorconfig-checker
alias: ec
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# nf-core/tools: Changelog

## [v3.2.0 - Pewter Pangolin](https://github.com/nf-core/tools/releases/tag/3.2.0) - [2025-01-27]

### Template

- Remove automated release tweets ([#3419](https://github.com/nf-core/tools/pull/3419))
- Update template components ([#3426](https://github.com/nf-core/tools/pull/3426))
- Fix `process.shell` in `nextflow.config` ([#3416](https://github.com/nf-core/tools/pull/3416)) and split into new lines ([#3425](https://github.com/nf-core/tools/pull/3425))

### Modules

- Modules created in pipelines "local" dir now use the full template ([#3256](https://github.com/nf-core/tools/pull/3256))

### Subworkflows

- Subworkflows created in pipelines "local" dir now use the full template ([#3256](https://github.com/nf-core/tools/pull/3256))

### General

- Update pre-commit hook editorconfig-checker/editorconfig-checker.python to v3.1.2 ([#3414](https://github.com/nf-core/tools/pull/3414))
- Update python:3.12-slim Docker digest to 123be56 ([#3421](https://github.com/nf-core/tools/pull/3421))

## [v3.1.2 - Brass Boxfish Patch](https://github.com/nf-core/tools/releases/tag/3.1.2) - [2025-01-20]

### Template
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.12-slim@sha256:10f3aaab98db50cba827d3b33a91f39dc9ec2d02ca9b85cbc5008220d07b17f3
FROM python:3.12-slim@sha256:123be5684f39d8476e64f47a5fddf38f5e9d839baff5c023c815ae5bdfae0df7
LABEL authors="[email protected],[email protected]" \
description="Docker image containing requirements for nf-core/tools"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# modules_structure

```{eval-rst}
.. automethod:: nf_core.pipelines.lint.PipelineLint.local_component_structure
```
4 changes: 4 additions & 0 deletions nf_core/components/components_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def get_local_components(self) -> List[str]:
"""
local_component_dir = Path(self.directory, self.component_type, "local")
return [
str(Path(directory).relative_to(local_component_dir))
for directory, _, files in os.walk(local_component_dir)
if "main.nf" in files
] + [
str(path.relative_to(local_component_dir)) for path in local_component_dir.iterdir() if path.suffix == ".nf"
]

Expand Down
4 changes: 1 addition & 3 deletions nf_core/components/components_differ.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,7 @@ def print_diff(
console = Console(force_terminal=nf_core.utils.rich_force_colors())
if current_version is not None and new_version is not None:
log.info(
f"Changes in component '{Path(repo_path, component)}' between"
f" ({current_version}) and"
f" ({new_version})"
f"Changes in component '{Path(repo_path, component)}' between ({current_version}) and ({new_version})"
)
else:
log.info(f"Changes in component '{Path(repo_path, component)}'")
Expand Down
96 changes: 36 additions & 60 deletions nf_core/components/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ def create(self) -> bool:
e.g bam_sort or bam_sort_samtools, respectively.
If <directory> is a pipeline, this function creates a file called:
'<directory>/modules/local/tool.nf'
'<directory>/modules/local/tool/main.nf'
OR
'<directory>/modules/local/tool_subtool.nf'
'<directory>/modules/local/tool/subtool/main.nf'
OR for subworkflows
'<directory>/subworkflows/local/subworkflow_name.nf'
'<directory>/subworkflows/local/subworkflow_name/main.nf'
If <directory> is a clone of nf-core/modules, it creates or modifies the following files:
Expand Down Expand Up @@ -355,70 +355,46 @@ def _get_component_dirs(self) -> Dict[str, Path]:
"""
file_paths = {}
if self.repo_type == "pipeline":
local_component_dir = Path(self.directory, self.component_type, "local")
# Check whether component file already exists
component_file = local_component_dir / f"{self.component_name}.nf"
if component_file.exists() and not self.force_overwrite:
raise UserWarning(
f"{self.component_type[:-1].title()} file exists already: '{component_file}'. Use '--force' to overwrite"
)

if self.component_type == "modules":
# If a subtool, check if there is a module called the base tool name already
if self.subtool and (local_component_dir / f"{self.component}.nf").exists():
raise UserWarning(
f"Module '{self.component}' exists already, cannot make subtool '{self.component_name}'"
)

# If no subtool, check that there isn't already a tool/subtool
tool_glob = glob.glob(f"{local_component_dir}/{self.component}_*.nf")
if not self.subtool and tool_glob:
raise UserWarning(
f"Module subtool '{tool_glob[0]}' exists already, cannot make tool '{self.component_name}'"
)

# Set file paths
file_paths["main.nf"] = component_file
component_dir = Path(self.directory, self.component_type, "local", self.component_dir)

elif self.repo_type == "modules":
component_dir = Path(self.directory, self.component_type, self.org, self.component_dir)
# Check if module/subworkflow directories exist already
if component_dir.exists() and not self.force_overwrite and not self.migrate_pytest:
raise UserWarning(
f"{self.component_type[:-1]} directory exists: '{component_dir}'. Use '--force' to overwrite"
)
else:
raise ValueError("`repo_type` not set correctly")

if self.component_type == "modules":
# If a subtool, check if there is a module called the base tool name already
parent_tool_main_nf = Path(
self.directory,
self.component_type,
self.org,
self.component,
"main.nf",
# Check if module/subworkflow directories exist already
if component_dir.exists() and not self.force_overwrite and not self.migrate_pytest:
raise UserWarning(
f"{self.component_type[:-1]} directory exists: '{component_dir}'. Use '--force' to overwrite"
)

if self.component_type == "modules":
# If a subtool, check if there is a module called the base tool name already
parent_tool_main_nf = Path(
self.directory,
self.component_type,
self.org,
self.component,
"main.nf",
)
if self.subtool and parent_tool_main_nf.exists() and not self.migrate_pytest:
raise UserWarning(
f"Module '{parent_tool_main_nf}' exists already, cannot make subtool '{self.component_name}'"
)
if self.subtool and parent_tool_main_nf.exists() and not self.migrate_pytest:
raise UserWarning(
f"Module '{parent_tool_main_nf}' exists already, cannot make subtool '{self.component_name}'"
)

# If no subtool, check that there isn't already a tool/subtool
tool_glob = glob.glob(
f"{Path(self.directory, self.component_type, self.org, self.component)}/*/main.nf"
# If no subtool, check that there isn't already a tool/subtool
tool_glob = glob.glob(f"{Path(self.directory, self.component_type, self.org, self.component)}/*/main.nf")
if not self.subtool and tool_glob and not self.migrate_pytest:
raise UserWarning(
f"Module subtool '{tool_glob[0]}' exists already, cannot make tool '{self.component_name}'"
)
if not self.subtool and tool_glob and not self.migrate_pytest:
raise UserWarning(
f"Module subtool '{tool_glob[0]}' exists already, cannot make tool '{self.component_name}'"
)
# Set file paths
# For modules - can be tool/ or tool/subtool/ so can't do in template directory structure
file_paths["main.nf"] = component_dir / "main.nf"
file_paths["meta.yml"] = component_dir / "meta.yml"
if self.component_type == "modules":
file_paths["environment.yml"] = component_dir / "environment.yml"
file_paths["tests/main.nf.test.j2"] = component_dir / "tests" / "main.nf.test"
else:
raise ValueError("`repo_type` not set correctly")
# Set file paths
# For modules - can be tool/ or tool/subtool/ so can't do in template directory structure
file_paths["main.nf"] = component_dir / "main.nf"
file_paths["meta.yml"] = component_dir / "meta.yml"
if self.component_type == "modules":
file_paths["environment.yml"] = component_dir / "environment.yml"
file_paths["tests/main.nf.test.j2"] = component_dir / "tests" / "main.nf.test"

return file_paths

Expand Down
4 changes: 2 additions & 2 deletions nf_core/components/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,9 @@ def generate_component_info_help(self):
intro_text.append(
Text.from_markup(
":globe_with_meridians: Repository: "
f"{ '[link={self.remote_location}]' if self.remote_location.startswith('http') else ''}"
f"{'[link={self.remote_location}]' if self.remote_location.startswith('http') else ''}"
f"{self.remote_location}"
f"{'[/link]' if self.remote_location.startswith('http') else '' }"
f"{'[/link]' if self.remote_location.startswith('http') else ''}"
"\n"
)
)
Expand Down
6 changes: 5 additions & 1 deletion nf_core/components/lint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def __init__(
continue
if isinstance(components, str):
raise LookupError(
f"Error parsing modules.json: {components}. " f"Please check the file for errors or try again."
f"Error parsing modules.json: {components}. Please check the file for errors or try again."
)
for org, comp in components:
self.all_remote_components.append(
Expand Down Expand Up @@ -162,6 +162,10 @@ def _set_registry(self, registry) -> None:
self.registry = registry
log.debug(f"Registry set to {self.registry}")

@property
def local_module_exclude_tests(self):
return ["module_version", "module_changes", "modules_patch"]

@staticmethod
def get_all_module_lint_tests(is_pipeline):
if is_pipeline:
Expand Down
30 changes: 19 additions & 11 deletions nf_core/components/nfcore_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def __init__(
repo_dir = self.component_dir.parts[:name_index][-1]

self.org = repo_dir
self.nftest_testdir = Path(self.component_dir, "tests")
self.nftest_main_nf = Path(self.nftest_testdir, "main.nf.test")
self.nftest_testdir: Optional[Path] = Path(self.component_dir, "tests")
self.nftest_main_nf: Optional[Path] = Path(self.nftest_testdir, "main.nf.test")

if self.repo_type == "pipeline":
patch_fn = f"{self.component_name.replace('/', '-')}.diff"
Expand All @@ -85,15 +85,23 @@ def __init__(
self.patch_path = patch_path
else:
# The main file is just the local module
self.main_nf = self.component_dir
self.component_name = self.component_dir.stem
# These attributes are only used by nf-core modules
# so just initialize them to None
self.meta_yml = None
self.environment_yml = None
self.test_dir = None
self.test_yml = None
self.test_main_nf = None
if self.component_dir.is_dir():
self.main_nf = Path(self.component_dir, "main.nf")
self.component_name = self.component_dir.stem
# These attributes are only required by nf-core modules
# so just set them to None if they don't exist
self.meta_yml = p if (p := Path(self.component_dir, "meta.yml")).exists() else None
self.environment_yml = p if (p := Path(self.component_dir, "environment.yml")).exists() else None
self.nftest_testdir = p if (p := Path(self.component_dir, "tests")).exists() else None
if self.nftest_testdir is not None:
self.nftest_main_nf = p if (p := Path(self.nftest_testdir, "main.nf.test")).exists() else None
else:
self.main_nf = self.component_dir
self.component_dir = self.component_dir.parent
self.meta_yml = None
self.environment_yml = None
self.nftest_testdir = None
self.nftest_main_nf = None

self.process_name: str = self._get_process_name()

Expand Down
2 changes: 1 addition & 1 deletion nf_core/components/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,6 @@ def remove(self, component, removed_by=None, removed_components=None, force=Fals
f"Did not remove '{component}', because it was also manually installed. Only updated 'installed_by' entry in modules.json."
)
log.info(
f"""Did not remove {self.component_type[:-1]} '{component}', because it was also installed by {', '.join(f"'{d}'" for d in installed_by)}. Only updated the 'installed_by' entry in modules.json."""
f"""Did not remove {self.component_type[:-1]} '{component}', because it was also installed by {", ".join(f"'{d}'" for d in installed_by)}. Only updated the 'installed_by' entry in modules.json."""
)
return removed
22 changes: 19 additions & 3 deletions nf_core/modules/lint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def lint(
"""
# TODO: consider unifying modules and subworkflows lint() function and add it to the ComponentLint class
# Prompt for module or all
if module is None and not all_modules and len(self.all_remote_components) > 0:
if module is None and not (local or all_modules) and len(self.all_remote_components) > 0:
questions = [
{
"type": "list",
Expand Down Expand Up @@ -170,7 +170,7 @@ def lint(
self.lint_modules(local_modules, registry=registry, local=True, fix_version=fix_version)

# Lint nf-core modules
if len(remote_modules) > 0:
if not local and len(remote_modules) > 0:
self.lint_modules(remote_modules, registry=registry, local=False, fix_version=fix_version)

if print_results:
Expand Down Expand Up @@ -234,7 +234,23 @@ def lint_module(
# TODO: consider unifying modules and subworkflows lint_module() function and add it to the ComponentLint class
# Only check the main script in case of a local module
if local:
self.main_nf(mod, fix_version, self.registry, progress_bar)
mod.get_inputs_from_main_nf()
mod.get_outputs_from_main_nf()
# Update meta.yml file if requested
if self.fix and mod.meta_yml is not None:
self.update_meta_yml_file(mod)

for test_name in self.lint_tests:
if test_name in self.local_module_exclude_tests:
continue
if test_name == "main_nf":
getattr(self, test_name)(mod, fix_version, self.registry, progress_bar)
elif test_name in ["meta_yml", "environment_yml"]:
# Allow files to be missing for local
getattr(self, test_name)(mod, allow_missing=True)
else:
getattr(self, test_name)(mod)

self.passed += [LintResult(mod, *m) for m in mod.passed]
warned = [LintResult(mod, *m) for m in (mod.warned + mod.failed)]
if not self.fail_warned:
Expand Down
11 changes: 10 additions & 1 deletion nf_core/modules/lint/environment_yml.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
log = logging.getLogger(__name__)


def environment_yml(module_lint_object: ComponentLint, module: NFCoreComponent) -> None:
def environment_yml(module_lint_object: ComponentLint, module: NFCoreComponent, allow_missing: bool = False) -> None:
"""
Lint an ``environment.yml`` file.
Expand All @@ -23,6 +23,15 @@ def environment_yml(module_lint_object: ComponentLint, module: NFCoreComponent)
env_yml = None
# load the environment.yml file
if module.environment_yml is None:
if allow_missing:
module.warned.append(
(
"environment_yml_exists",
"Module's `environment.yml` does not exist",
Path(module.component_dir, "environment.yml"),
),
)
return
raise LintExceptionError("Module does not have an `environment.yml` file")
try:
with open(module.environment_yml) as fh:
Expand Down
16 changes: 10 additions & 6 deletions nf_core/modules/lint/meta_yml.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
log = logging.getLogger(__name__)


def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent) -> None:
def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent, allow_missing: bool = False) -> None:
"""
Lint a ``meta.yml`` file
Expand Down Expand Up @@ -42,7 +42,13 @@ def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent) -> None
module (NFCoreComponent): The module to lint
"""

if module.meta_yml is None:
if allow_missing:
module.warned.append(
("meta_yml_exists", "Module `meta.yml` does not exist", Path(module.component_dir, "meta.yml"))
)
return
raise LintExceptionError("Module does not have a `meta.yml` file")
# Check if we have a patch file, get original file in that case
meta_yaml = read_meta_yml(module_lint_object, module)
if module.is_patched and module_lint_object.modules_repo.repo_path is not None:
Expand All @@ -56,9 +62,7 @@ def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent) -> None
).get("meta.yml")
if lines is not None:
yaml = ruamel.yaml.YAML()
meta_yaml = yaml.safe_load("".join(lines))
if module.meta_yml is None:
raise LintExceptionError("Module does not have a `meta.yml` file")
meta_yaml = yaml.load("".join(lines))
if meta_yaml is None:
module.failed.append(("meta_yml_exists", "Module `meta.yml` does not exist", module.meta_yml))
return
Expand All @@ -78,7 +82,7 @@ def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent) -> None
if len(e.path) > 0:
hint = f"\nCheck the entry for `{e.path[0]}`."
if e.message.startswith("None is not of type 'object'") and len(e.path) > 2:
hint = f"\nCheck that the child entries of {str(e.path[0])+'.'+str(e.path[2])} are indented correctly."
hint = f"\nCheck that the child entries of {str(e.path[0]) + '.' + str(e.path[2])} are indented correctly."
if e.schema and isinstance(e.schema, dict) and "message" in e.schema:
e.message = e.schema["message"]
incorrect_value = meta_yaml
Expand Down
Loading

0 comments on commit 52e8109

Please sign in to comment.