Not able to access dictionary value in Macro DBT - jinja2

I am accessing a dictionary variable which is defined in the marco
{% macro normalize_state(column_name) -%}
{% set states_dict = {
"Alabama" : "AL",
"Alaska" : "AK",
...
....
} -%}
CASE WHEN {{column_name}} IS NOT NULL THEN '{{ states_dict.get(column_name) }}'
ELSE NULL END
{%- endmacro %}
But '{{ states_dict.get(column_name) }}' produced the output None

Macros are compiled (templated) before the query is run. That means that the data in your database doesn't run through the jinja templater.
{{ states_dict.get(column_name) }} looks up the name of the column in your dictionary, not the data it contains.
What you need to do is use your dictionary to write a case statement that performs the get operation. That looks like this:
{% macro normalize_state(column_name) -%}
{% set states_dict = {
"Alabama" : "AL",
"Alaska" : "AK",
...
} -%}
case
{% for k, v in states_dict.items() %}
when {{ column_name }} = '{{ k }}'
then '{{ v }}'
{% endfor %}
end
{%- endmacro %}
Then you need to pass in column_name as a string (quoted) when you call the macro:
select {{ normalize_state("my_column") }}

Related

How can I fetch a value from a JSON dictionary?

I am trying to grab the value green from the below JSON data dictionary.
The API endpoint located at http://localhost:9200/api/status give the below data:
{
"name":"prod01",
"uuid":"3430c40-e786-4325-bc48-e0a096956000",
"version":{
"number":"7.17.0",
"build_hash":"60a9838d21b6420bbdb5a4d07099111b74c68ceb",
"build_number":46534,
"build_snapshot":false
},
"status":{
"overall":{
"since":"2023-02-13T22:47:05.381Z",
"state":"green",
"title":"Green",
"nickname":"Looking good",
"icon":"success",
"uiColor":"secondary"
},
"statuses":[
{
"id":"core:elasticsearch#7.17.0",
"message":"Elasticsearch is available",
"since":"2023-02-13T22:47:05.381Z",
"state":"green",
"icon":"success",
"uiColor":"secondary"
},
{
"id":"core:savedObjects#7.17.0",
"message":"SavedObjects service has completed migrations and is available",
"since":"2023-02-13T22:47:05.381Z",
"state":"green",
"icon":"success",
"uiColor":"secondary"
}
]
}
}
And the test.sls file doing the job is:
{% set json_data = salt.cp.get_url('http://localhost:9200/api/status', dest=None) | load_json %}
{% for key, value in json_data.items() %}
{% if value['state'] == 'green' %}
{{ key }}: {{ value }} (found)
{% else %}
{{ key }}: {{ value }}
{% endif %}
{% endfor %}
Executing it, I get the error:
Rendering SLS 'base:svr.salt.dev.test' failed: Jinja variable 'str object' has no attribute 'state'
You are looping on all the key-value pairs of the object json_data, with json_data.items(), so, you do not have a my_dictionary['state'] anymore, what you have is a key which will be state and its value will be green.
This said, your {"state": "green"} is not in the root of your dictionary, so you will never find any key-value pair matching what you need.
What you can do, though is:
{% if load_json.status.overall.state == 'green' -%}
state: {{ load_json.status.overall.nickname }}
{% endif %}
Which would yield:
state: Looking good

String manipulation in DBT - Jinja

