Referencing jinja2 variable from a variable - variables

I want to be able to apply DRY and not have to repeat myself when building my jinja2 template. So I would like to be able to reference a variable within the jinja2 template from a dynamically constructed variable name, eg:
{% for a in [ 'a', 'b', 'c'] %}
{% set name = a + "_name" %}
{% set value = {{ name }} %}
hello there {{ value }}
{% endfor %}
where my input variables into jinja would be
a_name = 1
b_name = 2
c_name = 3
and I the result would be
hello there 1
hello there 2
hello there 3
is this possible?
I know i would just pass in a datastructure into jinja2 to do something similar, but I am not at liberty to modify what goes into the template.

i got the answer from here
basically, define a contextfunction and reference it within the jinja code:
from jinja2 import Environment, FileSystemLoader, contextfunction
j2_env = Environment( loader=FileSystemLoader(directory), trim_blocks=False )
this_template = j2_env.get_template( """
{% for i in [ 'a', 'b', 'c'] %}
hello there {{ context()[i+"_name"] }}
{% endfor %}
""" )
#contextfunction
def get_context(c):
return c
this_template.globals['context'] = get_context
this_template.render( **{ 'a_name': 1, 'b_name': 2, 'c_name': 3 } )

Related

With dbt how do I use a model cte in a macro call in a jinja expression?

How would I reference a previously defined cte inside of a macro call, inside of a jinja expression block?
with stg_example_table as (
select *
from {{ ref('stg_db__example_table') }}
where {{ ref('stg_db__example_table') }}.example_column = 'foobar'
),
earliest_date as (
THIS
{{ vvvvvvvvvvvvvvvvv
get_earliest_date('month', 'created_at', stg_example_table)
}} ^^^^^^^^^^^^^^^^^
)
select * from earliest_date
I can't seem to reference the cte, stg_example_table, in that location in a way that works. How should it be referenced in a way that will work?
The macro get_earliest_date(), works if I use ref('stg_db__example_table'). But then I'm getting the wrong value since the table isn't reduced as it would be from the cte.
I could create another stg model that has the filters I need and ref() that one, but it'd be nice to just use the cte here.
I have also tried various forms of:
{% set earliest_date = run_query("select min(created_at)::date from stg_db__example_table").columns[0][0] %}
And then referencing the set earliest_date, but I could not get that to work.
For reference, this is the get_earliest_date() macro:
{% macro get_earliest_date(date_component, column_name, relation) %}
{% set query %}
select
date_trunc({{ date_component }}, min({{ column_name }}))::date as earliest
from {{ relation }}
{% endset %}
{% set results = run_query(query) %}
{% if execute %}
{% set result = results.columns[0][0] %}
{% else %}
{% set result = null %}
{% endif %}
{{ return(result) }}
{% endmacro %}
The example code is simplified, but eventually I want to get a date_spine() with:
{{
dbt_utils.date_spine(
datepart = "month",
start_date = get_earliest_date('month', 'created_at', stg_example_table),
end_date = "date_trunc('month', current_date())"
)
}}
dbt doesn't parse your model, so it simply doesn't know what stg_example_table is.
If you're looking to re-use a CTE, it should probably be its own model (a macro would be another choice). You can use the ephemeral materialization and dbt won't persist anything to your warehouse -- it just interpolates the model as a CTE. There are some limitations for how you can ref an ephemeral model, but I think in this case, since you're calling get_earliest_date from a model, it should work fine.
-- in stg_example_table.sql
{{ config(materialized="ephemeral") }}
select *
from {{ ref('stg_db__example_table') }}
where {{ ref('stg_db__example_table') }}.example_column = 'foobar'
-- in your_model.sql
...
{{
dbt_utils.date_spine(
datepart = "month",
start_date = get_earliest_date('month', 'created_at', ref('stg_example_table')),
end_date = "date_trunc('month', current_date())"
)
}}

How to create histogram bins for use in dbt using Jinja template?

I am trying to create histogram bins in dbt using jinja. This is the code I am using.
{% set sql_statement %}
select min(eir) as min_eir, floor((max(eir) - min(eir))/10) + 1 as bin_size from {{ ref('interest_rate_table') }}
{% endset %}
{% set query_result = dbt_utils.get_query_results_as_dict(sql_statement) %}
{% set min_eir = query_result['min_eir'][0] %}
{% set bin_size = query_result['bin_size'][0] %}
{% set eir_bucket = [] %}
{% for i in range(10) %}
{% set eir_bucket = eir_bucket.append(min_eir + i*bin_size) %}
{% endfor %}
{{ log(eir_bucket, info=True) }}
select 1 as num
The above code returns dbt.exceptions.UndefinedMacroException.
Below is the error log.
dbt.exceptions.UndefinedMacroException: Compilation Error in model terms_dist (/my/file/dir)
'bin_size' is undefined. This can happen when calling a macro that does not exist. Check for typos and/or install package dependencies with "dbt deps".
Now, I haven't written the SQL yet. I want to build an array containing the historical bins, that I can use in my code.

Assign a value to array by index in Liquid

I am inside of a bit complex loops and I need to assign a value to an array by index, so that if the value is already there it will replace it, if not it will create it.
So I need to do something like this:
{% assign arr = '' | split: '' %}
{% assign arr[index] = value %}
which is not working, the array is still empty.
Is there any workaround to do this?
There is no direct workaround.
You can always re-create the array with a default value though that would only give you a single value.
One potential work around would be to re-create the source and fill in any missing defaults then re-split into an array
{% assign arr = someValue | split: '' %} <!-- splitting to single chars ? -->
{% assign withDefaults = '' %}
{% for ...%}
{% unless arr[loop.index0] == true %}
{% withDefaults = withDefaults | append : 'defaultValue,' %}
{% else %}
{% withDefaults = withDefaults | append : arr[loop.index0] | append : ',' %}
{% endfor %}
{% assign arr = withDefaults | split: ',' %} <!-- you'll have an extra blank element but that may not matter -->

How to use variables in Twig filter 'replace'

Handing over an array from php of form
$repl_arr = array('serach-string1' => 'replace1', ...)
to a Twig template I would like to replace strings in a Twig variable per replace filter similar to this:
{{ block | replace({ repl_arr }) }}
That does not function and neither a variable loop like
{% for key,item in repla_arr %}
{% set var = block | replace({ key : item }) %}
{% endfor %}
does. What is wrong with it? How could it work?
Either you pass the whole array, or you loop the replaces.
But when looping the replaces you need to wrap key and value in parentheses to force interpolation of those
{% set replaces = {
'{site}' : '{stackoverflow}',
'{date}' : "NOW"|date('d-m-Y'),
} %}
{% set haystack = '{site} foobar {site} {date} bar' %}
{{ haystack | replace(replaces) }}
{% set output = haystack %}
{% for key, value in replaces %}
{% set output = output|replace({(key) : (value),}) %}
{% endfor %}
{{ output }}
fiddle

Shopify - Increment or Counter

I'm trying to set up a simple way to increment within for loops in my themes and can't figure out how to get it to work. I'm familiar with two ways to increment:
{% assign variable = 0 %}
{% for .....
{% assign variable = variable | plus: 1 %}
.... endfor %}
and
{% assign variable = 0 %}
{% increment variable %}
However neither of these work. Update: Currently the following block of code will output "0" when it should be "1"
{% assign variable = 0 %}
{% assign variable = variable | plus: 1 %}
{{ variable }}
What am I doing wrong?
What you are doing with the assign should work however there is an easier way:
{{forloop.index0}}
See the docs for the loop object