I'm trying to develop a macro to create a range of dates (datetime) that will be kept in an array.
I'm a beginner with Jinja (DBT dialect) and I'm very near a solution but it seems that something is missing.
I received an error message that let me think that this is only a problem of casting.
I want to keep all code inside a macro in DBT.
Here is my code (DBT macro in Jinja) to generate the array of dates (datetime in fact) :
{%- macro get_range_of_dates() -%}
{%- set dates = [] -%}
{%- set start = modules.datetime.datetime.strptime(var("currentDate"), "%Y-%m-%d") -%}
{%- set end = modules.datetime.datetime.now() -%}
{%- if start and end -%}
{%- set duration = (end - start).days + 1 -%}
{%- for day in range(0, duration) -%}
{%- set tempo = dbt.dateadd(datepart="day", interval=day, from_date_or_timestamp="'" ~ start ~ "'") -%}
{%- set final = modules.datetime.datetime.strptime(tempo,'%Y/%m/%d') -%}
{%- do dates.append(final) -%}
{%- endfor -%}
{%- endif -%}
{{ dates }}
{%- endmacro -%}
When I run this code, I received the following error message:
time data "\n\n dateadd(\n day,\n 0,\n '2023-02-07 00:00:00'\n )\n\n" does not match format '%Y/%m/%d'
(As you can guess with this message, my variable called "CurrentDate" coming from DBT is the first date of the range and is equal to "2023-02-07").
The question: How to keep the result of "dbt.dateadd...." as a datetime in variable "tempo" ?
Do I need to cast it ? and if yes, how ?
Many thanks in advance for your help.
I finally found myself a solution. I post it in case this can help someone.
The principal point where I was wrong was the utilization of the function "dbt.dateadd..." instead of the utilization of the function "timedelta(..." and also the fact that I didn't use directly the function "strftime" with the datetime object.
Here is the final code with comments:
{%- macro get_range_of_dates() -%}
{# Create an empty array to keep all dates #}
{%- set datesArray = [] -%}
{# Create a date representing the starting date of the range from a variable defined in dbt_project.yml #}
{%- set start = modules.datetime.datetime.strptime(var("currentDate"), "%Y-%m-%d") -%}
{# Create a date reprsenting the ending date of the range (always currentDay) #}
{%- set end = modules.datetime.datetime.now() -%}
{# If start and end variables are not empty...#}
{%- if start and end -%}
{# Calculate the lenght of the range #}
{%- set duration = (end - start).days + 1 -%}
{# Loop the range #}
{%- for day in range(0, duration) -%}
{# Add one day at each iteration #}
{%- set dateIterated = (start + modules.datetime.timedelta(day)) -%}
{# Convert datetime to string with the desired format #}
{%- set finalDate = dateIterated.strftime("%Y-%m-%d") -%}
{# Append the new date to the array of dates #}
{%- do datesArray.append(finalDate) -%}
{%- endfor -%}
{%- endif -%}
{# return the array of dates #}
{{ datesArray }} {%- endmacro -%}
Here is the call:
select {{ get_range_of_dates() }} from
And here is the results of this call:
select
['2023-02-07', '2023-02-08', '2023-02-09', '2023-02-10', '2023-02-11', '2023-02-12', '2023-02-13', '2023-02-14', '2023-02-15', '2023-02-16', '2023-02-17', '2023-02-18', '2023-02-19', '2023-02-20'] from
Hoping that this can help somebody.
Regards.
Related
My SLS file is as below and When trying to access the variable tst1 i get an error, details are as follows
{% load_yaml as test %}
value:
val1: 'string1'
val2: 'string2'
value1: ['sub1','sub2']
{% endload %}
{%- for tst in test.value1 -%}
{% set tst1 = test.value1[tst] %}
{{ tst1 }}
{%- endfor -%}
When trying to access the variable tst1 i get the below error as below.
rendering SLS 'base:test.test1' failed: Jinja variable 'list object' has no attribute 'sub1'
Can anyone help on what exactly the error pointing to ?
{% set tst1 = test.value1[tst] %}
In this line you are trying to access a dict. test.value1 is a list, not a dict.
I think this is exactly what you want:
Code
{%- for tst in test.value1 -%}
{{ tst }}
{%- endfor -%}
Output
sub1sub2
Actually the SLS file should contain IDs (tasks) that are to be executed with modules. Like:
do-something:
module.name:
- args
...
Just putting something like {{ tst }} will not work as it expects a mapping like do-something:
So in the for loop, you should "invoke" some module. I'm using test.echo to show the values in the below example:
{% for tst in test.value1 %}
show-value1-{{ tst }}:
module.run:
- name: test.echo
- text: '{{ tst }}'
{% endfor %}
I merged values of two arrays in a new array.
But I would like to take random values from this array and put them in a loop. That those values iterate in this loop.
{% set myArray = [] %}
{% set list1 = options.transitions_repeater %}
{% set list2 = options.transitions_wahou_repeater %}
{% set myArray = list1|merge(list2) %}
{% for key, val in myArray %}
{{ val|join(', ') }}
{% endfor %}
{% for item in options.projets %}
<li data-transisition="{{ myArray }}"></li>
{% endfor %}
I got the message : Array to string conversion in XX on line XX
Output :
animBottom
animTop
animLeft
directionRight
circles
cube
Your merged list is still a multidimensional array.You could solve your issue with the following code, however it's preferable to move the logic of creating the (single dimensional) array to your controller (then you could remove the filter first in the snippet)
{% for item in options.projets %}
<li data-transisition="{{ myArray[random(myArray| keys)] | first }}"></li>
{% endfor %}
demo
I want to use the loop.index variable in twig to get the corresponding alphabet letter (1 = A, 2 = B, etc).
{% for item in form.items %}
{% set nom_item = 'Item'~loop.index %}
{% endfor %}
How could I do to get alphabet letter in loop ? I can't find a twig function for that.
Simplest solution
{{ range('A','Z')[loop.index0] }}
try with this!
{% set foo = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] %}
{% for index,item in form.items %}
{% set nom_item = 'Item'~foo[index] %}
{% endfor %}
I have the following example of YAML data structure: (data and structure are subject to change)
settings:
subset1:
sub1: val1
sub2: val2
sub3: val3
subset2: val1
I want to use Jinja to print it as such: (ordering is not important)
subset1.sub2 = val2
subset1.sub3 = val3
subset1.sub1 = val1
subset2 = val1
This is what I have:
{% for key, value in settings.items() recursive %}
{{ key }}{% if value is mapping %}.{{ loop(value.items()) }}{% else %} = {{ value }}
{% endif %}
{% endfor %}
But it gives me this:
subset1.sub2 = val2
sub3 = val3
sub1 = val1
subset2 = val1
Only the first sub element is printed with its respected parent. How can I print all the sub elements with their respected parents properly?
(Again, ordering is not important)
Thanks!
This is how I solved my use case:
from jinja2 import Template
settings = {
'k1': {
'k1': '1.1',
'k2': {
'k1': '1.2.1',
'k2': {
'k1': '1.2.2.1'
},
'k3': {
'k1': {
'k1': '1.2.3.1.1'
},
},
},
},
'k2': {
'k1': '2.1'
},
'k3': '3'
}
template = """
{%- macro treedot(dict,string) %}
{%- for key, value in dict.items() -%}
{%- if value is mapping -%}
{%- if string -%}
{%- set str = string ~ '.' ~ key -%}
{%- else -%}
{%- set str = key -%}
{%- endif -%}
{{ treedot(value,str) }}
{%- else -%}
{%- if string %}
{{ string ~ '.' ~ key }} = {{ value }}
{%- else %}
{{ key }} = {{ value }}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- endmacro -%}
{{ treedot(settings,none) }}
"""
tpl = Template(template)
print tpl.render(settings=settings)
The result will be :
k3 = 3
k2.k1 = 2.1
k1.k2.k3.k1.k1 = 1.2.3.1.1
k1.k2.k2.k1 = 1.2.2.1
k1.k2.k1 = 1.2.1
k1.k1 = 1.1
Better than using the keyword "recursive", using a macro allow recursivity too and allow to pass as an argument the path to the value.
Before this I tried with "recursive" keyword too but wasn't able to achieve my needs since variables inside a loop are reset at each iteration.
I have to set a variable with a macro, that returns a number, so I have this macro:
{% import _self as self %}
{% macro function_size(field) %}
{% import _self as self %}
{# initial Setup #}
{% set field_length = 0 %}
{# Field #}
{% for key, value in field %}
{% if not (key starts with "#") %}
{% set field_length = field_length + 1 %}
{% endif %}
{% endfor %}
{{ field_length }}
{% endmacro %}
The Macro loops through the entries of a field and returns the count of values, that don't start with "#".
So I set a variable with that value:
{% set image_size = self.function_size(content.field_teaser_image) %}
ATTENTION: With this you will set the Variable with Twig Markup. (You can see that when you debug the variable afterwards.)
To get a number/integer as value you have to convert it to a String (that will be interpreted as a number if you calculate with it)
{% set image_size = image_size.__toString %}
With this I set the Variable successfully with a macro.
My Question: Is this a bad practice, are there better ways how to set an Variable?
Thank you!
Two ways to set variables for twig are fine in my opinion.
1. Use theme and other hooks (inside theme or any module) and pass variable from php,
2. create twig extension/filter
Examples:
Filter:
http://leopathu.com/content/create-custom-twig-filter-drupal-8
Extension:
http://symfony.com/doc/current/templating/twig_extension.html