diff options
-rw-r--r-- | pecan/commands/base.py | 2 | ||||
-rw-r--r-- | pecan/commands/create.py | 2 | ||||
-rw-r--r-- | pecan/core.py | 15 | ||||
-rw-r--r-- | pecan/middleware/debug.py | 8 | ||||
-rw-r--r-- | pecan/tests/middleware/test_debug.py | 2 | ||||
-rw-r--r-- | pecan/tests/test_no_thread_locals.py | 87 |
6 files changed, 106 insertions, 10 deletions
diff --git a/pecan/commands/base.py b/pecan/commands/base.py index 8f7d3dc..441d577 100644 --- a/pecan/commands/base.py +++ b/pecan/commands/base.py @@ -46,7 +46,7 @@ class CommandManager(object): continue try: cmd = ep.load() - assert hasattr(cmd, 'run') + cmd.run # ensure existance; catch AttributeError otherwise except Exception as e: # pragma: nocover warn("Unable to load plugin %s: %s" % (ep, e), RuntimeWarning) continue diff --git a/pecan/commands/create.py b/pecan/commands/create.py index b6eec2a..1187489 100644 --- a/pecan/commands/create.py +++ b/pecan/commands/create.py @@ -22,7 +22,7 @@ class ScaffoldManager(object): log.debug('%s loading scaffold %s', self.__class__.__name__, ep) try: cmd = ep.load() - assert hasattr(cmd, 'copy_to') + cmd.copy_to # ensure existance; catch AttributeError otherwise except Exception as e: # pragma: nocover warn( "Unable to load scaffold %s: %s" % (ep, e), RuntimeWarning diff --git a/pecan/core.py b/pecan/core.py index 934931a..49c7e8e 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) @@ -255,7 +259,8 @@ class PecanBase(object): module = __import__(name, fromlist=fromlist) kallable = getattr(module, parts[-1]) msg = "%s does not represent a callable class or function." - assert hasattr(kallable, '__call__'), msg % item + if not six.callable(kallable): + raise TypeError(msg % item) return kallable() raise ImportError('No item named %s' % item) @@ -737,7 +742,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/middleware/debug.py b/pecan/middleware/debug.py index 0203245..764e8e9 100644 --- a/pecan/middleware/debug.py +++ b/pecan/middleware/debug.py @@ -269,9 +269,11 @@ class DebugMiddleware(object): self.debugger = debugger def __call__(self, environ, start_response): - assert not environ['wsgi.multiprocess'], ( - "The DebugMiddleware middleware is not usable in a " - "multi-process environment") + if environ['wsgi.multiprocess']: + raise RuntimeError( + "The DebugMiddleware middleware is not usable in a " + "multi-process environment" + ) if environ.get('paste.testing'): return self.app(environ, start_response) diff --git a/pecan/tests/middleware/test_debug.py b/pecan/tests/middleware/test_debug.py index de9d4cd..bc9d65f 100644 --- a/pecan/tests/middleware/test_debug.py +++ b/pecan/tests/middleware/test_debug.py @@ -58,7 +58,7 @@ class TestDebugMiddleware(PecanTestCase): app = TestApp(MultiProcessApp(DebugMiddleware(conditional_error_app))) self.assertRaises( - AssertionError, + RuntimeError, app.get, '/' ) 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' |