summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in1
-rw-r--r--docs/source/hooks.rst4
-rw-r--r--pecan/core.py28
-rw-r--r--pecan/hooks.py11
-rw-r--r--pecan/rest.py28
-rw-r--r--pecan/tests/test_hooks.py409
-rw-r--r--pecan/tests/test_rest.py123
-rw-r--r--setup.cfg2
8 files changed, 576 insertions, 30 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 79e6393..bcffd6c 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,3 +4,4 @@ recursive-include pecan/scaffolds/rest-api *
include pecan/scaffolds/rest-api/*
include pecan/middleware/resources/*
include LICENSE README.rst requirements.txt
+recursive-include pecan/tests *
diff --git a/docs/source/hooks.rst b/docs/source/hooks.rst
index 1cdb4ef..3b9d364 100644
--- a/docs/source/hooks.rst
+++ b/docs/source/hooks.rst
@@ -71,6 +71,10 @@ response objects, and which controller was selected by Pecan's routing::
# and used to generate the response body
#
assert state.controller.__func__ is RootController.index.__func__
+ assert isinstance(state.arguments, inspect.Arguments)
+ print state.arguments.args
+ print state.arguments.varargs
+ print state.arguments.keywords
assert isinstance(state.request, webob.Request)
assert isinstance(state.response, webob.Response)
assert isinstance(state.hooks, list)
diff --git a/pecan/core.py b/pecan/core.py
index 29de33a..a52dae2 100644
--- a/pecan/core.py
+++ b/pecan/core.py
@@ -2,6 +2,7 @@ try:
from simplejson import dumps, loads
except ImportError: # pragma: no cover
from json import dumps, loads # noqa
+from inspect import Arguments
from itertools import chain, tee
from mimetypes import guess_type, add_type
from os.path import splitext
@@ -31,12 +32,14 @@ logger = logging.getLogger(__name__)
class RoutingState(object):
- def __init__(self, request, response, app, hooks=[], controller=None):
+ def __init__(self, request, response, app, hooks=[], controller=None,
+ arguments=None):
self.request = request
self.response = response
self.app = app
self.hooks = hooks
self.controller = controller
+ self.arguments = arguments
class Request(WebObRequest):
@@ -326,6 +329,7 @@ class PecanBase(object):
passed the argument specification for the controller.
'''
args = []
+ varargs = []
kwargs = dict()
valid_args = argspec.args[1:] # pop off `self`
pecan_state = state.request.pecan
@@ -354,7 +358,7 @@ class PecanBase(object):
if [i for i in remainder if i]:
if not argspec[1]:
abort(404)
- args.extend(remainder)
+ varargs.extend(remainder)
# get the default positional arguments
if argspec[3]:
@@ -377,7 +381,7 @@ class PecanBase(object):
if name not in argspec[0]:
kwargs[encode_if_needed(name)] = value
- return args, kwargs
+ return args, varargs, kwargs
def render(self, template, namespace):
renderer = self.renderers.get(
@@ -492,9 +496,6 @@ class PecanBase(object):
)
raise exc.HTTPNotFound
- # handle "before" hooks
- self.handle_hooks(self.determine_hooks(controller), 'before', state)
-
# fetch any parameters
if req.method == 'GET':
params = dict(req.GET)
@@ -502,15 +503,19 @@ class PecanBase(object):
params = dict(req.params)
# fetch the arguments for the controller
- args, kwargs = self.get_args(
+ args, varargs, kwargs = self.get_args(
state,
params,
remainder,
cfg['argspec'],
im_self
)
+ state.arguments = Arguments(args, varargs, kwargs)
+
+ # handle "before" hooks
+ self.handle_hooks(self.determine_hooks(controller), 'before', state)
- return controller, args, kwargs
+ return controller, args+varargs, kwargs
def invoke_controller(self, controller, args, kwargs, state):
'''
@@ -697,11 +702,11 @@ class ExplicitPecan(PecanBase):
except IndexError:
raise signature_error
- args, kwargs = super(ExplicitPecan, self).get_args(
+ args, varargs, kwargs = super(ExplicitPecan, self).get_args(
state, all_params, remainder, argspec, im_self
)
args = [state.request, state.response] + args
- return args, kwargs
+ return args, varargs, kwargs
class Pecan(PecanBase):
@@ -753,12 +758,14 @@ class Pecan(PecanBase):
state.hooks = []
state.app = self
state.controller = None
+ state.arguments = None
return super(Pecan, self).__call__(environ, start_response)
finally:
del state.hooks
del state.request
del state.response
del state.controller
+ del state.arguments
del state.app
def init_context_local(self, local_factory):
@@ -772,6 +779,7 @@ class Pecan(PecanBase):
state.response = _state.response
controller, args, kw = super(Pecan, self).find_controller(_state)
state.controller = controller
+ state.arguments = _state.arguments
return controller, args, kw
def handle_hooks(self, hooks, *args, **kw):
diff --git a/pecan/hooks.py b/pecan/hooks.py
index 57392d7..f1f7073 100644
--- a/pecan/hooks.py
+++ b/pecan/hooks.py
@@ -1,3 +1,4 @@
+import types
import sys
from inspect import getmembers
@@ -27,7 +28,15 @@ def walk_controller(root_class, controller, hooks):
for hook in hooks:
value._pecan.setdefault('hooks', set()).add(hook)
elif hasattr(value, '__class__'):
- if name.startswith('__') and name.endswith('__'):
+ # Skip non-exposed methods that are defined in parent classes;
+ # they're internal implementation details of that class, and
+ # not actual routable controllers, so we shouldn't bother
+ # assigning hooks to them.
+ if (
+ isinstance(value, types.MethodType) and
+ any(filter(lambda c: value.__func__ in c.__dict__.values(),
+ value.im_class.mro()[1:]))
+ ):
continue
walk_controller(root_class, value, hooks)
diff --git a/pecan/rest.py b/pecan/rest.py
index e78d287..a6feae2 100644
--- a/pecan/rest.py
+++ b/pecan/rest.py
@@ -43,6 +43,19 @@ class RestController(object):
return argspec.args[3:]
return argspec.args[1:]
+ def _handle_bad_rest_arguments(self, controller, remainder, request):
+ """
+ Ensure that the argspec for a discovered controller actually matched
+ the positional arguments in the request path. If not, raise
+ a webob.exc.HTTPBadRequest.
+ """
+ argspec = self._get_args_for_controller(controller)
+ fixed_args = len(argspec) - len(
+ request.pecan.get('routing_args', [])
+ )
+ if len(remainder) < fixed_args:
+ abort(400)
+
@expose()
def _route(self, args, request=None):
'''
@@ -89,10 +102,10 @@ class RestController(object):
_lookup_result = self._handle_lookup(args, request)
if _lookup_result:
return _lookup_result
- except exc.HTTPNotFound:
+ except (exc.HTTPClientError, exc.HTTPNotFound):
#
- # If the matching handler results in a 404, attempt to handle
- # a _lookup method (if it exists)
+ # If the matching handler results in a 400 or 404, attempt to
+ # handle a _lookup method (if it exists)
#
_lookup_result = self._handle_lookup(args, request)
if _lookup_result:
@@ -201,14 +214,10 @@ class RestController(object):
# route to a get_all or get if no additional parts are available
if not remainder or remainder == ['']:
+ remainder = list(six.moves.filter(bool, remainder))
controller = self._find_controller('get_all', 'get')
if controller:
- argspec = self._get_args_for_controller(controller)
- fixed_args = len(argspec) - len(
- request.pecan.get('routing_args', [])
- )
- if len(remainder) < fixed_args:
- abort(404)
+ self._handle_bad_rest_arguments(controller, remainder, request)
return controller, []
abort(404)
@@ -232,6 +241,7 @@ class RestController(object):
# finally, check for the regular get_one/get requests
controller = self._find_controller('get_one', 'get')
if controller:
+ self._handle_bad_rest_arguments(controller, remainder, request)
return controller, remainder
abort(404)
diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py
index 8f1ca39..d3fe05b 100644
--- a/pecan/tests/test_hooks.py
+++ b/pecan/tests/test_hooks.py
@@ -1,11 +1,13 @@
+import inspect
+import operator
+
from webtest import TestApp
+from six import PY3
from six import b as b_
from six import u as u_
from six.moves import cStringIO as StringIO
-from webob import Response
-
-from pecan import make_app, expose, redirect, abort
+from pecan import make_app, expose, redirect, abort, rest, Request, Response
from pecan.hooks import (
PecanHook, TransactionHook, HookController, RequestViewerHook
)
@@ -13,6 +15,9 @@ from pecan.configuration import Config
from pecan.decorators import transactional, after_commit, after_rollback
from pecan.tests import PecanTestCase
+# The `inspect.Arguments` namedtuple is different between PY2/3
+kwargs = operator.attrgetter('varkw' if PY3 else 'keywords')
+
class TestHooks(PecanTestCase):
@@ -412,6 +417,364 @@ class TestHooks(PecanTestCase):
assert run_hook[3] == 'last - before hook', run_hook[3]
+class TestStateAccess(PecanTestCase):
+
+ def setUp(self):
+ super(TestStateAccess, self).setUp()
+ self.args = None
+
+ class RootController(object):
+ @expose()
+ def index(self):
+ return 'Hello, World!'
+
+ @expose()
+ def greet(self, name):
+ return 'Hello, %s!' % name
+
+ @expose()
+ def greetmore(self, *args):
+ return 'Hello, %s!' % args[0]
+
+ @expose()
+ def kwargs(self, **kw):
+ return 'Hello, %s!' % kw['name']
+
+ @expose()
+ def mixed(self, first, second, *args):
+ return 'Mixed'
+
+ class SimpleHook(PecanHook):
+ def before(inself, state):
+ self.args = (state.controller, state.arguments)
+
+ self.root = RootController()
+ self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
+
+ def test_no_args(self):
+ self.app.get('/')
+ assert self.args[0] == self.root.index
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_single_arg(self):
+ self.app.get('/greet/joe')
+ assert self.args[0] == self.root.greet
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['joe']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_single_vararg(self):
+ self.app.get('/greetmore/joe')
+ assert self.args[0] == self.root.greetmore
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == ['joe']
+ assert kwargs(self.args[1]) == {}
+
+ def test_single_kw(self):
+ self.app.get('/kwargs/?name=joe')
+ assert self.args[0] == self.root.kwargs
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'name': 'joe'}
+
+ def test_single_kw_post(self):
+ self.app.post('/kwargs/', params={'name': 'joe'})
+ assert self.args[0] == self.root.kwargs
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'name': 'joe'}
+
+ def test_mixed_args(self):
+ self.app.get('/mixed/foo/bar/spam/eggs')
+ assert self.args[0] == self.root.mixed
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['foo', 'bar']
+ assert self.args[1].varargs == ['spam', 'eggs']
+
+
+class TestStateAccessWithoutThreadLocals(PecanTestCase):
+
+ def setUp(self):
+ super(TestStateAccessWithoutThreadLocals, self).setUp()
+ self.args = None
+
+ class RootController(object):
+ @expose()
+ def index(self, req, resp):
+ return 'Hello, World!'
+
+ @expose()
+ def greet(self, req, resp, name):
+ return 'Hello, %s!' % name
+
+ @expose()
+ def greetmore(self, req, resp, *args):
+ return 'Hello, %s!' % args[0]
+
+ @expose()
+ def kwargs(self, req, resp, **kw):
+ return 'Hello, %s!' % kw['name']
+
+ @expose()
+ def mixed(self, req, resp, first, second, *args):
+ return 'Mixed'
+
+ class SimpleHook(PecanHook):
+ def before(inself, state):
+ self.args = (state.controller, state.arguments)
+
+ self.root = RootController()
+ self.app = TestApp(make_app(
+ self.root,
+ hooks=[SimpleHook()],
+ use_context_locals=False
+ ))
+
+ def test_no_args(self):
+ self.app.get('/')
+ assert self.args[0] == self.root.index
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert len(self.args[1].args) == 2
+ assert isinstance(self.args[1].args[0], Request)
+ assert isinstance(self.args[1].args[1], Response)
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_single_arg(self):
+ self.app.get('/greet/joe')
+ assert self.args[0] == self.root.greet
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert len(self.args[1].args) == 3
+ assert isinstance(self.args[1].args[0], Request)
+ assert isinstance(self.args[1].args[1], Response)
+ assert self.args[1].args[2] == 'joe'
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_single_vararg(self):
+ self.app.get('/greetmore/joe')
+ assert self.args[0] == self.root.greetmore
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert len(self.args[1].args) == 2
+ assert isinstance(self.args[1].args[0], Request)
+ assert isinstance(self.args[1].args[1], Response)
+ assert self.args[1].varargs == ['joe']
+ assert kwargs(self.args[1]) == {}
+
+ def test_single_kw(self):
+ self.app.get('/kwargs/?name=joe')
+ assert self.args[0] == self.root.kwargs
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert len(self.args[1].args) == 2
+ assert isinstance(self.args[1].args[0], Request)
+ assert isinstance(self.args[1].args[1], Response)
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'name': 'joe'}
+
+ def test_single_kw_post(self):
+ self.app.post('/kwargs/', params={'name': 'joe'})
+ assert self.args[0] == self.root.kwargs
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert len(self.args[1].args) == 2
+ assert isinstance(self.args[1].args[0], Request)
+ assert isinstance(self.args[1].args[1], Response)
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'name': 'joe'}
+
+ def test_mixed_args(self):
+ self.app.get('/mixed/foo/bar/spam/eggs')
+ assert self.args[0] == self.root.mixed
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert len(self.args[1].args) == 4
+ assert isinstance(self.args[1].args[0], Request)
+ assert isinstance(self.args[1].args[1], Response)
+ assert self.args[1].args[2:] == ['foo', 'bar']
+ assert self.args[1].varargs == ['spam', 'eggs']
+
+
+class TestRestControllerStateAccess(PecanTestCase):
+
+ def setUp(self):
+ super(TestRestControllerStateAccess, self).setUp()
+ self.args = None
+
+ class RootController(rest.RestController):
+
+ @expose()
+ def _default(self, _id, *args, **kw):
+ return 'Default'
+
+ @expose()
+ def get_all(self, **kw):
+ return 'All'
+
+ @expose()
+ def get_one(self, _id, *args, **kw):
+ return 'One'
+
+ @expose()
+ def post(self, *args, **kw):
+ return 'POST'
+
+ @expose()
+ def put(self, _id, *args, **kw):
+ return 'PUT'
+
+ @expose()
+ def delete(self, _id, *args, **kw):
+ return 'DELETE'
+
+ class SimpleHook(PecanHook):
+ def before(inself, state):
+ self.args = (state.controller, state.arguments)
+
+ self.root = RootController()
+ self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
+
+ def test_get_all(self):
+ self.app.get('/')
+ assert self.args[0] == self.root.get_all
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_get_all_with_kwargs(self):
+ self.app.get('/?foo=bar')
+ assert self.args[0] == self.root.get_all
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+ def test_get_one(self):
+ self.app.get('/1')
+ assert self.args[0] == self.root.get_one
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_get_one_with_varargs(self):
+ self.app.get('/1/2/3')
+ assert self.args[0] == self.root.get_one
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == ['2', '3']
+ assert kwargs(self.args[1]) == {}
+
+ def test_get_one_with_kwargs(self):
+ self.app.get('/1?foo=bar')
+ assert self.args[0] == self.root.get_one
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+ def test_post(self):
+ self.app.post('/')
+ assert self.args[0] == self.root.post
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_post_with_varargs(self):
+ self.app.post('/foo/bar')
+ assert self.args[0] == self.root.post
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == ['foo', 'bar']
+ assert kwargs(self.args[1]) == {}
+
+ def test_post_with_kwargs(self):
+ self.app.post('/', params={'foo': 'bar'})
+ assert self.args[0] == self.root.post
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == []
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+ def test_put(self):
+ self.app.put('/1')
+ assert self.args[0] == self.root.put
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_put_with_method_argument(self):
+ self.app.post('/1?_method=put')
+ assert self.args[0] == self.root.put
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'_method': 'put'}
+
+ def test_put_with_varargs(self):
+ self.app.put('/1/2/3')
+ assert self.args[0] == self.root.put
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == ['2', '3']
+ assert kwargs(self.args[1]) == {}
+
+ def test_put_with_kwargs(self):
+ self.app.put('/1?foo=bar')
+ assert self.args[0] == self.root.put
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+ def test_delete(self):
+ self.app.delete('/1')
+ assert self.args[0] == self.root.delete
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {}
+
+ def test_delete_with_method_argument(self):
+ self.app.post('/1?_method=delete')
+ assert self.args[0] == self.root.delete
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'_method': 'delete'}
+
+ def test_delete_with_varargs(self):
+ self.app.delete('/1/2/3')
+ assert self.args[0] == self.root.delete
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == ['2', '3']
+ assert kwargs(self.args[1]) == {}
+
+ def test_delete_with_kwargs(self):
+ self.app.delete('/1?foo=bar')
+ assert self.args[0] == self.root.delete
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+ def test_post_with_invalid_method_kwarg(self):
+ self.app.post('/1?_method=invalid')
+ assert self.args[0] == self.root._default
+ assert isinstance(self.args[1], inspect.Arguments)
+ assert self.args[1].args == ['1']
+ assert self.args[1].varargs == []
+ assert kwargs(self.args[1]) == {'_method': 'invalid'}
+
+
class TestTransactionHook(PecanTestCase):
def test_transaction_hook(self):
run_hook = []
@@ -1293,3 +1656,43 @@ class TestRequestViewerHook(PecanTestCase):
viewer = RequestViewerHook(conf)
assert viewer.items == ['url']
+
+
+class TestRestControllerWithHooks(PecanTestCase):
+
+ def test_restcontroller_with_hooks(self):
+
+ class SomeHook(PecanHook):
+
+ def before(self, state):
+ state.response.headers['X-Testing'] = 'XYZ'
+
+ class BaseController(rest.RestController):
+
+ @expose()
+ def delete(self, _id):
+ return 'Deleting %s' % _id
+
+ class RootController(BaseController, HookController):
+
+ __hooks__ = [SomeHook()]
+
+ @expose()
+ def get_all(self):
+ return 'Hello, World!'
+
+ app = TestApp(
+ make_app(
+ RootController()
+ )
+ )
+
+ response = app.get('/')
+ assert response.status_int == 200
+ assert response.body == b_('Hello, World!')
+ assert response.headers['X-Testing'] == 'XYZ'
+
+ response = app.delete('/100/')
+ assert response.status_int == 200
+ assert response.body == b_('Deleting 100')
+ assert response.headers['X-Testing'] == 'XYZ'
diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py
index 7e1e8b6..e63c5e7 100644
--- a/pecan/tests/test_rest.py
+++ b/pecan/tests/test_rest.py
@@ -7,7 +7,7 @@ except:
from six import b as b_
-from pecan import abort, expose, make_app, response
+from pecan import abort, expose, make_app, response, redirect
from pecan.rest import RestController
from pecan.tests import PecanTestCase
@@ -681,6 +681,117 @@ class TestRestController(PecanTestCase):
assert r.status_int == 200
assert len(loads(r.body.decode())['items']) == 1
+ def test_nested_get_all(self):
+
+ class BarsController(RestController):
+
+ @expose()
+ def get_one(self, foo_id, id):
+ return '4'
+
+ @expose()
+ def get_all(self, foo_id):
+ return '3'
+
+ class FoosController(RestController):
+
+ bars = BarsController()
+
+ @expose()
+ def get_one(self, id):
+ return '2'
+
+ @expose()
+ def get_all(self):
+ return '1'
+
+ class RootController(object):
+ foos = FoosController()
+
+ # create the app
+ app = TestApp(make_app(RootController()))
+
+ r = app.get('/foos/')
+ assert r.status_int == 200
+ assert r.body == b_('1')
+
+ r = app.get('/foos/1/')
+ assert r.status_int == 200
+ assert r.body == b_('2')
+
+ r = app.get('/foos/1/bars/')
+ assert r.status_int == 200
+ assert r.body == b_('3')
+
+ r = app.get('/foos/1/bars/2/')
+ assert r.status_int == 200
+ assert r.body == b_('4')
+
+ r = app.get('/foos/bars/', status=400)
+ assert r.status_int == 400
+
+ r = app.get('/foos/bars/1', status=400)
+ assert r.status_int == 400
+
+ def test_nested_get_all_with_lookup(self):
+
+ class BarsController(RestController):
+
+ @expose()
+ def get_one(self, foo_id, id):
+ return '4'
+
+ @expose()
+ def get_all(self, foo_id):
+ return '3'
+
+ @expose('json')
+ def _lookup(self, id, *remainder):
+ redirect('/lookup-hit/')
+
+ class FoosController(RestController):
+
+ bars = BarsController()
+
+ @expose()
+ def get_one(self, id):
+ return '2'
+
+ @expose()
+ def get_all(self):
+ return '1'
+
+ class RootController(object):
+ foos = FoosController()
+
+ # create the app
+ app = TestApp(make_app(RootController()))
+
+ r = app.get('/foos/')
+ assert r.status_int == 200
+ assert r.body == b_('1')
+
+ r = app.get('/foos/1/')
+ assert r.status_int == 200
+ assert r.body == b_('2')
+
+ r = app.get('/foos/1/bars/')
+ assert r.status_int == 200
+ assert r.body == b_('3')
+
+ r = app.get('/foos/1/bars/2/')
+ assert r.status_int == 200
+ assert r.body == b_('4')
+
+ r = app.get('/foos/bars/', status=400)
+ assert r.status_int == 400
+
+ r = app.get('/foos/bars/', status=400)
+
+ r = app.get('/foos/bars/1')
+ assert r.status_int == 302
+ assert r.headers['Location'].endswith('/lookup-hit/')
+
def test_bad_rest(self):
class ThingsController(RestController):
@@ -773,16 +884,16 @@ class TestRestController(PecanTestCase):
# test get_all
r = app.get('/foos')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=FoosController.data)))
+ self.assertEqual(r.status_int, 200)
+ self.assertEqual(r.body, b_(dumps(dict(items=FoosController.data))))
# test nested get_all
r = app.get('/foos/1/bars')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=BarsController.data[1])))
+ self.assertEqual(r.status_int, 200)
+ self.assertEqual(r.body, b_(dumps(dict(items=BarsController.data[1]))))
r = app.get('/foos/bars', expect_errors=True)
- assert r.status_int == 404
+ self.assertEqual(r.status_int, 400)
def test_custom_with_trailing_slash(self):
diff --git a/setup.cfg b/setup.cfg
index 821e1d3..dcd5b92 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,4 +6,4 @@ cover-package=pecan
cover-erase=1
[pytest]
-norecursedirs = +package+
+norecursedirs = +package+ config_fixtures docs .git *.egg .tox