pallets / jinja

A very fast and expressive template engine.
https://jinja.palletsprojects.com
BSD 3-Clause "New" or "Revised" License
10.23k stars 1.6k forks source link

Introducing `NeverUndefined` #1923

Closed James4Ever0 closed 8 months ago

James4Ever0 commented 8 months ago

This feature is done by introducing a new type of undefined class called NeverUndefined.

The problem is that when using the previously most restricted undefined policy StrictUndefined is not enough.

Take example on the following template:

{% macro test(a, b, c) %}
a macro with three parameters but not used
{% endmacro %}
{{ test() }}

NeverUndefined will capture this error because in the normal case one shall pass three parameters to this macro in order to call it. StrictUndefined will not.

And this problem would be harder to debug, if one pass these undefined variables to another macro or function calls.

Take another example:

{% macro test(a, b, c) %}
append undefined parameters to list
{% set s = [] %}
{% do s.append(a) %}
{{ s }}
{% endmacro %}
{{ test() }}

StrictUndefined will not throw error. It will produce:

append undefined parameters to list
[Undefined]

However, NeverUndefined will:

  File "template.j2", line 9, in top-level template code
    {{mtest()}}
      └ <Macro 'test'>
  File "/usr/local/lib/python3.9/dist-packages/jinja2/runtime.py", line 777, in _invoke
    rv = self._func(*arguments)
         │           └ [missing, missing, missing]
         └ <Macro 'test'>
  File "template.j2", line 2, in template
    {% macro test(a, b, c) %}
  File "./test_neverundefined/../jinja_utils.py", line 99, in __init__
    raise Exception(info)
                    └ "parameter 'a' was not provided"
Exception: parameter 'a' was not provided

The implementation:

class NeverUndefined(jinja2.StrictUndefined):
    def __init__(self, *args, **kwargs):
        # ARGS: ("parameter 'myvar2' was not provided",)
        # KWARGS: {'name': 'myvar2'}
        if len(args) == 1:
            info = args[0]
        elif "name" in kwargs.keys():
            info = f"Undefined variable '{kwargs['name']}"
        else:
            infoList = ["Not allowing any undefined variable."]
            infoList.append(f"ARGS: {args}")
            infoList.append(f"KWARGS: {kwargs}")
            info = "\n".join(infoList)

        raise Exception(info)
davidism commented 8 months ago

I don't plan on adding this to Jinja core. This doesn't match the general behavior of other Undefined types. If you think it would be generally useful to others, you can release it as an extension on PyPI, there's nothing required from Jinja itself for this.

James4Ever0 commented 8 months ago

I will release some extension for this.

The reason I do this is because in Python we do not have such concept of undefined and it should be turned off from the very beginning, as default. This concept is most likely in Javascript, where lawlessness is up to a whole new level.

James4Ever0 commented 8 months ago

I don't plan on adding this to Jinja core. This doesn't match the general behavior of other Undefined types. If you think it would be generally useful to others, you can release it as an extension on PyPI, there's nothing required from Jinja itself for this.

Consider reopen this. I can tell you StrictUndefined is a mistake. I have updated examples above.