Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

### Modules

- sort meta.yml based on keys in schema ([#3958](https://github.com/nf-core/tools/pull/3958))

### Subworkflows

- Update to new topic version handling ([#3929](https://github.com/nf-core/tools/pull/3929))
Expand Down
72 changes: 45 additions & 27 deletions nf_core/modules/lint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
nf-core modules lint
"""

import json
import logging
import os
import re
from collections.abc import Mapping
from pathlib import Path
from typing import Any

import questionary
import rich
Expand Down Expand Up @@ -79,6 +82,7 @@ def __init__(
registry=registry,
hide_progress=hide_progress,
)
self.meta_schema: Mapping[str, Any] | None = None

def lint(
self,
Expand Down Expand Up @@ -287,6 +291,28 @@ def lint_module(

self.failed += [LintResult(mod, *m) for m in mod.failed]

def load_meta_schema(self) -> Mapping[str, Any]:
"""
Load the meta.yml JSON schema from the local modules repository cache.
The schema is cached in self.meta_schema to avoid reloading.

Returns:
dict: The meta.yml JSON schema

Raises:
LookupError: If the local module cache is not found
"""
# Return cached schema if already loaded
if self.meta_schema is not None:
return self.meta_schema

if self.modules_repo.local_repo_dir is None:
raise LookupError("Local module cache not found")

with open(Path(self.modules_repo.local_repo_dir, "modules/meta-schema.json")) as fh:
self.meta_schema = json.load(fh)
return self.meta_schema

def update_meta_yml_file(self, mod):
"""
Update the meta.yml file with the correct inputs and outputs
Expand Down Expand Up @@ -324,33 +350,25 @@ def _find_meta_info(meta_yml: dict, element_name: str, is_output=False) -> dict:
return {}

def _sort_meta_yml(meta_yml: dict) -> dict:
"""Ensure topics comes after input/output and before authors"""
# Early return if no topics to reorder
if "topics" not in meta_yml:
return meta_yml

result = {}
topics_value = meta_yml["topics"]
topics_added = False

for key, value in meta_yml.items():
if key == "topics":
continue # Skip topics, we'll add it in the right place

# Add topics before authors key (if not already added)
if key == "authors" and not topics_added:
result["topics"] = topics_value
topics_added = True

result[key] = value

# Add topics after output (preferred) or after input (if no output)
if key == "output":
result["topics"] = topics_value
topics_added = True
elif key == "input" and "output" not in meta_yml:
result["topics"] = topics_value
topics_added = True
"""Sort meta.yml keys according to the schema's property order"""
# Get the schema to determine the correct key order
try:
schema = self.load_meta_schema()
schema_keys = list(schema["properties"].keys())
except (LintExceptionError, KeyError) as e:
raise UserWarning("Failed to load meta schema", e)

result: dict = {}

# First, add keys in the order they appear in the schema
for key in schema_keys:
if key in meta_yml:
result[key] = meta_yml[key]

# Then add any keys that aren't in the schema (to preserve custom keys)
for key in meta_yml.keys():
if key not in result:
result[key] = meta_yml[key]

return result

Expand Down
12 changes: 8 additions & 4 deletions nf_core/modules/lint/meta_yml.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
from __future__ import annotations

import logging
from pathlib import Path
from typing import TYPE_CHECKING

import ruamel.yaml
from jsonschema import exceptions, validators
Expand All @@ -9,10 +11,13 @@
from nf_core.components.lint import ComponentLint, LintExceptionError
from nf_core.components.nfcore_component import NFCoreComponent

if TYPE_CHECKING:
from nf_core.modules.lint import ModuleLint

log = logging.getLogger(__name__)


def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent, allow_missing: bool = False) -> None:
def meta_yml(module_lint_object: ModuleLint, module: NFCoreComponent, allow_missing: bool = False) -> None:
"""
Lint a ``meta.yml`` file

Expand Down Expand Up @@ -76,8 +81,7 @@ def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent, allow_m
# Confirm that the meta.yml file is valid according to the JSON schema
valid_meta_yml = False
try:
with open(Path(module_lint_object.modules_repo.local_repo_dir, "modules/meta-schema.json")) as fh:
schema = json.load(fh)
schema = module_lint_object.load_meta_schema()
validators.validate(instance=meta_yaml, schema=schema)
module.passed.append(("meta_yml", "meta_yml_valid", "Module `meta.yml` is valid", module.meta_yml))
valid_meta_yml = True
Expand Down
Loading