play_hosts is a list of all machines for a play. I want to take these and use something like format() to rewrite them like rabbitmq#%s and then join them together with something like join(). So:
{{ play_hosts|format(???)|join(', ') }}
All the examples of format use piping where the input is the format string and not a list. Is there a way to use these (or something else) to accomplish what I want? The output should looks something like:
['rabbitmq#server1', 'rabbitmq#server2', rabbitmq#server3', ...]
The jinja2 doc describes format like this:
format(value, *args, **kwargs)
Apply python string formatting on an object:
{{ "%s - %s"|format("Hello?", "Foo!") }}
-> Hello? - Foo!
So it gives three kinds of input but doesn't describe those inputs in the example, which shows one in the pipe and the other two passed in via args. Is there a keyword arg to specify the string that's piped? Please help, python monks!
In ansible you can use regex_replace filter:
{{ play_hosts | map('regex_replace', '^(.*)$', 'rabbitmq#\\1') | list }}
I believe another way would be using the joiner global function, as you can read in http://jinja.pocoo.org/docs/2.9/templates/#list-of-global-functions:
A joiner is passed a string and will return that string every time itโs called, except the first time (in which case it returns an empty string). You can use this to join things
So your code would be something like:
[
{% set comma = joiner(",") %}
{% for host in play_hosts %}
{{ comma() }}
{{ "rabbitmq#%s"|format(host) }}
{% endfor %}
]
You can create custom filter
# /usr/share/ansible/plugins/filter/format_list.py (check filter_plugins path in ansible.cfg)
def format_list(list_, pattern):
return [pattern % s for s in list_]
class FilterModule(object):
def filters(self):
return {
'format_list': format_list,
}
and use it
{{ play_hosts | format_list('rabbitmq#%s') }}
You could simply join not only by , but also add the prefix together with it. Now that's not very pythonic or sophisticated but a very simple working solution:
[rabbitmq#{{ play_hosts | join(', rabbitmq#') }}]
If you want to format a string, through a list.
l: list = ["world", "stackoverflow"]
"Hello %s and %s"|format(*l)
Related
So I'm trying to reach dictionary parameters with a variable but looks like you can't use it with the traditional way (mydict[var])
I'm passing a dictionary and a list to HTML:
#app.route('/')
def setup():
jinda = {
'first':'1234'
'second':'4567'
'third': '7890'
}
zirna = ['first', 'third']
return render_template('table.html', zirna=zirna, jinda=jinda)
Now in HTML I want to use a for loop two reach specific elements in the dictionary which is also in the list but I don't know how to do that also this one is not working ๐:
{% for i in zirna %}
<p> {{ jinda[i] }}<p>
{% endfor %}
This works for me and outputs:
<p> 1234<p>
<p> 7890<p>
However there is a syntax error in your python code. Commas should be used between each key:value when defining the dictionary:
jinda = {
'first':'1234',
'second':'4567',
'third': '7890',
}
Perhaps this is your issue, although I would expect this to throw a syntax error when launching your app.
In my template (XXX.sql) I have:
{{ params.etl_date if params.etl_date is not none else execution_date.strftime('%Y%m%d') }}
Then in my BigQueryOperator:
BigQueryOperator(task_id='XXX',
bigquery_conn_id=GOOGLE_CLOUD_PLATFORM_CONNECTION_ID,
write_disposition='WRITE_TRUNCATE',
create_disposition='CREATE_IF_NEEDED',
sql='XXX.sql',
schema=None,
use_legacy_sql=False,
destination_dataset_table=XXX,
params={
'etl_date': '20200605'
})
it works but with:
BigQueryOperator(task_id='XXX',
bigquery_conn_id=GOOGLE_CLOUD_PLATFORM_CONNECTION_ID,
write_disposition='WRITE_TRUNCATE',
create_disposition='CREATE_IF_NEEDED',
sql='XXX.sql',
schema=None,
use_legacy_sql=False,
destination_dataset_table=XXX,
# NO PARAMS
)
I can seem to access and format the execution date? It just returns an empty string. Any advice?
To add to Sergiy's response, it depends on where you want to make your intervention. Sergiy's is the only way for it to work with your template:
{{ params.etl_date if params.etl_date is not None else execution_date.strftime('%Y%m%d') }}
The reason why you have to provide it in this case as expressly None is owing to the the safe way your template is interpreted and rendered. That is there is an equivalent to dictionary.get(key, "") underneath params.etl_date... simply because this is safe when building string or text templates.
What you may want to do is use this expressly and change your template to be:
{{ params.etl_date if params.etl_date else execution_date.strftime('%Y%m%d') }}
This is because the empty string returned by default by params.etl_date will be evaluated by Python to be False.
In the second case you can set:
params={ 'etl_date': None}
I'd like to extend the behaviour of trans by rendering variables not as as values from the context, but instead as html (without using the context). My aim is to be able to populate those variables on the client through JavaScript.
Jinja as it seems doesn't allow for a great deal of customisation of this kind or I'm just unable to find the right hooks.
Here's what I'd like to achieve:
{% etrans name=username %}
My name is {{ name }}
{% endetrans %}
This should render to:
My name is <span id='#username'></span>
Of course, I could just use the normal {% trans %} directive and pass my html code to template.render(html_code_params), but that would require to have them defined in the template and the rendering code which I'd like to avoid.
Here's what I got so far (not much) which allows for a new etrans tag and the ability to use whatever goodies InternationalizationExtension has to offer.
from jinja2.ext import InternationalizationExtension
from jinja2.runtime import concat
class JavaScriptVariableExtension(InternationalizationExtension):
tagname = 'etrans'
tags = set([tagname])
def _parse_block(self, parser, allow_pluralize):
"""Parse until the next block tag with a given name.
Copy from InternationalizationExtension, as this uses hardcoded
`name:endtrans` instead of relying on tag name
"""
referenced = []
buf = []
while 1:
if parser.stream.current.type == 'data':
buf.append(parser.stream.current.value.replace('%', '%%'))
next(parser.stream)
elif parser.stream.current.type == 'variable_begin':
next(parser.stream)
name = parser.stream.expect('name').value
referenced.append(name)
buf.append('%%(%s)s' % name)
parser.stream.expect('variable_end')
elif parser.stream.current.type == 'block_begin':
next(parser.stream)
# can't use hardcoded "endtrans"
# if parser.stream.current.test('name:endtrans'):
if parser.stream.current.test('name:end%s' % self.tagname):
break
elif parser.stream.current.test('name:pluralize'):
if allow_pluralize:
break
parser.fail('a translatable section can have only one '
'pluralize section')
parser.fail('control structures in translatable sections are '
'not allowed')
elif parser.stream.eos:
parser.fail('unclosed translation block')
else:
assert False, 'internal parser error'
return referenced, concat(buf)
i18n_extended = JavaScriptVariableExtension
I don't mind overloading more methods (although the reason for above one should perhaps fixed upstream).
Stepping through the code is quite an interesting adventure. However, I hit a snag and am interested if anyone can give some advice.
The problem I see is that during the compilation, the function context.resolve() gets baked into the compiled code. jinja2.jinja2.compiler.CodeGenerator doesn't really allow any different handling here (correct me if I'm wrong). Ideally, I would define another node (for the variable) and this node would handle the way it's dealt with during compilation, but I don't see how this is possible. I might be too focussed on this as a solution, so perhaps someone can provide alternatives.
As suggested by #Garrett's comment, a much easier solution is to pass in a function to the template renderer that interpolates the variables. In my case, my target client-side framework is Angular, but this also works for any JS variables that you want to use within a {% trans %} environment. Here are the building blocks:
def text_to_javascript(string):
# modify as needed...
return "<span>{{ %s }}</span>" % string
def render():
tmpl = jinja_env.get_template(template_filename)
return tmpl.render({'js': text_to_javascript})
And this how I make use of it in the template file:
{% trans username=js('user.name') %}
My name is {{ username }}
{% endtrans %}
In the Angular controller, the variable user is bound to the $scope like so:
$scope.user = {'name': 'Bugs Bunny'}
I'm trying to render a jinja2 template from a python dict created from a CSV file, but jinja2 is taking exception to the dict and I don't understand why.
This is the template:
{% for key, value in hostname.iteritems() %}
interface {{ key }}
description {{ value }}
{% endfor %}
and this is the python code:
import csv
from pprint import pprint
import os
import jinja2
CSVDATA_FILENAME = 'port_descriptions.csv'
TEMPLATE_FILENAME = 'cisco.j2'
hostnames = []
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.getcwd()),
trim_blocks=True, lstrip_blocks=True)
template = env.get_template(TEMPLATE_FILENAME)
for row in csv.DictReader(open(CSVDATA_FILENAME)):
if row['hostname'] not in hostnames:
hostnames.append(row['hostname'])
for hostname in hostnames:
x = hostname
hostname = {}
for row in csv.DictReader(open(CSVDATA_FILENAME)):
if x == row['hostname']:
hostname[row['port']] = row['des']
pprint(hostname)
print template.render(hostname)
At the penultimate line (pprint(hostname)) the code will print the hostname dictionaries as desired so I know they are there, but jinja2 won't render them. Am I missing something obvious here, in the template maybe?
As far as I can see my code follows this - http://keepingitclassless.net/2014/03/network-config-templates-jinja2/ pretty closely, but I must be overlooking something?
Looking at this example in the docs, you probably need to pass your dictionary as a named keyword argument, like this: print template.render(hostname=hostname)
Note that only the part on the left of the equals sign is relevant to the template; you could just do template.render(hostname={'testy': 'testy'}) and the same template would keep working.
I have a json string stored in the database and when this is pulled out and shown on the template it is a string. I want to convert this into a dict object so I can access the contents directly.
string = "{'a':1, 'b':3}"
{{ obj }} ---> string "{'a':1, 'b':3}"
{{ obj.b }} ---> I want to access it like an object.
obj_in_string = "{'a':1, 'b':3}"
{{ obj_in_string | tojson }}
Ref: https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.tojson
Write the following code in a temporary file and write a function back-end side:
{{ obj.b | str_to_obj }}
Put this code in your jinja filter file:
for convert str to dic object
def str_to_obj(str):
return eval(str)
And write this code in your admin file:
app.jinja_env.filters['str_to_obj'] = jinja_filters.str_to_obj