summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-06-25 21:25:49 +0000
committerGerrit Code Review <review@openstack.org>2014-06-25 21:25:49 +0000
commit9017d113cdd9cddb53894fbba67227e94755a509 (patch)
tree60a4ff6883f2bb7a0428d86fcb51ac14a49797eb
parent3ab38a675086426aca086dbd66bfde667ecb6ade (diff)
parent21f70bbba74ab940b3bf24bfb4cc859fe8926492 (diff)
downloadpecan-9017d113cdd9cddb53894fbba67227e94755a509.tar.gz
Merge "Add support for specifying custom request and response implementations."
-rw-r--r--docs/source/routing.rst32
-rw-r--r--pecan/__init__.py8
-rw-r--r--pecan/core.py32
-rw-r--r--pecan/tests/test_base.py39
4 files changed, 95 insertions, 16 deletions
diff --git a/docs/source/routing.rst b/docs/source/routing.rst
index be8e987..68a46ba 100644
--- a/docs/source/routing.rst
+++ b/docs/source/routing.rst
@@ -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
----------------------------
diff --git a/pecan/__init__.py b/pecan/__init__.py
index c294572..4c52713 100644
--- a/pecan/__init__.py
+++ b/pecan/__init__.py
@@ -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'
]
diff --git a/pecan/core.py b/pecan/core.py
index eb3fe69..4227d42 100644
--- a/pecan/core.py
+++ b/pecan/core.py
@@ -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
@@ -569,8 +581,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
@@ -599,7 +611,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):
@@ -672,6 +684,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):
diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py
index c076139..c6f9024 100644
--- a/pecan/tests/test_base.py
+++ b/pecan/tests/test_base.py
@@ -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):