diff options
-rw-r--r-- | docs/source/hooks.rst | 4 | ||||
-rw-r--r-- | pecan/core.py | 28 | ||||
-rw-r--r-- | pecan/tests/test_hooks.py | 369 |
3 files changed, 388 insertions, 13 deletions
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 cb4cf2c..925e7ab 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): ''' @@ -691,11 +696,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): @@ -747,12 +752,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): @@ -766,6 +773,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/tests/test_hooks.py b/pecan/tests/test_hooks.py index 8f1ca39..0bfd216 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 = [] |