pallets / jinja

A very fast and expressive template engine.

Home Page:https://jinja.palletsprojects.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot preserve types in templating context

ddluke opened this issue · comments

Note that this is my first issue in this project, I hope the classification of a bug is correct, but please correct me if I'm mistaken here.

Description

There appears to be no way to prevent jinja from mutating/corrupting types of the templating context when rendering.

This issue causes nightmares in the situation where jinja is not used to render some sort of html template or the likes.
A very popular and broadly applied usecase would be apache-airflow, where jinja templating is a builtin feature of
operators.

In this case, the operator can mark individual fields of the class constructor as template-able, and dynamic jinja
blocks inside arbitrary operator instance fields can be replaced with dynamic jinja references at execution time.

The issue is: There appears to be no way to tell jinja to preserve the types of the templating context when
resolving jinja refs.

At least the following two approaches do not work (are there any other approaches I'm not aware of?):

  • The default jinja environment apparently dumps all resolved jinja references to string, which causes data corruption if
    the jinja template ref points to anything other than a string (some integer, a list, a dict, a datetime object, etc.).

  • However, the jinja native environment is also no help in this scenario, because it also causes data corruption (the
    ast.literal_eval approach is not safe to use as seen in below example)

Code Replication

The following code snipped and the two test functions demonstrate the issue that results from this bug (if it is one)

from typing import Any

import jinja2
import jinja2.nativetypes

# this is the object we want to render (note that we do not want to render to a string here!)
training_config = {
    "vpc_subnet_ids": "{{ subnet_ids }}",
    "python_version": "{{ python_version }}"
}

# this is the templating context
templating_context = {
    "subnet_ids": ["subnet-1", "subnet-2"],
    "python_version": "3.10"
}

# this is what we want to get
desired_object = {
    "vpc_subnet_ids": ["subnet-1", "subnet-2"],
    "python_version": "3.10"
}


def render(data: dict[str, str], context: dict[str, Any], env: jinja2.Environment) -> dict[str, Any]:
    """a simplified rendering function """
    for k, v in data.items():
        template = env.from_string(v)
        data[k] = template.render(context)
    return data


def test_with_standard_env():
    result = render(training_config, templating_context, jinja2.Environment())
    assert result == desired_object, result
    # assertion error, because vpc_subnet_ids is now a string ...
    # {'vpc_subnet_ids': "['subnet-1', 'subnet-2']", 'python_version': '3.10'}


def test_with_native_env():
    result = render(training_config, templating_context, jinja2.nativetypes.NativeEnvironment())
    assert result == desired_object, result
    # assertion error, because python version is now 3.1 ...
    # {'vpc_subnet_ids': ['subnet-1', 'subnet-2'], 'python_version': 3.1}

Environment

  • Python version: 3.10.10
  • Jinja version: 3.1.2

This sounds like it's up to Airflow to address with how they use Jinja. Jinja is a string templating language, so the fact that rendering produces strings is not a bug. You may also be interested in Jinja's NativeEnvironment instead of the default one.

Thanks for your reply, I understand that jinja is a string templating language. Alas, I cannot easily replace the templating engine used by apache-airflow. And Jinja's NativeEnvironment alas doesn't help either (see how python version above becomes 3.1, where it should really be 3.10).