Add documentation for generic REST controllers.
Fixes bug 1386837 Change-Id: I9cf30c7d55026d2364d7add3f7a9ca96b3348d31
This commit is contained in:
@@ -1,13 +1,81 @@
|
||||
.. _rest:
|
||||
|
||||
Writing RESTful Web Services with Pecan
|
||||
=======================================
|
||||
Writing RESTful Web Services with Generic Controllers
|
||||
=====================================================
|
||||
|
||||
If you need to write controllers to interact with objects, using the
|
||||
:class:`~pecan.rest.RestController` may help speed things up. It follows the
|
||||
Representational State Transfer Protocol, also known as REST, by routing the
|
||||
standard HTTP verbs of ``GET``, ``POST``, ``PUT``, and ``DELETE`` to individual
|
||||
methods.
|
||||
Pecan simplifies RESTful web services by providing a way to overload URLs based
|
||||
on the request method. For most API's, the use of `generic controller`
|
||||
definitions give you everything you need to build out robust RESTful
|
||||
interfaces (and is the *recommended* approach to writing RESTful web services
|
||||
in pecan):
|
||||
|
||||
::
|
||||
|
||||
from pecan import abort, expose
|
||||
|
||||
# Note: this is *not* thread-safe. In real life, use a persistent data store.
|
||||
BOOKS = {
|
||||
'0': 'The Last of the Mohicans',
|
||||
'1': 'Catch-22'
|
||||
}
|
||||
|
||||
|
||||
class BookController(object):
|
||||
|
||||
def __init__(self, id_):
|
||||
self.id_ = id_
|
||||
assert self.book
|
||||
|
||||
@property
|
||||
def book(self):
|
||||
if self.id_ in BOOKS:
|
||||
return dict(id=self.id_, name=BOOKS[self.id_])
|
||||
abort(404)
|
||||
|
||||
# HTTP GET /<id>/
|
||||
@expose(generic=True, template='json')
|
||||
def index(self):
|
||||
return self.book
|
||||
|
||||
# HTTP PUT /<id>/
|
||||
@index.when(method='PUT', template='json')
|
||||
def index_PUT(self, **kw):
|
||||
BOOKS[self.id_] = kw['name']
|
||||
return self.book
|
||||
|
||||
# HTTP DELETE /<id>/
|
||||
@index.when(method='DELETE', template='json')
|
||||
def index_DELETE(self):
|
||||
del BOOKS[self.id_]
|
||||
return dict()
|
||||
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose()
|
||||
def _lookup(self, id_, *remainder):
|
||||
return BookController(id_), remainder
|
||||
|
||||
# HTTP GET /
|
||||
@expose(generic=True, template='json')
|
||||
def index(self):
|
||||
return [dict(id=k, name=v) for k, v in BOOKS.items()]
|
||||
|
||||
# HTTP POST /
|
||||
@index.when(method='POST', template='json')
|
||||
def index_POST(self, **kw):
|
||||
id_ = len(BOOKS)
|
||||
BOOKS[id_] = kw['name']
|
||||
return dict(id=id_, name=kw['name'])
|
||||
|
||||
|
||||
Writing RESTful Web Services with RestController
|
||||
================================================
|
||||
|
||||
.. _TurboGears2: http://turbogears.org
|
||||
|
||||
For compatability with the TurboGears2_ library, Pecan also provides
|
||||
a class-based solution to RESTful routing, :class:`~pecan.rest.RestController`:
|
||||
|
||||
::
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ Let's look at an example using ``template`` and ``content_type``:
|
||||
def hello(self):
|
||||
return {'msg': 'Hello!'}
|
||||
|
||||
You'll notice that we called :func:`~pecan.decoators.expose` three times, with
|
||||
You'll notice that we called :func:`~pecan.decorators.expose` three times, with
|
||||
different arguments.
|
||||
|
||||
::
|
||||
@@ -136,6 +136,34 @@ use the ``text/html`` content type by default.
|
||||
* :ref:`pecan_decorators`
|
||||
|
||||
|
||||
Routing Based on Request Method
|
||||
-------------------------------
|
||||
|
||||
The ``generic`` argument to :func:`~pecan.decorators.expose` provides support for overloading URLs
|
||||
based on the request method. In the following example, the same URL can be
|
||||
serviced by two different methods (one for handling HTTP ``GET``, another for
|
||||
HTTP ``POST``) using `generic controllers`:
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose
|
||||
|
||||
|
||||
class RootController(object):
|
||||
|
||||
# HTTP GET /
|
||||
@expose(generic=True, template='json')
|
||||
def index(self):
|
||||
return dict()
|
||||
|
||||
# HTTP POST /
|
||||
@index.when(method='POST', template='json')
|
||||
def index_POST(self, **kw):
|
||||
uuid = create_something()
|
||||
return dict(uuid=uuid)
|
||||
|
||||
|
||||
|
||||
|
||||
Pecan's Routing Algorithm
|
||||
-------------------------
|
||||
@@ -148,44 +176,6 @@ these methods on your controller objects provides additional flexibility for
|
||||
processing all or part of a URL.
|
||||
|
||||
|
||||
Setting a Return Status Code
|
||||
----------------------------
|
||||
|
||||
Set a specific HTTP response code (such as ``201 Created``) by
|
||||
modifying the ``status`` attribute of the response object.
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose, response
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose('json')
|
||||
def hello(self):
|
||||
response.status = 201
|
||||
return {'foo': 'bar'}
|
||||
|
||||
Use the utility function :func:`~pecan.core.abort` to raise HTTP errors.
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose, abort
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose('json')
|
||||
def hello(self):
|
||||
abort(404)
|
||||
|
||||
|
||||
:func:`~pecan.core.abort` raises an instance of
|
||||
:class:`~webob.exc.WSGIHTTPException` which is used by Pecan to render
|
||||
:default response bodies for HTTP errors. This exception is stored in
|
||||
:the WSGI request environ at ``pecan.original_exception``, where it
|
||||
:can be accessed later in the request cycle (by, for example, other
|
||||
:middleware or :ref:`errors`).
|
||||
|
||||
|
||||
Routing to Subcontrollers with ``_lookup``
|
||||
------------------------------------------
|
||||
|
||||
@@ -268,7 +258,7 @@ a :func:`_route` method will enable you to have total control.
|
||||
|
||||
|
||||
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
|
||||
@@ -295,6 +285,58 @@ directly, there may be situations where you want to access them, such as:
|
||||
* Manually rendering a response body
|
||||
|
||||
|
||||
Specifying a Custom Response
|
||||
----------------------------
|
||||
|
||||
Set a specific HTTP response code (such as ``203 Non-Authoritative Information``) by
|
||||
modifying the ``status`` attribute of the response object.
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose, response
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose('json')
|
||||
def hello(self):
|
||||
response.status = 203
|
||||
return {'foo': 'bar'}
|
||||
|
||||
Use the utility function :func:`~pecan.core.abort` to raise HTTP errors.
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose, abort
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose('json')
|
||||
def hello(self):
|
||||
abort(404)
|
||||
|
||||
|
||||
:func:`~pecan.core.abort` raises an instance of
|
||||
:class:`~webob.exc.WSGIHTTPException` which is used by Pecan to render
|
||||
default response bodies for HTTP errors. This exception is stored in
|
||||
the WSGI request environ at ``pecan.original_exception``, where it
|
||||
can be accessed later in the request cycle (by, for example, other
|
||||
middleware or :ref:`errors`).
|
||||
|
||||
If you'd like to return an explicit response, you can do so using
|
||||
:class:`~pecan.core.Response`:
|
||||
|
||||
::
|
||||
|
||||
from pecan import expose, Response
|
||||
|
||||
class RootController(object):
|
||||
|
||||
@expose()
|
||||
def hello(self):
|
||||
return Response('Hello, World!', 202)
|
||||
|
||||
|
||||
|
||||
Extending Pecan's Request and Response Object
|
||||
---------------------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user