Skip to content

feat: Add a wrapper for quarto markdown rendering #3003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

votti
Copy link
Contributor

@votti votti commented Jun 20, 2024

Quarto has become a frequently used markdown notebook authoring system.

This wrapper uses quarto to render R Quarto .qmd notebooks using the the Quarto params mechanism to pass parameters from snakemake.

R dependency handling with renv is supported.

The meta.yml contains a more detailed description of the wrapper.
Test cases for all the major functionality and demonstrating the renv integration are included.

QC

  • I confirm that:

For all wrappers added by this PR,

  • there is a test case which covers any introduced changes,
  • input: and output: file paths in the resulting rule can be changed arbitrarily,
  • either the wrapper can only use a single core, or the example rule contains a threads: x statement with x being a reasonable default,
  • rule names in the test case are in snake_case and somehow tell what the rule is about or match the tools purpose or name (e.g., map_reads for a step that maps reads),
  • all environment.yaml specifications follow the respective best practices,
  • the environment.yaml pinning has been updated by running snakedeploy pin-conda-envs environment.yaml on a linux machine,
  • wherever possible, command line arguments are inferred and set automatically (e.g. based on file extensions in input: or output:),
  • all fields of the example rules in the Snakefiles and their entries are explained via comments (input:/output:/params: etc.),
  • stderr and/or stdout are logged correctly (log:), depending on the wrapped tool,
  • temporary files are either written to a unique hidden folder in the working directory, or (better) stored where the Python function tempfile.gettempdir() points to (see here; this also means that using any Python tempfile default behavior works),
  • the meta.yaml contains a link to the documentation of the respective tool or command,
  • Snakefiles pass the linting (snakemake --lint),
  • Snakefiles are formatted with snakefmt,
  • Python wrapper scripts are formatted with black.
  • Conda environments use a minimal amount of channels, in recommended ordering. E.g. for bioconda, use (conda-forge, bioconda, nodefaults, as conda-forge should have highest priority and defaults channels are usually not needed because most packages are in conda-forge nowadays).

Summary by CodeRabbit

  • New Features

    • Introduced a Quarto wrapper for seamless rendering of Quarto markdown reports within workflows, supporting parameter and resource passing, R environment management, and multiple output formats.
    • Added comprehensive documentation and metadata describing wrapper usage, inputs, outputs, and limitations.
    • Provided example Quarto documents and a Snakemake workflow to demonstrate and test various rendering scenarios, including resource and thread handling, custom dependencies, and environment restoration.
  • Tests

    • Added new tests to validate the Quarto wrapper's functionality across different output formats, parameters, and resource configurations.
  • Chores

    • Added explicit Conda environment specification files for reproducible environment setup on Linux-64 systems.

This wrapper uses quarto to render R quarto markdown
.qmd notebooks.
R dependency handling with conda or renv is supported.
@votti votti changed the title Feature: Add a wrapper for quarto markdown rendering feat: Add a wrapper for quarto markdown rendering Jun 20, 2024
@fgvieira
Copy link
Collaborator

fgvieira commented Jun 21, 2024

Do you need to specify all those dependencies?
Can you try to specify just the absolutely needed packages (no dependencies)? For example, - r-quarto =1.4.

And why do you need a second environment?

Vito Zanotelli added 2 commits June 21, 2024 09:22
This simplifies the dependencies in the main
conda environment.

This required then to rewrite the tests to use
a separate conda environment with renv installed
in order to build and restore the renv.
@votti
Copy link
Contributor Author

votti commented Jun 21, 2024

Hi @fgvieira ,
Thanks a lot for your comments!
Maybe all this build dependencies for r-packages were indeed not needed. I simplified this to just depend on r-quarto.
Looking at the other packages, it seems like pining the version in the environment.yml is not needed as the pin.txt files are used. Thus I did not pin the r-quarto version.

@multiple environments:
The other environments are simply for testings and to demonstrate how:

  1. r dependencies required to render the markdown can be installed by overwriting the conda: directive of the wrapper. This is tested by having additionally data.table installed in the extra.yml environment.
  2. demonstrate and test how renv may be used together with this quarto wrapper. This is achieved by installing data.table with renv. This requires a conda env with r-renv installed.

@fgvieira
Copy link
Collaborator

fgvieira commented Jun 21, 2024

@votti we need a version in the environment.yaml for the update github action. Can you add one?

Don't think we need the test/.gitignore file, right?

