summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2019-12-05 13:50:42 -0800
committerGitHub <noreply@github.com>2019-12-05 13:50:42 -0800
commitf81c74437e555d43529fd7746307d0dfd577f6c5 (patch)
treed1fc038749766a946ff47d76d8aed7a5a11a888c
parent9176fbd8183ac491bfa9a69eb6d42b485c2749c6 (diff)
parent2ec62dc4141a9a1f866dc0e348a5fe4f24d1778f (diff)
downloadjinja2-f81c74437e555d43529fd7746307d0dfd577f6c5.tar.gz
Merge pull request #1114 from pallets/import-undefined
handle Undefined in get/select_template
-rw-r--r--CHANGES.rst6
-rw-r--r--jinja2/environment.py16
-rw-r--r--jinja2/exceptions.py30
-rw-r--r--jinja2/runtime.py43
-rw-r--r--tests/test_api.py25
5 files changed, 95 insertions, 25 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 945e57f..d0f5d6d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -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
--------------
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 2bfc018..8a9548a 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -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
diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py
index c9f08ac..36e4716 100644
--- a/jinja2/exceptions.py
+++ b/jinja2/exceptions.py
@@ -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)
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index cb0bcf5..19ff838 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -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):
diff --git a/tests/test_api.py b/tests/test_api.py
index 2eb9ce7..6d31885 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -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: