summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2014-07-21 23:43:26 -0400
committerRyan Petrello <lists@ryanpetrello.com>2014-07-28 22:10:20 -0400
commit01c9a110fc605bbd59f31a341a55d634b3a8119f (patch)
tree3f5682fc5c16116e5e1237c6619eecae6bf68f16
parent236301f6d7a8128513b8d45114b04a33189502ea (diff)
downloadpecan-01c9a110fc605bbd59f31a341a55d634b3a8119f.tar.gz
Provide `pecan.state.arguments` for inspecting controller call arguments
Change-Id: Ibbd8b2f075a875b109c7309bc42e0d1f1d5ae610
-rw-r--r--docs/source/hooks.rst4
-rw-r--r--pecan/core.py28
-rw-r--r--pecan/tests/test_hooks.py369
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 = []