~brad-marshall/charms/trusty/apache2-wsgi/fix-haproxy-relations

« back to all changes in this revision

Viewing changes to hooks/lib/jinja2/debug.py

  • Committer: Robin Winslow
  • Date: 2014-05-27 14:00:44 UTC
  • Revision ID: robin.winslow@canonical.com-20140527140044-8rpmb3wx4djzwa83
Add all files

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
"""
 
3
    jinja2.debug
 
4
    ~~~~~~~~~~~~
 
5
 
 
6
    Implements the debug interface for Jinja.  This module does some pretty
 
7
    ugly stuff with the Python traceback system in order to achieve tracebacks
 
8
    with correct line numbers, locals and contents.
 
9
 
 
10
    :copyright: (c) 2010 by the Jinja Team.
 
11
    :license: BSD, see LICENSE for more details.
 
12
"""
 
13
import sys
 
14
import traceback
 
15
from types import TracebackType
 
16
from jinja2.utils import missing, internal_code
 
17
from jinja2.exceptions import TemplateSyntaxError
 
18
from jinja2._compat import iteritems, reraise, code_type
 
19
 
 
20
# on pypy we can take advantage of transparent proxies
 
21
try:
 
22
    from __pypy__ import tproxy
 
23
except ImportError:
 
24
    tproxy = None
 
25
 
 
26
 
 
27
# how does the raise helper look like?
 
28
try:
 
29
    exec("raise TypeError, 'foo'")
 
30
except SyntaxError:
 
31
    raise_helper = 'raise __jinja_exception__[1]'
 
32
except TypeError:
 
33
    raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
 
34
 
 
35
 
 
36
class TracebackFrameProxy(object):
 
37
    """Proxies a traceback frame."""
 
38
 
 
39
    def __init__(self, tb):
 
40
        self.tb = tb
 
41
        self._tb_next = None
 
42
 
 
43
    @property
 
44
    def tb_next(self):
 
45
        return self._tb_next
 
46
 
 
47
    def set_next(self, next):
 
48
        if tb_set_next is not None:
 
49
            try:
 
50
                tb_set_next(self.tb, next and next.tb or None)
 
51
            except Exception:
 
52
                # this function can fail due to all the hackery it does
 
53
                # on various python implementations.  We just catch errors
 
54
                # down and ignore them if necessary.
 
55
                pass
 
56
        self._tb_next = next
 
57
 
 
58
    @property
 
59
    def is_jinja_frame(self):
 
60
        return '__jinja_template__' in self.tb.tb_frame.f_globals
 
61
 
 
62
    def __getattr__(self, name):
 
63
        return getattr(self.tb, name)
 
64
 
 
65
 
 
66
def make_frame_proxy(frame):
 
67
    proxy = TracebackFrameProxy(frame)
 
68
    if tproxy is None:
 
69
        return proxy
 
70
    def operation_handler(operation, *args, **kwargs):
 
71
        if operation in ('__getattribute__', '__getattr__'):
 
72
            return getattr(proxy, args[0])
 
73
        elif operation == '__setattr__':
 
74
            proxy.__setattr__(*args, **kwargs)
 
75
        else:
 
76
            return getattr(proxy, operation)(*args, **kwargs)
 
77
    return tproxy(TracebackType, operation_handler)
 
78
 
 
79
 
 
80
class ProcessedTraceback(object):
 
81
    """Holds a Jinja preprocessed traceback for printing or reraising."""
 
82
 
 
83
    def __init__(self, exc_type, exc_value, frames):
 
84
        assert frames, 'no frames for this traceback?'
 
85
        self.exc_type = exc_type
 
86
        self.exc_value = exc_value
 
87
        self.frames = frames
 
88
 
 
89
        # newly concatenate the frames (which are proxies)
 
90
        prev_tb = None
 
91
        for tb in self.frames:
 
92
            if prev_tb is not None:
 
93
                prev_tb.set_next(tb)
 
94
            prev_tb = tb
 
95
        prev_tb.set_next(None)
 
96
 
 
97
    def render_as_text(self, limit=None):
 
98
        """Return a string with the traceback."""
 
99
        lines = traceback.format_exception(self.exc_type, self.exc_value,
 
100
                                           self.frames[0], limit=limit)
 
101
        return ''.join(lines).rstrip()
 
102
 
 
103
    def render_as_html(self, full=False):
 
104
        """Return a unicode string with the traceback as rendered HTML."""
 
105
        from jinja2.debugrenderer import render_traceback
 
106
        return u'%s\n\n<!--\n%s\n-->' % (
 
107
            render_traceback(self, full=full),
 
108
            self.render_as_text().decode('utf-8', 'replace')
 
109
        )
 
