Files
deb-python-pecan/pecan/routing.py
Ryan Petrello e93b929edf Add support for Pecan *without* thread local request/response objects
Change-Id: I5a5a05e1f57ef2d8ad64e925c7ffa6907b914273
2014-05-28 16:06:30 -04:00

143 lines
4.8 KiB
Python

import warnings
from inspect import getargspec
from webob import exc
from .secure import handle_security, cross_boundary
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'
the URI. This is typically a ``POST`` request which requires a trailing
slash.
'''
def __init__(self, controller, remainder):
self.controller = controller
self.remainder = remainder
def lookup_controller(obj, remainder, request):
'''
Traverses the requested url path and returns the appropriate controller
object, including default routes.
Handles common errors gracefully.
'''
notfound_handlers = []
while True:
try:
obj, remainder = find_object(obj, remainder, notfound_handlers,
request)
handle_security(obj)
return obj, remainder
except (exc.HTTPNotFound, PecanNotFound):
while notfound_handlers:
name, obj, remainder = notfound_handlers.pop()
if name == '_default':
# Notfound handler is, in fact, a controller, so stop
# traversal
return obj, remainder
else:
# Notfound handler is an internal redirect, so continue
# traversal
result = handle_lookup_traversal(obj, remainder)
if result:
# If no arguments are passed to the _lookup, yet the
# argspec requires at least one, raise a 404
if (
remainder == ['']
and len(obj._pecan['argspec'].args) > 1
):
raise exc.HTTPNotFound
obj_, remainder_ = result
return lookup_controller(obj_, remainder_, request)
else:
raise exc.HTTPNotFound
def handle_lookup_traversal(obj, args):
try:
result = obj(*args)
if result:
prev_obj = obj
obj, remainder = result
# crossing controller boundary
cross_boundary(prev_obj, obj)
return result
except TypeError as te:
msg = 'Got exception calling lookup(): %s (%s)'
warnings.warn(
msg % (te, te.args),
RuntimeWarning
)
def find_object(obj, remainder, notfound_handlers, request):
'''
'Walks' the url path in search of an action for which a controller is
implemented and returns that controller object along with what's left
of the remainder.
'''
prev_obj = None
while True:
if obj is None:
raise PecanNotFound
if iscontroller(obj):
return obj, remainder
# are we traversing to another controller
cross_boundary(prev_obj, obj)
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, [])
default = getattr(obj, '_default', None)
if iscontroller(default):
notfound_handlers.append(('_default', default, remainder))
lookup = getattr(obj, '_lookup', None)
if iscontroller(lookup):
notfound_handlers.append(('_lookup', lookup, remainder))
route = getattr(obj, '_route', None)
if iscontroller(route):
if len(getargspec(route).args) == 2:
warnings.warn(
(
"The function signature for %s.%s._route is changing "
"in the next version of pecan.\nPlease update to: "
"`def _route(self, args, request)`." % (
obj.__class__.__module__,
obj.__class__.__name__
)
),
DeprecationWarning
)
next_obj, next_remainder = route(remainder)
else:
next_obj, next_remainder = route(remainder, request)
cross_boundary(route, next_obj)
return next_obj, next_remainder
if not remainder:
raise PecanNotFound
prev_obj = obj
remainder = rest
obj = getattr(obj, next_obj, None)