Skip to content

Commit

Permalink
refactor the toolbar out of the parent app
Browse files Browse the repository at this point in the history
the tween will now dispatch directly to the toolbar for content
instead of invoking subrequests into the parent application

the benefits of this approach are that we are no longer subject to any
odd configuration in the parent app that may affect the toolbar's view

this change also stops polluting the parent application with
pyramid_mako and a dbtmako renderer - all toolbar features are now
rendered within the toolbar's isolated wsgi app
  • Loading branch information
mmerickel committed Apr 22, 2016
1 parent 20b0581 commit 469cc2a
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 122 deletions.
33 changes: 19 additions & 14 deletions pyramid_debugtoolbar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from pyramid.config import Configurator
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.settings import asbool
from pyramid.wsgi import wsgiapp2
from pyramid_debugtoolbar.utils import (
as_cr_separated_list,
as_display_debug_or_false,
Expand All @@ -14,9 +12,13 @@
SETTINGS_PREFIX,
STATIC_PATH,
)
from pyramid_debugtoolbar.toolbar import (IRequestAuthorization,
toolbar_tween_factory) # API
toolbar_tween_factory = toolbar_tween_factory # pyflakes
from pyramid_debugtoolbar.toolbar import (
IRequestAuthorization,
IToolbarWSGIApp,
toolbar_tween_factory,
)

toolbar_tween_factory = toolbar_tween_factory # API

