Loop through a list of macros using jinja - jinja2

I want to know if its possible to loop through a list of macros in another macro in Jinja.
For example:
{% set macro_list = [macro_one,macro_two,macro_three] %}
{% for macro in macro_list%}
{{ macro(param) }}
{% endfor %}
Similar to how in python you can loop through a list of functions. Currently this does not work for me, as the macro name is being recognized as a string and not a reference to a macro.

Do you really want references to macros?
Keep it simple and try to use macros with parameter, see Jijnja documentation. Then you can use the code as the following example:
{% macro example(counter) %}
<p>Example {{ counter }}</p>
{% endmacro %}
{% for i in range(1, 4) %}
{{ example(i) }}
{% endfor %}
This gives you:
<p>Example 1</p>
<p>Example 2</p>
<p>Example 3</p>

I have resolved the problem.
You can also qualify a macro in your own project by prefixing it with your package name (this is mainly useful for package authors).
source
An example:
[% set list = ["macro_one","macro_two]%]
{% for macro in list%}
{{ package_name[macro](params) }}
{% endfor %}

Related

How to dynamically call dbt macros using jinja?

I have a use case where I would like to define the name of a macro and then apply it to one column.
A simplified example could be as follows. I have two macros defined that I want to call dynamically in my model (both take one column as an input):
cast_to_string
convert_empty_string_to_null_value
Now, I want to call them dynamically. See the example below
{%- set macro_names = ["cast_to_string", "convert_empty_string_to_null_value"] -%}
select
{% for macro_name in macro_names %}
-- this should dynamically be evaluated to `{{ cast_to_string(my_column) }}`
-- and `{{ convert_empty_string_to_null_value(my_column) }}`
{{ macro_name(my_column) }}
{% endfor %}
from my_model
However, this will throw an error saying that a string is not callable.
I also tried using {% raw %} {{ {% endraw %} to escape brackets, but that didn’t work either.
So, my question is, if there is a way to dynamically call macros in jinja/dbt?
I think it should work if you remove the quotes :
{%- set macro_names = [cast_to_string, convert_empty_string_to_null_value] -%}
So that jinja doesn't interpret it as string and you can use it as a Callable
I achieve it using this example :
{%- set macro_names = [print_lower, print_upper] -%}
{% for macro_name in macro_names %}
{{ macro_name("test") }}
{% endfor %}
and
{% macro print_lower(string) %}
{{ print(string|lower) }}
{% endmacro %}
{% macro print_upper(string) %}
{{ print(string|upper) }}
{% endmacro %}

How can I use {{this}} as a parameter in a dbt jinja macro?

How can I pass the name of the current model using {{this}} as a parameter for a macro in a config function?
I have tried a couple of options and none of them works.
model/Table1.sql
{{ config(post_hook= calculate_test("{{this}}") ) }}
macro/calculate_test.sql
{% macro calculate_test(tableN) %}
{%- set tableName = tableN -%}
{% set sql %}
SELECT
COUNT(*) as cnt
FROM {{ tableName }}
{% endset %}
{% set results = run_query(sql) %}
{% endmacro %}
The error is:
You’re almost there! Only thing is that you’ve got extra curly brackets and misplaced quotes. The entire config block is Jinja so you don’t need the curly brackets around ‘this’.
{{ config(post_hook= “calculate_test(this)” ) }}

Passing list of Relation object to dbt_utils.union_relation macro fails

Related to dbt and jinja2
I am using union_relations from dbt_utils package (0.5.0).
I created my macro which takes list of fully qualified name (like database.schema.identifier) splits it and uses api.Relations.create (link) to create a relation and append each relation to a list.
{{ list_of_relation }} is given to dbt_utils.union_relations(as relations=my_macro([list of fully qualified names])), it's giving me an _is_relation error, I did use log to debug and see if it actually creates a relation and it does. What could be wrong?
It sounds like you have a macro written something like this:
{% macro my_macro(names) %}
{% set list_of_relations = [] %}
{% for name in names %}
{% set relation = something(name) %}
{% do list_of_relations.append(relation) %}
{% endfor %}
{{ list_of_relations }}
{% endmacro %}
Instead of using {{ list_of_relation }}, you’ll want {{ return(list_of_relation) }} or {% do return(list_of_relation) %}. The problem is that {{ ... }} turns things into strings in jinja macros, and macros by default return strings.
The documentation on return is here.

Jekyll - Include multiple parameters in single include?

Is it possible to include multiple parameters in single include?
Single:
{% include card.html class=include.class1 %}
Multiple??
{% include card.html class=include.class1 && include.class2 %}
Or do I have to do class1=include.class1 class2=include.class2?
Multiple include parameters can be passed separated with a space param1=value1 param2=value2,e.g.:
{% include image.html url="http://jekyllrb.com"
max-width="200px" file="logo.png" alt="Jekyll logo"
caption="This is the Jekyll logo." %}
Then you can access them inside the include file prefixing it with include., for example:
{{include.file}} {{include.caption}}
As stated by #marcanuy,
One way is to use the capture function to include multiple values into a single parameter.
{% capture classes %} {{include.class1}} {{include.class2}} {% endcapture %}
{% include card.html class=classes %}
Here's my use case for this-
an include that has html for a radiobutton set, like so:
<label>{{include.label}}</label>
{% for option in include.options %}
<input type="radio" name="{{include.label}}" id="{{include.option}}" value="{{include.option}}" checked="checked"/><label for=" {{include.option}}">{{include.option}}</label>
{% endfor %}
that you call like this:
{% include radiobuttons.html label="favorite color" options="green", "blue", "orange", "red" %}

Check if an array is not empty in Jinja2

I need to check if the variable texts is defined or not in index.html.
If the variable is defined and not empty then I should render the loop. Otherwise, I want to show the error message {{error}}.
Basically this in PHP
if (isset($texts) && !empty($texts)) {
for () { ... }
}
else {
print $error;
}
index.html
{% for text in texts %}
<div>{{error}}</div>
<div class="post">
<div class="post-title">{{text.subject}}</div>
<pre class="post-content">{{text.content}}</pre>
</div>
{% endfor %}
How do I say this in jinja2?
I think your best bet is a combination of defined() check along with looking at the length of the array via length() function:
{% if texts is defined and texts|length > 0 %}
...
{% endif %}
To test for presence ("defined-ness"?), use is defined.
To test that a present list is not empty, use the list itself as the condition.
While it doesn't seem to apply to your example, this form of the emptiness check is useful if you need something other than a loop.
An artificial example might be
{% if (texts is defined) and texts %}
The first text is {{ texts[0] }}
{% else %}
Error!
{% endif %}
Take a look at the documentation of Jinja2 defined(): http://jinja.pocoo.org/docs/templates/#defined
{% if variable is defined %}
value of variable: {{ variable }}
{% else %}
variable is not defined
{% endif %}
Is it clear enough? In your case it could look like this:
{% if texts is defined %}
{% for text in texts %}
<div>{{ error }}</div>
<div class="post">
<div class="post-title">{{ text.subject }}</div>
<pre class="post-content">{{ text.content }}</pre>
</div>
{% endfor %}
{% else %}
Error!
{% endif %}
As mentioned in the documentation, you could also write:
{% for text in texts %}
<div class="post">
<div class="post-title">{{text.subject}}</div>
<pre class="post-content">{{text.content}}</pre>
</div>
{% else %}
<div>{{ error }}</div>
{% endfor %}
It handles both the case where texts is undefined, and the case where texts is empty.
This is a neat and simple solution that worked well for me!
{% if texts is defined and texts[0] is defined %}
...
{% endif %}
It's possible that texts could be defined but contain a single list element which is an empty string; For example:
texts = ['']
In this case, testing if texts is defined will produce a true result so you should test the first element instead:
{% if texts[0] != '' %}
..code here..
{% endif %}
You might also want to combine that with the |length filter to make sure it only has one element.
This worked for me when working with the UPS API where if there is only one object in a parent object the child is just an object, but when there is more than one child it's a array of objects.
{% if texts[0] %}
..code here..
{% endif %}
This is what worked for my use case in my Django app:
I needed to pass a queryset as context to an html template and display the block only if the queryset had values
Queryset:
events = Event.objects.filter(schedule_end__gte=date.today()).order_by('-created_at')
Passed context dictionary as follows:
{ "events" : events }
HTML template
{% if events %}
<h3>Upcoming Events</h3>
<ul>
{% for event in events %}
<li><h4>{{ event.title }}</h4></li>
{% endfor %}
</ul>
{% endif %}
This works for me ( But I make sure to return an empty array [] and not None if its empty )
{% if array %}
<table class="table">
...
</table>
{% endif %}
We can check if array is not empty by writing below jinja code.
where the content2 is an array defined under py file. #app.route("/<name>") def home(name): return render_template("index.html", content=name, content2=[])
{% if content2 %}
<div>
<h2>Array elements are available</h2>
{% for con2 in content2 %}
<p> {{con2}} </p>
{% endfor %}
</div>
{% endif %}
Thanks