Skip to content

Commit e51911d

Browse files
authored
Merge branch 'dev' into 3444-args-in-meta-yml
2 parents d9af68b + abb3acf commit e51911d

23 files changed

+547
-250
lines changed

.github/workflows/pytest.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ jobs:
7575
name: Run ${{matrix.test}} with Python ${{ needs.setup.outputs.python-version }} on ${{ needs.setup.outputs.runner }}
7676
needs: [setup, list_tests]
7777
if: ${{ needs.setup.outputs.run-tests }}
78-
# run on self-hosted runners for test_components.py (because of the gitlab branch), based on the input if it is dispatched manually, on github if it is a rerun or on self-hosted by default
79-
runs-on: ${{ matrix.test == 'test_components.py' && 'self-hosted' || (github.event.inputs.runners || github.run_number > 1 && 'ubuntu-latest' || 'self-hosted') }}
78+
# run on self-hosted runners for test_components_generate_snapshot.py (because of the gitlab branch), based on the input if it is dispatched manually, on github if it is a rerun or on self-hosted by default
79+
runs-on: ${{ matrix.test == 'components/test_components_generate_snapshot.py' && 'self-hosted' || (github.event.inputs.runners || github.run_number > 1 && 'ubuntu-latest' || 'self-hosted') }}
8080
strategy:
8181
matrix: ${{ fromJson(needs.list_tests.outputs.tests) }}
8282
fail-fast: false # run all tests even if one fails

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Template
66

