How to dynamically call dbt macros using jinja? - jinja2

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 %}

Related

jinja2 using {if} within {for}

I'm trying to make a template which iterates over a list to check if a string exists. if it does then something happens, if the string isn't in the list, something else should happen. The issue is the result is repeated for all the lines in the list, which i do not want.
e.g.
The list is some simple yaml
LIST:
- A
- B
- C
Jinja looks like
{% for line in mylist %}
{% if 'A' in line %}
{{ A }} Was found in the list
{% else %}
{{ A }} Was not found
{% endif %}
{% endfor %}
So when 'A' matches i get this
A Was found in the list
A Was not found
A Was not found
And when 'A' does not match i get this:
A Was not found
A Was not found
A Was not found
Basically i need to stop it iterating over the list and just do one thing if it matches or one thing if it doesn't match
So if 'A' matches i need it to just do
A was found
If 'A' doesn't match i need it to just do A was not found
Use some kind of flag variable:
{% set ns = namespace(found=false) %}
{% for line in mylist %}
{% if 'A' in line %}
{% set ns.found=true %}
{% endif %}
{% endfor %}
{% if ns.foo %}
{{ A }} Was found in the list
{% else %}
{{ A }} Was not found
{% endif %}

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.

Listing all the key-value properites in site.pages

On my blog, I have a debug page which lists several things, including all the properties for each page in site.pages, like so:
{%- for page in site.pages -%}
<strong>{{ page.path }}:</strong><br>
<table>
{% for key_value in page %}
{% if key_value[0] == "content" %}
<tr><td>content</td><td>[{{ key_value[1] | size }} characters]</td></tr>
{% else %}
<tr><td>{{ key_value[0] }}</td><td><span>{{ key_value[1] }}</span></td></tr>
{% endif %}
{% endfor %}
</table>
{%- endfor -%}
This produces output like this:
So far, so good.
Now, I want to do the exact same thing with site.posts, but it doesn't work.
In particular, key_value[0] and key_value[1] don't have any value. The iteration does produce something, but it is not a key an value.
For example, the following produces a list of key names:
{%- for pp in site.posts -%}
<strong>{{ pp.path }}:</strong><br>
{% for kv in pp %}
{% capture cap %}{{ kv }}{% endcapture %}
<div>{{ cap }}</div>
{% endfor %}
<br>
{%- endfor -%}
like so:
I can't seem to get the content of the properties in a generic way, however. I guess the type of the things in the site.posts is not the same as sites.pages, in particular not a simple liquid dictionary.
I've tried messing around with to_liquid mentioned here without luck.

Salt jinja2 and trailing lines inside a loop and if statement

I can't figure out how to remove the last empty line from the output generated by a for and an if Jinja2 statments on SaltStack. I couldn't find a post pointing out the solution neither, as the solutions on internet that I could find don't involve for and if nested statements.
Any help is welcome, thanks.
This is what I tried so far:
{% for user in list_users -%}
{% if user.enabled == True -%}
{{ user.name }}:{{ user.passwd }}
{%- endif %}
{% endfor %}
user1:encrypted_pass
user2:encrypted_pass
user3:encrypted_pass
I also tried adding - at the beginning of the {endfor}:
{% for user in list_users -%}
{% if user.enabled == True -%}
{{ user.name }}:{{ user.passwd }}
{%- endif %}
{%- endfor %}
user1:encrypted_passuser2:encrypted_passuser3:encrypted_pass
Edit: this is the output that I am trying to achieve:
user1:encrypted_pass
user2:encrypted_pass
user3:encrypted_pass
PS: I had a look to the Jinja's whitespace control without success.

Change variable in included jinja2 template

Say I have two templates:
main.j2
{% include "vars.j2" %}
main: {{ var1 }}
vars.j2
{% set var1 = 123 %}
vars: {{ var1 }}
When run, only this line is output:
vars: 123
i.e. var1 is undefined in main.j2, even though it gets set to a value in the included vars.j2 template.
How can I pass variables from included template back to template that includes it? I considered chaining extends, but wondered if there's a more elegant approach.
I recently had a need to do the same thing, and found 2 solutions.
If you have Jinja version 2.10 or later, namespaces can be used:
main_ns.j2:
{% set ns = namespace() %}
{% include "vars_ns.j2" %}
main_ns: {{ ns.var1 }}
vars_ns.j2:
{% set ns.var1 = 123 %}
vars_ns: {{ ns.var1 }}
In Jinja 2.2 or later, it can be accomplished with block scoping of variables. I put the variable settings in the base template so that multiple children can extend it.
vars_block.j2:
{% set var1 = 123 %}
vars_block: {{ var1 }}
{% block content scoped %}{% endblock %}
main_block.j2:
{% extends "vars_block.j2" %}
{% block content %}
main_block: {{ var1 }}
{% endblock %}
You can try using with:
{% with var1=0 %}
{% include "vars.j2" %}
vars: {{ var1 }}
{% endwith %}