Skip to content

Commit a7351a2

Browse files
authored
Merge pull request #3228 from mashehu/fix-yml-config-type
allow mixed str and dict in lint config
2 parents 9d3d074 + eed0598 commit a7351a2

File tree

16 files changed

+413
-114
lines changed

16 files changed

+413
-114
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
### Linting
2626

27-
- General: Run pre-commit when testing linting the template pipeline ([#3280](https://github.com/nf-core/tools/pull/3280))
27+
- allow mixed `str` and `dict` entries in lint config ([#3228](https://github.com/nf-core/tools/pull/3228))
2828

2929
### Modules
3030

@@ -55,6 +55,7 @@
5555
- Update python:3.12-slim Docker digest to 2a6386a ([#3284](https://github.com/nf-core/tools/pull/3284))
5656
- Update pre-commit hook astral-sh/ruff-pre-commit to v0.8.0 ([#3299](https://github.com/nf-core/tools/pull/3299))
5757
- Update gitpod/workspace-base Docker digest to 12853f7 ([#3309](https://github.com/nf-core/tools/pull/3309))
58+
- Run pre-commit when testing linting the template pipeline ([#3280](https://github.com/nf-core/tools/pull/3280))
5859

5960
## [v3.0.2 - Titanium Tapir Patch](https://github.com/nf-core/tools/releases/tag/3.0.2) - [2024-10-11]
6061

nf_core/components/lint/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from nf_core.components.nfcore_component import NFCoreComponent
2323
from nf_core.modules.modules_json import ModulesJson
2424
from nf_core.pipelines.lint_utils import console
25-
from nf_core.utils import LintConfigType
25+
from nf_core.utils import NFCoreYamlLintConfig
2626
from nf_core.utils import plural_s as _s
2727

2828
log = logging.getLogger(__name__)
@@ -80,7 +80,7 @@ def __init__(
8080
self.failed: List[LintResult] = []
8181
self.all_local_components: List[NFCoreComponent] = []
8282

83-
self.lint_config: Optional[LintConfigType] = None
83+
self.lint_config: Optional[NFCoreYamlLintConfig] = None
8484
self.modules_json: Optional[ModulesJson] = None
8585

8686
if self.component_type == "modules":

nf_core/pipelines/create/create.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import re
99
import shutil
1010
from pathlib import Path
11-
from typing import Dict, List, Optional, Tuple, Union, cast
11+
from typing import Dict, List, Optional, Tuple, Union
1212

1313
import git
1414
import git.config
@@ -22,7 +22,7 @@
2222
from nf_core.pipelines.create_logo import create_logo
2323
from nf_core.pipelines.lint_utils import run_prettier_on_file
2424
from nf_core.pipelines.rocrate import ROCrate
25-
from nf_core.utils import LintConfigType, NFCoreTemplateConfig
25+
from nf_core.utils import NFCoreTemplateConfig, NFCoreYamlLintConfig
2626

2727
log = logging.getLogger(__name__)
2828

@@ -68,7 +68,7 @@ def __init__(
6868
_, config_yml = nf_core.utils.load_tools_config(outdir if outdir else Path().cwd())
6969
# Obtain a CreateConfig object from `.nf-core.yml` config file
7070
if config_yml is not None and getattr(config_yml, "template", None) is not None:
71-
self.config = CreateConfig(**config_yml["template"].model_dump())
71+
self.config = CreateConfig(**config_yml["template"].model_dump(exclude_none=True))
7272
else:
7373
raise UserWarning("The template configuration was not provided in '.nf-core.yml'.")
7474
# Update the output directory
@@ -206,7 +206,7 @@ def obtain_jinja_params_dict(
206206
config_yml = None
207207

208208
# Set the parameters for the jinja template
209-
jinja_params = self.config.model_dump()
209+
jinja_params = self.config.model_dump(exclude_none=True)
210210

211211
# Add template areas to jinja params and create list of areas with paths to skip
212212
skip_areas = []
@@ -369,8 +369,8 @@ def render_template(self) -> None:
369369
config_fn, config_yml = nf_core.utils.load_tools_config(self.outdir)
370370
if config_fn is not None and config_yml is not None:
371371
with open(str(config_fn), "w") as fh:
372-
config_yml.template = NFCoreTemplateConfig(**self.config.model_dump())
373-
yaml.safe_dump(config_yml.model_dump(), fh)
372+
config_yml.template = NFCoreTemplateConfig(**self.config.model_dump(exclude_none=True))
373+
yaml.safe_dump(config_yml.model_dump(exclude_none=True), fh)
374374
log.debug(f"Dumping pipeline template yml to pipeline config file '{config_fn.name}'")
375375

376376
# Run prettier on files
@@ -401,9 +401,9 @@ def fix_linting(self):
401401
# Add the lint content to the preexisting nf-core config
402402
config_fn, nf_core_yml = nf_core.utils.load_tools_config(self.outdir)
403403
if config_fn is not None and nf_core_yml is not None:
404-
nf_core_yml.lint = cast(LintConfigType, lint_config)
404+
nf_core_yml.lint = NFCoreYamlLintConfig(**lint_config)
405405
with open(self.outdir / config_fn, "w") as fh:
406-
yaml.dump(nf_core_yml.model_dump(), fh, default_flow_style=False, sort_keys=False)
406+
yaml.dump(nf_core_yml.model_dump(exclude_none=True), fh, default_flow_style=False, sort_keys=False)
407407

408408
def make_pipeline_logo(self):
409409
"""Fetch a logo for the new pipeline from the nf-core website"""

nf_core/pipelines/lint/__init__.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
from nf_core import __version__
2828
from nf_core.components.lint import ComponentLint
2929
from nf_core.pipelines.lint_utils import console
30+
from nf_core.utils import NFCoreYamlConfig, NFCoreYamlLintConfig, strip_ansi_codes
3031
from nf_core.utils import plural_s as _s
31-
from nf_core.utils import strip_ansi_codes
3232

3333
from .actions_awsfulltest import actions_awsfulltest
3434
from .actions_awstest import actions_awstest
@@ -112,7 +112,7 @@ def __init__(
112112
# Initialise the parent object
113113
super().__init__(wf_path)
114114

115-
self.lint_config = {}
115+
self.lint_config: Optional[NFCoreYamlLintConfig] = None
116116
self.release_mode = release_mode
117117
self.fail_ignored = fail_ignored
118118
self.fail_warned = fail_warned
@@ -173,13 +173,12 @@ def _load_lint_config(self) -> bool:
173173
Add parsed config to the `self.lint_config` class attribute.
174174
"""
175175
_, tools_config = nf_core.utils.load_tools_config(self.wf_path)
176-
self.lint_config = getattr(tools_config, "lint", {}) or {}
176+
self.lint_config = getattr(tools_config, "lint", None) or None
177177
is_correct = True
178-
179178
# Check if we have any keys that don't match lint test names
180179
if self.lint_config is not None:
181-
for k in self.lint_config:
182-
if k != "nfcore_components" and k not in self.lint_tests:
180+
for k, v in self.lint_config:
181+
if v is not None and k != "nfcore_components" and k not in self.lint_tests:
183182
# nfcore_components is an exception to allow custom pipelines without nf-core components
184183
log.warning(f"Found unrecognised test name '{k}' in pipeline lint config")
185184
is_correct = False
@@ -594,7 +593,7 @@ def run_linting(
594593
lint_obj._load_lint_config()
595594
lint_obj.load_pipeline_config()
596595

597-
if "nfcore_components" in lint_obj.lint_config and not lint_obj.lint_config["nfcore_components"]:
596+
if lint_obj.lint_config and not lint_obj.lint_config["nfcore_components"]:
598597
module_lint_obj = None
599598
subworkflow_lint_obj = None
600599
else:
@@ -679,5 +678,4 @@ def run_linting(
679678
if len(lint_obj.failed) > 0:
680679
if release_mode:
681680
log.info("Reminder: Lint tests were run in --release mode.")
682-
683681
return lint_obj, module_lint_obj, subworkflow_lint_obj

nf_core/pipelines/lint/multiqc_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ def multiqc_config(self) -> Dict[str, List[str]]:
3131
lint:
3232
multiqc_config: False
3333
34+
To disable this test only for specific sections, you can specify a list of section names.
35+
For example:
36+
37+
.. code-block:: yaml
38+
lint:
39+
multiqc_config:
40+
- report_section_order
41+
- report_comment
42+
3443
"""
3544

3645
passed: List[str] = []

nf_core/pipelines/lint/nfcore_yml.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import re
21
from pathlib import Path
32
from typing import Dict, List
43

4+
from ruamel.yaml import YAML
5+
56
from nf_core import __version__
67

78
REPOSITORY_TYPES = ["pipeline", "modules"]
@@ -26,21 +27,23 @@ def nfcore_yml(self) -> Dict[str, List[str]]:
2627
failed: List[str] = []
2728
ignored: List[str] = []
2829

30+
yaml = YAML()
31+
2932
# Remove field that should be ignored according to the linting config
3033
ignore_configs = self.lint_config.get(".nf-core", []) if self.lint_config is not None else []
31-
try:
32-
with open(Path(self.wf_path, ".nf-core.yml")) as fh:
33-
content = fh.read()
34-
except FileNotFoundError:
35-
with open(Path(self.wf_path, ".nf-core.yaml")) as fh:
36-
content = fh.read()
34+
for ext in (".yml", ".yaml"):
35+
try:
36+
nf_core_yml = yaml.load(Path(self.wf_path) / f".nf-core{ext}")
37+
break
38+
except FileNotFoundError:
39+
continue
40+
else:
41+
raise FileNotFoundError("No `.nf-core.yml` file found.")
3742

3843
if "repository_type" not in ignore_configs:
3944
# Check that the repository type is set in the .nf-core.yml
40-
repo_type_re = r"repository_type: (.+)"
41-
match = re.search(repo_type_re, content)
42-
if match:
43-
repo_type = match.group(1)
45+
if "repository_type" in nf_core_yml:
46+
repo_type = nf_core_yml["repository_type"]
4447
if repo_type not in REPOSITORY_TYPES:
4548
failed.append(
4649
f"Repository type in `.nf-core.yml` is not valid. "
@@ -55,10 +58,8 @@ def nfcore_yml(self) -> Dict[str, List[str]]:
5558

5659
if "nf_core_version" not in ignore_configs:
5760
# Check that the nf-core version is set in the .nf-core.yml
58-
nf_core_version_re = r"nf_core_version: (.+)"
59-
match = re.search(nf_core_version_re, content)
60-
if match:
61-
nf_core_version = match.group(1).strip('"')
61+
if "nf_core_version" in nf_core_yml:
62+
nf_core_version = nf_core_yml["nf_core_version"]
6263
if nf_core_version != __version__ and "dev" not in nf_core_version:
6364
warned.append(
6465
f"nf-core version in `.nf-core.yml` is not set to the latest version. "

nf_core/pipelines/lint/readme.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ def readme(self):
2323
2424
* If pipeline is released but still contains a 'zenodo.XXXXXXX' tag, the test fails
2525
26+
To disable this test, add the following to the pipeline's ``.nf-core.yml`` file:
27+
28+
.. code-block:: yaml
29+
lint:
30+
readme: False
31+
32+
To disable subsets of these tests, add the following to the pipeline's ``.nf-core.yml`` file:
33+
34+
.. code-block:: yaml
35+
36+
lint:
37+
readme:
38+
- nextflow_badge
39+
- zenodo_release
40+
2641
"""
2742
passed = []
2843
warned = []

nf_core/pipelines/lint/template_strings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def template_strings(self):
3939
ignored = []
4040
# Files that should be ignored according to the linting config
4141
ignore_files = self.lint_config.get("template_strings", []) if self.lint_config is not None else []
42-
files = self.list_files()
4342

43+
files = self.list_files()
4444
# Loop through files, searching for string
4545
num_matches = 0
4646
for fn in files:

nf_core/pipelines/sync.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
import shutil
88
from pathlib import Path
9-
from typing import Dict, Optional, Union
9+
from typing import Any, Dict, Optional, Tuple, Union
1010

1111
import git
1212
import questionary
@@ -105,7 +105,7 @@ def __init__(
105105
with open(template_yaml_path) as f:
106106
self.config_yml.template = yaml.safe_load(f)
107107
with open(self.config_yml_path, "w") as fh:
108-
yaml.safe_dump(self.config_yml.model_dump(), fh)
108+
yaml.safe_dump(self.config_yml.model_dump(exclude_none=True), fh)
109109
log.info(f"Saved pipeline creation settings to '{self.config_yml_path}'")
110110
raise SystemExit(
111111
f"Please commit your changes and delete the {template_yaml_path} file. Then run the sync command again."
@@ -120,7 +120,7 @@ def __init__(
120120
requests.auth.HTTPBasicAuth(self.gh_username, os.environ["GITHUB_AUTH_TOKEN"])
121121
)
122122

123-
def sync(self):
123+
def sync(self) -> None:
124124
"""Find workflow attributes, create a new template pipeline on TEMPLATE"""
125125

126126
# Clear requests_cache so that we don't get stale API responses
@@ -271,7 +271,7 @@ def make_template_pipeline(self):
271271

272272
self.config_yml.template.force = True
273273
with open(self.config_yml_path, "w") as config_path:
274-
yaml.safe_dump(self.config_yml.model_dump(), config_path)
274+
yaml.safe_dump(self.config_yml.model_dump(exclude_none=True), config_path)
275275

276276
try:
277277
pipeline_create_obj = nf_core.pipelines.create.create.PipelineCreate(
@@ -291,7 +291,7 @@ def make_template_pipeline(self):
291291
self.config_yml.template.outdir = "."
292292
# Update nf-core version
293293
self.config_yml.nf_core_version = nf_core.__version__
294-
dump_yaml_with_prettier(self.config_yml_path, self.config_yml.model_dump())
294+
dump_yaml_with_prettier(self.config_yml_path, self.config_yml.model_dump(exclude_none=True))
295295

296296
except Exception as err:
297297
# Reset to where you were to prevent git getting messed up.
@@ -416,12 +416,8 @@ def close_open_template_merge_prs(self):
416416
list_prs_url = f"https://api.github.com/repos/{self.gh_repo}/pulls"
417417
with self.gh_api.cache_disabled():
418418
list_prs_request = self.gh_api.get(list_prs_url)
419-
try:
420-
list_prs_json = json.loads(list_prs_request.content)
421-
list_prs_pp = json.dumps(list_prs_json, indent=4)
422-
except Exception:
423-
list_prs_json = list_prs_request.content
424-
list_prs_pp = list_prs_request.content
419+
420+
list_prs_json, list_prs_pp = self._parse_json_response(list_prs_request)
425421

426422
log.debug(f"GitHub API listing existing PRs:\n{list_prs_url}\n{list_prs_pp}")
427423
if list_prs_request.status_code != 200:
@@ -462,12 +458,8 @@ def close_open_pr(self, pr) -> bool:
462458
# Update the PR status to be closed
463459
with self.gh_api.cache_disabled():
464460
pr_request = self.gh_api.patch(url=pr["url"], data=json.dumps({"state": "closed"}))
465-
try:
466-
pr_request_json = json.loads(pr_request.content)
467-
pr_request_pp = json.dumps(pr_request_json, indent=4)
468-
except Exception:
469-
pr_request_json = pr_request.content
470-
pr_request_pp = pr_request.content
461+
462+
pr_request_json, pr_request_pp = self._parse_json_response(pr_request)
471463

472464
# PR update worked
473465
if pr_request.status_code == 200:
@@ -481,6 +473,22 @@ def close_open_pr(self, pr) -> bool:
481473
log.warning(f"Could not close PR ('{pr_request.status_code}'):\n{pr['url']}\n{pr_request_pp}")
482474
return False
483475

476+
@staticmethod
477+
def _parse_json_response(response) -> Tuple[Any, str]:
478+
"""Helper method to parse JSON response and create pretty-printed string.
479+
480+
Args:
481+
response: requests.Response object
482+
483+
Returns:
484+
Tuple of (parsed_json, pretty_printed_str)
485+
"""
486+
try:
487+
json_data = json.loads(response.content)
488+
return json_data, json.dumps(json_data, indent=4)
489+
except Exception:
490+
return response.content, str(response.content)
491+
484492
def reset_target_dir(self):
485493
"""
486494
Reset the target pipeline directory. Check out the original branch.

0 commit comments

Comments
 (0)