Jinja2 using numeric expressions in Template Expressions - jinja2

I need to do something apparently very simple:
typedef enum {
{% for e in mylist %}
{{ e }} = 0x{{ '%04X' % (1 << loop.index0) }},
{%- endfor %}
ALL = 0x0FFF
} Sound_Region_t;
but this bombs with "jinja2.exceptions.TemplateSyntaxError: unexpected '<'"
Intention is to get something like:
typedef enum {
foo = 0x0001,
bar = 0x0002,
fie = 0x0004,
fom = 0x0008,
...,
ALL = 0x0FFF
} Sound_Region_t;
I.e.: value is a "walking bit" so I can "bitwise or" together them.
Same behavior if I try to use other variations including "{% with bit = 1 << loop.index %}" or similar.
What am I missing?

Jinja2 does not allow bitwise operators inside the templates, so we need to create a small global function that executes such operators and returns a number:
def leftshift(amount):
return 1 << amount
# Get or create a Jinja2 environment
from jinja2 import Environment
env = Environment(...)
# Add leftshift function to the global context
env.globals['leftshift'] = leftshift
And in the templates now we can call leftshift function with the loop index:
typedef enum {
{% for e in mylist %}
{{ e }} = 0x{{ '{:04X}'.format(leftshift(loop.index0))}},
{%- endfor %}
ALL = 0x0FFF
} Sound_Region_t;

Related

In Jinja how to access Macro parameters in a set block that runs SQL?

