Skip to content

Commit

Permalink
Started refactoring of debugging system for better AppEngine/Pylons s…
Browse files Browse the repository at this point in the history
…upport.

--HG--
branch : trunk
extra : rebase_source : 30b87a402e0847f95eaf277d0fc50e1a63177d5b
  • Loading branch information
mitsuhiko committed Mar 5, 2009
1 parent 4435b43 commit a18872d
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 30 deletions.
116 changes: 98 additions & 18 deletions jinja2/debug.py
Expand Up @@ -8,10 +8,85 @@
with correct line numbers, locals and contents.
:copyright: (c) 2009 by the Jinja Team.
:license: BSD.
:license: BSD, see LICENSE for more details.
"""
import sys
import traceback
from jinja2.utils import CodeType, missing, internal_code
from jinja2.exceptions import TemplateSyntaxError


class TracebackFrameProxy(object):
"""Proxies a traceback frame."""

def __init__(self, tb):
self.tb = tb

def _set_tb_next(self, next):
if tb_set_next is not None:
tb_set_next(self.tb, next and next.tb or None)
self._tb_next = next

def _get_tb_next(self):
return self._tb_next

tb_next = property(_get_tb_next, _set_tb_next)
del _get_tb_next, _set_tb_next

@property
def is_jinja_frame(self):
return '__jinja_template__' in self.tb.tb_frame.f_globals

def __getattr__(self, name):
return getattr(self.tb, name)


class ProcessedTraceback(object):
"""Holds a Jinja preprocessed traceback for priting or reraising."""

This comment has been minimized.

Copy link
@jab

jab Sep 26, 2011

Member

s/priting/printing


def __init__(self, exc_type, exc_value, frames):
assert frames, 'no frames for this traceback?'
self.exc_type = exc_type
self.exc_value = exc_value
self.frames = frames

def chain_frames(self):
"""Chains the frames. Requires ctypes or the speedups extension."""
prev_tb = None
for tb in self.frames:
if prev_tb is not None:
prev_tb.tb_next = tb
prev_tb = tb
prev_tb.tb_next = None

def render_as_text(self, limit=None):
"""Return a string with the traceback."""
lines = traceback.format_exception(self.exc_type, self.exc_value,
self.frames[0], limit=limit)
return ''.join(lines).rstrip()

@property
def is_template_syntax_error(self):
"""`True` if this is a template syntax error."""
return isinstance(self.exc_value, TemplateSyntaxError)

@property
def exc_info(self):
"""Exception info tuple with a proxy around the frame objects."""
return self.exc_type, self.exc_value, self.frames[0]

@property
def standard_exc_info(self):
"""Standard python exc_info for re-raising"""
return self.exc_type, self.exc_value, self.frames[0].tb


def make_traceback(exc_info, source_hint=None):
"""Creates a processed traceback object from the exc_info."""
exc_type, exc_value, tb = exc_info
if isinstance(exc_value, TemplateSyntaxError):
exc_info = translate_syntax_error(exc_value, source_hint)
return translate_exception(exc_info)


def translate_syntax_error(error, source=None):
Expand All @@ -29,32 +104,44 @@ def translate_exception(exc_info):
"""If passed an exc_info it will automatically rewrite the exceptions
all the way down to the correct line numbers and frames.
"""
result_tb = prev_tb = None
initial_tb = tb = exc_info[2].tb_next
frames = []

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)
if tb.tb_frame.f_code in internal_code:
tb = tb.tb_next
continue

# save a reference to the next frame if we override the current
# one with a faked one.
next = tb.tb_next

# fake template exceptions
template = tb.tb_frame.f_globals.get('__jinja_template__')
if template is not None:
lineno = template.get_corresponding_lineno(tb.tb_lineno)
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
lineno, prev_tb)[2]
if result_tb is None:
result_tb = tb
prev_tb = tb
tb = tb.tb_next
lineno)[2]

frames.append(TracebackFrameProxy(tb))
tb = next

# if we don't have any exceptions in the frames left, we have to
# reraise it unchanged.
# XXX: can we backup here? when could this happen?
if not frames:
raise exc_info[0], exc_info[1], exc_info[2]

return exc_info[:2] + (result_tb or initial_tb,)
traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
if tb_set_next is not None:
traceback.chain_frames()
return traceback


def fake_exc_info(exc_info, filename, lineno, tb_back=None):
def fake_exc_info(exc_info, filename, lineno):
"""Helper for `translate_exception`."""
exc_type, exc_value, tb = exc_info

Expand Down Expand Up @@ -115,13 +202,6 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
exc_info = sys.exc_info()
new_tb = exc_info[2].tb_next

# now we can patch the exc info accordingly
if tb_set_next is not None:
if tb_back is not None:
tb_set_next(tb_back, new_tb)
if tb is not None:
tb_set_next(new_tb, tb.tb_next)

# return without this frame
return exc_info[:2] + (new_tb,)

Expand Down
40 changes: 28 additions & 12 deletions jinja2/environment.py
Expand Up @@ -24,6 +24,10 @@
# for direct template usage we have up to ten living environments
_spontaneous_environments = LRUCache(10)

# the function to create jinja traceback objects. This is dynamically
# imported on the first exception in the exception handler.
_make_traceback = None


def get_spontaneous_environment(*args):
"""Return a new spontaneous environment. A spontaneous environment is an
Expand Down Expand Up @@ -196,6 +200,9 @@ class Environment(object):
#: must not be modified
shared = False