default_panel_names = (
'pyramid_debugtoolbar.panels.headers.HeaderDebugPanel',
Expand Down Expand Up @@ -124,8 +126,6 @@ def includeme(config):
# Update the current registry with the new settings
config.registry.settings.update(settings)

config.include('pyramid_mako')
config.add_mako_renderer('.dbtmako', settings_prefix='dbtmako.')
config.add_tween('pyramid_debugtoolbar.toolbar_tween_factory')
config.add_subscriber(
'pyramid_debugtoolbar.toolbar.beforerender_subscriber',
Expand All @@ -138,12 +138,13 @@ def includeme(config):

# Create the new application using the updated settings
application = make_application(settings, config.registry)
config.add_route('debugtoolbar', '/_debug_toolbar/*subpath')
config.add_view(wsgiapp2(application), route_name='debugtoolbar',
permission=NO_PERMISSION_REQUIRED)
config.registry.registerUtility(application, IToolbarWSGIApp)

# register routes and views that can be used within the tween
config.add_route('debugtoolbar', '/_debug_toolbar/*subpath', static=True)
config.add_static_view('/_debug_toolbar/static', STATIC_PATH, static=True)
config.introspection = introspection

config.introspection = introspection

def make_application(settings, parent_registry):
""" WSGI application for rendering the debug toolbar."""
Expand All @@ -157,11 +158,15 @@ def make_application(settings, parent_registry):
config.add_route('debugtoolbar.source', '/source')
config.add_route('debugtoolbar.execute', '/execute')
config.add_route('debugtoolbar.console', '/console')
config.add_route('debugtoolbar.redirect', '/redirect')
config.add_route(EXC_ROUTE_NAME, '/exception')
config.add_route('debugtoolbar.sql_select', '/{request_id}/sqlalchemy/select/{query_index}')
config.add_route('debugtoolbar.sql_explain', '/{request_id}/sqlalchemy/explain/{query_index}')
config.add_route(
'debugtoolbar.sql_select',
'/{request_id}/sqlalchemy/select/{query_index}')
config.add_route(
'debugtoolbar.sql_explain',
'/{request_id}/sqlalchemy/explain/{query_index}')
config.add_route('debugtoolbar.request', '/{request_id}')
config.add_route('debugtoolbar.main', '/')
config.scan('pyramid_debugtoolbar.views')

return config.make_wsgi_app()
24 changes: 12 additions & 12 deletions pyramid_debugtoolbar/panels/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,17 @@ class TracebackPanel(DebugPanel):

def __init__(self, request):
self.request = request
self.traceback = None
self.exc_history = request.exc_history

@property
def has_content(self):
if hasattr(self.request, 'pdbt_tb'):
return True
else:
return False
return self.traceback is not None

def process_response(self, response):
if self.has_content:
traceback = self.request.pdbt_tb

self.traceback = traceback = getattr(self.request, 'pdbt_tb', None)
if self.traceback is not None:
exc = escape(traceback.exception)
summary = Traceback.render_summary(traceback, include_title=False, request=self.request)
token = self.request.registry.pdtb_token
url = '' # self.request.route_url(EXC_ROUTE_NAME, _query=qs)
evalex = self.exc_history.eval_exc
Expand All @@ -42,19 +38,23 @@ def process_response(self, response):
'title': exc,
'exception': exc,
'exception_type': escape(traceback.exception_type),
'summary': summary,
'plaintext': traceback.plaintext,
'plaintext_cs': re.sub('-{2,}', '-', traceback.plaintext),
'traceback_id': traceback.id,
'token': token,
'url': url,
}

def render_content(self, request):
return super(TracebackPanel, self).render_content(request)
# stop hanging onto the request after the response is processed
del self.request

def render_vars(self, request):
return {
'static_path': request.static_url(STATIC_PATH),
'root_path': request.route_url(ROOT_ROUTE_NAME)
'root_path': request.route_url(ROOT_ROUTE_NAME),

# render the summary using the toolbar's request object, not
# the original request that generated the traceback!
'summary': self.traceback.render_summary(
include_title=False, request=request),
}
74 changes: 40 additions & 34 deletions pyramid_debugtoolbar/toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from pyramid.exceptions import URLDecodeError
from pyramid.httpexceptions import WSGIHTTPException
from pyramid.interfaces import Interface
from pyramid.renderers import render
from pyramid.threadlocal import get_current_request
from pyramid_debugtoolbar.compat import bytes_
from pyramid_debugtoolbar.compat import url_unquote
from pyramid_debugtoolbar.tbtools import get_traceback
from pyramid_debugtoolbar.utils import addr_in
from pyramid_debugtoolbar.utils import debug_toolbar_url
from pyramid_debugtoolbar.utils import dispatch_to_wsgi
from pyramid_debugtoolbar.utils import get_setting
from pyramid_debugtoolbar.utils import hexlify
from pyramid_debugtoolbar.utils import last_proxy
Expand All @@ -21,14 +21,20 @@

html_types = ('text/html', 'application/xhtml+xml')

class IToolbarWSGIApp(Interface):
""" Marker interface for the toolbar WSGI application."""
def __call__(environ, start_response):
pass

class IRequestAuthorization(Interface):

class IRequestAuthorization(Interface):
def __call__(request):
"""
Toolbar per-request authorization.
Should return bool values whether toolbar is permitted to be shown
within provided request.
Should return bool values whether toolbar is permitted to monitor
the provided request.
"""


Expand Down Expand Up @@ -156,36 +162,38 @@ def sget(opt, default=None):
registry.exc_history = exc_history = ExceptionHistory()
exc_history.eval_exc = intercept_exc == 'debug'

def toolbar_tween(request):
request.exc_history = exc_history
request.history = request_history
root_url = request.route_path('debugtoolbar', subpath='')
exclude = [root_url] + exclude_prefixes
last_proxy_addr = None
toolbar_app = registry.getUtility(IToolbarWSGIApp)

def toolbar_tween(request):
try:
p = request.path
p = request.path_info
except UnicodeDecodeError as e:
raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
starts_with_excluded = list(filter(None, map(p.startswith, exclude)))

last_proxy_addr = None
if request.remote_addr:
last_proxy_addr = last_proxy(request.remote_addr)

if last_proxy_addr is None \
or starts_with_excluded \
or not addr_in(last_proxy_addr, hosts) \
or auth_check and not auth_check(request):
return handler(request)
if (
last_proxy_addr is None
or any(p.startswith(e) for e in exclude_prefixes)
or not addr_in(last_proxy_addr, hosts)
or auth_check and not auth_check(request)
):
return handler(request)

root_path = request.route_url('debugtoolbar', subpath='', _app_url='')
if p.startswith(root_path):
return dispatch_to_wsgi(toolbar_app, request, root_path)

request.exc_history = exc_history
request.history = request_history
request.pdtb_id = hexlify(id(request))
toolbar = DebugToolbar(request, panel_classes, global_panel_classes,
default_active_panels)
request.debug_toolbar = toolbar

_handler = handler

# XXX
for panel in toolbar.panels:
_handler = panel.wrap_handler(_handler)

Expand All @@ -209,14 +217,8 @@ def toolbar_tween(request):
exc_msg = msg % (request.url, exc_url)
_logger.exception(exc_msg)

subenviron = request.environ.copy()
del subenviron['PATH_INFO']
del subenviron['QUERY_STRING']
subrequest = type(request).blank(exc_url, subenviron)
subrequest.script_name = request.script_name
subrequest.path_info = \
subrequest.path_info[len(request.script_name):]
response = request.invoke_subrequest(subrequest)
subrequest = type(request).blank(exc_url)
response = dispatch_to_wsgi(toolbar_app, subrequest, root_path)

# The original request must be processed so that the panel data exists
# if the request is later examined in the full toolbar view.
Expand All @@ -241,13 +243,17 @@ def toolbar_tween(request):
redirect_to = response.location
redirect_code = response.status_int
if redirect_to:
content = render(
'pyramid_debugtoolbar:templates/redirect.dbtmako',
{
'redirect_to': redirect_to,
'redirect_code': redirect_code,
},
request=request)
qs = {
'token': registry.pdtb_token,
'redirect_to': redirect_to,
'redirect_code': redirect_code,
}
redirect_url = debug_toolbar_url(
request, 'redirect', _query=qs)
subrequest = type(request).blank(redirect_url)
redirect_response = dispatch_to_wsgi(
toolbar_app, subrequest, root_path)
content = redirect_response.text
content = content.encode(response.charset)
response.content_length = len(content)
response.location = None
Expand Down
10 changes: 9 additions & 1 deletion pyramid_debugtoolbar/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,18 @@ def find_request_history(request):
def debug_toolbar_url(request, *elements, **kw):
return request.route_url('debugtoolbar', subpath=elements, **kw)


def hexlify(value):
"""Hexlify int, str then returns native str type."""
# If integer
str_ = str(value)
hexified = text_(binascii.hexlify(bytes_(str_)))
return hexified

def dispatch_to_wsgi(app, request, root_path=None):
new_request = request.copy()
if root_path is not None:
if root_path.endswith('/'):
root_path = root_path[:-1]
new_request.script_name = request.script_name + root_path
new_request.path_info = request.path_info[len(root_path):]
return new_request.get_response(app)
Loading

0 comments on commit 469cc2a

Please sign in to comment.