Hi all I'm currently writing Jinja macro that runs a sql query based on some parameters passed to it.
I'm having some issue with the macro definition itself. I cannot find a way to use the macros paramters within the set block. It interprets the value as literally 'col1' not the value stored in it.
How can I run a SQL query within the macro that uses my parameters stored value?
How I run the macro:
WITH DRIVER
AS(
SELECT
'apple' as col1
)
SELECT
{{ select_test('col1') }} as output
FROM
DRIVER
The macro definition is shown below:
{% macro select_test(val1) %}
{% set query_to_run%}
select
concat({{val1}},'banana')
{% endset %}
{% set results = run_query(query_to_run) %}
{% if execute %}
{# Return the first column #}
{% set results_list = results.columns[0].values() %}
{% else %}
{% set results_list = [] %}
{% endif %}
{{ return(results_list) }}
{% endmacro %}
I currently get the following output:
col1banana
My expected output:
applebanana
note: edited for clarity
You're very close -- I think you're confused by the nested nature of this query.
I suspect when you created this example, you didn't include a set of quotes that is in your original query. This query will return col1banana if col1 is double-quoted (either in the macro or the model), and will return a Database Error otherwise:
select
concat('{{ val1 }}','banana')
-- {{ select_test('col1') }} returns 'col1banana'
select
concat({{ val1 }}, 'banana')
-- {{ select_test("'col1'") }} returns 'col1banana'
select
concat({{ val1 }}, 'banana')
-- {{ select_test('col1') }} raises a Database Error: column "col1" does not exist
The last example is always going to be a database error, since it compiles to this:
select
concat(col1, 'banana')
and there is no from clause in your query_to_run, so col1 can't exist.
Most macros don't use run_query and are just shortcuts for snippets of sql. Those then get templated back into the model query and executed against your database when you build your model.
A macro to concatenate a string onto a column name is as simple as:
{% macro cat_banana(column_name) %}
concat({{ column_name }}, 'banana')
{% endmacro %}
And then you call it from your model:
WITH DRIVER
AS(
SELECT
'apple' as col1
)
SELECT
{{ cat_banana('col1') }} as output
FROM
DRIVER
Then after dbt run -s my_model, if you select * from my_model you'll get applebanana.

Passing column divided by value as parameter in macro dbt jinja

I'd like to pass a column divided by a value as a parameter in a jinja macro.
I'm using the macro in a dbt model like this {{ pmt('rate'/1200, 'nper', 'pv', 'fv') }}
However, this gives the error message
"Encountered an error: unsupported operand type(s) for /: 'str' and 'int'"
Most likely you have to treat the whole argument as a string literal (quote the whole thing):
{{ pmt('rate/1200', 'nper', 'pv', 'fv') }}
The reason this works is because it is likely that the macro templates this string into SQL code, e.g.,
{% macro pmt(arg1, arg2, arg3, arg4) %}
...
select {{ arg1 }}
...
{% endmacro %}
In this toy example, {{ arg1 }} will take on the value {{ 'rate/1200' }}, which enters the template (unquoted) as
...
select rate/1200
...
which is valid sql (if you have a field called rate).
It's possible this won't work with all macros, though! In dbt, since the macros are typically templating SQL code, you usually want to pass in arguments that contain field or table references as string literals. However, the argument to the macro could stay inside the jinja context, in which case, you'll need to keep the argument unquoted, or modify a variable before it's passed into the jinja macro. As another toy example:
{% macro print_value(val) %}
{{ log(val, info=True) }}
{% endmacro %}
-- if val is a string literal:
{{ print_value('rate/1200') }}
-- $ rate/1200
-- if val is unquoted:
{% set rate = 2400 %}
{{ print_value(rate/1200) }}
-- $ 2

In jinja, can I list all variables passed to the render() function?

Is it possible to access, in jinja, all variables passed to the render function as dict, without knowing their names?
from jinja2 import Template
template_vars = {"environment": "dev", "var1": "value1", "var2" : "value2", ... , "var12": "value12", ... , "varN": "valueN"}
api_payload = Template("template.json.j2").render(template_vars)
Goal of the template is to create a json file like. Here is base for jinja2.
{
"env": "{{environment}}",
{% for item in [my list of variables passed] %}
{% if '2 'if item %}
"{{item}}": "{{item}}"
{% endif %}
{% endofr %}
}
You could iterate over dictionary like,
for key, value in d.items():
# do whatever with key and value
Or you could pass the data as a list and iterate over it too.

If condition in django templates not working

I have tried the if condition based on the value defined in the django template
{% if randomgen == 2 %}
<p style="float:right;text-align: center;padding:5px 5px;"><b>{% randomgen %}1</p>
{% else %}
<p style="float:right;text-align: center;padding:5px 5px;"><b>{% randomgen %} 2</p>
{% endif %}
the randomgen is defined to pick in random between 1 and 2 and the value is being displayed correctly in tag but irrespective of the value it always going to else condition
register = template.Library()
#register.tag(name="randomgen")
def randomgen(parser, token):
items = []
bits = token.split_contents()
for item in bits:
items.append(item)
return RandomgenNode(items[1:])
def render(self, context):
arg1 = 0
arg2 = 10
if "float" in self.items:
result = random.randint(1,20)
elif not self.items:
result = random.randint(1,20)
else:
result = random.randint(1,2)
return result
In your HTML, set randomgen to another variable:
{% randomgen as rgen %}
Then use the newly set variable for your conditional:
{% if rgen == 2 %}
Honestly, I was surprised your code didn't work as your usage makes sense intuitively. Knowing that it doesn't work though, my guess is the template is comparing a function with an integer which is always going to return False. Good question!

Insert Environment Variable using Jinja in SaltStack

I am trying to read a JSON file inside a folder. using import_json.
Here is my code
{% set instance_id = grains['INSTANCE_ID'] %}
INSTANCE_ID Env Var:
environ.setenv:
- name: INSTANCE_ID
- value: {{ grains['INSTANCE_ID'] }}
- update_minion: True
{% import_json "/tmp/$INSTANCE_ID/conf.json" as config_properties %}
But I am getting this error
Data failed to compile:
Rendering SLS 'base:cloud.steps.conf' failed: Jinja error: /tmp/$INSTANCE_ID/conf.json.
Although when I insert the INSTANCE_ID manually it works as expected.
What I want is to be able to insert either $INSTANCE_ID or directly the grain value {{ grains['INSTANCE_ID'] }}
Can someone please help me with this?
Thanks.
{% import_json "/tmp/$INSTANCE_ID/conf.json" as config_properties %}
I imagine you are trying to evaluate the variable $INSTANCE_ID in the above statement. Jinja template evaluates the variables in expression statements.
In this case, the variable is set in the first line, using set
{% set instance_id = grains['INSTANCE_ID'] %}
So, you can use it in expression along with string appends, like
{% import_json "/tmp/" ~ instance_id ~ "/conf.json" as config_properties %}
The above statement should resolve your error.
Also, I would suggest using a variable to evaluate the value of the string expression above, like
{% set conf_json_path = "/tmp/" ~ instance_id ~ "/conf.json" %}
and use it like this
{% import_json conf_json_path as config_properties %}
Hope this help!
In case, you wish to use grains dictionary directly, you can use the value like so
{% set conf_json_path = "/tmp/" ~ grains['INSTANCE_ID'] ~ "/conf.json" %}