Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix: support multiple input files for template_engine rules (#1571)
* fix: support multiple input files for template_engine rules

* docs

* fix: use module specific config for report caption rendering

* update template engine input expectations
  • Loading branch information
johanneskoester committed Apr 6, 2022
1 parent b0b206c commit aee7cf2
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 20 deletions.
18 changes: 16 additions & 2 deletions docs/snakefiles/rules.rst
Expand Up @@ -2009,10 +2009,24 @@ Consider the following example:
"jinja2"
Here, Snakemake will automatically use the specified template engine `Jinja2 <https://jinja.palletsprojects.com/>`_ to render the template given as input file into the given output file.
Template rendering rules may only have a single input and output file.
The template_engine instruction has to be specified at the end of the rule.
Template rendering rules may only have a single output file.
If the rule needs more than one input file, there has to be one input file called ``template``, pointing to the main template to be used for the rendering:

The template itself has access to ``params``, ``wildcards``, and ``config``,
.. code-block:: python
rule render_jinja2_template:
input:
template="some-jinja2-template.txt",
other_file="some-other-input-file-used-by-the-template.txt"
output:
"results/{sample}.rendered-version.txt"
params:
foo=0.1
template_engine:
"jinja2"
The template itself has access to ``input``, ``params``, ``wildcards``, and ``config``,
which are the same objects you can use for example in the ``shell`` or ``run`` directive,
and the same objects as can be accessed from ``script`` or ``notebook`` directives (but in the latter two cases they are stored behind the ``snakemake`` object which serves as a dedicated namespace to avoid name clashes).

Expand Down
2 changes: 1 addition & 1 deletion snakemake/parser.py
Expand Up @@ -654,7 +654,7 @@ class TemplateEngine(Script):
end_func = "render_template"

def args(self):
yield (", input, output, params, wildcards, config")
yield (", input, output, params, wildcards, config, rule")


class CWL(Script):
Expand Down
2 changes: 1 addition & 1 deletion snakemake/report/__init__.py
Expand Up @@ -473,7 +473,7 @@ def render(self, env, rst_links, categories, files):
job.threads,
job.resources,
job.log,
job.dag.workflow.config,
job.rule.module_globals["config"],
job.rule.name,
None,
)
Expand Down
2 changes: 2 additions & 0 deletions snakemake/rules.py
Expand Up @@ -124,6 +124,7 @@ def __init__(self, *args, lineno=None, snakefile=None, restart_times=0):
self.log_modifier = None
self.benchmark_modifier = None
self.ruleinfo = None
self.module_globals = None
elif len(args) == 1:
other = args[0]
self.name = other.name
Expand Down Expand Up @@ -177,6 +178,7 @@ def __init__(self, *args, lineno=None, snakefile=None, restart_times=0):
self.log_modifier = other.log_modifier
self.benchmark_modifier = other.benchmark_modifier
self.ruleinfo = other.ruleinfo
self.module_globals = other.module_globals

def dynamic_branch(self, wildcards, input=True):
def get_io(rule):
Expand Down
50 changes: 34 additions & 16 deletions snakemake/template_rendering/__init__.py
@@ -1,12 +1,26 @@
from abc import ABC, abstractmethod

from snakemake.exceptions import WorkflowError


class TemplateRenderer(ABC):
def __init__(self, input, output, params, wildcards, config):
assert len(input) == 1
assert len(output) == 1

self.input_file = input[0]
if len(output) != 1:
raise ValueError(
"More than one output file specified for template_engine rule."
)
if len(input) != 1:
if "template" not in input.keys():
raise ValueError(
"More than one input file specified for template engine rule, but no "
"input file named as 'template'."
)
else:
self.input_file = input.template
else:
self.input_file = input[0]

self.input = input
self.output_file = output[0]
self.params = params
self.wildcards = wildcards
Expand All @@ -18,24 +32,28 @@ def variables(self):
"params": self.params,
"wildcards": self.wildcards,
"config": self.config,
"input": self.input,
}

@abstractmethod
def render(self):
...


def render_template(engine, input, output, params, wildcards, config):
if engine == "yte":
from snakemake.template_rendering.yte import YteRenderer
def render_template(engine, input, output, params, wildcards, config, rule):
try:
if engine == "yte":
from snakemake.template_rendering.yte import YteRenderer

return YteRenderer(input, output, params, wildcards, config).render()
elif engine == "jinja2":
from snakemake.template_rendering.jinja2 import Jinja2Renderer
return YteRenderer(input, output, params, wildcards, config).render()
elif engine == "jinja2":
from snakemake.template_rendering.jinja2 import Jinja2Renderer

return Jinja2Renderer(input, output, params, wildcards, config).render()
else:
raise WorkflowError(
f"Unsupported template engine {engine}. "
"So far, only yte and jinja2 are supported."
)
return Jinja2Renderer(input, output, params, wildcards, config).render()
else:
raise WorkflowError(
f"Unsupported template engine {engine} in rule {rule}. "
"So far, only yte and jinja2 are supported."
)
except Exception as e:
raise WorkflowError(f"Error rendering template in rule {rule}.", e)
1 change: 1 addition & 0 deletions snakemake/workflow.py
Expand Up @@ -1391,6 +1391,7 @@ def decorate(ruleinfo):
)
rule = self.get_rule(name)
rule.is_checkpoint = checkpoint
rule.module_globals = self.modifier.globals

def decorate(ruleinfo):
nonlocal name
Expand Down

0 comments on commit aee7cf2

Please sign in to comment.