- Fixed loop length calculation for some iterators.
- Changed how Jinja2 enforces strings to be native strings in
Python 2 to work when people break their default encoding.
+- Added :func:`make_logging_undefined` which returns an undefined
+ object that logs failures into a logger.
Version 2.7.2
-------------
.. autoclass:: jinja2.StrictUndefined()
+There is also a factory function that can decorate undefined objects to
+implement logging on failures:
+
+.. autofunction:: jinja2.make_logging_undefined
+
Undefined objects are created by calling :attr:`undefined`.
.. admonition:: Implementation
MemcachedBytecodeCache
# undefined types
-from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
+ make_logging_undefined
# exceptions
from jinja2.exceptions import TemplateError, UndefinedError, \
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
- 'evalcontextfilter', 'evalcontextfunction'
+ 'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
]
def make_logging_undefined(logger=None, base=None):
- """Given a logger object this returns a new undefined class that
- will log all failures into it. If no logger is given a default
- logger is created.
+ """Given a logger object this returns a new undefined class that will
+ log certain failures. It will log iterations and printing. If no
+ logger is given a default logger is created.
+
+ Example::
+
+ logger = logging.getLogger(__name__)
+ LoggingUndefined = make_logging_undefined(
+ logger=logger,
+ base=Undefined
+ )
+
+ .. versionadded:: 2.8
+
+ :param logger: the logger to use. If not provided, a default logger
+ is created.
+ :param base: the base class to add logging functionality to. This
+ defaults to :class:`Undefined`.
"""
if logger is None:
import logging
if base is None:
base = Undefined
+ def _log_message(undef):
+ if undef._undefined_hint is None:
+ if undef._undefined_obj is missing:
+ hint = '%s is undefined' % undef._undefined_name
+ elif not isinstance(undef._undefined_name, string_types):
+ hint = '%s has no element %s' % (
+ object_type_repr(undef._undefined_obj),
+ undef._undefined_name)
+ else:
+ hint = '%s has no attribute %s' % (
+ object_type_repr(undef._undefined_obj),
+ undef._undefined_name)
+ else:
+ hint = undef._undefined_hint
+ logger.warning('Template variable warning: %s', hint)
+
class LoggingUndefined(base):
+
+ def _fail_with_undefined_error(self, *args, **kwargs):
+ try:
+ return base._fail_with_undefined_error(self, *args, **kwargs)
+ except self._undefined_exception as e:
+ logger.error('Template variable error: %s', str(e))
+ raise e
+
def __str__(self):
- if self._undefined_hint is None:
- if self._undefined_obj is missing:
- hint = '%s is undefined' % self._undefined_name
- elif not isinstance(self._undefined_name, string_types):
- hint = '%s has no element %s' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name)
- else:
- hint = '%s has no attribute %s' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name)
- else:
- hint = self._undefined_hint
- logger.error('Template error: %s', hint)
- return base.__str__(self)
+ rv = base.__str__(self)
+ _log_message(self)
+ return rv
+
+ if PY2:
+ def __unicode__(self):
+ rv = base.__unicode__(self)
+ _log_message(self)
+ return rv
+
+ def __iter__(self):
+ rv = base.__iter__(self)
+ _log_message(self)
+ return rv
+
+ def __nonzero__(self):
+ rv = base.__nonzero__(self)
+ _log_message(self)
+ return rv
return LoggingUndefined
from jinja2 import Environment, Undefined, DebugUndefined, \
StrictUndefined, UndefinedError, meta, \
- is_undefined, Template, DictLoader
+ is_undefined, Template, DictLoader, make_logging_undefined
from jinja2.utils import Cycler
env = Environment()
else:
assert False, "Expected actual attribute error"
+ def test_logging_undefined(self):
+ _messages = []
+ class DebugLogger(object):
+ def warning(self, msg, *args):
+ _messages.append('W:' + msg % args)
+ def error(self, msg, *args):
+ _messages.append('E:' + msg % args)
+
+ logging_undefined = make_logging_undefined(DebugLogger())
+ env = Environment(undefined=logging_undefined)
+ self.assert_equal(env.from_string('{{ missing }}').render(), u'')
+ self.assert_raises(UndefinedError,
+ env.from_string('{{ missing.attribute }}').render)
+ self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
+ self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
+ self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42), '')
+ self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
+ self.assert_equal(_messages, [
+ 'W:Template variable warning: missing is undefined',
+ "E:Template variable error: 'missing' is undefined",
+ 'W:Template variable warning: missing is undefined',
+ 'W:Template variable warning: int object has no attribute missing',
+ 'W:Template variable warning: missing is undefined',
+ ])
+
def test_default_undefined(self):
env = Environment(undefined=Undefined)
self.assert_equal(env.from_string('{{ missing }}').render(), u'')