If not needed, best to have a single env. You can always add data.table to the main environment (if you think it is needed).

# resources CLI flag: --resources renv_lock=1 --set-resource-scopes renv_lock=global


rule renv_example_init:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar the renv testcase requires renv as an additional dependency which I would track in the separate renv.yaml.

Generally these should not need to be updated.
If updating renv.yaml, the renv_dt.lock file needs to be
adapted as well.
Vito Zanotelli added 3 commits June 21, 2024 11:47
As discussed in the PR this does not need to be
explicitly shown. Instead I have added this more expliiclty to the
documentation.
@votti votti force-pushed the feature/quarto branch 2 times, most recently from f337a87 to b17f428 Compare June 22, 2024 04:37
votti and others added 2 commits June 22, 2024 06:48
Before this would have raised an error `log` not defined
Comment on lines 44 to 45
threads: lambda wc, threads: threads
mem: lambda wc, resources: resources.mem
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense for the wrapper to use threads and mem specified under resources?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely think it would make sense and attempted to implement this, but technically I did not find a way to achieve it:
We currently pass parameters for the markdown rendering using the --P, --execute-param KEY:VALUE syntax of the quarto render command. This only accepts parameters that are actually defined in the markdown document to be rendered and fails otherwise. This behaviour is coded in the knit_params_get used under the hood: https://github.com/rstudio/rmarkdown/blob/4b2f3426c1f46a8d207b0d661fc51c2508ecdad7/R/params.R#L28-L34
I dont see any possiblity to easily change this behaviour.

Thus if we would pass threads and mem as a parameter automatically, these would require the explicit declaration of these parameters in the markdown rendered.
If the markdown rendered does not explicitly supports these parameters, rendering would fail.

Thus to me requiring all markdowns to be rendered using this wrapper to support these two resource parameters would not be a good solution, as it would limit the applicability of the wrapper.
Also it would not be a complete solution as supporting any other resources would still require a workaround as the one documented here as well.
Automatically passing any resource parameter would be even more brittle as additional resources may be passed depending on the snakemake configuration used.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, params/threads/mem need to be specified inside the .qmd file AND passed as command-line arguments (--P, --execute-param KEY:VALUE)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is how it works.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a bit unpractical since the params of the rule might not match the ones in the .qmd file! On the other hand, it is also not possible to add more parameters.

How are parameters included in the .qmd file? Is it an upstream rule?
Would it make sense for the wrapper to overwrite (or check) the params in the .qmd file to ensure they match?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think of the qmd file like an extension to Rmarkdown or akin to a Jupyter notebook.
I imagine the typical use of the wrapper would be users to hand-write the .qmd notebook, like they would do in a Rmarkdown or Jupyter notebook.
The parameter are included when authoring the file. The defined parameters are then used in the report to eg customize titles or as parameters in functions.

Would it make sense for the wrapper to overwrite (or check) the params in the .qmd file to ensure they match?

@overwriting: I am not sure if this would be sensible: adding additional parameters would not typically not really having an effect. I would say is is really rare for people to write code that handles arbitrary additional parameters and thus typically these would simply be ignored.

@cheking to ensure they match: this is what quarto render does at runtime and the error message raised is quite understandable, so I would say this is already done.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if the wrapper just read the parameters in the .qmd file and add those with --execute-param KEY:VALUE? Would that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could make sense and it should be 'easy' to implement using knitr:knit_params: https://rdrr.io/cran/knitr/man/knit_params.html
or by writing a python function that parses the 'front matter' of such documents based some regexp.

This definitely would be quite convenient to implicitly pass these parameters if appropriately named parameters exist in the document.
Personally I am not so sure if I like such an implicit parameter passing as I tend to prefer explicit over implicit for such cases. Implicitly passing these may make it difficult to detect errors. Eg if in the parameter in the quarto document is called 'thread' instead of 'threads' this would lead to a silent ignoring of the parameter.
Also it is not possible to see at a glance from the rule if the document is respecting the resources.

What would you think as an alternative of a parameter: 'params: resource_parameters' that allow the user to specify a list of strings with resources to be passed as parameters:
Eg:

params:
    resource_parameters: ['threads', 'mem_mb']

What would you prefer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now implemented that if a quarto params named resources_<resource_name> is defined
the corresponding snakemake resource parameter is automatically passed.
eg quarto param resources_mem_mb -> snakemake.resources.mem_mb will be
passed.
threads will also be passed if a quarto parameter resrouces_threads is present.

