]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
handle Undefined in get/select_template 1114/head
authorDavid Lord <davidism@gmail.com>
Thu, 5 Dec 2019 21:46:36 +0000 (13:46 -0800)
committerDavid Lord <davidism@gmail.com>
Thu, 5 Dec 2019 21:46:36 +0000 (13:46 -0800)
CHANGES.rst
jinja2/environment.py
jinja2/exceptions.py
jinja2/runtime.py
tests/test_api.py

index 945e57fc2b0639060fc9f54496b51ff3ce3956d1..d0f5d6dc66add0a90f86463bb75c12aaeedd05f1 100644 (file)
@@ -94,7 +94,11 @@ Unreleased
     that were previously overlooked. :issue:`733`
 -   ``TemplateSyntaxError.source`` is not empty when raised from an
     included template. :issue:`457`
-
+-   Passing an ``Undefined`` value to ``get_template`` (such as through
+    ``extends``, ``import``, or ``include``), raises an
+    ``UndefinedError`` consistently. ``select_template`` will show the
+    undefined message in the list of attempts rather than the empty
+    string. :issue:`1037`
 
 Version 2.10.3
 --------------
index 2bfc0181754855959803480e6f47c5a8e31aaa7b..8a9548aec50172605521466e3e729738c9bfcdac 100644 (file)
@@ -25,7 +25,7 @@ from jinja2.nodes import EvalContext
 from jinja2.compiler import generate, CodeGenerator
 from jinja2.runtime import Undefined, new_context, Context
 from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
-     TemplatesNotFound, TemplateRuntimeError
+     TemplatesNotFound, TemplateRuntimeError, UndefinedError
 from jinja2.utils import import_string, LRUCache, Markup, missing, \
      concat, consume, internalcode, have_async_gen
 from jinja2._compat import imap, ifilter, string_types, iteritems, \
@@ -812,12 +812,20 @@ class Environment(object):
         before it fails.  If it cannot find any of the templates, it will
         raise a :exc:`TemplatesNotFound` exception.
 
-        .. versionadded:: 2.3
+        .. versionchanged:: 2.11
+            If names is :class:`Undefined`, an :exc:`UndefinedError` is
+            raised instead. If no templates were found and names
+            contains :class:`Undefined`, the message is more helpful.
 
         .. versionchanged:: 2.4
            If `names` contains a :class:`Template` object it is returned
            from the function unchanged.
+
+        .. versionadded:: 2.3
         """
+        if isinstance(names, Undefined):
+            names._fail_with_undefined_error()
+
         if not names:
             raise TemplatesNotFound(message=u'Tried to select from an empty list '
                                             u'of templates.')
@@ -829,7 +837,7 @@ class Environment(object):
                 name = self.join_path(name, parent)
             try:
                 return self._load_template(name, globals)
-            except TemplateNotFound:
+            except (TemplateNotFound, UndefinedError):
                 pass
         raise TemplatesNotFound(names)
 
@@ -842,7 +850,7 @@ class Environment(object):
 
         .. versionadded:: 2.3
         """
-        if isinstance(template_name_or_list, string_types):
+        if isinstance(template_name_or_list, (string_types, Undefined)):
             return self.get_template(template_name_or_list, parent, globals)
         elif isinstance(template_name_or_list, Template):
             return template_name_or_list
index c9f08ac4e14b75c7b64c2e1d356cb4f08627e4e6..36e4716a31948c74c33094f96e40065f9305d261 100644 (file)
@@ -43,7 +43,12 @@ class TemplateError(Exception):
 
 @implements_to_string
 class TemplateNotFound(IOError, LookupError, TemplateError):
-    """Raised if a template does not exist."""
+    """Raised if a template does not exist.
+
+    .. versionchanged:: 2.11
+        If the given name is :class:`Undefined` and no message was
+        provided, an :exc:`UndefinedError` is raised.
+    """
 
     # looks weird, but removes the warning descriptor that just
     # bogusly warns us about message being deprecated
@@ -51,8 +56,15 @@ class TemplateNotFound(IOError, LookupError, TemplateError):
 
     def __init__(self, name, message=None):
         IOError.__init__(self, name)
+
         if message is None:
+            from jinja2.runtime import Undefined
+
+            if isinstance(name, Undefined):
+                name._fail_with_undefined_error()
+
             message = name
+
         self.message = message
         self.name = name
         self.templates = [name]
