diff options
Diffstat (limited to 'paste/debug/prints.py')
-rw-r--r-- | paste/debug/prints.py | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/paste/debug/prints.py b/paste/debug/prints.py new file mode 100644 index 0000000..b660bfa --- /dev/null +++ b/paste/debug/prints.py @@ -0,0 +1,149 @@ +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php +""" +Middleware that displays everything that is printed inline in +application pages. + +Anything printed during the request will get captured and included on +the page. It will usually be included as a floating element in the +top right hand corner of the page. If you want to override this +you can include a tag in your template where it will be placed:: + + <pre id="paste-debug-prints"></pre> + +You might want to include ``style="white-space: normal"``, as all the +whitespace will be quoted, and this allows the text to wrap if +necessary. + +""" + +from cStringIO import StringIO +import re +import cgi +from paste.util import threadedprint +from paste import wsgilib +from paste import response +import six +import sys + +_threadedprint_installed = False + +__all__ = ['PrintDebugMiddleware'] + +class TeeFile(object): + + def __init__(self, files): + self.files = files + + def write(self, v): + if isinstance(v, unicode): + # WSGI is picky in this case + v = str(v) + for file in self.files: + file.write(v) + +class PrintDebugMiddleware(object): + + """ + This middleware captures all the printed statements, and inlines + them in HTML pages, so that you can see all the (debug-intended) + print statements in the page itself. + + There are two keys added to the environment to control this: + ``environ['paste.printdebug_listeners']`` is a list of functions + that will be called everytime something is printed. + + ``environ['paste.remove_printdebug']`` is a function that, if + called, will disable printing of output for that request. + + If you have ``replace_stdout=True`` then stdout is replaced, not + captured. + """ + + log_template = ( + '<pre style="width: 40%%; border: 2px solid #000; white-space: normal; ' + 'background-color: #ffd; color: #000; float: right;">' + '<b style="border-bottom: 1px solid #000">Log messages</b><br>' + '%s</pre>') + + def __init__(self, app, global_conf=None, force_content_type=False, + print_wsgi_errors=True, replace_stdout=False): + # @@: global_conf should be handled separately and only for + # the entry point + self.app = app + self.force_content_type = force_content_type + if isinstance(print_wsgi_errors, six.string_types): + from paste.deploy.converters import asbool + print_wsgi_errors = asbool(print_wsgi_errors) + self.print_wsgi_errors = print_wsgi_errors + self.replace_stdout = replace_stdout + self._threaded_print_stdout = None + + def __call__(self, environ, start_response): + global _threadedprint_installed + if environ.get('paste.testing'): + # In a testing environment this interception isn't + # useful: + return self.app(environ, start_response) + if (not _threadedprint_installed + or self._threaded_print_stdout is not sys.stdout): + # @@: Not strictly threadsafe + _threadedprint_installed = True + threadedprint.install(leave_stdout=not self.replace_stdout) + self._threaded_print_stdout = sys.stdout + removed = [] + def remove_printdebug(): + removed.append(None) + environ['paste.remove_printdebug'] = remove_printdebug + logged = StringIO() + listeners = [logged] + environ['paste.printdebug_listeners'] = listeners + if self.print_wsgi_errors: + listeners.append(environ['wsgi.errors']) + replacement_stdout = TeeFile(listeners) + threadedprint.register(replacement_stdout) + try: + status, headers, body = wsgilib.intercept_output( + environ, self.app) + if status is None: + # Some error occurred + status = '500 Server Error' + headers = [('Content-type', 'text/html')] + start_response(status, headers) + if not body: + body = 'An error occurred' + content_type = response.header_value(headers, 'content-type') + if (removed or + (not self.force_content_type and + (not content_type + or not content_type.startswith('text/html')))): + if replacement_stdout == logged: + # Then the prints will be lost, unless... + environ['wsgi.errors'].write(logged.getvalue()) + start_response(status, headers) + return [body] + response.remove_header(headers, 'content-length') + body = self.add_log(body, logged.getvalue()) + start_response(status, headers) + return [body] + finally: + threadedprint.deregister() + + _body_re = re.compile(r'<body[^>]*>', re.I) + _explicit_re = re.compile(r'<pre\s*[^>]*id="paste-debug-prints".*?>', + re.I+re.S) + + def add_log(self, html, log): + if not log: + return html + text = cgi.escape(log) + text = text.replace('\n', '<br>') + text = text.replace(' ', ' ') + match = self._explicit_re.search(html) + if not match: + text = self.log_template % text + match = self._body_re.search(html) + if not match: + return text + html + else: + return html[:match.end()] + text + html[match.end():] |