diff options
| -rw-r--r-- | CHANGES.txt | 16 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 22 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | docs/narr/views.rst | 43 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 25 | ||||
| -rw-r--r-- | repoze/bfg/router.py | 9 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_router.py | 54 |
7 files changed, 166 insertions, 4 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 5bda29b6d..0c5251d3a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -40,6 +40,22 @@ Features of registries in order to make it possible to call one BFG application from inside another. +- An interface specific to the HTTP verb (GET/PUT/POST/DELETE/HEAD) is + attached to each request object on ingress. The HTTP-verb-related + interfaces are defined in ``repoze.bfg.interfaces`` and are + ``IGETRequest``, ``IPOSTRequest``, ``IPUTRequest``, + ``IDELETERequest`` and ``IHEADRequest``. These interfaces can be + specified as the ``request_type`` attribute of a bfg view + declaration. A view naming a specific HTTP-verb-matching interface + will be found only if the view is defined with a request_type that + matches the HTTP verb in the incoming request. The more general + ``IRequest`` interface can be used as the request_type to catch all + requests (and this is indeed the default). All requests implement + ``IRequest``. The HTTP-verb-matching idea was pioneered by + `repoze.bfg.restrequest + <http://pypi.python.org/pypi/repoze.bfg.restrequest/1.0.1>`_ . That + package is no longer required, but still functions fine. + Bug Fixes --------- diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst new file mode 100644 index 000000000..e174966a4 --- /dev/null +++ b/docs/api/interfaces.rst @@ -0,0 +1,22 @@ +.. _interfaces_module: + +:mod:`repoze.bfg.interfaces` +============================ + +Request-related interfaces +--------------------------- + +.. automodule:: repoze.bfg.interfaces + + .. autoclass:: IRequest + + .. autoclass:: IGETRequest + + .. autoclass:: IPOSTRequest + + .. autoclass:: IPUTRequest + + .. autoclass:: IDELETERequest + + .. autoclass:: IHEADRequest + diff --git a/docs/index.rst b/docs/index.rst index 917bf8fe4..13edb8a48 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,6 +47,7 @@ Per-module :mod:`repoze.bfg` API documentation. :maxdepth: 2 api/events + api/interfaces api/push api/router api/security diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 8cb00767a..3b60d1394 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -264,13 +264,52 @@ View Request Types You can optionally add a *request_type* attribute to your ``view`` declaration, which indicates what "kind" of request the view should be -used for. For example: +used for. If the request type for a request doesn't match the request +type that a view defines as its ``request_type`` argument, that view +won't be called. + +For example: .. code-block:: xml :linenos: <view - for=".models.IHello" + for=".models.Hello" + view=".views.handle_post" + name="handle_post" + request_type="repoze.bfg.interfaces.IPOSTRequest" + /> + +The above example registers a view for the ``IPOSTRequest`` type, so +it will only be called if the request is a POST request. Even if all +the other specifiers match (e.g. the model type is the class +``.models.Hello``, and the view_name is ``handle_post``), if the +request verb is not POST, it will not be invoked. This provides a way +to ensure that views you write are only called via specific HTTP +verbs. + +The least specific request type is ``repoze.bfg.interfaces.IRequest``. +All requests are guaranteed to implement this request type. It is +also the default request type for views that omit a ``request_type`` +argument. + +:mod:`repoze.bfg` also makes available more specific request types +matching HTTP verbs. When these are specified as a ``request_type`` +for a view, the view will be called only when the request has an HTTP +verb (aka HTTP method) matching the request type. See +:ref:`interfaces_module` for more information about available request +types. + +Custom View Request Types +------------------------- + +You can make use of *custom* view request types. For example: + +.. code-block:: xml + :linenos: + + <view + for=".models.Hello" view=".views.hello_json" name="hello.json" request_type=".interfaces.IJSONRequest" diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 70d6648e3..92e906e51 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -29,7 +29,22 @@ deprecated( ) class IRequest(Interface): - """ Marker interface for a request object """ + """ Request type interface attached to all request objects """ + +class IPOSTRequest(IRequest): + """ Request type interface attached to POST requests""" + +class IGETRequest(IRequest): + """ Request type interface attached to GET requests""" + +class IPUTRequest(IRequest): + """ Request type interface attached to PUT requests""" + +class IDELETERequest(IRequest): + """ Request type interface attached to DELETE requests""" + +class IHEADRequest(IRequest): + """ Request type interface attached to HEAD requests""" class IResponse(Interface): status = Attribute('WSGI status code of response') @@ -149,3 +164,11 @@ class ILocation(Interface): class ILogger(Interface): """ Interface representing a PEP 282 logger """ +HTTP_METHOD_INTERFACES = { + 'GET':IGETRequest, + 'POST':IPOSTRequest, + 'PUT':IPUTRequest, + 'DELETE':IDELETERequest, + 'HEAD':IHEADRequest, + } + diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index ffbd853dc..8e1b998d8 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -7,6 +7,7 @@ from zope.component import queryUtility from zope.component.event import dispatch from zope.interface import directlyProvides +from zope.interface import alsoProvides from zope.interface import implements from webob import Request @@ -20,6 +21,8 @@ from repoze.bfg.events import WSGIApplicationCreatedEvent from repoze.bfg.interfaces import ILogger from repoze.bfg.interfaces import ITraverserFactory from repoze.bfg.interfaces import IRequest +from repoze.bfg.interfaces import HTTP_METHOD_INTERFACES + from repoze.bfg.interfaces import IRouter from repoze.bfg.interfaces import IRootFactory from repoze.bfg.interfaces import ISettings @@ -52,9 +55,13 @@ class Router(object): try: request = Request(environ) + directlyProvides(request, IRequest) - dispatch(NewRequest(request)) + also = HTTP_METHOD_INTERFACES.get(request.method) + if also is not None: + alsoProvides(request, also) + dispatch(NewRequest(request)) root_factory = getUtility(IRootFactory) root = root_factory(environ) traverser = getAdapter(root, ITraverserFactory) diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 35ab21f9c..298065d9b 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -440,6 +440,60 @@ class RouterTests(unittest.TestCase, PlacelessSetup): self.assertEqual(request_events[0].request.environ, environ) self.assertEqual(len(response_events), 1) self.assertEqual(response_events[0].response, response) + + def test_call_post_method(self): + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.interfaces import IPOSTRequest + from repoze.bfg.interfaces import IPUTRequest + from repoze.bfg.interfaces import IRequest + rootfactory = make_rootfactory(None) + context = DummyContext() + traversalfactory = make_traversal_factory(context, '', []) + response = DummyResponse() + response.app_iter = ['Hello world'] + view = make_view(response) + environ = self._makeEnviron(REQUEST_METHOD='POST') + self._registerTraverserFactory(traversalfactory, '', None) + self._registerView(view, '', None, None) + self._registerRootFactory(rootfactory) + router = self._makeOne(None) + start_response = DummyStartResponse() + request_events = [] + def handle_request(event): + request_events.append(event) + self._registerEventListener(handle_request, INewRequest) + result = router(environ, start_response) + request = request_events[0].request + self.failUnless(IPOSTRequest.providedBy(request)) + self.failIf(IPUTRequest.providedBy(request)) + self.failUnless(IRequest.providedBy(request)) + + def test_call_put_method(self): + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.interfaces import IPUTRequest + from repoze.bfg.interfaces import IPOSTRequest + from repoze.bfg.interfaces import IRequest + rootfactory = make_rootfactory(None) + context = DummyContext() + traversalfactory = make_traversal_factory(context, '', []) + response = DummyResponse() + response.app_iter = ['Hello world'] + view = make_view(response) + environ = self._makeEnviron(REQUEST_METHOD='PUT') + self._registerTraverserFactory(traversalfactory, '', None) + self._registerView(view, '', None, None) + self._registerRootFactory(rootfactory) + router = self._makeOne(None) + start_response = DummyStartResponse() + request_events = [] + def handle_request(event): + request_events.append(event) + self._registerEventListener(handle_request, INewRequest) + result = router(environ, start_response) + request = request_events[0].request + self.failUnless(IPUTRequest.providedBy(request)) + self.failIf(IPOSTRequest.providedBy(request)) + self.failUnless(IRequest.providedBy(request)) class MakeAppTests(unittest.TestCase, PlacelessSetup): def setUp(self): |