Would this work for you?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how would those quarto params be defined?

I am not very familiar with quarto but my concern is that, if the rule resources change then the same input won't work (since the resources are hard-coded inside the file).
As I see it, the only way to ensure it, is to always overwrite (or add) the resources in the .qmd file with those in snakemake.resources. If you think the current wrapper can do it, then I am happy. 😄

Co-authored-by: Filipe G. Vieira <1151762+fgvieira@users.noreply.github.com>
votti and others added 6 commits June 27, 2024 08:41
As suggested resources that match the name should be automatically
passed.

Tests added:
- test if a custom resource added will be passed
- test if the special resource 'threads' will be passed
If a quarto params named resources_<resource_name> is defined
the corresponding snakemake resource parameter is automatically passed.
eg quarto param resources_mem_mb -> snakemake.resources.mem_mb will be
passed.
Accidentally the header of the notes section
in meta.yaml was removed
@fgvieira
Copy link
Collaborator

fgvieira commented Aug 8, 2024

The idea behind renv is to allow to install other R packages not included in the original wrapper environment, no?
Would it be possible to just use conda for that? I mean, use the wrapper but with a custom conda environment (conda:).

@johanneskoester what do you think of using renv inside the wrapper? Or should it be done at the snakemake level (like conda or singularity)?

@johanneskoester
Copy link
Contributor

I think it is worth adding any further missing R packages to conda first. While it is fine to support renv as an alternative to conda at some point in general, I would say it is not really needed here, isn't it? We autobump and lock conda packages here, supporting a different kind of environments for wrappers would only further increase the complexity without improving the user experience (since conda is capable to handle R packages just fine).

@johanneskoester
Copy link
Contributor

I just checked some of the packages in the renv, and all of them are available on conda-forge. I did not check all of them, but maybe they are even all already available for adding them to the environment.yaml?

Copy link
Contributor

github-actions bot commented May 1, 2025

This PR was marked as stale because it has been open for 6 months with no activity.

@github-actions github-actions bot added the Stale label May 1, 2025
Copy link
Contributor

coderabbitai bot commented May 1, 2025

📝 Walkthrough

Walkthrough

This change introduces a new Quarto wrapper for Snakemake workflows, including the wrapper implementation, metadata, environment specifications, example Quarto documents, and comprehensive tests. The wrapper enables rendering of Quarto markdown reports with support for parameter passing, resource management, and R environment handling via both conda and renv. Multiple new test cases and a Snakemake workflow are added to validate the wrapper's functionality for various output formats and configurations. Existing tests are reformatted for readability. Explicit environment files ensure reproducible setups for both the main Quarto environment and renv-based workflows.

Changes

File(s) Change Summary
test.py Reformatted existing test invocations for readability and added eight new test functions for the "utils/quarto" wrapper, each testing different output targets and configurations with Snakemake.
utils/quarto/wrapper.py Added new Python wrapper script for rendering Quarto reports in Snakemake, supporting parameter/resource passing, environment setup, output management, and logging.
utils/quarto/meta.yaml Added wrapper metadata file defining interface, inputs, outputs, parameters, and usage notes for the Quarto wrapper.
utils/quarto/environment.linux-64.pin.txt, utils/quarto/environment.yaml Added explicit conda environment files for Quarto: one with exact package URLs for Linux-64 and one YAML-based with pinned r-quarto version.
utils/quarto/test/env/renv.linux-64.pin.txt, utils/quarto/test/env/renv.yaml Added explicit conda environment files for renv-based R environments, including both pin file and YAML specification.
utils/quarto/test/Snakefile Added new Snakemake workflow with rules for rendering Quarto documents in various formats, handling resources, parameters, and renv management.
utils/quarto/test/simple.qmd, full.qmd, extra_datatable.qmd, resources_custom.qmd, resources_threads.qmd Added example Quarto markdown documents demonstrating basic rendering, parameter passing, file operations, extra dependencies, and resource validation.

Sequence Diagram(s)

sequenceDiagram
    participant Snakemake
    participant Wrapper (wrapper.py)
    participant Quarto
    participant R Environment

    Snakemake->>Wrapper (wrapper.py): Invoke with inputs, outputs, params, resources
    Wrapper (wrapper.py)->>R Environment: Setup/activate (conda or renv)
    Wrapper (wrapper.py)->>Quarto: Run `quarto render` with parameters
    Quarto->>R Environment: Execute R code in .qmd
    Quarto-->>Wrapper (wrapper.py): Output rendered report
    Wrapper (wrapper.py)-->>Snakemake: Move/copy output to target location