110
 
 
111
    @property
 
112
    def is_template_syntax_error(self):
 
113
        """`True` if this is a template syntax error."""
 
114
        return isinstance(self.exc_value, TemplateSyntaxError)
 
115
 
 
116
    @property
 
117
    def exc_info(self):
 
118
        """Exception info tuple with a proxy around the frame objects."""
 
119
        return self.exc_type, self.exc_value, self.frames[0]
 
120
 
 
121
    @property
 
122
    def standard_exc_info(self):
 
123
        """Standard python exc_info for re-raising"""
 
124
        tb = self.frames[0]
 
125
        # the frame will be an actual traceback (or transparent proxy) if
 
126
        # we are on pypy or a python implementation with support for tproxy
 
127
        if type(tb) is not TracebackType:
 
128
            tb = tb.tb
 
129
        return self.exc_type, self.exc_value, tb
 
130
 
 
131
 
 
132
def make_traceback(exc_info, source_hint=None):
 
133
    """Creates a processed traceback object from the exc_info."""
 
134
    exc_type, exc_value, tb = exc_info
 
135
    if isinstance(exc_value, TemplateSyntaxError):
 
136
        exc_info = translate_syntax_error(exc_value, source_hint)
 
137
        initial_skip = 0
 
138
    else:
 
139
        initial_skip = 1
 
140
    return translate_exception(exc_info, initial_skip)
 
141
 
 
142
 
 
143
def translate_syntax_error(error, source=None):
 
144
    """Rewrites a syntax error to please traceback systems."""
 
145
    error.source = source
 
146
    error.translated = True
 
147
    exc_info = (error.__class__, error, None)
 
148
    filename = error.filename
 
149
    if filename is None:
 
150
        filename = '<unknown>'
 
151
    return fake_exc_info(exc_info, filename, error.lineno)
 
152
 
 
153
 
 
154
def translate_exception(exc_info, initial_skip=0):
 
155
    """If passed an exc_info it will automatically rewrite the exceptions
 
156
    all the way down to the correct line numbers and frames.
 
157
    """
 
158
    tb = exc_info[2]
 
159
    frames = []
 
160
 
 
161
    # skip some internal frames if wanted
 
162
    for x in range(initial_skip):
 
163
        if tb is not None:
 
164
            tb = tb.tb_next
 
165
    initial_tb = tb
 
166
 
 
167
    while tb is not None:
 
168
        # skip frames decorated with @internalcode.  These are internal
 
169
        # calls we can't avoid and that are useless in template debugging
 
170
        # output.
 
171
        if tb.tb_frame.f_code in internal_code:
 
172
            tb = tb.tb_next
 
173
            continue
 
174
 
 
175
        # save a reference to the next frame if we override the current
 
176
        # one with a faked one.
 
177
        next = tb.tb_next
 
178
 
 
179
        # fake template exceptions
 
180
        template = tb.tb_frame.f_globals.get('__jinja_template__')
 
181
        if template is not None:
 
182
            lineno = template.get_corresponding_lineno(tb.tb_lineno)
 
183
            tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
 
184
                               lineno)[2]
 
185
 
 
186
        frames.append(make_frame_proxy(tb))
 
187
        tb = next
 
188
 
 
189
    # if we don't have any exceptions in the frames left, we have to
 
190
    # reraise it unchanged.
 
191
    # XXX: can we backup here?  when could this happen?
 
192
    if not frames:
 
193
        reraise(exc_info[0], exc_info[1], exc_info[2])
 
194
 
 
195
    return ProcessedTraceback(exc_info[0], exc_info[1], frames)
 
196
 
 
197
 
 
198
def fake_exc_info(exc_info, filename, lineno):
 
199
    """Helper for `translate_exception`."""
 
200
    exc_type, exc_value, tb = exc_info
 
201
 
 
202
    # figure the real context out
 
203
    if tb is not None:
 
204
        real_locals = tb.tb_frame.f_locals.copy()
 
205
        ctx = real_locals.get('context')
 
206
        if ctx:
 
207
            locals = ctx.get_all()
 
208
        else:
 
209
            locals = {}
 
210
        for name, value in iteritems(real_locals):
 
211
            if name.startswith('l_') and value is not missing:
 
212
                locals[name[2:]] = value
 
213
 
 
214
        # if there is a local called __jinja_exception__, we get
 
215
        # rid of it to not break the debug functionality.
 
216
        locals.pop('__jinja_exception__', None)
 
217
    else:
 
218
        locals = {}
 
219
 
 
220
    # assamble fake globals we need
 
