Jinja2 - Use Set (Assignments) to call customer_function - python

I want to call a python function from a jinja template. At the doucmentation (http://jinja.pocoo.org/docs/2.10/templates/ see Assignments) the following example is offered:
{% set key, value = call_something() %}
But the following source does not work.
import jinja2
#jinja2.contextfunction
def hw():
return "World"
template = jinja2.Template('{% set test = hw() %} Hello {{ test }}!')
template.render(name='John Doe')
Error: ... UndefinedError: 'hw' is undefined
Does any one knows how to solve the problem?!
Thank's to all!

You may simply supply it as a key=value pair in the template.render function, just as you have supplied the name variable.
import jinja2
def hw():
return "World"
template = jinja2.Template('{% set test = hw() %} Hello {{ test }}!')
print(template.render(name='John Doe', hw=hw))
Alternatively, if and when you plan to use a Jinja environment, you may add it to the globals dictionary before any templates are created, should you want the function to be accessible from every template within said environment.
import jinja2
def hw():
return "World"
template = """
{% set test = hw() %} Hello {{ test }}!
"""
env = jinja2.Environment()
env.globals['hw'] = hw
template = env.from_string(template)
print(template.render(name='John Doe'))
I've used the from_string method here to load your template, but there is a whole loaders system for environments which you may want to investigate.

Related

Calling a jinja2 filter with only the context as an argument

I'm trying to render a jinja2 template in my service (not using flask, just pure jinja2 and constructing a jinja2 Environment.
Let's say I have the following filter defined
import jinja2
#jinja2.contextfilter
def my_function(context):
if context.get('my_var'):
return "hello"
else:
return "world"
Super simplified example, but the point of it is that I have some logic that conditionally returns a value based on some variable passed into the context.
Also, I'm using jinja2 2.11 or something like that, which is why I'm using #contextfilter instead of #pass_context.
I've added this filter to my environment using env.filters['my_function'] = my_function
In rendering the template, I'm calling
template = env.get_template('my_template.html')
template.render({'my_var': 'some_value'})
where the template might look something like
... some html here
{{ my_function }}
... some more html
This doesn't actually return "hello", and instead just is empty/blank.
I managed to get it by passing in a dummy variable
#jinja2.contextfilter
def my_function(context, value):
.... code is the same
And then in the template, I call it with {{ 'hi' | my_function }}. But obviously this is just a hack and not very desirable.
So my question is, how can I call a jinja2 filter function that only takes the context in as an argument? I've tried {{ my_function() }} which returns the error UndefinedError: 'my_function' is undefined, and {{ | my_function }}, which returns the error TemplateSyntaxError: unexpected '|'`
Or is there some other jinja2 construct I should be using?
Edit: my suspicion is that jinja2 uses the | to identify a filter vs a variable, and since I don't have |, then it tries to just render the variable my_function from the context, and since it doesn't exist in the context, it just outputs an empty string.
Jinja2 calls these kind of functions global functions (like range()), not filters. Just change filters to globals in this line:
env.globals['my_function'] = my_function
And then you can call your function in the templates: {{ my_function() }}.

how to extend a jinja2 template with a super block that is a string in the same Python script

I don't understand how to extend a jinja2 template with a super block that is a string in the same Python script
Sample code
from jinja2 import Template
hello = """
hello
"""
world = """
{% extends 'hello' %}
world
"""
j2_template = Template(world)
print(j2_template.render())
I want to print "hello world", obviously, but I get an error
TypeError: no loader for this environment specified
I checked the Jinja2 loader doc, but cannot find how to ref a string as a super block.
Any help solving this would be greatly appreciated.
Jinja doesn't know where hello template is. You need to remove {% extends 'hello' %} and render hello first and insert it as a variable in the string template.
world = Template("""{} world""".format(Template(hello).render()))
print(world.render())
from jinja2 import Environment, BaseLoader, DictLoader
loader = DictLoader({'hello': '<div>hello html</div>'})
# Need environment dictLoader object to loader
template_env = Environment(loader=loader, cache_size=1000)
template = template_env.from_string("<div>ss {{name}} {% include 'hello' %} </div>")
dictt = {"name": "sumit"}
parsed_html = template.render(dictt)
# Parsed output in html string
https://jinja.palletsprojects.com/en/3.0.x/api/
link of documentation
worked for me

How can I set a (non-global) variable in a jinja context function?

I want to set variable values, but only when they are not already assigned and within a local context.
So there is a solution to this:
{% with x=( x | default(1)) %}
{{ x }}
{% endwith %}
{% with x=2 %}
{% with x=( x | default(1)) %}
{{ x }}
{% endwith %}
{% endwith %}
This works nicely but it is a lot of text. I have many situations where I don't have just one but up to 20 variables that are being set and then a macro is called, or a template is included with those values.
Writing all those default conditions is just a mess and provokes mistakes. So I would love to be able to set a value on the current context in e.g. in a context function. But if I try the following:
#contextfunction
def defaults(ctx, **vals):
for k,v in vals.iteritems():
if k not in ctx:
ctx[k] = v
I get the an exception:
TypeError: 'Context' object does not support item assignment
And trying to set a value on ctx.vars would not help either:
vars
The template local variables. This list contains environment and context functions from the parent scope as well as local
modifications and exported variables from the template. The template
will modify this dict during template evaluation but filters and
context functions are not allowed to modify it.
http://jinja.pocoo.org/docs/2.9/api/#jinja2.Context.vars
I tried with
#contextfunction
def defaults(ctx, **vals):
for k,v in vals.iteritems():
if k not in ctx.vars:
ctx.vars[k] = v
And it gives no exception but just seems to not assign the value to the context.
I know I could write to the global context but that's not what I would like to do as it would produce side effects.
Is there a possibility to get just the current context and set a value on it? I didn't find any instructions on it and how this could be done and I did not really grasp that from reading the jinja source.
I found a solution, somehow I keep solving my own problems. It is not exactly an answer to the question of "How can I set a (non-global) variable in a jinja context function?", but it solves the problem.
I have written a jinja extension, that allows for a simple "default" tag:
from jinja2 import nodes
from jinja2.ext import Extension
"""
DefaultExtension
~~~~~~~~~~~~~~~~
Very simple jinja extension that allows for the following
{% set x=(x | default(1)) %}
{% set y=(y | default(2)) %}
to be written as
{% default x=1, y=2 %}
:copyright: (c) 2017 by the Roman Seidl
:license: BSD
"""
class DefaultExtension(Extension):
# a set of names that trigger the extension.
tags = set(['default'])
def parse(self, parser):
#just dump the tag
lineno = next(parser.stream).lineno
#parse through assignments (similar to parser.parse_with)
assignments = []
while parser.stream.current.type != 'block_end':
lineno = parser.stream.current.lineno
if assignments:
parser.stream.expect('comma')
target = parser.parse_assign_target()
parser.stream.expect('assign')
expr = (parser.parse_expression())
#consruct a 'default' filter
filter = nodes.Filter(nodes.Name(target.name, 'load'), 'default', [expr], [], None, None, lineno=lineno)
#produce an assignment with this filter as value
assignment = nodes.Assign(target, filter, lineno=lineno)
assignments.append(assignment)
return assignments
I just had to add it to my app:
app.jinja_env.add_extension(DefaultExtension)
and it works quite nicely, though I must confess I haven't yet tested it very thoroughly.
Anyone think I should submit this to jinja?

how can i get all required variables and attributes in a jinja2 templates to render

I am trying to build a configuration generator for myself using jinja2. I need to know what are the expected variables and their keys before trying to render template to provide a sample file to fill before template rendering. Required parameters will change based on selected configuration type and used templates. I don't want to store all required parameters because templates will be changed time to time and also new templates will be added. There will be 50ish parameter per template
here is a sample template file
{{data.config1[0].field1}}
{{data.config2[0].field3}}
firt level change test
{% for row in data.config1 %}
{% if row.field3=='1' %}
something {{row.field1}} {{row.field2}}
{% else %}
something {{row.field1}}
{% endif %}
{% endfor %}
footer thingy
{{data.config2[0].field5}}
i tried to use find_undeclared_variables from jinja2.meta package
here is my sample code
import os
from jinja2 import Environment, FileSystemLoader,meta
template_filename = 'change.txt'
PATH = os.path.dirname(os.path.abspath(__file__))
TEMPLATE_ENVIRONMENT = Environment(
autoescape=False,
loader=FileSystemLoader(os.path.join(PATH)),
trim_blocks=False)
template_source =TEMPLATE_ENVIRONMENT.loader.get_source(TEMPLATE_ENVIRONMENT, template_filename)[0]
parsed_content = TEMPLATE_ENVIRONMENT.parse(template_source)
variables= meta.find_undeclared_variables(parsed_content)
print (variables)
this is what i can get
{'data'}
my desired output something like this.
{'config1':['field1','field2','field3'], 'config2':['field3','field5']}
please suggest.
I have used, Jinja2schema to generate expexted output.
There exists the
jinja2.meta.find_undeclared_variables function that may help you:
https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.meta.find_undeclared_variables

Keep undefined variables

I am interested in rendering a template in multiple steps or keeping the tags for the undefined variables in Jinja2. I believe this would mean not only creating the 'UndefinedSilent" class (so the template won't crash on missing data) but also keeping the tags with the appropriate variable names if they are missing.
Example:
Let's say we have the name = "Test" included in the context, but quantity is missing.
Givent the following template:
<p>{{name}} has {{quantity}}</p>
After rendering, I need the template to become:
<p>test has {{quantity}}</p>
Does anyone know if this is achievable?
Providing DebugUndefined to named parameter undefined in the env, apparently does the trick. The rendered template preserves the {{<undefined variable}}.
Like here:
from jinja2 import Environment, BaseLoader, DebugUndefined
rtemplate = Environment(loader=BaseLoader,undefined=DebugUndefined).from_string("{{ a }} is defined, but {{ b}} is undefined")
print(rtemplate.render({"a":"a"}))
The result is:
a is defined, but {{ b }} is undefined
It is achievable using the default built-in filter.
<p>{{name|default('{{name}}')}} has {{quantity|default('{{quantity}}')}}</p>
The drawback is that the code becomes uglier and the variable names are duplicated, thus reducing maintainability.
Here is another approach that preserves undefined double curly expressions after rendering, including those that contain "multilevel" (dot-notated) references as well as any others.
The answer provided by Willy Pregliasco does not support preservation of undefined list types, eg {{ some_list[4] }} which is something I required. The below solution addresses this, as well as all possible schema types.
The idea is to parse the input template and try to resolve each expression with the provided context. Any that can not be resolved, we replace with a double curly expression that simply resolves to the original expression as a string.
Pass your template and context through the below preserve_undefineds_in_template function before calling render:
from jinja2 import Template, StrictUndefined, UndefinedError
import re
def preserve_undefineds_in_template(template, context):
patt = re.compile(r'(\{\{[^\{]*\}\})')
j2_expressions = patt.findall(template)
for j2_expression in set(j2_expressions):
try:
Template(j2_expression, undefined=StrictUndefined).render(context)
except UndefinedError:
template = template.replace(j2_expression, f"{{% raw %}}{j2_expression}{{% endraw %}}")
return template
Example:
template = """hello {{ name }}, {{ preserve_me }} {{ preserve.me[2] }}"""
context = { "name": "Alice" }
# pass it through the preserver function
template = preserve_undefineds_in_template(template, context)
# template is now:
# hello {{ name }}, {% raw %}{{ preserve.me }}{% endraw %} {% raw %}{{ preserve.me.too[0] }}{% endraw %}
# render the new template as normal
result = Template(template).render(context)
print(result)
The output is:
hello Alice, {{ preserve_me }} {{ preserve.me[2] }}
I also wated the same behaviour. The library jinja2schema provides the schema of variables needed for your template.
The steps for my solution are:
have a template
obtain the schema structure
give some data to fill
complete de data filling the missing items with the original string
from jinja2 import Template
import jinja2schema
def assign(schema, data, root=''):
'''Returns a corrected data with untouched missing fields
'''
out = {}
for key in schema.keys():
if isinstance(schema[key], (str, jinja2schema.model.Scalar)):
try:
out[key] = data[key]
except:
out[key] = f'{{{{ {root+key} }}}}'
elif isinstance(schema[key], (dict, jinja2schema.model.Dictionary)):
out[key]={}
try:
data[key]
except:
data[key] = {}
out[key] = assign(schema[key], data[key], root+key+'.')
return out
# original template
template_str = '<p>{{name}} has {{quantity}}</p>'
# read schema
schema = jinja2schema.infer(template_str)
# available data
data = {'name':'test'}
# data autocompleted
data_corrected = assign(schema, data)
# render
template = Template(template_str)
print(template.render(data_corrected))
The output is
<p>test has {{ quantity }}</p>
That was the intended result.
Hope it will help. This solution doesn't work with lists, but I think it is possible to extend the solution. You can also obtain the list of missing fields, if you need them.
This is the version with Template rather than Environment:
from jinja2 import Template, DebugUndefined
template = Template("<p>{{name}} has {{quantity}}</p>", undefined=DebugUndefined)
new_template = Template(template.render(name="Test"))
Thanks #giwyni
Another little hack if you just have few variables:
<p>{{name}} has {{ "{{quantity}}" }}</p>
The second replacement will replace to {{quantity}} so all is good ;)

Categories

Resources