exception_handler = None
exception_formatter = None

def __init__(self,
block_start_string=BLOCK_START_STRING,
block_end_string=BLOCK_END_STRING,
Expand Down Expand Up @@ -362,9 +369,7 @@ def parse(self, source, name=None, filename=None):
try:
return Parser(self, source, name, filename).parse()
except TemplateSyntaxError, e:
from jinja2.debug import translate_syntax_error
exc_type, exc_value, tb = translate_syntax_error(e, source)
raise exc_type, exc_value, tb
self.handle_exception(sys.exc_info(), source_hint=source)

def lex(self, source, name=None, filename=None):
"""Lex the given sourcecode and return a generator that yields
Expand All @@ -380,9 +385,7 @@ def lex(self, source, name=None, filename=None):
try:
return self.lexer.tokeniter(source, name, filename)
except TemplateSyntaxError, e:
from jinja2.debug import translate_syntax_error
exc_type, exc_value, tb = translate_syntax_error(e, source)
raise exc_type, exc_value, tb
self.handle_exception(sys.exc_info(), source_hint=source)

def preprocess(self, source, name=None, filename=None):
"""Preprocesses the source with all extensions. This is automatically
Expand Down Expand Up @@ -473,6 +476,23 @@ def compile_expression(self, source, undefined_to_none=True):
template = self.from_string(nodes.Template(body, lineno=1))
return TemplateExpression(template, undefined_to_none)

def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
"""Exception handling helper. This is used internally to either raise
rewritten exceptions or return a rendered traceback for the template.
"""
global _make_traceback
if exc_info is None:
exc_info = sys.exc_info()
if _make_traceback is None:
from jinja2.debug import make_traceback as _make_traceback
traceback = _make_traceback(exc_info, source_hint)
if rendered and self.exception_formatter is not None:
return self.exception_formatter(traceback)
if self.exception_handler is not None:
self.exception_handler(traceback)
exc_type, exc_value, tb = traceback.standard_exc_info
raise exc_type, exc_value, tb

def join_path(self, template, parent):
"""Join a template with the parent. By default all the lookups are
relative to the loader root so this method returns the `template`
Expand Down Expand Up @@ -625,9 +645,7 @@ def render(self, *args, **kwargs):
try:
return concat(self.root_render_func(self.new_context(vars)))
except:
from jinja2.debug import translate_exception
exc_type, exc_value, tb = translate_exception(sys.exc_info())
raise exc_type, exc_value, tb
return self.environment.handle_exception(sys.exc_info(), True)

def stream(self, *args, **kwargs):
"""Works exactly like :meth:`generate` but returns a
Expand All @@ -648,9 +666,7 @@ def generate(self, *args, **kwargs):
for event in self.root_render_func(self.new_context(vars)):
yield event
except:
from jinja2.debug import translate_exception
exc_type, exc_value, tb = translate_exception(sys.exc_info())
raise exc_type, exc_value, tb
yield self.environment.handle_exception(sys.exc_info(), True)

def new_context(self, vars=None, shared=False, locals=None):
"""Create a new :class:`Context` for this template. The vars
Expand Down

0 comments on commit a18872d

Please sign in to comment.