77
- Remove the on `pull_request_target` trigger and `pull_request` types from the download test. Also drop `push` triggers on other CI tests. ([#3399](https://github.com/nf-core/tools/pull/3399))
8+
- Add nf-core template version badges to README ([#3396](https://github.com/nf-core/tools/pull/3396))
89

910
### Linting
1011

@@ -76,7 +77,7 @@
7677
- Use outputs instead of the environment to pass around values between steps in the Download Test Action ([#3351](https://github.com/nf-core/tools/pull/3351))
7778
- Fix pre commit template ([#3358](https://github.com/nf-core/tools/pull/3358))
7879
- Set LICENSE copyright to nf-core community ([#3366](https://github.com/nf-core/tools/pull/3366))
79-
- fix including modules.config ([#3356](https://github.com/nf-core/tools/pull/3356))
80+
- Fix including modules.config ([#3356](https://github.com/nf-core/tools/pull/3356))
8081

8182
### Linting
8283

nf_core/components/components_utils.py

+71-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import re
33
from pathlib import Path
4-
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
4+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
55

66
import questionary
77
import requests
@@ -165,9 +165,9 @@ def get_components_to_install(subworkflow_dir: Union[str, Path]) -> Tuple[List[s
165165
return modules, subworkflows
166166

167167

168-
def get_biotools_id(tool_name) -> str:
168+
def get_biotools_response(tool_name: str) -> Optional[Dict]:
169169
"""
170-
Try to find a bio.tools ID for 'tool'
170+
Try to get bio.tools information for 'tool'
171171
"""
172172
url = f"https://bio.tools/api/t/?q={tool_name}&format=json"
173173
try:
@@ -176,16 +176,74 @@ def get_biotools_id(tool_name) -> str:
176176
response.raise_for_status() # Raise an error for bad status codes
177177
# Parse the JSON response
178178
data = response.json()
179+
log.info(f"Found bio.tools information for '{tool_name}'")
180+
return data
179181

180-
# Iterate through the tools in the response to find the tool name
181-
for tool in data["list"]:
182-
if tool["name"].lower() == tool_name:
183-
return tool["biotoolsCURIE"]
182+
except requests.exceptions.RequestException as e:
183+
log.warning(f"Could not find bio.tools information for '{tool_name}': {e}")
184+
return None
184185

185-
# If the tool name was not found in the response
186-
log.warning(f"Could not find a bio.tools ID for '{tool_name}'")
187-
return ""
188186

189-
except requests.exceptions.RequestException as e:
190-
log.warning(f"Could not find a bio.tools ID for '{tool_name}': {e}")
191-
return ""
187+
def get_biotools_id(data: dict, tool_name: str) -> str:
188+
"""
189+
Try to find a bio.tools ID for 'tool'
190+
"""
191+
# Iterate through the tools in the response to find the tool name
192+
for tool in data["list"]:
193+
if tool["name"].lower() == tool_name:
194+
log.info(f"Found bio.tools ID: '{tool['biotoolsCURIE']}'")
195+
return tool["biotoolsCURIE"]
196+
197+
# If the tool name was not found in the response
198+
log.warning(f"Could not find a bio.tools ID for '{tool_name}'")
199+
return ""
200+
201+
202+
DictWithStrAndTuple = Dict[str, Tuple[List[str], List[str], List[str]]]
203+
204+
205+
def get_channel_info_from_biotools(
206+
data: dict, tool_name: str
207+
) -> Optional[Tuple[DictWithStrAndTuple, DictWithStrAndTuple]]:
208+
"""
209+
Try to find input and output channels and the respective EDAM ontology terms
210+
211+
Args:
212+
data (dict): The bio.tools API response
213+
tool_name (str): The name of the tool
214+
"""
215+
inputs = {}
216+
outputs = {}
217+
218+
def _iterate_input_output(type) -> DictWithStrAndTuple:
219+
type_info = {}
220+
if type in funct:
221+
for element in funct[type]:
222+
if "data" in element:
223+
element_name = "_".join(element["data"]["term"].lower().split(" "))
224+
uris = [element["data"]["uri"]]
225+
terms = [element["data"]["term"]]
226+
patterns = []
227+
if "format" in element:
228+
for format in element["format"]:
229+
# Append the EDAM URI
230+
uris.append(format["uri"])
231+
# Append the EDAM term, getting the first word in case of complicated strings. i.e. "FASTA format"
232+
patterns.append(format["term"].lower().split(" ")[0])
233+
terms.append(format["term"])
234+
type_info[element_name] = (uris, terms, patterns)
235+
return type_info
236+
237+
# Iterate through the tools in the response to find the tool name
238+
for tool in data["list"]:
239+
if tool["name"].lower() == tool_name:
240+
if "function" in tool:
241+
# Parse all tool functions
242+
for funct in tool["function"]:
243+
inputs.update(_iterate_input_output("input"))
244+
outputs.update(_iterate_input_output("output"))
245+
return inputs, outputs
246+
247+
# If the tool name was not found in the response
248+
log.warning(f"Could not find an EDAM ontology term for '{tool_name}'")
249+
return None

nf_core/components/create.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import nf_core
2222
import nf_core.utils
2323
from nf_core.components.components_command import ComponentCommand
24-
from nf_core.components.components_utils import get_biotools_id
24+
from nf_core.components.components_utils import get_biotools_id, get_biotools_response, get_channel_info_from_biotools
2525
from nf_core.pipelines.lint_utils import run_prettier_on_file
2626

2727
log = logging.getLogger(__name__)
@@ -151,8 +151,15 @@ def create(self) -> bool:
151151
if self.component_type == "modules":
152152
# Try to find a bioconda package for 'component'
153153
self._get_bioconda_tool()
154+
name = self.tool_conda_name if self.tool_conda_name else self.component
154155
# Try to find a biotools entry for 'component'
155-
self.tool_identifier = get_biotools_id(self.component)
156+
biotools_data = get_biotools_response(name)
157+
if biotools_data:
158+
self.tool_identifier = get_biotools_id(biotools_data, name)
159+
# Obtain EDAM ontologies for inputs and outputs
160+
channel_info = get_channel_info_from_biotools(biotools_data, name)
161+
if channel_info:
162+
self.inputs, self.outputs = channel_info
156163

157164
# Prompt for GitHub username
158165
self._get_username()
@@ -176,6 +183,8 @@ def create(self) -> bool:
176183

177184
new_files = [str(path) for path in self.file_paths.values()]
178185

186+
run_prettier_on_file(new_files)
187+
179188
log.info("Created following files:\n " + "\n ".join(new_files))
180189
return True
181190

nf_core/module-template/environment.yml

+3
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ channels:
44
- conda-forge
55
- bioconda
66
dependencies:
7+
# TODO nf-core: List required Conda package(s).
8+
# Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10").
9+
# For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems.
710
- "{{ bioconda if bioconda else 'YOUR-TOOL-HERE' }}"

nf_core/module-template/main.nf

+41-10
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ process {{ component_name_underscore|upper }} {
2222
label '{{ process_label }}'
2323

2424
{% if not_empty_template -%}
25-
// TODO nf-core: List required Conda package(s).
26-
// Software MUST be pinned to channel (i.e. "bioconda"), version (i.e. "1.10").
27-
// For Conda, the build (i.e. "h9402c20_2") must be EXCLUDED to support installation on different operating systems.
2825
// TODO nf-core: See section in main README for further information regarding finding and adding container addresses to the section below.
2926
{% endif -%}
3027
conda "${moduleDir}/environment.yml"
@@ -33,6 +30,12 @@ process {{ component_name_underscore|upper }} {
3330
'{{ docker_container if docker_container else 'biocontainers/YOUR-TOOL-HERE' }}' }"
3431

3532
input:
33+
{%- if inputs %}
34+
// TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct
35+
{%- for input_name, ontologies in inputs.items() %}
36+
{{ 'tuple val(meta), path(' + input_name + ')' if has_meta else 'path ' + input_name }}
37+
{%- endfor %}
38+
{%- else -%}
3639
{% if not_empty_template -%}
3740
// TODO nf-core: Where applicable all sample-specific information e.g. "id", "single_end", "read_group"
3841
// MUST be provided as an input via a Groovy Map called "meta".
@@ -44,16 +47,22 @@ process {{ component_name_underscore|upper }} {
4447
{%- else -%}
4548
{{ 'tuple val(meta), path(input)' if has_meta else 'path input' }}
4649
{%- endif %}
50+
{%- endif %}
4751

4852
output:
53+
{%- if outputs %}
54+
// TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct
55+
{%- for output_name, ontologies in outputs.items() %}
56+
{{ 'tuple val(meta), path("*.{' + ontologies[2]|join(',') + '}")' if has_meta else 'path ' + output_name }}, emit: {{ output_name }}
57+
{%- endfor %}
58+
{%- else %}
4959
{% if not_empty_template -%}
5060
// TODO nf-core: Named file extensions MUST be emitted for ALL output channels
5161
{{ 'tuple val(meta), path("*.bam")' if has_meta else 'path "*.bam"' }}, emit: bam
62+
// TODO nf-core: List additional required output channels/values here
5263
{%- else -%}
5364
{{ 'tuple val(meta), path("*")' if has_meta else 'path "*"' }}, emit: output
5465
{%- endif %}
55-
{% if not_empty_template -%}
56-
// TODO nf-core: List additional required output channels/values here
5766
{%- endif %}
5867
path "versions.yml" , emit: versions
5968

@@ -78,20 +87,33 @@ process {{ component_name_underscore|upper }} {
7887
{%- endif %}
7988
"""
8089
{% if not_empty_template -%}
81-
samtools \\
82-
sort \\
90+
{{ component }} \\
8391
$args \\
8492
-@ $task.cpus \\
8593
{%- if has_meta %}
94+
{%- if inputs %}
95+
{%- for input_name, ontologies in inputs.items() %}
96+
{%- set extensions = ontologies[2] %}
97+
{%- for ext in extensions %}
98+
-o ${prefix}.{{ ext }} \\
99+
{%- endfor %}
100+
{%- endfor %}
101+
{%- else %}
86102
-o ${prefix}.bam \\
87-
-T $prefix \\
88103
{%- endif %}
104+
{%- endif %}
105+
{%- if inputs %}
106+
{%- for input_name, ontologies in inputs.items() %}
107+
${{ input_name }} \\
108+
{%- endfor %}
109+
{%- else %}
89110
$bam
111+
{%- endif %}
90112
{%- endif %}
91113
92114
cat <<-END_VERSIONS > versions.yml
93115
"${task.process}":
94-
{{ component }}: \$(samtools --version |& sed '1!d ; s/samtools //')
116+
{{ component }}: \$({{ component }} --version)
95117
END_VERSIONS
96118
"""
97119

@@ -108,12 +130,21 @@ process {{ component_name_underscore|upper }} {
108130
{%- endif %}
109131
"""
110132
{% if not_empty_template -%}
133+
{%- if inputs %}
134+
{%- for input_name, ontologies in inputs.items() %}
135+
{%- set extensions = ontologies[2] %}
136+
{%- for ext in extensions %}
137+
touch ${prefix}.{{ ext }}
138+
{%- endfor %}
139+
{%- endfor %}
140+
{%- else %}
111141
touch ${prefix}.bam
112142
{%- endif %}
143+
{%- endif %}
113144
114145
cat <<-END_VERSIONS > versions.yml
115146
"${task.process}":
116-
{{ component }}: \$(samtools --version |& sed '1!d ; s/samtools //')
147+
{{ component }}: \$({{ component }} --version)
117148
END_VERSIONS
118149
"""
119150
}

nf_core/module-template/meta.yml

+52-9
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,32 @@ extra_args:
3333
## TODO nf-core: Add a description of all of the variables used as input
3434
{% endif -%}
3535
input:
36+
{% if inputs -%}
37+
{% for input_name, ontologies in inputs.items() -%}
38+
{% if has_meta %}
39+
- - meta:
40+
type: map
41+
description: |
42+
Groovy Map containing sample information
43+
e.g. `[ id:'sample1' ]`
44+
{% endif %}
45+
- {{ input_name }}:
46+
# TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct
47+
type: file
48+
description: {{ input_name }} file
49+
pattern: {{ "\"*.{" + ontologies[2]|join(",") + "}\"" }}
50+
ontologies:
51+
{% for ontology in ontologies[0] -%}
52+
- edam: "{{ ontology }}" # {{ ontologies[1][loop.index0] }}
53+
{% endfor -%}
54+
{% endfor -%}
55+
{% else -%}
3656
#{% if has_meta %} Only when we have meta
3757
- - meta:
3858
type: map
3959
description: |
4060
Groovy Map containing sample information
41-
e.g. `[ id:'sample1', single_end:false ]`
61+
e.g. `[ id:'sample1' ]`
4262
{% endif %}
4363
{% if not_empty_template -%}
4464
## TODO nf-core: Delete / customise this example input
@@ -49,24 +69,46 @@ input:
4969
pattern: {{ '"*.{bam,cram,sam}"' if not_empty_template else "" }}
5070
ontologies:
5171
{% if not_empty_template -%}
52-
- edam: "http://edamontology.org/format_25722"
53-
- edam: "http://edamontology.org/format_2573"
54-
- edam: "http://edamontology.org/format_3462"
55-
{% else %}
72+
- edam: "http://edamontology.org/format_25722" # BAM
73+
- edam: "http://edamontology.org/format_2573" # CRAM
74+
- edam: "http://edamontology.org/format_3462" # SAM
75+
{% else -%}
5676
- edam: ""
5777
{%- endif %}
78+
{%- endif %}
5879

5980
{% if not_empty_template -%}
6081
## TODO nf-core: Add a description of all of the variables used as output
6182
{% endif -%}
6283
output:
84+
{% if outputs -%}
85+
{% for output_name, ontologies in outputs.items() -%}
86+
- {{ output_name }}:
87+
{% if has_meta -%}
88+
- meta:
89+
type: map
90+
description: |
91+
Groovy Map containing sample information
92+
e.g. `[ id:'sample1' ]`
93+
{%- endif %}
94+
- {{ "\"*.{" + ontologies[2]|join(",") + "}\"" }}:
95+
# TODO nf-core: Update the information obtained from bio.tools and make sure that it is correct
96+
type: file
97+
description: {{ output_name }} file
98+
pattern: {{ "\"*.{" + ontologies[2]|join(",") + "}\"" }}
99+
ontologies:
100+
{%- for ontology in ontologies[0] %}
101+
- edam: "{{ ontology }}" # {{ ontologies[1][loop.index0] }}
102+
{%- endfor %}
103+
{% endfor -%}
104+
{% else -%}
63105
- {{ 'bam:' if not_empty_template else "output:" }}
64106
#{% if has_meta -%} Only when we have meta
65107
- meta:
66108
type: map
67109
description: |
68110
Groovy Map containing sample information
69-
e.g. `[ id:'sample1', single_end:false ]`
111+
e.g. `[ id:'sample1' ]`
70112
{%- endif %}
71113
{% if not_empty_template -%}
72114
## TODO nf-core: Delete / customise this example output
@@ -77,12 +119,13 @@ output:
77119
pattern: {{ '"*.{bam,cram,sam}"' if not_empty_template else "" }}
78120
ontologies:
79121
{% if not_empty_template -%}
80-
- edam: "http://edamontology.org/format_25722"
81-
- edam: "http://edamontology.org/format_2573"
82-
- edam: "http://edamontology.org/format_3462"
122+
- edam: "http://edamontology.org/format_25722" # BAM
123+
- edam: "http://edamontology.org/format_2573" # CRAM
124+
- edam: "http://edamontology.org/format_3462" # SAM
83125
{% else -%}
84126
- edam: ""
85127
{%- endif %}
128+
{%- endif %}
86129
- versions:
87130
- "versions.yml":
88131
type: file

0 commit comments

Comments
 (0)