jinja icon indicating copy to clipboard operation
jinja copied to clipboard

Loop-variables are not accessable in @pass_context / @contextfilter

Open pstapers-acheron opened this issue 4 years ago • 7 comments

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

pstapers-acheron avatar Jul 24 '21 14:07 pstapers-acheron

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)

pstapers-acheron avatar Jul 24 '21 14:07 pstapers-acheron

Use context.resolve to lookup context variables.

davidism avatar Jul 24 '21 20:07 davidism

Hi @davidism , thanks for your quick reply

Unfortunately, context.resolve is not able to retrieve the value of "env". Instead, it returns an "Undefined":

image

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
"""

pstapers-acheron avatar Jul 25 '21 10:07 pstapers-acheron

A small investigation..

I found that the loop-variables are properly stored in idtracking -> self.stores.

image

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.

pstapers-acheron avatar Jul 25 '21 11:07 pstapers-acheron

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.

mitsuhiko avatar Sep 18 '21 21:09 mitsuhiko

I'm going to investigate this

marian-vignau avatar May 02 '22 19:05 marian-vignau

Hi all. Are there any updates or new workarounds for this issue? (aside from explicitly passing loop vars as params into the function).

antonpp avatar Aug 18 '23 13:08 antonpp