feat(API): Support custom Request and Response classes
This patch adds support for supplying custom request and response classes. As part of this work, a new "context" attribute was added to Request, and its type may be overridden by setting the "context_type" class attribute in a custom Request class. The alternative to using a class attribute would have been to pass the context type into the api, and then each time a class was instantiated, pass the type into it's initializer. After discussion on IRC, it was decided that using a class attribute would be more elegant. See commentary at: https://github.com/racker/falcon/pull/256 Closes #256 and #248
This commit is contained in:
@@ -42,13 +42,22 @@ class API(object):
|
||||
after (callable, optional): A global action hook (or list of hooks)
|
||||
to call after each on_* responder, for all resources. Similar to
|
||||
the ``after`` decorator, but applies to the entire API.
|
||||
request_type (Request, optional): Request-alike class to use instead
|
||||
of Falcon's default class. Useful if you wish to extend
|
||||
``falcon.request.Request`` with a custom ``context_type``.
|
||||
(default falcon.request.Request)
|
||||
response_type (Response, optional): Response-alike class to use
|
||||
instead of Falcon's default class. (default
|
||||
falcon.response.Response)
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('_after', '_before', '_error_handlers', '_media_type',
|
||||
__slots__ = ('_after', '_before', '_request_type', '_response_type',
|
||||
'_error_handlers', '_media_type',
|
||||
'_routes', '_default_route', '_sinks')
|
||||
|
||||
def __init__(self, media_type=DEFAULT_MEDIA_TYPE, before=None, after=None):
|
||||
def __init__(self, media_type=DEFAULT_MEDIA_TYPE, before=None, after=None,
|
||||
request_type=Request, response_type=Response):
|
||||
self._routes = []
|
||||
self._sinks = []
|
||||
self._default_route = None
|
||||
@@ -57,6 +66,9 @@ class API(object):
|
||||
self._before = helpers.prepare_global_hooks(before)
|
||||
self._after = helpers.prepare_global_hooks(after)
|
||||
|
||||
self._request_type = request_type
|
||||
self._response_type = response_type
|
||||
|
||||
self._error_handlers = []
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
@@ -75,8 +87,8 @@ class API(object):
|
||||
|
||||
"""
|
||||
|
||||
req = Request(env)
|
||||
resp = Response()
|
||||
req = self._request_type(env)
|
||||
resp = self._response_type()
|
||||
|
||||
responder, params = self._get_responder(
|
||||
req.path, req.method)
|
||||
|
||||
@@ -61,6 +61,14 @@ class Request(object):
|
||||
hosting).
|
||||
env (dict): Reference to the WSGI *environ* dict passed in from the
|
||||
server. See also PEP-3333.
|
||||
context (dict): Dictionary to hold any data about the request which is
|
||||
specific to your app (e.g. session object). Falcon itself will
|
||||
not interact with this attribute after it has been initialized.
|
||||
context_type (None): Custom callable/type to use for initializing the
|
||||
``context`` attribute. To change this value so that ``context``
|
||||
is initialized to the type of your choice (e.g. OrderedDict), you
|
||||
will need to extend this class and pass that new type to the
|
||||
``request_type`` argument of ``falcon.API()``.
|
||||
uri (str): The fully-qualified URI for the request.
|
||||
url (str): alias for ``uri``.
|
||||
relative_uri (str): The path + query string portion of the full URI.
|
||||
@@ -137,12 +145,24 @@ class Request(object):
|
||||
'path',
|
||||
'query_string',
|
||||
'stream',
|
||||
'context',
|
||||
'_wsgierrors',
|
||||
)
|
||||
|
||||
# Allow child classes to override this
|
||||
context_type = None
|
||||
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
|
||||
if self.context_type is None:
|
||||
# Literal syntax is more efficient than using dict()
|
||||
self.context = {}
|
||||
else:
|
||||
# pylint will detect this as not-callable because it only sees the
|
||||
# declaration of None, not whatever type a subclass may have set.
|
||||
self.context = self.context_type() # pylint: disable=not-callable
|
||||
|
||||
self._wsgierrors = env['wsgi.errors']
|
||||
self.stream = env['wsgi.input']
|
||||
self.method = env['REQUEST_METHOD']
|
||||
|
||||
33
tests/test_request_context.py
Normal file
33
tests/test_request_context.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import falcon.testing as testing
|
||||
|
||||
from falcon.request import Request
|
||||
|
||||
|
||||
class TestRequestContext(testing.TestBase):
|
||||
|
||||
def test_default_request_context(self):
|
||||
env = testing.create_environ()
|
||||
req = Request(env)
|
||||
self.assertIsInstance(req.context, dict)
|
||||
|
||||
def test_custom_request_context(self):
|
||||
|
||||
# Define a Request-alike with a custom context type
|
||||
class MyCustomContextType():
|
||||
pass
|
||||
|
||||
class MyCustomRequest(Request):
|
||||
context_type = MyCustomContextType
|
||||
|
||||
env = testing.create_environ()
|
||||
req = MyCustomRequest(env)
|
||||
self.assertIsInstance(req.context, MyCustomContextType)
|
||||
|
||||
def test_custom_request_context_failure(self):
|
||||
|
||||
# Define a Request-alike with a non-callable custom context type
|
||||
class MyCustomRequest(Request):
|
||||
context_type = False
|
||||
|
||||
env = testing.create_environ()
|
||||
self.assertRaises(TypeError, MyCustomRequest, env)
|
||||
Reference in New Issue
Block a user