I want to develop a macro that will loop rows from a seeding query's result and create a dynamic query for another task.
Let's assume my row would be similar to
<agate.Row: ('product_available', Decimal('0.6'), 'Positive')>
<agate.Row: ('product_quality', Decimal('0.5'), 'Negative')>
I intend to generate an array selection for downstream queries which have my_udf that will take arguments from my seeding query's result. For example,
my_udf("product_available", 0.6, 'Positive')
my_udf("product_quality", 0.5, 'Negative')
The problem that I have is some arguments are actual column names, while others are values. Hence, column names should have double quotes, while value must have single quote. For instance, "product_available" vs 'Positive'
My code is
{% macro generate_list_select_from(seeding_query) %}
{# ... other code to execute my seeding query ... #}
{# loop query goes here #}
{% for i in results_list %}
{% set item = "my_udf( {{ i[0] }} , {{ i[1] }}, '{{ i[2] }}' )" %}
{{items.append(item)}}
{% endfor %}
{{ return(items) }}
{% endmacro %}
Below is output when I use my macro
select foo_column,
my_udf( {{ i[0] }} , {{ i[1] }}, '{{ i[2] }}' ),
my_udf( {{ i[0] }} , {{ i[1] }}, '{{ i[2] }}' )
from foo_table
My question is how to create a such string?
Update:
Tried other way, {% set item = "my_udf(" + {{i[0]}} + ")" %}, I end up with a compilation error expected token ':', got '}'
Don't nest your curlies.
~ is the string concatenation operator in jinja. You could use that to build up your function args, including the single quotes:
{% set item = "my_udf(" ~ i[0] ~ ", " ~ i[1] ~ ", '" ~ i[2] ~ "' )" %}
This is pretty hard to read, though. I'd probably add a macro called quoted:
{% macro quoted(s) %}
'{{ s }}'
{% endmacro %}
And then use the join filter to concatenate the items of a list:
{% set args = [i[0], i[1], quoted(i[2])] %}
{% set item = "my_udf(" ~ args | join(", ") ~ ")" %}
Lastly, if the purpose of this macro is to template SQL, then you don't need to use return(items). You should just have the body of the macro template the string you want (see the quoted macro above). That dramatically simplifies things:
{% macro quoted(s) %}
'{{ s }}'
{% endmacro %}
{% macro generate_list_select_from(seeding_query) %}
{# ... other code to execute my seeding query ... #}
{# loop query goes here #}
{% for i in results_list %}
{% set args = [i[0], i[1], quoted(i[2])] %}
my_udf({{ args | join(", "}}){% if not loop.last %},{% endif %}
{% endfor %}
{% endmacro %}

How to Add a timestamp within dbt macro or log

I want to add a timestamp to my macro log that shows the current date and time of the local place. I tried to place the run_started_at function in and out of the log, but it doesn't work. I prefer to have inside the log. ie,'Operation Completed 01:10:2023 11:30' Below are the works I have done;
{% macro create_local_database(username, reset_environment = false) %}
{% set setup_script1 %}
{%- if reset_environment == false -%}
CREATE DATABASE ANALYTICS_LOCAL_MART_{{username}}
{% do log('Operation Completed', True) %} {{ run_started_at.strftime("%Y-%m-%d") }}
{%- endif -%}
{% endset %}
{% endmacro %}
I would like to have a macro log that includes the message and timestamp.
Just append the timestamp to the string you send to log()
{%- macro logt(msg) -%}
{{ log(this ~ " (" ~ run_started_at.strftime('%Y-%m-%d') ~ "): " ~ msg, true) }}
{%- endmacro -%}

jekyll: Is it possible to use a page.variable, as an operator inside a conditional if statement?

JSON file in path: _data/integers.json which looks like this:
{
"100": [
{
"value": "true"
}
]
}
In Jekyll page:
---
integers:
- 100
- 200
---
What I'm trying to do:
{% assign json = site.data.integers %}
{% for integer in page.integers %} // loop
{% if json.{{ integer }}[0].value == "true" %} ... {% endif %}
{% endfor %}
e.g. use the {{ integer }} (aka page.integer[0]) as an operator inside the conditional statement.
Is there a method? ... asking for a friend.
If we keep your json and page.integers as is :
{% assign json = site.data.integers %}
{{ json | inspect }}
{% for integer in page.integers %}
{% comment %} Here we cast our integer to a string,
as json keys are strings
(100 | append:"" => "100")
{% endcomment %}
{% assign intAsStr = integer | append:"" %}
{% comment %} as a possible json[intAsStr] returns an array,
we retrieve the first and only element in it
{% endcomment %}
{% assign data = json[intAsStr].first %}
{% if data["value"] == "true" %}
<h1>We have a match on {{ intAsStr }}</h1>
{% endif %}
{% endfor %}
We can simplify a little with some refactoring
data/integers.json
{
"100": { "value": true }
}
jekyll page
---
integers:
- "100"
- "200"
---
{% assign json = site.data.integers %}
{{ json | inspect }}
{% for integer in page.integers %}
{% assign data = json[integer] %}
{% if data["value"] == true %}
<h1>We have a match on {{ integer }}</h1>
{% endif %}
{% endfor %}

How to access sub level JSON trough Django

So, I get this JSON from Django:
{'something' :
{'value':'somethingName','editable':'false'}
},
{'somethingElse':
{'value':'somethingElseName','editable':'true'}
}
And show it like this:
{% for key, value in obj.items %}
{{ key }} : {{ value }}
{% endfor %}
The problem is {{ value }} returns {'value':'somethingName','editable':'false'}, and I can't access value or editable trough {{ value.value }} or {{ value.editable }}.
I'd like to show {{ value.value }} as somethingName instead of the entire JSON.
Is there a way to access 'sub-level' JSON trough Django itself?
You cannot use template variable name as a dictionary key using the . notation. The second value in value.value is not interpreted as a string value because you have a variable name value in the loop.
Just rename key and value to obj_key and obj_value respectively:
{% for obj_key, obj_value in obj.items %}
{{ obj_key }} : {{ obj_value.value }}
{% endfor %}
Demo:
>>> from django.template import Context, Template
>>> t = Template("""
... {% for obj_key, obj_value in obj.items %}
... {{ obj_key }} : {{ obj_value.value }}
... {% endfor %}""")
>>> obj = {'something' : {'value':'somethingName','editable':'false'}}
>>> t.render(Context({'obj': obj}))
u'something : somethingName'
Hope that helps.