Back to index

moin  1.9.0~rc2
__init__.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     werkzeug.debug
00004     ~~~~~~~~~~~~~~
00005 
00006     WSGI application traceback debugger.
00007 
00008     :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
00009     :license: BSD, see LICENSE for more details.
00010 """
00011 import mimetypes
00012 from os.path import join, dirname, basename, isfile
00013 from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
00014 from werkzeug.debug.repr import debug_repr
00015 from werkzeug.debug.tbtools import get_current_traceback
00016 from werkzeug.debug.console import Console
00017 from werkzeug.debug.utils import render_template
00018 
00019 
00020 class _ConsoleFrame(object):
00021     """Helper class so that we can reuse the frame console code for the
00022     standalone console.
00023     """
00024 
00025     def __init__(self, namespace):
00026         self.console = Console(namespace)
00027         self.id = 0
00028 
00029 
00030 class DebuggedApplication(object):
00031     """Enables debugging support for a given application::
00032 
00033         from werkzeug.debug import DebuggedApplication
00034         from myapp import app
00035         app = DebuggedApplication(app, evalex=True)
00036 
00037     The `evalex` keyword argument allows evaluating expressions in a
00038     traceback's frame context.
00039 
00040     :param app: the WSGI application to run debugged.
00041     :param evalex: enable exception evaluation feature (interactive
00042                    debugging).  This requires a non-forking server.
00043     :param request_key: The key that points to the request object in ths
00044                         environment.  This parameter is ignored in current
00045                         versions.
00046     :param console_path: the URL for a general purpose console.
00047     :param console_init_func: the function that is executed before starting
00048                               the general purpose console.  The return value
00049                               is used as initial namespace.
00050     :param show_hidden_frames: by default hidden traceback frames are skipped.
00051                                You can show them by setting this parameter
00052                                to `True`.
00053     """
00054 
00055     def __init__(self, app, evalex=False, request_key='werkzeug.request',
00056                  console_path='/console', console_init_func=None,
00057                  show_hidden_frames=False):
00058         if not console_init_func:
00059             console_init_func = dict
00060         self.app = app
00061         self.evalex = evalex
00062         self.frames = {}
00063         self.tracebacks = {}
00064         self.request_key = request_key
00065         self.console_path = console_path
00066         self.console_init_func = console_init_func
00067         self.show_hidden_frames = show_hidden_frames
00068 
00069     def debug_application(self, environ, start_response):
00070         """Run the application and conserve the traceback frames."""
00071         app_iter = None
00072         try:
00073             app_iter = self.app(environ, start_response)
00074             for item in app_iter:
00075                 yield item
00076             if hasattr(app_iter, 'close'):
00077                 app_iter.close()
00078         except:
00079             if hasattr(app_iter, 'close'):
00080                 app_iter.close()
00081             traceback = get_current_traceback(skip=1, show_hidden_frames=
00082                                               self.show_hidden_frames,
00083                                               ignore_system_exceptions=True)
00084             for frame in traceback.frames:
00085                 self.frames[frame.id] = frame
00086             self.tracebacks[traceback.id] = traceback
00087 
00088             try:
00089                 start_response('500 INTERNAL SERVER ERROR', [
00090                     ('Content-Type', 'text/html; charset=utf-8')
00091                 ])
00092             except:
00093                 # if we end up here there has been output but an error
00094                 # occurred.  in that situation we can do nothing fancy any
00095                 # more, better log something into the error log and fall
00096                 # back gracefully.
00097                 environ['wsgi.errors'].write(
00098                     'Debugging middleware catched exception in streamed '
00099                     'response at a point where response headers were already '
00100                     'sent.\n')
00101             else:
00102                 yield traceback.render_full(evalex=self.evalex) \
00103                                .encode('utf-8', 'replace')
00104 
00105             traceback.log(environ['wsgi.errors'])
00106 
00107     def execute_command(self, request, command, frame):
00108         """Execute a command in a console."""
00109         return Response(frame.console.eval(command), mimetype='text/html')
00110 
00111     def display_console(self, request):
00112         """Display a standalone shell."""
00113         if 0 not in self.frames:
00114             self.frames[0] = _ConsoleFrame(self.console_init_func())
00115         return Response(render_template('console.html'), mimetype='text/html')
00116 
00117     def paste_traceback(self, request, traceback):
00118         """Paste the traceback and return a JSON response."""
00119         paste_id = traceback.paste()
00120         return Response('{"url": "http://paste.pocoo.org/show/%s/", "id": %s}'
00121                         % (paste_id, paste_id), mimetype='application/json')
00122 
00123     def get_source(self, request, frame):
00124         """Render the source viewer."""
00125         return Response(frame.render_source(), mimetype='text/html')
00126 
00127     def get_resource(self, request, filename):
00128         """Return a static resource from the shared folder."""
00129         filename = join(dirname(__file__), 'shared', basename(filename))
00130         if isfile(filename):
00131             mimetype = mimetypes.guess_type(filename)[0] \
00132                 or 'application/octet-stream'
00133             f = file(filename, 'rb')
00134             try:
00135                 return Response(f.read(), mimetype=mimetype)
00136             finally:
00137                 f.close()
00138         return Response('Not Found', status=404)
00139 
00140     def __call__(self, environ, start_response):
00141         """Dispatch the requests."""
00142         # important: don't ever access a function here that reads the incoming
00143         # form data!  Otherwise the application won't have access to that data
00144         # any more!
00145         request = Request(environ)
00146         response = self.debug_application
00147         if self.evalex and self.console_path is not None and \
00148            request.path == self.console_path:
00149             response = self.display_console(request)
00150         elif request.path.rstrip('/').endswith('/__debugger__'):
00151             cmd = request.args.get('cmd')
00152             arg = request.args.get('f')
00153             traceback = self.tracebacks.get(request.args.get('tb', type=int))
00154             frame = self.frames.get(request.args.get('frm', type=int))
00155             if cmd == 'resource' and arg:
00156                 response = self.get_resource(request, arg)
00157             elif cmd == 'paste' and traceback is not None:
00158                 response = self.paste_traceback(request, traceback)
00159             elif cmd == 'source' and frame:
00160                 response = self.get_source(request, frame)
00161             elif self.evalex and cmd is not None and frame is not None:
00162                 response = self.execute_command(request, cmd, frame)
00163         return response(environ, start_response)