summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2014-09-27 09:38:31 -0400
committerRyan Petrello <lists@ryanpetrello.com>2014-09-28 21:10:18 -0400
commit2d5f5e482d77bfcaab2b4677241e85e9f1ac9b39 (patch)
tree2ba44b6158112623457ea127fbbb8fe7c02929ba
parent921a8b77dcb11d4e3668bf23a5731b665b748a92 (diff)
downloadpecan-2d5f5e482d77bfcaab2b4677241e85e9f1ac9b39.tar.gz
Resolve a bug that mixes up argument order for generic functions.
When context locals are disabled, the order of arguments passed explicitly to generic controller handlers is incorrect (and causes the user to interact with e.g., a Response object, when they're really getting a Request object). Fixes-bug: 1374683 Change-Id: I5922b0a441f1ebae032d5b0d64c9ee0f4cf018e0
-rw-r--r--pecan/core.py12
-rw-r--r--pecan/tests/test_no_thread_locals.py87
2 files changed, 96 insertions, 3 deletions
diff --git a/pecan/core.py b/pecan/core.py
index 934931a..d5e4bc2 100644
--- a/pecan/core.py
+++ b/pecan/core.py
@@ -11,6 +11,10 @@ import operator
import types
import six
+if six.PY3:
+ from .compat import is_bound_method as ismethod
+else:
+ from inspect import ismethod
from webob import (Request as WebObRequest, Response as WebObResponse, exc,
acceptparse)
@@ -737,7 +741,13 @@ class ExplicitPecan(PecanBase):
args, varargs, kwargs = super(ExplicitPecan, self).get_args(
state, all_params, remainder, argspec, im_self
)
- args = [state.request, state.response] + args
+
+ if ismethod(state.controller):
+ args = [state.request, state.response] + args
+ else:
+ # generic controllers have an explicit self *first*
+ # (because they're decorated functions, not instance methods)
+ args[1:1] = [state.request, state.response]
return args, varargs, kwargs
diff --git a/pecan/tests/test_no_thread_locals.py b/pecan/tests/test_no_thread_locals.py
index a114840..0345391 100644
--- a/pecan/tests/test_no_thread_locals.py
+++ b/pecan/tests/test_no_thread_locals.py
@@ -1,4 +1,5 @@
-from json import dumps
+import time
+from json import dumps, loads
import warnings
from webtest import TestApp
@@ -7,7 +8,7 @@ from six import u as u_
import webob
import mock
-from pecan import Pecan, expose, abort
+from pecan import Pecan, expose, abort, Request, Response
from pecan.rest import RestController
from pecan.hooks import PecanHook, HookController
from pecan.tests import PecanTestCase
@@ -1355,3 +1356,85 @@ class TestHooks(PecanTestCase):
assert run_hook[3] == 'inside_sub'
assert run_hook[4] == 'after1'
assert run_hook[5] == 'after2'
+
+
+class TestGeneric(PecanTestCase):
+
+ @property
+ def root(self):
+ class RootController(object):
+
+ def __init__(self, unique):
+ self.unique = unique
+
+ @expose(generic=True, template='json')
+ def index(self, req, resp):
+ assert self.__class__.__name__ == 'RootController'
+ assert isinstance(req, Request)
+ assert isinstance(resp, Response)
+ assert self.unique == req.headers.get('X-Unique')
+ return {'hello': 'world'}
+
+ @index.when(method='POST', template='json')
+ def index_post(self, req, resp):
+ assert self.__class__.__name__ == 'RootController'
+ assert isinstance(req, Request)
+ assert isinstance(resp, Response)
+ assert self.unique == req.headers.get('X-Unique')
+ return req.json
+
+ @expose(template='json')
+ def echo(self, req, resp):
+ assert self.__class__.__name__ == 'RootController'
+ assert isinstance(req, Request)
+ assert isinstance(resp, Response)
+ assert self.unique == req.headers.get('X-Unique')
+ return req.json
+
+ @expose(template='json')
+ def extra(self, req, resp, first, second):
+ assert self.__class__.__name__ == 'RootController'
+ assert isinstance(req, Request)
+ assert isinstance(resp, Response)
+ assert self.unique == req.headers.get('X-Unique')
+ return {'first': first, 'second': second}
+
+ return RootController
+
+ def test_generics_with_im_self_default(self):
+ uniq = str(time.time())
+ with mock.patch('threading.local', side_effect=AssertionError()):
+ app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
+ r = app.get('/', headers={'X-Unique': uniq})
+ assert r.status_int == 200
+ json_resp = loads(r.body.decode())
+ assert json_resp['hello'] == 'world'
+
+ def test_generics_with_im_self_with_method(self):
+ uniq = str(time.time())
+ with mock.patch('threading.local', side_effect=AssertionError()):
+ app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
+ r = app.post_json('/', {'foo': 'bar'}, headers={'X-Unique': uniq})
+ assert r.status_int == 200
+ json_resp = loads(r.body.decode())
+ assert json_resp['foo'] == 'bar'
+
+ def test_generics_with_im_self_with_path(self):
+ uniq = str(time.time())
+ with mock.patch('threading.local', side_effect=AssertionError()):
+ app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
+ r = app.post_json('/echo/', {'foo': 'bar'},
+ headers={'X-Unique': uniq})
+ assert r.status_int == 200
+ json_resp = loads(r.body.decode())
+ assert json_resp['foo'] == 'bar'
+
+ def test_generics_with_im_self_with_extra_args(self):
+ uniq = str(time.time())
+ with mock.patch('threading.local', side_effect=AssertionError()):
+ app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
+ r = app.get('/extra/123/456', headers={'X-Unique': uniq})
+ assert r.status_int == 200
+ json_resp = loads(r.body.decode())
+ assert json_resp['first'] == '123'
+ assert json_resp['second'] == '456'