Add documentation for generic REST controllers.

Fixes bug 1386837

Change-Id: I9cf30c7d55026d2364d7add3f7a9ca96b3348d31
This commit is contained in:
Ryan Petrello
2014-12-03 17:44:24 -05:00
parent 318bd20167
commit 232a0d4ef0
2 changed files with 157 additions and 47 deletions

View File

@@ -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`:
::

View File

@@ -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
---------------------------------------------