Add documentation for generic REST controllers.
Fixes bug 1386837 Change-Id: I9cf30c7d55026d2364d7add3f7a9ca96b3348d31
This commit is contained in:
@@ -1,13 +1,81 @@
|
|||||||
.. _rest:
|
.. _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
|
Pecan simplifies RESTful web services by providing a way to overload URLs based
|
||||||
:class:`~pecan.rest.RestController` may help speed things up. It follows the
|
on the request method. For most API's, the use of `generic controller`
|
||||||
Representational State Transfer Protocol, also known as REST, by routing the
|
definitions give you everything you need to build out robust RESTful
|
||||||
standard HTTP verbs of ``GET``, ``POST``, ``PUT``, and ``DELETE`` to individual
|
interfaces (and is the *recommended* approach to writing RESTful web services
|
||||||
methods.
|
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):
|
def hello(self):
|
||||||
return {'msg': 'Hello!'}
|
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.
|
different arguments.
|
||||||
|
|
||||||
::
|
::
|
||||||
@@ -136,6 +136,34 @@ use the ``text/html`` content type by default.
|
|||||||
* :ref:`pecan_decorators`
|
* :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
|
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.
|
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``
|
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
|
Interacting with the Request and Response Object
|
||||||
------------------------------------------------
|
================================================
|
||||||
|
|
||||||
For every HTTP request, Pecan maintains a :ref:`thread-local reference
|
For every HTTP request, Pecan maintains a :ref:`thread-local reference
|
||||||
<contextlocals>` to the request and response object, ``pecan.request`` and
|
<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
|
* 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
|
Extending Pecan's Request and Response Object
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user