summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2013-09-24 15:50:02 -0400
committerRyan Petrello <lists@ryanpetrello.com>2013-09-25 01:21:31 -0400
commit861f1bdecc64e7144bb7a7d7a9ffffc42bc17f13 (patch)
treebfc6d4dbde1396915c651a621e36c01906f60b7f
parent09ea1274589d51104d89d2b325906aa0a36c181a (diff)
downloadpecan-861f1bdecc64e7144bb7a7d7a9ffffc42bc17f13.tar.gz
Squeeze some more performance out of the WSGI __call__ and dispatch algorithm.
Change-Id: I3e25381d526866d6feae05f0dcbdca5707f86df7
-rw-r--r--pecan/compat/__init__.py2
-rw-r--r--pecan/core.py80
-rw-r--r--pecan/routing.py42
3 files changed, 66 insertions, 58 deletions
diff --git a/pecan/compat/__init__.py b/pecan/compat/__init__.py
index 93883f4..c929f58 100644
--- a/pecan/compat/__init__.py
+++ b/pecan/compat/__init__.py
@@ -7,11 +7,13 @@ if six.PY3:
from urllib.parse import quote, unquote_plus
from urllib.request import urlopen, URLError
from html import escape
+ izip = zip
else:
import urlparse # noqa
from urllib import quote, unquote_plus # noqa
from urllib2 import urlopen, URLError # noqa
from cgi import escape # noqa
+ from itertools import izip
def is_bound_method(ob):
diff --git a/pecan/core.py b/pecan/core.py
index 0aad76a..85ae5e6 100644
--- a/pecan/core.py
+++ b/pecan/core.py
@@ -13,7 +13,7 @@ import six
from webob import Request, Response, exc, acceptparse
-from .compat import urlparse, unquote_plus
+from .compat import urlparse, unquote_plus, izip
from .templating import RendererFactory
from .routing import lookup_controller, NonCanonicalPath
from .util import _cfg, encode_if_needed
@@ -297,7 +297,6 @@ class Pecan(object):
``on_error``, and ``on_route``.
:param \*args: Arguments to pass to the hooks.
'''
-
if hook_type in ['before', 'on_route']:
hooks = state.hooks
else:
@@ -310,7 +309,7 @@ class Pecan(object):
if hook_type == 'on_error' and isinstance(result, Response):
return result
- def get_args(self, req, all_params, remainder, argspec, im_self):
+ def get_args(self, pecan_state, all_params, remainder, argspec, im_self):
'''
Determines the arguments for a controller based upon parameters
passed the argument specification for the controller.
@@ -329,9 +328,9 @@ class Pecan(object):
args.append(im_self)
# grab the routing args from nested REST controllers
- if 'routing_args' in req.pecan:
- remainder = req.pecan['routing_args'] + list(remainder)
- del req.pecan['routing_args']
+ if 'routing_args' in pecan_state:
+ remainder = pecan_state['routing_args'] + list(remainder)
+ del pecan_state['routing_args']
# handle positional arguments
if valid_args and remainder:
@@ -347,7 +346,7 @@ class Pecan(object):
# get the default positional arguments
if argspec[3]:
- defaults = dict(zip(argspec[0][-len(argspec[3]):], argspec[3]))
+ defaults = dict(izip(argspec[0][-len(argspec[3]):], argspec[3]))
else:
defaults = dict()
@@ -390,23 +389,24 @@ class Pecan(object):
# get a sorted list of hooks, by priority (no controller hooks yet)
state.hooks = self.hooks
+ pecan_state = req.pecan
# store the routing path for the current application to allow hooks to
# modify it
- req.pecan['routing_path'] = req.path_info
+ pecan_state['routing_path'] = path = req.encget('PATH_INFO')
# handle "on_route" hooks
self.handle_hooks('on_route', state)
# lookup the controller, respecting content-type as requested
# by the file extension on the URI
- path = req.pecan['routing_path']
- req.pecan['extension'] = None
+ pecan_state['extension'] = None
+ content_type = pecan_state['content_type']
# attempt to guess the content type based on the file extension
if self.guess_content_type_from_ext \
- and not req.pecan['content_type'] \
- and '.' in path.split('/')[-1]:
+ and not content_type \
+ and '.' in path:
new_path, extension = splitext(path)
# preface with a letter to ensure compat for 2.5
@@ -414,8 +414,8 @@ class Pecan(object):
if potential_type is not None:
path = new_path
- req.pecan['extension'] = extension
- req.pecan['content_type'] = potential_type
+ pecan_state['extension'] = extension
+ content_type = potential_type
controller, remainder = self.route(req, self.root, path)
cfg = _cfg(controller)
@@ -436,14 +436,14 @@ class Pecan(object):
# if unsure ask the controller for the default content type
content_types = cfg.get('content_types', {})
- if not req.pecan['content_type']:
+ if not content_type:
# attempt to find a best match based on accept headers (if they
# exist)
- accept = req.headers.get('Accept', '*/*')
+ accept = getattr(req.accept, 'header_value', '*/*')
if accept == '*/*' or (
accept.startswith('text/html,') and
list(content_types.keys()) in self.SIMPLEST_CONTENT_TYPES):
- req.pecan['content_type'] = cfg.get(
+ content_type = cfg.get(
'content_type',
'text/html'
)
@@ -460,23 +460,22 @@ class Pecan(object):
logger.error(
msg % (
controller.__name__,
- req.pecan['content_type'],
+ content_type,
content_types.keys()
)
)
raise exc.HTTPNotAcceptable()
- req.pecan['content_type'] = best_default
+ content_type = best_default
elif cfg.get('content_type') is not None and \
- req.pecan['content_type'] not in \
- content_types:
+ content_type not in content_types:
msg = "Controller '%s' defined does not support content_type " + \
"'%s'. Supported type(s): %s"
logger.error(
msg % (
controller.__name__,
- req.pecan['content_type'],
+ content_type,
content_types.keys()
)
)
@@ -485,15 +484,20 @@ class Pecan(object):
# get a sorted list of hooks, by priority
state.hooks = self.determine_hooks(controller)
+ pecan_state['content_type'] = content_type
+
# handle "before" hooks
self.handle_hooks('before', state)
# fetch any parameters
- params = dict(req.params)
+ if req.method == 'GET':
+ params = dict(req.GET)
+ else:
+ params = dict(req.params)
# fetch the arguments for the controller
args, kwargs = self.get_args(
- req,
+ pecan_state,
params,
remainder,
cfg['argspec'],
@@ -505,27 +509,25 @@ class Pecan(object):
# a controller can return the response object which means they've taken
# care of filling it out
- if result == response:
+ if result is response:
return
raw_namespace = result
# pull the template out based upon content type and handle overrides
- template = content_types.get(
- req.pecan['content_type']
- )
+ template = content_types.get(content_type)
# check if for controller override of template
- template = req.pecan.get('override_template', template)
- req.pecan['content_type'] = req.pecan.get(
+ template = pecan_state.get('override_template', template)
+ content_type = pecan_state.get(
'override_content_type',
- req.pecan['content_type']
+ content_type
)
# if there is a template, render it
if template:
if template == 'json':
- req.pecan['content_type'] = 'application/json'
+ content_type = 'application/json'
result = self.render(template, result)
# If we are in a test request put the namespace where it can be
@@ -543,8 +545,8 @@ class Pecan(object):
resp.body = result
# set the content type
- if req.pecan['content_type']:
- resp.content_type = req.pecan['content_type']
+ if content_type:
+ resp.content_type = content_type
def __call__(self, environ, start_response):
'''
@@ -553,8 +555,8 @@ class Pecan(object):
'''
# create the request and response object
- state.request = Request(environ)
- state.response = Response()
+ state.request = req = Request(environ)
+ state.response = resp = Response()
state.hooks = []
state.app = self
state.controller = None
@@ -562,10 +564,10 @@ class Pecan(object):
# handle the request
try:
# add context and environment to the request
- state.request.context = {}
- state.request.pecan = dict(content_type=None)
+ req.context = {}
+ req.pecan = dict(content_type=None)
- self.handle_request(state.request, state.response)
+ self.handle_request(req, resp)
except Exception as e:
# if this is an HTTP Exception, set it as the response
if isinstance(e, exc.HTTPException):
diff --git a/pecan/routing.py b/pecan/routing.py
index fc1a7d4..47eb354 100644
--- a/pecan/routing.py
+++ b/pecan/routing.py
@@ -8,6 +8,10 @@ from .util import iscontroller
__all__ = ['lookup_controller', 'find_object']
+class PecanNotFound(Exception):
+ pass
+
+
class NonCanonicalPath(Exception):
'''
Exception Raised when a non-canonical path is encountered when 'walking'
@@ -19,22 +23,20 @@ class NonCanonicalPath(Exception):
self.remainder = remainder
-def lookup_controller(obj, url_path):
+def lookup_controller(obj, remainder):
'''
Traverses the requested url path and returns the appropriate controller
object, including default routes.
Handles common errors gracefully.
'''
- remainder = url_path
notfound_handlers = []
-
while True:
try:
obj, remainder = find_object(obj, remainder, notfound_handlers)
handle_security(obj)
return obj, remainder
- except exc.HTTPNotFound:
+ except PecanNotFound:
while notfound_handlers:
name, obj, remainder = notfound_handlers.pop()
if name == '_default':
@@ -52,7 +54,7 @@ def lookup_controller(obj, url_path):
remainder == ['']
and len(obj._pecan['argspec'].args) > 1
):
- raise
+ raise exc.HTTPNotFound
return lookup_controller(*result)
else:
raise exc.HTTPNotFound
@@ -84,22 +86,24 @@ def find_object(obj, remainder, notfound_handlers):
prev_obj = None
while True:
if obj is None:
- raise exc.HTTPNotFound
+ raise PecanNotFound
if iscontroller(obj):
return obj, remainder
# are we traversing to another controller
cross_boundary(prev_obj, obj)
-
- if remainder and remainder[0] == '':
- index = getattr(obj, 'index', None)
- if iscontroller(index):
- return index, remainder[1:]
- elif not remainder:
+ try:
+ next_obj, rest = remainder[0], remainder[1:]
+ if next_obj == '':
+ index = getattr(obj, 'index', None)
+ if iscontroller(index):
+ return index, rest
+ except IndexError:
# the URL has hit an index method without a trailing slash
index = getattr(obj, 'index', None)
if iscontroller(index):
- raise NonCanonicalPath(index, remainder[1:])
+ raise NonCanonicalPath(index, [])
+
default = getattr(obj, '_default', None)
if iscontroller(default):
notfound_handlers.append(('_default', default, remainder))
@@ -110,12 +114,12 @@ def find_object(obj, remainder, notfound_handlers):
route = getattr(obj, '_route', None)
if iscontroller(route):
- next, next_remainder = route(remainder)
- cross_boundary(route, next)
- return next, next_remainder
+ next_obj, next_remainder = route(remainder)
+ cross_boundary(route, next_obj)
+ return next_obj, next_remainder
if not remainder:
- raise exc.HTTPNotFound
- next, remainder = remainder[0], remainder[1:]
+ raise PecanNotFound
prev_obj = obj
- obj = getattr(obj, next, None)
+ remainder = rest
+ obj = getattr(obj, next_obj, None)