Skip to content

Commit ee7b6a0

Browse files
refactor quartonotebook (#8623)
* refactor quartonotebook * Update meta.yml * Add tests for existence of parametrized `meta` * Add `meta` as implicit parameter * Always use implicit parameters Always include `meta`, `cpus` and `artifact_dir` as implicit parameters, as they are always useful regardless of whether the user has additional parameters for their notebook. This means the `use_parameters` input boolean can be removed. Also, remove the `input_dir` implicit variable, which is not needed for Quarto but used in the original R Markdown / Jupyter notebooks in the test data. * Fix `meta.yml` * Remove optional output on artifacts and params, and use artifact_dir value * Fix linting * Fix artifacts optional state. There may be no files in dir * Update module outputs * Reintroduce notebook as output so user can continue working on copy --------- Co-authored-by: Erik Fasterius <[email protected]>
1 parent e648108 commit ee7b6a0

File tree

7 files changed

+96
-134
lines changed

7 files changed

+96
-134
lines changed

modules/nf-core/quartonotebook/main.nf

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
include { dumpParamsYaml ; indentCodeBlock } from "./parametrize"
2-
31
// NB: You'll likely want to override this with a container containing all
42
// required dependencies for your analyses. Or use wave to build the container
53
// for you from the environment.yml You'll at least need Quarto itself,
@@ -15,58 +13,45 @@ process QUARTONOTEBOOK {
1513

1614
input:
1715
tuple val(meta), path(notebook)
18-
val parameters
16+
val(parameters)
1917
path input_files
2018
path extensions
2119

2220
output:
23-
tuple val(meta), path("*.html"), emit: html
24-
tuple val(meta), path("${notebook}"), emit: notebook
25-
tuple val(meta), path("artifacts/*"), emit: artifacts, optional: true
26-
tuple val(meta), path("params.yml"), emit: params_yaml, optional: true
27-
tuple val(meta), path("_extensions"), emit: extensions, optional: true
28-
path "versions.yml", emit: versions
21+
tuple val(meta), path("*.html") , emit: html
22+
tuple val(meta), path(notebook) , emit: notebook
23+
tuple val(meta), path("params.yml") , emit: params_yaml
24+
tuple val(meta), path("${notebook_parameters.artifact_dir}/*"), emit: artifacts , optional: true
25+
tuple val(meta), path("_extensions") , emit: extensions , optional: true
26+
path "versions.yml" , emit: versions
2927

3028
when:
3129
task.ext.when == null || task.ext.when
3230

3331
script:
3432
def args = task.ext.args ?: ''
3533
def prefix = task.ext.prefix ?: "${meta.id}"
36-
def parametrize = task.ext.parametrize == null ? true : task.ext.parametrize
37-
def implicit_params = task.ext.implicit_params == null ? true : task.ext.implicit_params
38-
def meta_params = task.ext.meta_params == null ? true : task.ext.meta_params
39-
40-
// Dump parameters to yaml file.
41-
// Using a YAML file over using the CLI params because
34+
// Implicit parameters can be overwritten by supplying a value with parameters
35+
notebook_parameters = [
36+
meta: meta,
37+
cpus: task.cpus,
38+
artifact_dir: "artifacts",
39+
] + (parameters ?: [:])
40+
// Parse parameters through a YAML file, which is better than CLI because:
4241
// - No issue with escaping
4342
// - Allows passing nested maps instead of just single values
4443
// - Allows running with the language-agnostic `--execute-params`
45-
def params_cmd = ""
46-
def render_args = ""
47-
if (parametrize) {
48-
nb_params = [:]
49-
if (implicit_params) {
50-
nb_params["cpus"] = task.cpus
51-
nb_params["artifact_dir"] = "artifacts"
52-
nb_params["input_dir"] = "./"
53-
}
54-
if (meta_params) {
55-
nb_params["meta"] = meta
56-
}
57-
nb_params += parameters
58-
params_cmd = dumpParamsYaml(nb_params)
59-
render_args = "--execute-params params.yml"
60-
}
61-
( params_cmd ?
62-
"""
63-
# Dump .params.yml heredoc (if applicable)
64-
${indentCodeBlock(params_cmd, 4)}
65-
""" : "")
66-
<<
44+
def yamlBuilder = new groovy.yaml.YamlBuilder()
45+
yamlBuilder(notebook_parameters)
46+
def yaml_content = yamlBuilder.toString().tokenize('\n').join("\n ")
6747
"""
48+
# Dump parameters to yaml file
49+
cat <<- END_YAML_PARAMS > params.yml
50+
${yaml_content}
51+
END_YAML_PARAMS
52+
6853
# Create output directory
69-
mkdir artifacts
54+
mkdir "${notebook_parameters.artifact_dir}"
7055
7156
# Set environment variables needed for Quarto rendering
7257
export XDG_CACHE_HOME="./.xdg_cache_home"
@@ -89,8 +74,8 @@ process QUARTONOTEBOOK {
8974
# Render notebook
9075
quarto render \\
9176
${notebook} \\
92-
${render_args} \\
9377
${args} \\
78+
--execute-params params.yml \\
9479
--output ${prefix}.html
9580
9681
cat <<-END_VERSIONS > versions.yml
@@ -102,6 +87,12 @@ process QUARTONOTEBOOK {
10287

10388
stub:
10489
def prefix = task.ext.prefix ?: "${meta.id}"
90+
// Implicit parameters can be overwritten by supplying a value with parameters
91+
notebook_parameters = [
92+
meta: meta,
93+
cpus: task.cpus,
94+
artifact_dir: "artifacts",
95+
] + (parameters ?: [:])
10596
"""
10697
# Fix Quarto for Apptainer (see https://community.seqera.io/t/confusion-over-why-a-tool-works-in-docker-but-fails-in-singularity-when-the-installation-doesnt-differ-i-e-using-wave-micromamba/1244)
10798
# Note: This is needed in the stub for `quarto -v` to work.
@@ -113,6 +104,7 @@ process QUARTONOTEBOOK {
113104
set -u
114105
115106
touch ${prefix}.html
107+
touch params.yml
116108
117109
cat <<-END_VERSIONS > versions.yml
118110
"${task.process}":

modules/nf-core/quartonotebook/meta.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,29 @@ output:
6464
description: |
6565
Groovy Map containing sample information
6666
e.g. `[ id:'sample1', single_end:false ]`.
67-
- ${notebook}:
67+
- notebook:
6868
type: file
69-
description: The original, un-rendered notebook.
70-
pattern: "*.[qmd,ipynb,rmd]"
71-
- artifacts:
69+
description: The Quarto notebook that was rendered. Allows user to continue working on the notebook.
70+
pattern: "*.{qmd}"
71+
- params_yaml:
7272
- meta:
7373
type: map
7474
description: |
7575
Groovy Map containing sample information
7676
e.g. `[ id:'sample1', single_end:false ]`.
77-
- artifacts/*:
77+
- params.yml:
7878
type: file
79-
description: Artifacts generated during report rendering.
79+
description: Parameters used during report rendering.
8080
pattern: "*"
81-
- params_yaml:
81+
- artifacts:
8282
- meta:
8383
type: map
8484
description: |
8585
Groovy Map containing sample information
8686
e.g. `[ id:'sample1', single_end:false ]`.
87-
- params.yml:
87+
- ${notebook_parameters.artifact_dir}/*:
8888
type: file
89-
description: Parameters used during report rendering.
89+
description: Artifacts generated during report rendering.
9090
pattern: "*"
9191
- extensions:
9292
- meta:

modules/nf-core/quartonotebook/parametrize.nf

Lines changed: 0 additions & 36 deletions
This file was deleted.

modules/nf-core/quartonotebook/tests/main.nf.test

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ nextflow_process {
1010

1111
test("test notebook - [qmd:r]") {
1212

13-
config "./no-parametrization.config"
14-
1513
when {
1614
process {
1715
"""
@@ -42,16 +40,14 @@ nextflow_process {
4240

4341
test("test notebook - [qmd:python]") {
4442

45-
config "./no-parametrization.config"
46-
4743
when {
4844
process {
4945
"""
5046
input[0] = [
5147
[ id:'test' ], // meta map
5248
file(params.modules_testdata_base_path + 'generic/notebooks/quarto/quarto_python.qmd', checkIfExists: true) // Notebook
5349
]
54-
input[1] = [] // Parameters
50+
input[1] = [:] // Parameters
5551
input[2] = [] // Input files
5652
input[3] = [] // Extensions
5753
"""
@@ -74,16 +70,14 @@ nextflow_process {
7470

7571
test("test notebook - parametrized - [qmd:r]") {
7672

77-
config "./with-parametrization.config"
78-
7973
when {
8074
process {
8175
"""
8276
input[0] = [
8377
[ id:'test' ], // meta map
8478
file(params.modules_testdata_base_path + 'generic/notebooks/quarto/quarto_r.qmd', checkIfExists: true) // Notebook
8579
]
86-
input[1] = [input_filename: "hello.txt", n_iter: 12] // parameters
80+
input[1] = [input_filename: "hello.txt", n_iter: 12] // Parameters
8781
input[2] = file(params.modules_testdata_base_path + 'generic/txt/hello.txt', checkIfExists: true) // Input files
8882
input[3] = [] // Extensions
8983
"""
@@ -99,23 +93,22 @@ nextflow_process {
9993
process.out.params_yaml
10094
).match() },
10195
{ assert path(process.out.html[0][1]).readLines().any { it.contains('Hello World 1') } },
96+
{ assert path(process.out.params_yaml[0][1]).readLines().any { it.contains('meta:') } },
10297
)
10398
}
10499

105100
}
106101

107102
test("test notebook - parametrized - [qmd:python]") {
108103

109-
config "./with-parametrization.config"
110-
111104
when {
112105
process {
113106
"""
114107
input[0] = [
115108
[ id:'test' ], // meta map
116109
file(params.modules_testdata_base_path + 'generic/notebooks/quarto/quarto_python.qmd', checkIfExists: true) // Notebook
117110
]
118-
input[1] = [input_filename: "hello.txt", n_iter: 12] // parameters
111+
input[1] = [input_filename: "hello.txt", n_iter: 12] // Parameters
119112
input[2] = file(params.modules_testdata_base_path + 'generic/txt/hello.txt', checkIfExists: true) // Input files
120113
input[3] = [] // Extensions
121114
"""
@@ -131,23 +124,22 @@ nextflow_process {
131124
process.out.params_yaml
132125
).match() },
133126
{ assert path(process.out.html[0][1]).readLines().any { it.contains('Hello World 1') } },
127+
{ assert path(process.out.params_yaml[0][1]).readLines().any { it.contains('meta:') } },
134128
)
135129
}
136130

137131
}
138132

139133
test("test notebook - parametrized - [rmd]") {
140134

141-
config "./with-parametrization.config"
142-
143135
when {
144136
process {
145137
"""
146138
input[0] = [
147139
[ id:'test' ], // meta map
148140
file(params.modules_testdata_base_path + 'generic/notebooks/rmarkdown/rmarkdown_notebook.Rmd', checkIfExists: true) // notebook
149141
]
150-
input[1] = [input_filename: "hello.txt", n_iter: 12] // parameters
142+
input[1] = [input_filename: "hello.txt", n_iter: 12] // Parameters
151143
input[2] = file(params.modules_testdata_base_path + 'generic/txt/hello.txt', checkIfExists: true) // Input files
152144
input[3] = [] // Extensions
153145
"""
@@ -163,23 +155,22 @@ nextflow_process {
163155
process.out.params_yaml
164156
).match() },
165157
{ assert path(process.out.html[0][1]).readLines().any { it.contains('Hello World 1') } },
158+
{ assert path(process.out.params_yaml[0][1]).readLines().any { it.contains('meta:') } },
166159
)
167160
}
168161

169162
}
170163

171164
test("test notebook - parametrized - [ipynb]") {
172165

173-
config "./with-parametrization.config"
174-
175166
when {
176167
process {
177168
"""
178169
input[0] = [
179170
[ id:'test' ], // meta map
180171
file(params.modules_testdata_base_path + 'generic/notebooks/jupyter/ipython_notebook.ipynb', checkIfExists: true) // notebook
181172
]
182-
input[1] = [input_filename: "hello.txt", n_iter: 12] // parameters
173+
input[1] = [input_filename: "hello.txt", n_iter: 12] // Parameters
183174
input[2] = file(params.modules_testdata_base_path + 'generic/txt/hello.txt', checkIfExists: true) // Input files
184175
input[3] = [] // Extensions
185176
"""
@@ -195,15 +186,14 @@ nextflow_process {
195186
process.out.params_yaml
196187
).match() },
197188
{ assert path(process.out.html[0][1]).readLines().any { it.contains('Hello World') } },
189+
{ assert path(process.out.params_yaml[0][1]).readLines().any { it.contains('meta:') } },
198190
)
199191
}
200192

201193
}
202194

203195
test("test notebook - stub - [qmd:r]") {
204196

205-
config "./no-parametrization.config"
206-
207197
options "-stub"
208198

209199
when {

0 commit comments

Comments
 (0)