Loading

Suggested reviewers

  • fgvieira
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (14)
utils/quarto/test/env/renv.linux-64.pin.txt (1)

93-93: Add missing newline at EOF.
Most linters and POSIX tools expect a trailing newline.

utils/quarto/environment.linux-64.pin.txt (1)

138-138: Add missing newline at EOF.
Include a final newline to satisfy POSIX file conventions and linting tools.

utils/quarto/environment.yaml (1)

5-5: Add trailing newline.
YAML linters flag missing newline at end-of-file; please add one.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 5-5: no new line character at the end of file

(new-line-at-end-of-file)

utils/quarto/test/env/renv.yaml (1)

6-6: Add trailing newline.
YAML files should end with a newline to comply with linters and POSIX standards.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 6-6: no new line character at the end of file

(new-line-at-end-of-file)

utils/quarto/meta.yaml (1)

53-53: Add missing newline at end of file
A newline is required at EOF to satisfy YAMLlint’s new-line-at-end-of-file rule.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 53-53: no new line character at the end of file

(new-line-at-end-of-file)

test.py (5)

6737-6748: Include "-F" flag for consistency
Most wrapper tests include the -F (force) flag to ensure outputs are overwritten and logs are reproducible. Please add "-F" to this Snakemake invocation.


6751-6762: Include "-F" flag for consistency
Add "-F" to match the pattern used by other wrapper tests when invoking Snakemake.


6765-6776: Include "-F" flag for consistency
Please append "-F" to the command array here to maintain uniform test behavior.


6779-6790: Include "-F" flag for consistency
To align with the existing tests, add "-F" to this Snakemake call.


6793-6804: Include "-F" flag for consistency
Add "-F" to this test invocation so it follows the same conventions as the other Quarto wrapper tests.

utils/quarto/test/Snakefile (1)

1-1: Consider removing unused import

The os module is imported but not used anywhere in this Snakefile.

-import os
utils/quarto/wrapper.py (3)

54-68: Fix type annotation and simplify list handling

The type annotation for the value parameter is missing, and there's a simpler way to handle list conversion.

-def add_parameter(name: str, value, param_list: list[str]) -> None:
+def add_parameter(name: str, value: any, param_list: list[str]) -> None:
    """Add a parameter to the parameter list in place

    Args:
        name (str): The name of the parameter
-        value (str): The value of the parameter
+        value (any): The value of the parameter
        param_list (list[str]): The list of parameters

    Returns:
        None
    """
    if isinstance(value, list):
-        value = ",".join(value)
+        value = ",".join(str(v) for v in value)
    param_list.append("-P {}:{}".format(name, value))

89-105: Optimize dictionary key lookup

The code uses .keys() unnecessarily when checking for key presence.

passed_resource_params = {
    f"{RESOURCE_PARAMS_PREFIX}{k}": v
    for k, v in resources.items()
-    if f"{RESOURCE_PARAMS_PREFIX}{k}" in params_header.keys()
+    if f"{RESOURCE_PARAMS_PREFIX}{k}" in params_header
}
🧰 Tools
🪛 Ruff (0.8.2)

95-95: Undefined name snakemake

(F821)


96-96: Undefined name snakemake

(F821)


100-100: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


127-144: Add stacklevel to warning and improve report path construction

The warning about PDF output doesn't specify a stacklevel, and the report path construction could be clearer.

        if report_path.suffix == ".pdf":
            warnings.warn(
                "PDF output is likely not supported yet, as tinytex not installable"
-                " via conda: https://github.com/conda-forge/quarto-feedstock/issues/26"
+                " via conda: https://github.com/conda-forge/quarto-feedstock/issues/26",
+                stacklevel=2
            )
🧰 Tools
🪛 Ruff (0.8.2)

140-140: No explicit stacklevel keyword argument found

(B028)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d47dd7 and aff004e.

