Simple counter loop in Jinja? - jinja2

Looking through the docs (https://jinja.palletsprojects.com/en/2.10.x/templates/), all the examples seem to be showing using for loops to iterate through lists.
I just want to have a simple numeric iterator, i.e. for ($i = 1; $i <= 10; $i++).
Is this possible in Jinja ?

You can use the range function to generate a list of integers. So for example:
{% for i in range(10) %}
{{ i }}
{% endfor %}
Results in:
0
1
2
3
4
5
6
7
8
9

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.

Unable to use Jinja variable inside adapter.get_relation in DBT

I am trying to use a Jinja variable inside my adapter.get_relation but I am unable to do so :confused: The code
{% set the_var = 'amazon_full_orders_denormalized_' ~ company_uuid %}
{{ log(the_var, info=True) }}
{%- set source_relation = adapter.get_relation(
database='282615',
schema='airbyte_mumbai',
identifier= the_var ) -%}
The command I am using
dbt compile --vars '{"company_uuid":"0703afd3_496b_4ed5_8e0c_594b71f4718b","dataset":"airbyte_mumbai"}' --models tag:copy_reports
The variable the_var is getting set to amazon_full_orders_denormalized_
Found 6 models, 0 tests, 0 snapshots, 0 analyses, 165 macros, 0 operations, 0 seed files, 0 sources, 0 exposures
17:11:08 | Concurrency: 3 threads (target='dev')
17:11:08 |
amazon_full_orders_denormalized_
Table does not exist
17:11:08 | Done.
Whereas I am expecting it to be set to amazon_full_orders_denormalized_0703afd3_496b_4ed5_8e0c_594b71f4718b
Using variables in dbt can be hard sometimes! I think the commenter #Kay is on the right track here in that you have three variables happening here: the_var, company_uuid, and dataset. It looks as if you'd like the a table name that is the concatenation of the_var and company_uuid, which you can do using jinja's concat operators, ~, like this:
-- just for testing
{{ log(the_var, info=True) }}
{{ log(var('company_uuid'), info=True) }}
{{ log(the_var ~ var('company_uuid'), info=True) }}
-- see change to `identifier`
{%- set source_relation = adapter.get_relation(
database='282615',
schema='airbyte_mumbai',
identifier= the_var ~ var('company_uuid') ) -%}

saltstack state file: how to access a list element in a salt grain via Jinja

I try to evaluate the value of a grains list in jinja but do not know how. The list entry I am looking for is the minor osversion in
grain:osrelease_info
salt-call -g |grep -C2 osrelease_info
osrelease:
15.99
osrelease_info:
- 15
- 99
In a state-file I would like to evaluate the minior osrelease value in a jinja expression like this one:
{% if grains['osmajorrelease'] == '15' and grains['osrelease_info'][1] >= 99 %}
...
{% endif %}
However the syntax I tried above to access index:1 of the osrelease_info list does not report an error but doesn't work either.
So how can I access the list entry containing value "99" in Jinja?
I think I found the answer myself alltough it is a little strange. The problem was not the expression
{% ... and grains['osrelease_info'][1] >= 99 %}
but instead the string comparison before:
{% if grains['osmajorrelease'] == '15' ... %}
All I changed was to convert the string '15' to an int. So the following expression does what I originally wanted:
{% if grains['osmajorrelease'] == 15 and grains['osrelease_info'][1] >= 99 %}
...
{% endif %}
This is weird since I use exactly the same comparision (grains['osmajorrelease'] == '15') in other state files with success.

Check if variable type is hash or array in liquid

I am attempting to write a somewhat generic layout that can take as a parameter either an array of strings or a hash of options, so you can either do:
option:
- "<li><b>One:</b> This is</li>"
- "<li><b>Two:</b> Raw HTML</li>"
Or you can do:
option:
One: This is
Two: a mapping
The reason I want to support both of these is that this is a public layout and the first option is already supported, but I would prefer to use the second option, so I want something of a deprecation period where both versions are supported.
I saw in check if a variable is type of string or array in liquid that there's a way to determine if something is an array or a string, but both arrays and hashes have a first attribute! A practical way to re-use this function might be to check if the first element of the variable also has a first attribute, like so:
{% if site.option.first %}
{% if site.option.first.first %}
hash
{% else %}
array
{% endif %}
{% else %}
Something else!
{% endif %}
But this seems a bit unwieldy and a bit of a hack - plus it will give the wrong answer if passed an array of arrays (even though "array of arrays" is not considered a valid input in this case). Is there a better way to do this?
For arrays you know will not include numbers, you can use the following:
---
arr:
- ""
- "2"
- three
- null
hash:
foo: bar
baz: null
"0": 1
string: "a string"
---
nil: {{ page.nil_prop | map: "" | join: "," | size }} # 0
str: {{ page.string | map: "" | join: "," | size }} # 0
hash: {{ page.hash | map: "" | join: "," | size }} # 0
arr: {{ page.arr | map: "" | join: "," | size }} # 3
However, if a number sneaks into your array, you get Liquid Exception: no implicit conversion of String into Integer.
This is on Jekyll 3.8.

How to iterate through the top 3 posts from the last two weeks in liquid/jekyll?

Let me add some context: i'm trying to automate my boring reports to management using jekyll!
I'd like to write a post for every activity i do and, every week, ship the compiled report with the "top 3 highlights" from this week and from last week.
My shot at it, as a jekyll newbie, was adding the week of publishing and if that post is a highlight or not in the yaml front matter:
---
layout: event
title: "Gave a Jelyll Talk!"
date: 2015-04-23 16:05:04
highlight: week
week: 17
---
And get the last two weeks in the template, like this:
---
layout: email
---
Here are my activities from the last two weeks:
{% assign hls = (site.posts | where: "highlight" , "week") %}
{% assign weeks = (hls | group_by: "week") %}
{% assign thisw = weeks[0] %}
{% assign lastw = weeks[1] %}
<h1>Week #{{thisw.name}}</h1>
<ul>
{% for post in thisw.items %}
<li>{{post.title}}</li>
{% endfor %}
</ul>
<h1>Week #{{lastw.name}}</h1>
<ul>
{% for post in lastw.items %}
<li>{{post.title}}</li>
{% endfor %}
</ul>
That kinda works, but, i'd like to ask:
Is it possible to automatically calculate the current week, get only posts from the past two weeks (not future or prior), only the latest 3 highlights from each week and avoid repeating the html template?
I would consider writing a plugin for Jekyll, something like this:
WeeklyHighlights.rb
module Jekyll
class WeeklyHighlights < Generator
safe true
priority :high
def week_id(time)
# to handle turn of year properly
return time.strftime('%Y-%W')
end
def generate(site)
# hash (dict) to store highlights grouped by week number
highlights_by_week = {}
today = Time.now
# initialize recent weeks with empty highlights
(0 .. 4).each do |i|
w = week_id(today - i)
highlights_by_week[w] = []
end
# group highlights according to week number
site.posts.each do |post|
if post['highlight'] != 'week'
next
end
week = week_id(post.date)
highlights_by_week[week] ||= []
highlights_by_week[week] << post
end
# make array of arrays of highlights, in the required order
weekly_highlights = []
highlights_by_week.keys.sort.reverse.each do |w|
highlights_by_week[w].sort! {|a,b| b.date <=> a.date} # = hl.sort{|a,b| b.date <=> a.date }
weekly_highlights << {'id' => w, 'hls' => highlights_by_week[w]}
end
# store prepared highlights
site.config['weekly_highlights'] = weekly_highlights
end
end
end
Put the file in the plugins directory (typically plugins or _plugins in the root of Jekyll site), and then in the template:
{% for week in site.weekly_highlights limit:2 %}
<h2>week {{week.id}}</h2>
<ol>
{% for p in week.hls limit:3 %}
<li>{{p.title}}</li>
{% endfor %}
</ol>
{% endfor %}
More on Jekyll plugins: official documentation
Yep... that should work.
At least you can get the current (i.e. build) time with {{ site.time }} and format it with a liquid filter to get the week number like so:
{% assign current_week = site.time | date: "%w" | plus: 0 %}
(You need to apply the plus: 0 filter too, to get a number instead of a string assigned.) Now things get nasty, as you might want to consider turn of year properly... but it should be doable.
I can think of getting strings like YEAR-WEEKNUMBER for the last three weeks, assign all posts with same date string to a new variable, say posts_lastweek and so on. Then, loop over the posts, compare and limit the for loop ({% for post in posts_lastweek | limit: 3 %}) or work with counters to limit the output.
Let me know if that works for you...