221
    globals = {
 
222
        '__name__':             filename,
 
223
        '__file__':             filename,
 
224
        '__jinja_exception__':  exc_info[:2],
 
225
 
 
226
        # we don't want to keep the reference to the template around
 
227
        # to not cause circular dependencies, but we mark it as Jinja
 
228
        # frame for the ProcessedTraceback
 
229
        '__jinja_template__':   None
 
230
    }
 
231
 
 
232
    # and fake the exception
 
233
    code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
 
234
 
 
235
    # if it's possible, change the name of the code.  This won't work
 
236
    # on some python environments such as google appengine
 
237
    try:
 
238
        if tb is None:
 
239
            location = 'template'
 
240
        else:
 
241
            function = tb.tb_frame.f_code.co_name
 
242
            if function == 'root':
 
243
                location = 'top-level template code'
 
244
            elif function.startswith('block_'):
 
245
                location = 'block "%s"' % function[6:]
 
246
            else:
 
247
                location = 'template'
 
248
        code = code_type(0, code.co_nlocals, code.co_stacksize,
 
249
                         code.co_flags, code.co_code, code.co_consts,
 
250
                         code.co_names, code.co_varnames, filename,
 
251
                         location, code.co_firstlineno,
 
252
                         code.co_lnotab, (), ())
 
253
    except:
 
254
        pass
 
255
 
 
256
    # execute the code and catch the new traceback
 
257
    try:
 
258
        exec(code, globals, locals)
 
259
    except:
 
260
        exc_info = sys.exc_info()
 
261
        new_tb = exc_info[2].tb_next
 
262
 
 
263
    # return without this frame
 
264
    return exc_info[:2] + (new_tb,)
 
265
 
 
266
 
 
267
def _init_ugly_crap():
 
268
    """This function implements a few ugly things so that we can patch the
 
269
    traceback objects.  The function returned allows resetting `tb_next` on
 
270
    any python traceback object.  Do not attempt to use this on non cpython
 
271
    interpreters
 
272
    """
 
273
    import ctypes
 
274
    from types import TracebackType
 
275
 
 
276
    # figure out side of _Py_ssize_t
 
277
    if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
 
278
        _Py_ssize_t = ctypes.c_int64
 
279
    else:
 
280
        _Py_ssize_t = ctypes.c_int
 
281
 
 
282
    # regular python
 
283
    class _PyObject(ctypes.Structure):
 
284
        pass
 
285
    _PyObject._fields_ = [
 
286
        ('ob_refcnt', _Py_ssize_t),
 
287
        ('ob_type', ctypes.POINTER(_PyObject))
 
288
    ]
 
289
 
 
290
    # python with trace
 
291
    if hasattr(sys, 'getobjects'):
 
292
        class _PyObject(ctypes.Structure):
 
293
            pass
 
294
        _PyObject._fields_ = [
 
295
            ('_ob_next', ctypes.POINTER(_PyObject)),
 
296
            ('_ob_prev', ctypes.POINTER(_PyObject)),
 
297
            ('ob_refcnt', _Py_ssize_t),
 
298
            ('ob_type', ctypes.POINTER(_PyObject))
 
299
        ]
 
300
 
 
301
    class _Traceback(_PyObject):
 
302
        pass
 
303
    _Traceback._fields_ = [
 
304
        ('tb_next', ctypes.POINTER(_Traceback)),
 
305
        ('tb_frame', ctypes.POINTER(_PyObject)),
 
306
        ('tb_lasti', ctypes.c_int),
 
307
        ('tb_lineno', ctypes.c_int)
 
308
    ]
 
309
 
 
310
    def tb_set_next(tb, next):
 
311
        """Set the tb_next attribute of a traceback object."""
 
312
        if not (isinstance(tb, TracebackType) and
 
313
                (next is None or isinstance(next, TracebackType))):
 
314
            raise TypeError('tb_set_next arguments must be traceback objects')
 
315
        obj = _Traceback.from_address(id(tb))
 
316
        if tb.tb_next is not None:
 
317
            old = _Traceback.from_address(id(tb.tb_next))
 
318
            old.ob_refcnt -= 1
 
319
        if next is None:
 
320
            obj.tb_next = ctypes.POINTER(_Traceback)()
 
321
        else:
 
322
            next = _Traceback.from_address(id(next))
 
323
            next.ob_refcnt += 1
 
324
            obj.tb_next = ctypes.pointer(next)
 
325
 
 
326
    return tb_set_next
 
327
 
 
328
 
 
329
# try to get a tb_set_next implementation if we don't have transparent
 
330
# proxies.
 
331
tb_set_next = None
 
332
if tproxy is None:
 
333
    try:
 
334
        tb_set_next = _init_ugly_crap()
 
335
    except:
 
336
        pass
 
337
    del _init_ugly_crap