From 972579b68740fb98de05695eed0347031fd9366b Mon Sep 17 00:00:00 2001 From: Jonathan LaCour Date: Wed, 29 Sep 2010 00:01:01 -0400 Subject: [PATCH] Switched over to Rick Copeland's iterative object-dispatch. Proved everything still works with tests. --- README | 8 ------- pecan/pecan.py | 58 +++++--------------------------------------- pecan/routing.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_base.py | 7 +++--- 4 files changed, 69 insertions(+), 64 deletions(-) create mode 100644 pecan/routing.py diff --git a/README b/README index 11e5f6a..e1f578c 100644 --- a/README +++ b/README @@ -3,14 +3,6 @@ much much smaller, with many fewer dependancies. TODO ==== - - * Change to alternate routing code lifted from Rick Copeland's - clutch: - - http://code.google.com/p/pyclutch/source/browse/trunk/clutch/controller_lookup.py - - This gives me support for _lookup controllers, which I don't - currently have. * Switch "hooks" over to Context Managers. diff --git a/pecan/pecan.py b/pecan/pecan.py index 8d4bf6f..05adf8b 100644 --- a/pecan/pecan.py +++ b/pecan/pecan.py @@ -1,6 +1,7 @@ from templating import renderers from webob import Request, Response, exc from threading import local +from routing import lookup_controller import string @@ -45,59 +46,12 @@ class Pecan(object): 'xhtml' : 'text/html', 'json' : 'application/json' }.get(format, 'text/html') - - def route(self, node, path): - - curpath = "" - nodeconf = {} - object_trail = [['root', self.root, nodeconf, curpath]] - - names = [x for x in path.strip('/').split('/') if x] + ['index'] - iternames = names[:] - while iternames: - name = iternames[0] - objname = name.translate(self.translate) - nodeconf = {} - subnode = getattr(node, objname, None) - name = iternames.pop(0) - node = subnode - curpath = "/".join((curpath, name)) - object_trail.append([name, node, nodeconf, curpath]) - - # try successive objects (reverse order) - num_candidates = len(object_trail) - 1 - for i in range(num_candidates, -1, -1): - name, candidate, nodeconf, curpath = object_trail[i] - if candidate is None: - continue - - # try a "_route" method on the current leaf. - if hasattr(candidate, "_route"): - processed_path = object_trail[i][-1] - unprocessed_path = object_trail[-1][-1].replace(processed_path, '') - return candidate._route(unprocessed_path) - - # try a "_lookup" method on the current leaf. - if hasattr(candidate, "_lookup"): - lookup = candidate._lookup(object_trail[i+1][0]) - processed_path = object_trail[i+1][-1] - unprocessed_path = object_trail[-2][-1].replace(processed_path, '') - return self.route(lookup, unprocessed_path) - - # try a "_default" method on the current leaf. - if hasattr(candidate, "_default"): - defhandler = candidate._default - if getattr(defhandler, 'exposed', False): - object_trail.insert(i+1, ["_default", defhandler, {}, curpath]) - return defhandler - - # try the current leaf. - if getattr(candidate, 'exposed', False): - return candidate - - # we didn't find anything - return None + def route(self, node, path): + path = path.split('/')[1:] + node, remainder = lookup_controller(node, path) + return node + def __call__(self, environ, start_response): # create the request object state.request = Request(environ) diff --git a/pecan/routing.py b/pecan/routing.py new file mode 100644 index 0000000..e596d8f --- /dev/null +++ b/pecan/routing.py @@ -0,0 +1,60 @@ +from webob import exc + + +def lookup_controller(obj, url_path): + remainder = url_path + notfound_handlers = [] + while True: + try: + obj, remainder = find_object(obj, remainder, notfound_handlers) + return obj, remainder + except exc.HTTPNotFound: + 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 + try: + result = obj(*remainder) + if result: + obj, remainder = result + break + except TypeError, te: + print 'Got exception calling lookup(): %s (%s)' % (te, te.args) + else: + raise exc.HTTPNotFound + + +def find_object(obj, remainder, notfound_handlers): + while True: + if obj is None: raise exc.HTTPNotFound + if iscontroller(obj): return obj, remainder + + if remainder and remainder[0] == '': + index = getattr(obj, 'index', None) + if iscontroller(index): return index, remainder[1:] + elif not remainder: + index = getattr(obj, 'index', None) + if iscontroller(index): + return index, remainder[1:] # TODO: why did I have to do this instead? + #raise exc.HTTPFound(add_slash=True) + + 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)) + + if not remainder: raise exc.HTTPNotFound + next, remainder = remainder[0], remainder[1:] + obj = getattr(obj, next, None) + + +def iscontroller(obj): + return getattr(obj, 'exposed', False) \ No newline at end of file diff --git a/tests/test_base.py b/tests/test_base.py index 69d0507..08f2285 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -78,8 +78,9 @@ class TestBase(object): def index(self): return '/' - def _lookup(self, someID): - return LookupController(someID) + @expose() + def _lookup(self, someID, *remainder): + return LookupController(someID), remainder app = TestApp(Pecan(RootController())) response = app.get('/') @@ -93,10 +94,8 @@ class TestBase(object): response = app.get('/100/name') assert response.status_int == 200 assert response.body == '/100/name' - - class TestEngines(object): def test_genshi(self):