Add support for specifying custom request and response implementations.
I noticed that people using pecan have taken to writing custom webob req/resp subclasses and monkeypatching onto `pecan.request` and `pecan.response`. Let's give them what they need to do this properly. Change-Id: If0ac953e381cec3a744388000a3b3afc0ea2525c
This commit is contained in:
@@ -272,8 +272,8 @@ Interacting with the Request and Response Object
|
||||
|
||||
For every HTTP request, Pecan maintains a :ref:`thread-local reference
|
||||
<contextlocals>` to the request and response object, ``pecan.request`` and
|
||||
``pecan.response``. These are instances of :class:`webob.request.BaseRequest`
|
||||
and :class:`webob.response.Response`, respectively, and can be interacted with
|
||||
``pecan.response``. These are instances of :class:`pecan.Request`
|
||||
and :class:`pecan.Response`, respectively, and can be interacted with
|
||||
from within Pecan controller code::
|
||||
|
||||
@pecan.expose()
|
||||
@@ -295,6 +295,34 @@ directly, there may be situations where you want to access them, such as:
|
||||
* Manually rendering a response body
|
||||
|
||||
|
||||
Extending Pecan's Request and Response Object
|
||||
---------------------------------------------
|
||||
|
||||
The request and response implementations provided by WebOb are powerful, but
|
||||
at times, it may be useful to extend application-specific behavior onto your
|
||||
request and response (such as specialized parsing of request headers or
|
||||
customized response body serialization). To do so, define custom classes that
|
||||
inherit from ``pecan.Request`` and ``pecan.Response``, respectively::
|
||||
|
||||
class MyRequest(pecan.Request):
|
||||
pass
|
||||
|
||||
class MyResponse(pecan.Response):
|
||||
pass
|
||||
|
||||
and modify your application configuration to use them::
|
||||
|
||||
from myproject import MyRequest, MyResponse
|
||||
|
||||
app = {
|
||||
'root' : 'project.controllers.root.RootController',
|
||||
'modules' : ['project'],
|
||||
'static_root' : '%(confdir)s/public',
|
||||
'template_path' : '%(confdir)s/project/templates',
|
||||
'request_cls': MyRequest,
|
||||
'response_cls': MyResponse
|
||||
}
|
||||
|
||||
Mapping Controller Arguments
|
||||
----------------------------
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from .core import (
|
||||
abort, override_template, Pecan, load_app, redirect, render,
|
||||
request, response
|
||||
abort, override_template, Pecan, Request, Response, load_app,
|
||||
redirect, render, request, response
|
||||
)
|
||||
from .decorators import expose
|
||||
from .hooks import RequestViewerHook
|
||||
@@ -21,8 +21,8 @@ import warnings
|
||||
|
||||
|
||||
__all__ = [
|
||||
'make_app', 'load_app', 'Pecan', 'request', 'response',
|
||||
'override_template', 'expose', 'conf', 'set_config', 'render',
|
||||
'make_app', 'load_app', 'Pecan', 'Request', 'Response', 'request',
|
||||
'response', 'override_template', 'expose', 'conf', 'set_config', 'render',
|
||||
'abort', 'redirect'
|
||||
]
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ import operator
|
||||
|
||||
import six
|
||||
|
||||
from webob import Request, Response, exc, acceptparse
|
||||
from webob import (Request as WebObRequest, Response as WebObResponse, exc,
|
||||
acceptparse)
|
||||
|
||||
from .compat import urlparse, unquote_plus, izip
|
||||
from .secure import handle_security
|
||||
@@ -37,6 +38,14 @@ class RoutingState(object):
|
||||
self.controller = controller
|
||||
|
||||
|
||||
class Request(WebObRequest):
|
||||
pass
|
||||
|
||||
|
||||
class Response(WebObResponse):
|
||||
pass
|
||||
|
||||
|
||||
def proxy(key):
|
||||
class ObjectProxy(object):
|
||||
|
||||
@@ -120,7 +129,7 @@ def redirect(location=None, internal=False, code=None, headers={},
|
||||
:param code: The HTTP status code to use for the redirect. Defaults to 302.
|
||||
:param headers: Any HTTP headers to send with the response, as a
|
||||
dictionary.
|
||||
:param request: The :class:`webob.request.BaseRequest` instance to use.
|
||||
:param request: The :class:`pecan.Request` instance to use.
|
||||
'''
|
||||
request = request or state.request
|
||||
|
||||
@@ -200,11 +209,14 @@ class PecanBase(object):
|
||||
template_path='templates', hooks=lambda: [],
|
||||
custom_renderers={}, extra_template_vars={},
|
||||
force_canonical=True, guess_content_type_from_ext=True,
|
||||
context_local_factory=None, **kw):
|
||||
context_local_factory=None, request_cls=Request,
|
||||
response_cls=Response, **kw):
|
||||
if isinstance(root, six.string_types):
|
||||
root = self.__translate_root__(root)
|
||||
|
||||
self.root = root
|
||||
self.request_cls = request_cls
|
||||
self.response_cls = response_cls
|
||||
self.renderers = RendererFactory(custom_renderers, extra_template_vars)
|
||||
self.default_renderer = default_renderer
|
||||
|
||||
@@ -304,7 +316,7 @@ class PecanBase(object):
|
||||
result = getattr(hook, hook_type)(*args)
|
||||
# on_error hooks can choose to return a Response, which will
|
||||
# be used instead of the standard error pages.
|
||||
if hook_type == 'on_error' and isinstance(result, Response):
|
||||
if hook_type == 'on_error' and isinstance(result, WebObResponse):
|
||||
return result
|
||||
|
||||
def get_args(self, state, all_params, remainder, argspec, im_self):
|
||||
@@ -516,7 +528,7 @@ class PecanBase(object):
|
||||
# care of filling it out
|
||||
if result is response:
|
||||
return
|
||||
elif isinstance(result, Response):
|
||||
elif isinstance(result, WebObResponse):
|
||||
state.response = result
|
||||
return
|
||||
|
||||
@@ -567,8 +579,8 @@ class PecanBase(object):
|
||||
'''
|
||||
|
||||
# create the request and response object
|
||||
req = Request(environ)
|
||||
resp = Response()
|
||||
req = self.request_cls(environ)
|
||||
resp = self.response_cls()
|
||||
state = RoutingState(req, resp, self)
|
||||
controller = None
|
||||
|
||||
@@ -597,7 +609,7 @@ class PecanBase(object):
|
||||
)
|
||||
|
||||
# if the on_error handler returned a Response, use it.
|
||||
if isinstance(on_error_result, Response):
|
||||
if isinstance(on_error_result, WebObResponse):
|
||||
state.response = on_error_result
|
||||
else:
|
||||
if not isinstance(e, exc.HTTPException):
|
||||
@@ -670,6 +682,10 @@ class Pecan(PecanBase):
|
||||
:param use_context_locals: When `True`, `pecan.request` and
|
||||
`pecan.response` will be available as
|
||||
thread-local references.
|
||||
:param request_cls: Can be used to specify a custom `pecan.request` object.
|
||||
Defaults to `pecan.Request`.
|
||||
:param response_cls: Can be used to specify a custom `pecan.response`
|
||||
object. Defaults to `pecan.Response`.
|
||||
'''
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
|
||||
@@ -14,8 +14,8 @@ from six import b as b_
|
||||
from six.moves import cStringIO as StringIO
|
||||
|
||||
from pecan import (
|
||||
Pecan, expose, request, response, redirect, abort, make_app,
|
||||
override_template, render
|
||||
Pecan, Request, Response, expose, request, response, redirect,
|
||||
abort, make_app, override_template, render
|
||||
)
|
||||
from pecan.templating import (
|
||||
_builtin_renderers as builtin_renderers, error_formatters
|
||||
@@ -954,6 +954,41 @@ class TestManualResponse(PecanTestCase):
|
||||
assert r.body == b_('Hello, World!')
|
||||
|
||||
|
||||
class TestCustomResponseandRequest(PecanTestCase):
|
||||
|
||||
def test_custom_objects(self):
|
||||
|
||||
class CustomRequest(Request):
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
headers = super(CustomRequest, self).headers
|
||||
headers['X-Custom-Request'] = 'ABC'
|
||||
return headers
|
||||
|
||||
class CustomResponse(Response):
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
headers = super(CustomResponse, self).headers
|
||||
headers['X-Custom-Response'] = 'XYZ'
|
||||
return headers
|
||||
|
||||
class RootController(object):
|
||||
@expose()
|
||||
def index(self):
|
||||
return request.headers.get('X-Custom-Request')
|
||||
|
||||
app = TestApp(Pecan(
|
||||
RootController(),
|
||||
request_cls=CustomRequest,
|
||||
response_cls=CustomResponse
|
||||
))
|
||||
r = app.get('/')
|
||||
assert r.body == b_('ABC')
|
||||
assert r.headers.get('X-Custom-Response') == 'XYZ'
|
||||
|
||||
|
||||
class TestThreadLocalState(PecanTestCase):
|
||||
|
||||
def test_thread_local_dir(self):
|
||||
|
||||
Reference in New Issue
Block a user