Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Improved Jinja's debugging support by introducing "@internalcode" whi…

…ch marks code objects that are skipped on tracebacks. Also template errors are now translated as well to help the pylons debugger.

--HG--
branch : trunk
  • Loading branch information...
commit d416a97260947fcd302f2e14e28cb6160dd412ab 1 parent 1d02108
@mitsuhiko authored
View
2  examples/basic/debugger.py
@@ -4,4 +4,4 @@
env = Environment(loader=FileSystemLoader('templates'))
tmpl = env.get_template('broken.html')
-print tmpl.render(seq=range(10))
+print tmpl.render(seq=[3, 2, 4, 5, 3, 2, 0, 2, 1])
View
3  examples/basic/templates/broken.html
@@ -1,5 +1,6 @@
+{% from 'subbroken.html' import may_break %}
<ul>
{% for item in seq %}
- <li>{{ item / 0 }}</li>
+ <li>{{ may_break(item) }}</li>
{% endfor %}
</ul>
View
3  examples/basic/templates/subbroken.html
@@ -0,0 +1,3 @@
+{% macro may_break(item) -%}
+ [{{ item / 0 }}]
+{%- endmacro %}
View
61 jinja2/debug.py
@@ -11,7 +11,18 @@
:license: BSD.
"""
import sys
-from jinja2.utils import CodeType, missing
+from jinja2.utils import CodeType, missing, internal_code
+
+
+def translate_syntax_error(error, source=None):
+ """Rewrites a syntax error to please traceback systems."""
+ error.source = source
+ error.translated = True
+ exc_info = (type(error), error, None)
+ filename = error.filename
+ if filename is None:
+ filename = '<unknown>'
+ return fake_exc_info(exc_info, filename, error.lineno)
def translate_exception(exc_info):
@@ -22,6 +33,14 @@ def translate_exception(exc_info):
initial_tb = tb = exc_info[2].tb_next
while tb is not None:
+ # skip frames decorated with @internalcode. These are internal
+ # calls we can't avoid and that are useless in template debugging
+ # output.
+ if tb_set_next is not None and tb.tb_frame.f_code in internal_code:
+ tb_set_next(prev_tb, tb.tb_next)
+ tb = tb.tb_next
+ continue
+
template = tb.tb_frame.f_globals.get('__jinja_template__')
if template is not None:
lineno = template.get_corresponding_lineno(tb.tb_lineno)
@@ -40,19 +59,22 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
exc_type, exc_value, tb = exc_info
# figure the real context out
- real_locals = tb.tb_frame.f_locals.copy()
- ctx = real_locals.get('context')
- if ctx:
- locals = ctx.get_all()
+ if tb is not None:
+ real_locals = tb.tb_frame.f_locals.copy()
+ ctx = real_locals.get('context')
+ if ctx:
+ locals = ctx.get_all()
+ else:
+ locals = {}
+ for name, value in real_locals.iteritems():
+ if name.startswith('l_') and value is not missing:
+ locals[name[2:]] = value
+
+ # if there is a local called __jinja_exception__, we get
+ # rid of it to not break the debug functionality.
+ locals.pop('__jinja_exception__', None)
else:
locals = {}
- for name, value in real_locals.iteritems():
- if name.startswith('l_') and value is not missing:
- locals[name[2:]] = value
-
- # if there is a local called __jinja_exception__, we get
- # rid of it to not break the debug functionality.
- locals.pop('__jinja_exception__', None)
# assamble fake globals we need
globals = {
@@ -68,13 +90,16 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
# if it's possible, change the name of the code. This won't work
# on some python environments such as google appengine
try:
- function = tb.tb_frame.f_code.co_name
- if function == 'root':
- location = 'top-level template code'
- elif function.startswith('block_'):
- location = 'block "%s"' % function[6:]
- else:
+ if tb is None:
location = 'template'
+ else:
+ function = tb.tb_frame.f_code.co_name
+ if function == 'root':
+ location = 'top-level template code'
+ elif function.startswith('block_'):
+ location = 'block "%s"' % function[6:]
+ else:
+ location = 'template'
code = CodeType(0, code.co_nlocals, code.co_stacksize,
code.co_flags, code.co_code, code.co_consts,
code.co_names, code.co_varnames, filename,
View
15 jinja2/environment.py
@@ -18,7 +18,7 @@
from jinja2.runtime import Undefined, new_context
from jinja2.exceptions import TemplateSyntaxError
from jinja2.utils import import_string, LRUCache, Markup, missing, \
- concat, consume
+ concat, consume, internalcode
# for direct template usage we have up to ten living environments
@@ -339,6 +339,7 @@ def getattr(self, obj, attribute):
except (TypeError, LookupError, AttributeError):
return self.undefined(obj=obj, name=attribute)
+ @internalcode
def parse(self, source, name=None, filename=None):
"""Parse the sourcecode and return the abstract syntax tree. This
tree of nodes is used by the compiler to convert the template into
@@ -353,8 +354,9 @@ def parse(self, source, name=None, filename=None):
try:
return Parser(self, source, name, filename).parse()
except TemplateSyntaxError, e:
- e.source = source
- raise e
+ from jinja2.debug import translate_syntax_error
+ exc_type, exc_value, tb = translate_syntax_error(e, source)
+ raise exc_type, exc_value, tb
def lex(self, source, name=None, filename=None):
"""Lex the given sourcecode and return a generator that yields
@@ -370,8 +372,9 @@ def lex(self, source, name=None, filename=None):
try:
return self.lexer.tokeniter(source, name, filename)
except TemplateSyntaxError, e:
- e.source = source
- raise e
+ from jinja2.debug import translate_syntax_error
+ exc_type, exc_value, tb = translate_syntax_error(e, source)
+ raise exc_type, exc_value, tb
def preprocess(self, source, name=None, filename=None):
"""Preprocesses the source with all extensions. This is automatically
@@ -393,6 +396,7 @@ def _tokenize(self, source, name, filename=None, state=None):
stream = TokenStream(stream, name, filename)
return stream
+ @internalcode
def compile(self, source, name=None, filename=None, raw=False):
"""Compile a node or template source code. The `name` parameter is
the load name of the template after it was joined using
@@ -473,6 +477,7 @@ def join_path(self, template, parent):
"""
return template
+ @internalcode
def get_template(self, name, parent=None, globals=None):
"""Load a template from the loader. If a loader is configured this
method ask the loader for the template and returns a :class:`Template`.
View
9 jinja2/exceptions.py
@@ -44,7 +44,16 @@ def __init__(self, message, lineno, name=None, filename=None):
self.filename = filename
self.source = None
+ # this is set to True if the debug.translate_syntax_error
+ # function translated the syntax error into a new traceback
+ self.translated = False
+
def __unicode__(self):
+ # for translated errors we only return the message
+ if self.translated:
+ return self.message.encode('utf-8')
+
+ # otherwise attach some stuff
location = 'line %d' % self.lineno
name = self.filename or self.name
if name:
View
3  jinja2/loaders.py
@@ -14,7 +14,7 @@
except ImportError:
from sha import new as sha1
from jinja2.exceptions import TemplateNotFound
-from jinja2.utils import LRUCache, open_if_exists
+from jinja2.utils import LRUCache, open_if_exists, internalcode
def split_template_path(template):
@@ -79,6 +79,7 @@ def get_source(self, environment, template):
"""
raise TemplateNotFound(template)
+ @internalcode
def load(self, environment, name, globals=None):
"""Loads a template. This method looks up the template in the cache
or loads one by calling :meth:`get_source`. Subclasses should not
View
7 jinja2/runtime.py
@@ -11,7 +11,7 @@
import sys
from itertools import chain, imap
from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
- concat, MethodType, FunctionType
+ concat, MethodType, FunctionType, internalcode
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
TemplateNotFound
@@ -156,6 +156,7 @@ def get_all(self):
"""
return dict(self.parent, **self.vars)
+ @internalcode
def call(__self, __obj, *args, **kwargs):
"""Call the callable with the arguments and keyword arguments
provided but inject the active context or environment as first
@@ -239,6 +240,7 @@ def super(self):
return BlockReference(self.name, self._context, self._stack,
self._depth + 1)
+ @internalcode
def __call__(self):
rv = concat(self._stack[self._depth](self._context))
if self._context.environment.autoescape:
@@ -281,6 +283,7 @@ def __len__(self):
def __iter__(self):
return LoopContextIterator(self)
+ @internalcode
def loop(self, iterable):
if self._recurse is None:
raise TypeError('Tried to call non recursive loop. Maybe you '
@@ -342,6 +345,7 @@ def __init__(self, environment, func, name, arguments, defaults,
self.catch_varargs = catch_varargs
self.caller = caller
+ @internalcode
def __call__(self, *args, **kwargs):
arguments = []
for idx, name in enumerate(self.arguments):
@@ -409,6 +413,7 @@ def __init__(self, hint=None, obj=None, name=None, exc=UndefinedError):
self._undefined_name = name
self._undefined_exception = exc
+ @internalcode
def _fail_with_undefined_error(self, *args, **kwargs):
"""Regular callback function for undefined objects that raises an
`UndefinedError` on call.
View
9 jinja2/utils.py
@@ -35,6 +35,9 @@
# special singleton representing missing values for the runtime
missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
+# internal code
+internal_code = set()
+
# concatenate a list of strings and convert them to unicode.
# unfortunately there is a bug in python 2.4 and lower that causes
@@ -120,6 +123,12 @@ def environmentfunction(f):
return f
+def internalcode(f):
+ """Marks the function as internally used"""
+ internal_code.add(f.func_code)
+ return f
+
+
def is_undefined(obj):
"""Check if the object passed is undefined. This does nothing more than
performing an instance check against :class:`Undefined` but looks nicer.
View
14 tests/test_debug.py
@@ -31,7 +31,19 @@
>>> tmpl = MODULE.env.get_template('syntaxerror.html')
Traceback (most recent call last):
...
-TemplateSyntaxError: unknown tag 'endif'
File "loaderres/templates/syntaxerror.html", line 4
{% endif %}
+TemplateSyntaxError: unknown tag 'endif'
+'''
+
+
+test_regular_syntax_error = '''
+>>> from jinja2.exceptions import TemplateSyntaxError
+>>> raise TemplateSyntaxError('wtf', 42)
+Traceback (most recent call last):
+ ...
+ File "<doctest test_regular_syntax_error[1]>", line 1, in <module>
+ raise TemplateSyntaxError('wtf', 42)
+TemplateSyntaxError: wtf
+ line 42
'''
Please sign in to comment.
Something went wrong with that request. Please try again.