Skip to content

Commit

Permalink
Added tests for logging undefined and added it to the docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jun 6, 2014
1 parent d2641be commit 6e9dfbf
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -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
-------------
Expand Down
5 changes: 5 additions & 0 deletions docs/api.rst
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions jinja2/__init__.py
Expand Up @@ -42,7 +42,8 @@
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, \
Expand All @@ -65,5 +66,5 @@
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
'evalcontextfilter', 'evalcontextfunction'
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
]
79 changes: 61 additions & 18 deletions jinja2/runtime.py
Expand Up @@ -527,9 +527,24 @@ def __repr__(self):


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
Expand All @@ -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

Expand Down
27 changes: 26 additions & 1 deletion jinja2/testsuite/api.py
Expand Up @@ -17,7 +17,7 @@

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()
Expand Down Expand Up @@ -199,6 +199,31 @@ def test_undefined_and_special_attributes(self):
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'')
Expand Down

0 comments on commit 6e9dfbf

Please sign in to comment.