Loop-variables are not accessable in @pass_context / @contextfilter
Inner-for-loop-variables are not accessible when a pass_context function is called from within a forloop.
As example, take the following pass_context-function.
@pass_context
def apply_prefix(context, value, **kwargs):
When the above function is called from within a forloop, I expect the values of the loop-variables "env" and "user" to be available in context.vars['env'] and context.vars['user'].
"""
{% for env in list_of_environments -%}
{% for user in list_of_users -%}
{{ user|apply_prefix() }}
{% endfor %}
{%- endfor %}
"""
However, only the value of "user" is available when I use the variable "value".
Working Example
def test_jinja_inner_forloop(basedir, env):
@pass_context
def apply_prefix(context, value, **kwargs):
prefix = context['prefix']
user = value # Should also be a available in context.vars['user']
env = context.vars['env'] # BUG: the variable "env" inner-forloop-variable is not accessible
return f'{prefix}_{user}_{env}'
# Make the context-filter available to the jinja-environment
env.filters['apply_prefix'] = apply_prefix
# Template that uses the context-filter
tmpl = env.from_string(
"""
{% for env in list_of_environments -%}
{% for user in list_of_users -%}
{{ user|apply_prefix() }}
{% endfor %}
{%- endfor %}
"""
)
# We like to instantiate the template with the following variables
variables = {}
variables['prefix'] = 'user'
variables['list_of_users'] = ['paul', 'rob']
variables['list_of_environments'] = ['test', 'acc', 'prod']
# Render the variables
actual = tmpl.render(variables)
# Desired result
assert actual == \
"""
user_paul_test
user_rob_test
user_paul_acc
user_rob_acc
user_paul_prod
user_rob_prod
"""
Environment:
- Python version: 3.9.6
- Jinja version: 3.0.1
As a workaround, it is possible to pass loop-variables as parameters into the pass_context-function. An example is here: https://stackoverflow.com/questions/8056516/how-to-access-a-for-variable-from-a-jinja2-contextfilter. Unfortunately, this workaround is not applicable to my use case (my pass_context-function is not allowed to have any parameters)
Use context.resolve to lookup context variables.
Hi @davidism , thanks for your quick reply
Unfortunately, context.resolve is not able to retrieve the value of "env". Instead, it returns an "Undefined":

Here is an updated example with your suggestion that was used to make the previous screenshot.
def test_jinja_inner_forloop(basedir, env):
@pass_context
def apply_prefix(context, value, **kwargs):
prefix = context['prefix']
user = value
env = context.resolve(key='env')
return f'{prefix}_{user}_{env}'
# Make the context-filter available to the jinja-environment
env.filters['apply_prefix'] = apply_prefix
# Template that uses the context-filter
tmpl = env.from_string(
"""
{% for env in list_of_environments -%}
{% for user in list_of_users -%}
{{ user|apply_prefix() }}
{% endfor %}
{%- endfor %}
"""
)
# We like to instantiate the template with the following variables
variables = {}
variables['prefix'] = 'user'
variables['list_of_users'] = ['paul', 'rob']
variables['list_of_environments'] = ['test', 'acc', 'prod']
# Render the variables
actual = tmpl.render(variables)
# Desired result
assert actual == \
"""
user_paul_test
user_rob_test
user_paul_acc
user_rob_acc
user_paul_prod
user_rob_prod
"""
A small investigation..
I found that the loop-variables are properly stored in idtracking -> self.stores.

I assume that there should be a mechanism that transfers these names to the context-object. However, every function that uses self.stores doesn't pass its values to the context.
Originally such data would never go into the context as the context in jinja2 was a data source, not a data store. I have since noticed though that jinja is nowadays storing some of such data in contexts. The issue with making this data available in the context is that it means losing the ability to use the speedups the fast locals provide.
I'm going to investigate this
Hi all. Are there any updates or new workarounds for this issue? (aside from explicitly passing loop vars as params into the function).