⛔ Files ignored due to path filters (1)
  • utils/quarto/test/env/renv_dt.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • test.py (2 hunks)
  • utils/quarto/environment.linux-64.pin.txt (1 hunks)
  • utils/quarto/environment.yaml (1 hunks)
  • utils/quarto/meta.yaml (1 hunks)
  • utils/quarto/test/Snakefile (1 hunks)
  • utils/quarto/test/env/renv.linux-64.pin.txt (1 hunks)
  • utils/quarto/test/env/renv.yaml (1 hunks)
  • utils/quarto/test/extra_datatable.qmd (1 hunks)
  • utils/quarto/test/full.qmd (1 hunks)
  • utils/quarto/test/resources_custom.qmd (1 hunks)
  • utils/quarto/test/resources_threads.qmd (1 hunks)
  • utils/quarto/test/simple.qmd (1 hunks)
  • utils/quarto/wrapper.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`**/*.py`: Do not try to improve formatting. Do not suggest type annotations for functions that are defined inside of functions or methods. Do not suggest type annotation of the `s...

**/*.py: Do not try to improve formatting.
Do not suggest type annotations for functions that are defined inside of functions or methods.
Do not suggest type annotation of the self argument of methods.
Do not suggest type annotation of the cls argument of classmethods.
Do not suggest return type annotation if a function or method does not contain a return statement.

  • utils/quarto/wrapper.py
  • test.py
`**/wrapper.py`: Do not complain about use of undefined variable called `snakemake`.

**/wrapper.py: Do not complain about use of undefined variable called snakemake.

  • utils/quarto/wrapper.py
🪛 YAMLlint (1.35.1)
utils/quarto/test/env/renv.yaml

[error] 6-6: no new line character at the end of file

(new-line-at-end-of-file)

utils/quarto/environment.yaml

[error] 5-5: no new line character at the end of file

(new-line-at-end-of-file)

utils/quarto/meta.yaml

[error] 53-53: no new line character at the end of file

(new-line-at-end-of-file)

🪛 Ruff (0.8.2)
utils/quarto/wrapper.py

70-70: Undefined name snakemake

(F821)


71-71: Undefined name snakemake

(F821)


72-72: Undefined name snakemake

(F821)


77-77: Undefined name snakemake

(F821)


81-81: Undefined name snakemake

(F821)


84-84: Undefined name snakemake

(F821)


95-95: Undefined name snakemake

(F821)


96-96: Undefined name snakemake

(F821)


100-100: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


110-110: Undefined name snakemake

(F821)


112-112: Undefined name snakemake

(F821)


113-113: Undefined name snakemake

(F821)


140-140: No explicit stacklevel keyword argument found

(B028)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Summary
🔇 Additional comments (36)
utils/quarto/test/env/renv.linux-64.pin.txt (3)

1-4: Explicit environment header is correct.
The comments and @EXPLICIT directive follow Conda’s format for explicit environment files.


5-93: Pinned package list ensures reproducibility.
This exhaustive list locks down the R renv environment for Linux-64, guaranteeing identical installs across systems.


1-93:

✅ Verification successful

Keep explicit file in sync with YAML definitions.
Ensure any dependency changes in renv.yaml or the wrapper’s conda: directive are mirrored here to prevent drift.

Run this script to compare the r-base and r-renv versions between the YAML and explicit file:


🏁 Script executed:

#!/bin/bash
# Compare r-base/renv versions
grep -E '^ *- r-base' utils/quarto/test/env/renv.yaml | sed 's/- //g' > yaml.txt
grep -E 'r-base' utils/quarto/test/env/renv.linux-64.pin.txt | head -1 | sed 's|.*/||; s/-[rb]*//g; s/_.*//g' > pin.txt
diff yaml.txt pin.txt

Length of output: 347


🏁 Script executed:

#!/bin/bash
set -eux

# Locate the YAML and pin files in the repo
RENV_YAML=$(fd -t f renv.yaml . | head -1)
PIN_FILE=$(fd -t f '*.pin.txt' . | head -1)

echo "Using renv.yaml: $RENV_YAML"
echo "Using pin file : $PIN_FILE"

# Extract r-base and r-renv versions from renv.yaml
grep -E '^ *- (r-base|r-renv)' "$RENV_YAML" | sed 's/- //g' > yaml_versions.txt

# Extract r-base and r-renv from the explicit pin file
grep -E '(r-base|r-renv)' "$PIN_FILE" | sed 's|.*/||; s|_.*||' > pin_versions.txt

# Show a unified diff if there are discrepancies
diff -u yaml_versions.txt pin_versions.txt || true

Length of output: 975


🏁 Script executed:

#!/bin/bash
set -eux

# Directly compare r-base and r-renv between YAML and explicit pin file

YAML_FILE="utils/quarto/test/env/renv.yaml"
PIN_FILE="utils/quarto/test/env/renv.linux-64.pin.txt"

echo "YAML: $YAML_FILE"
echo "PIN : $PIN_FILE"

# Extract versions
grep -E '^ *- (r-base|r-renv)' "$YAML_FILE" | sed 's/- //g' > yaml_versions.txt
grep -E '(r-base|r-renv)' "$PIN_FILE" | sed 's|.*/||; s|_.*||' > pin_versions.txt

# Show diff
diff -u yaml_versions.txt pin_versions.txt || true

Length of output: 749


r-base and r-renv versions are in sync
The r-base (4.3.3) and r-renv (1.0.7) entries match between renv.yaml and the explicit pin file—no drift detected.

utils/quarto/environment.linux-64.pin.txt (3)

1-4: Explicit environment header is correct.
The file header and @EXPLICIT directive are properly set for Linux-64.


5-137: Comprehensive pin list for Quarto environment.
Locking system libraries, R packages, and quarto-1.4.550 ensures a reproducible rendering environment.


81-81:

✅ Verification successful

Verify Quarto version alignment.
Confirm that the pinned quarto-1.4.550 matches the version expected by the wrapper’s metadata (r-quarto=1.4).


🏁 Script executed:

#!/bin/bash
# Check r-quarto in meta.yaml vs explicit pin
rg -n "r-quarto" utils/quarto/meta.yaml
grep -m1 -oP "quarto-[0-9]+\.[0-9]+\.[0-9]+" utils/quarto/environment.linux-64.pin.txt

Length of output: 266


🏁 Script executed:

#!/bin/bash
set -e

echo "1) Locating all meta.yaml files in the repo"
find . -name meta.yaml

echo -e "\n2) Searching for 'r-quarto' in all files"
grep -R -n "r-quarto" .

echo -e "\n3) Finding all environment pin files"
find . -type f -name "environment*.pin.txt"

echo -e "\n4) Extracting the first pinned Quarto version in those files"
grep -R -n -m1 -oP "quarto-[0-9]+\.[0-9]+\.[0-9]+" .

Length of output: 15010


🏁 Script executed:

#!/bin/bash
set -e

echo "1) Show package name and version in utils/quarto/meta.yaml"
grep -R -nE "^(package:|name:|version:)" -n utils/quarto/meta.yaml -A 3 -B 1

echo -e "\n2) Show 'requirements:' block in utils/quarto/meta.yaml"
grep -R -n "requirements:" -n utils/quarto/meta.yaml -A 10 -B 2

echo -e "\n3) Display the top of utils/quarto/environment.yaml"
sed -n '1,50p' utils/quarto/environment.yaml

echo -e "\n4) Locate pinned 'quarto-' entries in utils/quarto/environment.linux-64.pin.txt"
grep -n "quarto-[0-9]" -n utils/quarto/environment.linux-64.pin.txt | head -n 10

Length of output: 531


Quarto version alignment verified.
The wrapper’s environment.yaml specifies r-quarto=1.4, and the environment.linux-64.pin.txt pins quarto-1.4.550. These are consistent—no changes needed.

utils/quarto/environment.yaml (1)

1-5: Minimal environment.yaml is correctly configured.
Channels (conda-forge, nodefaults) and r-quarto=1.4 meet the wrapper’s dependency requirements.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 5-5: no new line character at the end of file

(new-line-at-end-of-file)

utils/quarto/test/env/renv.yaml (1)

1-6: Base renv.yaml is properly defined.
It includes the correct channels and pins r-base=4.3.3 and r-renv=1.0.7 for renv-based testing.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 6-6: no new line character at the end of file

(new-line-at-end-of-file)

utils/quarto/test/simple.qmd (2)

1-5: Front-matter is valid Quarto YAML.
The title, author, and format: html directives correctly configure an HTML output.


7-7: Minimal document body is sufficient.
“Hello Quarto!” provides a basic test case to verify rendering.

utils/quarto/test/extra_datatable.qmd (1)

1-15: Well-structured test document for dependency management.

This Quarto document effectively demonstrates the handling of additional R package dependencies. The document clearly explains its purpose and provides a good test case for verifying that the dependency management system (either conda or renv) correctly installs the required packages.

utils/quarto/test/resources_threads.qmd (1)

1-15: Good resource passing validation example.

This test document correctly validates that the "threads" resource is passed from Snakemake to the Quarto document. The error message is clear and informative, making it easy to diagnose issues if the parameter passing fails.

utils/quarto/test/full.qmd (1)

1-15: Comprehensive parameter and file operation example.

This example effectively demonstrates more complex Quarto usage, including dynamic titling with parameters and file operations. It's a good test case for ensuring that the wrapper correctly passes multiple parameters and that the R environment can perform file system operations.

utils/quarto/test/resources_custom.qmd (1)

1-15: Effective custom resource validation test case.

This document correctly checks for custom resource parameters, complementing the threads resource test. Having separate test cases for different types of resources is a good practice to ensure the wrapper handles all resource types correctly.

test.py (2)

6807-6818: Verify resource passing test
This test targets custom resource parameters but doesn’t pass any --resources flag to Snakemake. Ensure you explicitly supply the correct resource (e.g., --resources mem_mb=<value>) or clarify how the test environment provides those resources.


6821-6832: Test varying thread counts
Using --cores 1 only validates the default path. To fully test thread propagation, consider invoking with a non-default value (e.g., --cores 4) so the wrapper passes a distinct threads parameter into the Quarto script.

utils/quarto/test/Snakefile (13)

4-12: LGTM! Well-structured rule aggregation

The all rule correctly aggregates all test outputs as targets, providing a clear entry point for running the complete test suite.


14-17: LGTM! Simple and effective input generator

This rule efficiently creates test input files using wildcards, which is a good practice for parameterized testing.


19-32: LGTM! Comprehensive test rule for full rendering

This rule properly tests the rendering of a full Quarto document with both report and text output, demonstrating parameter passing via the lambda function.


34-43: LGTM! Simple HTML rendering test

Clean implementation for basic HTML rendering test case.


45-54: LGTM! DOCX format rendering test

This rule appropriately tests rendering to DOCX format, ensuring the wrapper handles different output formats correctly.


56-66: LGTM! Testing rendering without report output

Good test case that verifies the wrapper can handle scenarios where only data outputs (not reports) are needed.


68-79: LGTM! Custom resource passing test

This rule properly tests the ability to pass custom resources to the Quarto rendering process.


81-90: LGTM! Thread parameter passing test

This rule correctly tests passing thread parameters to the Quarto rendering process.


92-107: LGTM! Well-documented commented example

The commented-out example for conda environment provides valuable documentation for users who want to add R dependencies via conda, which aligns with the PR discussions about dependency management approaches.


109-113: LGTM! Proper wildcard constraint

The wildcard constraint is correctly defined to allow flexibility in renv directory paths, including empty paths.


115-119: LGTM! Good explanation of resource locking

Clear documentation of the purpose of the renv_lock resource and how to use it correctly.


121-139: LGTM! Well-implemented renv initialization

This rule correctly initializes and restores an R environment using renv with proper resource locking and logging.


160-174: LGTM! Proper Quarto rendering with renv

The rule correctly demonstrates rendering a Quarto document with an renv environment, with good documentation explaining the potential need to override the conda directive.

utils/quarto/wrapper.py (7)

1-11: LGTM! Well-structured header and imports

The file has proper attribution, licensing information, and appropriate imports.


13-27: LGTM! Clear and effective renv path parser

The parse_renv function provides good path resolution logic with appropriate type hints and documentation.


70-73: LGTM! Proper input extraction

The code correctly extracts the necessary input paths with default fallbacks.

🧰 Tools
🪛 Ruff (0.8.2)

70-70: Undefined name snakemake

(F821)


71-71: Undefined name snakemake

(F821)


72-72: Undefined name snakemake

(F821)


75-87: LGTM! Well-structured parameter collection

Parameters are correctly collected from inputs, params, and outputs, with appropriate exclusions.

🧰 Tools
🪛 Ruff (0.8.2)

77-77: Undefined name snakemake

(F821)


81-81: Undefined name snakemake

(F821)


84-84: Undefined name snakemake

(F821)


107-116: LGTM! Proper log path resolution

The code correctly resolves log paths and handles the case when no log is provided.

🧰 Tools
🪛 Ruff (0.8.2)

110-110: Undefined name snakemake

(F821)


112-112: Undefined name snakemake

(F821)


113-113: Undefined name snakemake

(F821)


119-126: LGTM! Good temporary output handling

The code correctly handles the case when no report output is specified by using a temporary directory.


145-159: LGTM! Well-structured shell execution

The shell command correctly copies the script, changes to the renv directory, renders the document, and moves the output to the final destination.

description: Render quarto markdown documents
url: https://github.com/quarto-dev/quarto-r
authors:
-Vito Zanotelli
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix YAML list syntax for authors
Add a space after the hyphen to conform to YAML list syntax. Also verify whether the wrapper schema expects a singular author: key instead of authors:.

-authors:
-  -Vito Zanotelli
+authors:
+  - Vito Zanotelli
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-Vito Zanotelli
authors:
- Vito Zanotelli

Comment on lines +141 to +158
rule restore_renv:
conda:
"env/renv.yaml"
input:
rprofile="{renv_dir}.Rprofile",
lock="{renv_dir}renv.lock",
output:
restored="{renv_dir}restored.Rprofile",
resources:
renv_lock=1,
log:
"logs/renv/{renv_dir}_restore.log",
shell:
"""
R -e 'source("{input.rprofile}", chdir=TRUE);renv::restore()'
echo 'source("{input.rprofile}", chdir=TRUE)' > {output.restored}
"""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix paths in restore_renv rule

There are missing path separators in the input paths, which could lead to errors when resolving file paths.

     input:
-        rprofile="{renv_dir}.Rprofile",
-        lock="{renv_dir}renv.lock",
+        rprofile="{renv_dir}/.Rprofile",
+        lock="{renv_dir}/renv.lock",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rule restore_renv:
conda:
"env/renv.yaml"
input:
rprofile="{renv_dir}.Rprofile",
lock="{renv_dir}renv.lock",
output:
restored="{renv_dir}restored.Rprofile",
resources:
renv_lock=1,
log:
"logs/renv/{renv_dir}_restore.log",
shell:
"""
R -e 'source("{input.rprofile}", chdir=TRUE);renv::restore()'
echo 'source("{input.rprofile}", chdir=TRUE)' > {output.restored}
"""
rule restore_renv:
conda:
"env/renv.yaml"
input:
rprofile="{renv_dir}/.Rprofile",
lock="{renv_dir}/renv.lock",
output:
restored="{renv_dir}restored.Rprofile",
resources:
renv_lock=1,
log:
"logs/renv/{renv_dir}_restore.log",
shell:
"""
R -e 'source("{input.rprofile}", chdir=TRUE);renv::restore()'
echo 'source("{input.rprofile}", chdir=TRUE)' > {output.restored}
"""

Comment on lines +29 to +52
def parse_qmd_header(fn_script):
"""Parse the yml header of the a
quarto markdown file

Args:
fn_script (path): Path to the qmd script

Raises:
ValueError: qmd has not valid header

Returns:
dict: parsed header
"""
with open(fn_script, mode="r") as f:
first = f.readline()
if first != "---\n":
raise ValueError(".qmd file has no valid yaml header.")
line = f.readline()
yml_lines = []
while line != "---\n":
yml_lines.append(line)
line = f.readline()
return yaml.safe_load("".join(yml_lines))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for YAML parsing

The function parses the YAML header but doesn't handle potential exceptions from yaml.safe_load().

     while line != "---\n":
         yml_lines.append(line)
         line = f.readline()
-    return yaml.safe_load("".join(yml_lines))
+    try:
+        return yaml.safe_load("".join(yml_lines))
+    except yaml.YAMLError as e:
+        raise ValueError(f"Invalid YAML in .qmd header: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def parse_qmd_header(fn_script):
"""Parse the yml header of the a
quarto markdown file
Args:
fn_script (path): Path to the qmd script
Raises:
ValueError: qmd has not valid header
Returns:
dict: parsed header
"""
with open(fn_script, mode="r") as f:
first = f.readline()
if first != "---\n":
raise ValueError(".qmd file has no valid yaml header.")
line = f.readline()
yml_lines = []
while line != "---\n":
yml_lines.append(line)
line = f.readline()
return yaml.safe_load("".join(yml_lines))
def parse_qmd_header(fn_script):
"""Parse the yml header of the a
quarto markdown file
Args:
fn_script (path): Path to the qmd script
Raises:
ValueError: qmd has not valid header
Returns:
dict: parsed header
"""
with open(fn_script, mode="r") as f:
first = f.readline()
if first != "---\n":
raise ValueError(".qmd file has no valid yaml header.")
line = f.readline()
yml_lines = []
while line != "---\n":
yml_lines.append(line)
line = f.readline()
try:
return yaml.safe_load("".join(yml_lines))
except yaml.YAMLError as e:
raise ValueError(f"Invalid YAML in .qmd header: {e}")

@github-actions github-actions bot removed the Stale label Jun 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants