summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt16
-rw-r--r--docs/api/interfaces.rst22
-rw-r--r--docs/index.rst1
-rw-r--r--docs/narr/views.rst43
-rw-r--r--repoze/bfg/interfaces.py25
-rw-r--r--repoze/bfg/router.py9
-rw-r--r--repoze/bfg/tests/test_router.py54
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):