summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2014-06-25 11:19:43 -0400
committerRyan Petrello <lists@ryanpetrello.com>2014-06-25 11:41:53 -0400
commit21f70bbba74ab940b3bf24bfb4cc859fe8926492 (patch)
treeab41f4d7fbc115680355babfbc05049d14984acd
parent6c7a8b3ee9658c4bb2a2d72f241720861d1da56f (diff)
downloadpecan-21f70bbba74ab940b3bf24bfb4cc859fe8926492.tar.gz
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
-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 44bad0e..c2f1dab 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
@@ -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):
diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py
index 81471ec..89b7a43 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):