Using a string as an argument in a Jinja macro in dbt - jinja2

I want to create a dbt macro to simplify the following lines
COALESCE(LOWER(tags::TEXT) ~ '.*my-first-query.*', FALSE),
COALESCE(LOWER(tags::TEXT) ~ '.*my-second-query.*', FALSE),
COALESCE(LOWER(tags::TEXT) ~ '.*my-other-query.*', FALSE)
I would like to convert the computation to a function such that I can convert the lines to
{{ extract_clean_tag(my-first-query) }},
{{ extract_clean_tag(my-second-query) }},
{{ extract_clean_tag(my-other-query) }}
How can I write this macro in dbt? I'm having issues passing strings as arguments to the functions.
So far, I've tried something like
{% macro extract_clean_tag(tag_regex) %}
COALESCE(LOWER(tags::TEXT) ~ '.*{{ tag_regex }}.*', FALSE)
{% endmacro %}
And calling it via extract_clean_tag(my-first-query), but dbt returns:
column "my-first-query" does not exist

You have to call it with 'my-first-query' as an argument, like this:
{{ extract_clean_tag('my-first-query') }}
Without the quotes, the Jinja parser is looking for a variable named my-first-query, whereas the quotes denote that you're passing a string.
See also here: https://docs.getdbt.com/docs/building-a-dbt-project/jinja-macros/#macros (the cents_to_dollars example)

Related

How to pass variable while calling macros in post hook?

Currently I am calling macros in such a way:
"post_hook":["{{macros_append('string1','string2')}}"]})}}
I want to call it as
"post_hook":["{{macros_append(var1,var2)}}"]})}}
I have already tried setting variable before config like
{% set var1='value' %}
"post_hook":["{{macros_append(var1,var2)}}"]})}}
But this does not work. It does not take any value while calling macros.
This doesn't work because dbt is parsing the jinja of your post-hook in a different context from the model file. In short, the post-hook needs to stand on its own.
This is true of all configs. The hints are:
post_hook takes a list of strings. Those strings later get templated by the jinja templater. (this is why you quote them and nest the curlies! you should never really nest curlies otherwise)
Configs can also get passed in through .yml files, etc., which is partially why the templating is deferred
Your question omits the actual call to the config macro, which makes this a little more clear:
{{
config({
"post_hook": ["{{macros_append('string1','string2')}}"]
})
}}
So what are we to do? You could use jinja to build the string that gets passed into the config block. This is hacky and ugly, but it works:
(Note that ~ is the jinja string concatenation operator.)
{% set var1 = "string1" %}
{% set var2 = "string2" %}
{{
config({
"post_hook": ["{{ macros_append(" ~ var1 ~ "," ~ var2 ~ ") }}"]
})
}}
A slightly cleaner version of this would be to define the whole macro call in a variable, so you don't have to do the concatenation:
{% set my_hook = "{{ macros_append('string1', 'string2') }}" %}
{{
config({
"post_hook": [my_hook]
})
}}
A Better Way
Another option is to use the var() macro, which allows you to access a global variable in the jinja context. You define these global variables in your dbt_project.yml file:
...
vars:
var1: string1
var2: string2
and then you can access them with {{ var('var1') }} from any process that is templating jinja. In the case of your config block, that would look like:
{{
config({
"post_hook": ["{{ macros_append(var('var1'), var('var2')) }}"]
})
}}
Note that the post-hook here is just a string that contains the string "var('var1')", but that's fine, since the templater will fill that in later, when the string is templated.

Why converting to JSON a hash I initialized yield a null?

I am trying to build a hash in order to later output is as JSON (and ultimately import it to be reused by a a script). This is part of my static site built with jekyll.
Following the documentation on Expressions and Variables, I created a file with
---
---
{% assign aaa['bbb'] = 'xxx' %}
{{ aaa | jsonify }}
This was compiled by jekyll to a null (as if the hash was not initialized). Why is it so?
Sadly the documentation is talking about reading hash or arrays, not writing.
The only thing you can write from liquid is arrays.
create an empty array : {% assign my-array = "" | split: "/" %}{{
y-array | inspect }}
store with push or shift {% assign my-array = my-array | push: anything %}
= empty-array }}, where anything can be a string, an integer, a hash or an array.

Call a constructed variable name in Jinja2 template?

Is there a way in Jinja2 to construct a variable name and then call it? I want to do something like this:
{% for type in ('Students', 'Faculty', 'Groups') %}
{% set import_name = 'latest_' + type|lower + '_import' %}
{{ type }}: {{ import_name.created_at }}
{% endfor %}
I would expect the output to be something like this:
Students: 5/26/2016
Faculty: 5/25/2016
Groups: 5/25/2016
I have the variables latest_students_import, latest_faculty_import, and latest_groups_import set in the template scope, and would like to avoid having a large conditional in my for loop. I set import_name based on the type, and then try to "call" import_name. I want something like {{ call(import_name) }}. Is this possible, or is there another way I can go about this?
In this case, I suppose I could do it in reverse order loop through the three template variable names, and then "print" the shortened name, capitalized, but I would prefer to do it this way.
One possibility is to create a dict or a list on the server-side which contains your variables. You can then send that object to Jinja as a template variable. As it stands you are just setting import_name equal to string, which won't have the .created_at attribute.

Creating data-structures for ansible templates

I'm trying to create a simple config file that enumerates all the (hostname, ip_address) pairs as part of an ansible task. What I'd really like to do is something like this (using ansible's global datastructures groups and hostvars):
def grouped_hosts():
ret = {}
for group in groups:
ret[group] = {}
for host in groups[group]:
ret[group][host] = hostvars[host]['ansible_eth0']['ipv4']['address']
return json.dumps(ret)
Which would emit a data structure similar to:
{"webservers":{"web0":"1.2.3.4","web1":"1.2.3.5"},{"caches":{"cache0":"1.2.3.6"}}}
However, I don't know how to build and pass this data structure to my jinja2 template. I really want to be able to create that datastructure and just put {{ grouped_hosts()|to_nice_json }} and call it a day. But how do I write, and where do I put, that grouped_hosts() function?
I'm not sure what you're trying to create with your template, but if you just want to output this as a json structure, you can do it this way :
{
{% set gdelim = '' %}
{% for group in groups %}
{{ gdelim }}"{{group}}": {
{% set hdelim = '' %}
{% for host in groups[group] %}
{{ hdelim }}"{{ host }}": "{{hostvars[host]['ansible_eth0']['ipv4']['address']}}"
{% set hdelim = ',' %}
{% endfor %}
}
{% set gdelim = ',' %}
{% endfor %}
}
The gdelim and hdelim are here to set the delimiter when required (note the delimiters prefixing objects).
At first run, the delimiter is empty, and then ",". Since the objects are prefixed by the delimiter, you don't have trailing comas, and thus the resulting JSON is valid (but a bit ugly).

Combining two variables for use in Twig

I'm using Google app engine and I'm trying to set the value of a textarea based on the concatenation of two string variables. Let's say I have 4 items, and each item has multiple fields. So in my Python I'm passing the dictionary { 'value0': newValue }. I'm using a for loop (iterator value num) and I want to use in my HTML something equivalent to {{ value }}{{ num }} where the variable referenced is value0.
I've tried {{ value~num }} but nothing works. If I could use an array that would be even better - such as {{ value[num] }}