From: Armin Ronacher Date: Fri, 6 Jun 2014 16:14:45 +0000 (+0600) Subject: Added tests for logging undefined and added it to the docs. X-Git-Tag: 2.8~76 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6e9dfbf2de06050b941425eef38bbf90362346be;p=thirdparty%2Fjinja.git Added tests for logging undefined and added it to the docs. --- diff --git a/CHANGES b/CHANGES index 907e1c06..b9606f6e 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,8 @@ Version 2.8 - 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 ------------- diff --git a/docs/api.rst b/docs/api.rst index ae2295fd..1fd02c65 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -316,6 +316,11 @@ disallows all operations beside testing if it's an undefined object. .. 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 diff --git a/jinja2/__init__.py b/jinja2/__init__.py index 8434dea1..f67fa91e 100644 --- a/jinja2/__init__.py +++ b/jinja2/__init__.py @@ -42,7 +42,8 @@ from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \ 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, \ @@ -65,5 +66,5 @@ __all__ = [ 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError', 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape', 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined', - 'evalcontextfilter', 'evalcontextfunction' + 'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined', ] diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 65476c43..937e8674 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -527,9 +527,24 @@ class Undefined(object): 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 @@ -538,23 +553,51 @@ def make_logging_undefined(logger=None, base=None): 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 diff --git a/jinja2/testsuite/api.py b/jinja2/testsuite/api.py index 4a0ec5a9..ea3739bf 100644 --- a/jinja2/testsuite/api.py +++ b/jinja2/testsuite/api.py @@ -17,7 +17,7 @@ from jinja2.testsuite import JinjaTestCase 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() @@ -199,6 +199,31 @@ class UndefinedTestCase(JinjaTestCase): 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'')