@@ -66,13 +78,27 @@ class TemplatesNotFound(TemplateNotFound):
     are selected.  This is a subclass of :class:`TemplateNotFound`
     exception, so just catching the base exception will catch both.
 
+    .. versionchanged:: 2.11
+        If a name in the list of names is :class:`Undefined`, a message
+        about it being undefined is shown rather than the empty string.
+
     .. versionadded:: 2.2
     """
 
     def __init__(self, names=(), message=None):
         if message is None:
+            from jinja2.runtime import Undefined
+
+            parts = []
+
+            for name in names:
+                if isinstance(name, Undefined):
+                    parts.append(name._undefined_message)
+                else:
+                    parts.append(name)
+
             message = u'none of the templates given were found: ' + \
-                      u', '.join(imap(text_type, names))
+                      u', '.join(imap(text_type, parts))
         TemplateNotFound.__init__(self, names and names[-1] or None, message)
         self.templates = list(names)
 
index cb0bcf5f3e74545bf853f5063ba3e9f88a150eaa..19ff8383d61f68a764c5a2fba1d79b24298f4ea6 100644 (file)
@@ -668,27 +668,34 @@ class Undefined(object):
         self._undefined_name = name
         self._undefined_exception = exc
 
+    @property
+    def _undefined_message(self):
+        """Build a message about the undefined value based on how it was
+        accessed.
+        """
+        if self._undefined_hint:
+            return self._undefined_hint
+
+        if self._undefined_obj is missing:
+            return '%r is undefined' % self._undefined_name
+
+        if not isinstance(self._undefined_name, string_types):
+            return '%s has no element %r' % (
+                object_type_repr(self._undefined_obj),
+                self._undefined_name
+            )
+
+        return '%r has no attribute %r' % (
+            object_type_repr(self._undefined_obj),
+            self._undefined_name
+        )
+
     @internalcode
     def _fail_with_undefined_error(self, *args, **kwargs):
-        """Regular callback function for undefined objects that raises an
-        `UndefinedError` on call.
+        """Raise an :exc:`UndefinedError` when operations are performed
+        on the undefined value.
         """
-        if self._undefined_hint is None:
-            if self._undefined_obj is missing:
-                hint = '%r is undefined' % self._undefined_name
-            elif not isinstance(self._undefined_name, string_types):
-                hint = '%s has no element %r' % (
-                    object_type_repr(self._undefined_obj),
-                    self._undefined_name
-                )
-            else:
-                hint = '%r has no attribute %r' % (
-                    object_type_repr(self._undefined_obj),
-                    self._undefined_name
-                )
-        else:
-            hint = self._undefined_hint
-        raise self._undefined_exception(hint)
+        raise self._undefined_exception(self._undefined_message)
 
     @internalcode
     def __getattr__(self, name):
index 2eb9ce7c9c8d3debad586be61db89f3028b98f8b..6d31885024df0cb9984c59f3113b40dd2fd61a81 100644 (file)
@@ -17,6 +17,7 @@ import pytest
 from jinja2 import Environment, Undefined, ChainableUndefined, \
      DebugUndefined, StrictUndefined, UndefinedError, meta, \
      is_undefined, Template, DictLoader, make_logging_undefined
+from jinja2 import TemplatesNotFound
 from jinja2.compiler import CodeGenerator
 from jinja2.runtime import Context
 from jinja2.utils import Cycler
@@ -125,6 +126,30 @@ class TestExtendedAPI(object):
         assert env.get_or_select_template([t]) is t
         assert env.get_or_select_template(t) is t
 
+    def test_get_template_undefined(self, env):
+        """Passing Undefined to get/select_template raises an
+        UndefinedError or shows the undefined message in the list.
+        """
+        env.loader=DictLoader({})
+        t = Undefined(name="no_name_1")
+
+        with pytest.raises(UndefinedError):
+            env.get_template(t)
+
+        with pytest.raises(UndefinedError):
+            env.get_or_select_template(t)
+
+        with pytest.raises(UndefinedError):
+            env.select_template(t)
+
+        with pytest.raises(TemplatesNotFound) as exc_info:
+            env.select_template([t, "no_name_2"])
+
+        exc_message = str(exc_info.value)
+        assert "'no_name_1' is undefined" in exc_message
+        assert "no_name_2" in exc_message
+
+
     def test_autoescape_autoselect(self, env):
         def select_autoescape(name):
             if name is None or '.' not in name: