From: Adrian Moennich Date: Sat, 12 Oct 2019 14:18:34 +0000 (+0200) Subject: Use separate undefined object for CondExpr X-Git-Tag: 2.11.0~42^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c4d39969e2c230b3d503833bb59f6d0b572216d7;p=thirdparty%2Fjinja.git Use separate undefined object for CondExpr --- diff --git a/CHANGES.rst b/CHANGES.rst index 305eb963..1ddb8cab 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -39,6 +39,10 @@ Unreleased :issue:`63` - When providing multiple paths to ``FileSystemLoader``, a template can have the same name as a directory. :issue:`821` +- Make the undefined object returned by ``{{ 'foo' if bar }}`` configurable, + and default to standard ``Undefined`` regardless of what is specified as + the environment's ``undefined`` object as doing so is not compatible with + ``StrictUndefined``. :issue:`710`, :pr:`1079` Version 2.10.3 diff --git a/docs/templates.rst b/docs/templates.rst index f6fb609b..13b693b7 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -1355,7 +1355,8 @@ The general syntax is `` if else ``. The `else` part is optional. If not provided, the else block implicitly -evaluates into an undefined object: +evaluates into an undefined object (using the environment's undefined object +specified in ``cond_expr_undefined``): .. sourcecode:: jinja diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 708186b1..850b26c0 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -609,6 +609,7 @@ class CodeGenerator(NodeVisitor): """ self.writeline('resolve = context.resolve_or_missing') self.writeline('undefined = environment.undefined') + self.writeline('cond_expr_undefined = environment.cond_expr_undefined') self.writeline('if 0: yield None') def push_parameter_definitions(self, frame): @@ -1623,7 +1624,7 @@ class CodeGenerator(NodeVisitor): def write_expr2(): if node.expr2 is not None: return self.visit(node.expr2, frame) - self.write('undefined(%r)' % ('the inline if-' + self.write('cond_expr_undefined(%r)' % ('the inline if-' 'expression on %s evaluated to false and ' 'no else section was defined.' % self.position(node))) diff --git a/jinja2/environment.py b/jinja2/environment.py index 2687889a..477385c9 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -101,6 +101,9 @@ def _environment_sanity_check(environment): """Perform a sanity check on the environment.""" assert issubclass(environment.undefined, Undefined), 'undefined must ' \ 'be a subclass of undefined because filters depend on it.' + assert issubclass(environment.cond_expr_undefined, Undefined), \ + 'cond_expr_undefined must be a subclass of undefined because filters ' \ + 'depend on it.' assert environment.block_start_string != \ environment.variable_start_string != \ environment.comment_start_string, 'block, variable and comment ' \ @@ -183,6 +186,13 @@ class Environment(object): :class:`Undefined` or a subclass of it that is used to represent undefined values in the template. + `cond_expr_undefined` + :class:`Undefined` or a subclass of it that is used to represent + undefined values in the template for conditional expressions that + have no ``else`` part. This is usually useful when using a more + strict undefined class without wanting to break the short version + of conditional expressions. + `finalize` A callable that can be used to process the result of a variable expression before it is output. For example one can convert @@ -283,7 +293,8 @@ class Environment(object): cache_size=400, auto_reload=True, bytecode_cache=None, - enable_async=False): + enable_async=False, + cond_expr_undefined=Undefined): # !!Important notice!! # The constructor accepts quite a few arguments that should be # passed by keyword rather than position. However it's important to @@ -311,6 +322,7 @@ class Environment(object): # runtime information self.undefined = undefined + self.cond_expr_undefined = cond_expr_undefined self.optimized = optimized self.finalize = finalize self.autoescape = autoescape @@ -363,7 +375,7 @@ class Environment(object): extensions=missing, optimized=missing, undefined=missing, finalize=missing, autoescape=missing, loader=missing, cache_size=missing, auto_reload=missing, - bytecode_cache=missing): + bytecode_cache=missing, cond_expr_undefined=missing): """Create a new overlay environment that shares all the data with the current environment except for cache and the overridden attributes. Extensions cannot be removed for an overlayed environment. An overlayed @@ -936,14 +948,15 @@ class Template(object): undefined=Undefined, finalize=None, autoescape=False, - enable_async=False): + enable_async=False, + cond_expr_undefined=Undefined): env = get_spontaneous_environment( block_start_string, block_end_string, variable_start_string, variable_end_string, comment_start_string, comment_end_string, line_statement_prefix, line_comment_prefix, trim_blocks, lstrip_blocks, newline_sequence, keep_trailing_newline, frozenset(extensions), optimized, undefined, finalize, autoescape, - None, 0, False, None, enable_async) + None, 0, False, None, enable_async, cond_expr_undefined) return env.from_string(source, template_class=cls) @classmethod diff --git a/tests/test_api.py b/tests/test_api.py index a4576029..b29594b3 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -368,6 +368,15 @@ class TestUndefined(object): with pytest.raises(AttributeError): getattr(StrictUndefined, '__slots__') + def test_cond_expr_undefined(self): + env = Environment(cond_expr_undefined=StrictUndefined) + env2 = Environment(undefined=StrictUndefined) + pytest.raises(UndefinedError, + env.from_string('{{ "foo" if false }}').render) + assert env.from_string('{{ ("foo" if false) is defined }}').render() == 'False' + assert env2.from_string('{{ "foo" if false }}').render() == '' + assert env2.from_string('{{ ("foo" if false) is defined }}').render() == 'False' + def test_indexing_gives_undefined(self): t = Template("{{ var[42].foo }}") pytest.raises(UndefinedError, t.render, var=0)