From 2d5f5e482d77bfcaab2b4677241e85e9f1ac9b39 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Sat, 27 Sep 2014 09:38:31 -0400 Subject: 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 --- pecan/core.py | 12 ++++- pecan/tests/test_no_thread_locals.py | 87 +++++++++++++++++++++++++++++++++++- 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' -- cgit v1.2.1