diff options
author | Ryan Petrello <lists@ryanpetrello.com> | 2014-12-03 17:44:24 -0500 |
---|---|---|
committer | Ryan Petrello <lists@ryanpetrello.com> | 2014-12-04 14:14:49 -0500 |
commit | 232a0d4ef0c1750e8b859c89c02061663532a6a0 (patch) | |
tree | b3151b3a0bef0b8b8c69aafc031bd09140a2f2e7 | |
parent | 318bd2016742b3854930942c551f9ac4f819f8da (diff) | |
download | pecan-232a0d4ef0c1750e8b859c89c02061663532a6a0.tar.gz |
Add documentation for generic REST controllers.
Fixes bug 1386837
Change-Id: I9cf30c7d55026d2364d7add3f7a9ca96b3348d31
-rw-r--r-- | docs/source/rest.rst | 84 | ||||
-rw-r--r-- | docs/source/routing.rst | 116 |
2 files changed, 155 insertions, 45 deletions
diff --git a/docs/source/rest.rst b/docs/source/rest.rst index 8432a86..6f6a01b 100644 --- a/docs/source/rest.rst +++ b/docs/source/rest.rst @@ -1,13 +1,81 @@ .. _rest: -Writing RESTful Web Services with Pecan -======================================= - -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. +Writing RESTful Web Services with Generic Controllers +===================================================== + +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`: :: diff --git a/docs/source/routing.rst b/docs/source/routing.rst index 68a46ba..4b80472 100644 --- a/docs/source/routing.rst +++ b/docs/source/routing.rst @@ -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,54 +136,44 @@ use the ``text/html`` content type by default. * :ref:`pecan_decorators` +Routing Based on Request Method +------------------------------- -Pecan's Routing Algorithm -------------------------- - -Sometimes, the standard object-dispatch routing isn't adequate to properly -route a URL to a controller. Pecan provides several ways to short-circuit -the object-dispatch system to process URLs with more control, including the -special :func:`_lookup`, :func:`_default`, and :func:`_route` methods. Defining -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. +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, response + from pecan import expose - class RootController(object): - @expose('json') - def hello(self): - response.status = 201 - return {'foo': 'bar'} + class RootController(object): -Use the utility function :func:`~pecan.core.abort` to raise HTTP errors. + # 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) - from pecan import expose, abort - class RootController(object): - @expose('json') - def hello(self): - abort(404) +Pecan's Routing Algorithm +------------------------- -: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`). +Sometimes, the standard object-dispatch routing isn't adequate to properly +route a URL to a controller. Pecan provides several ways to short-circuit +the object-dispatch system to process URLs with more control, including the +special :func:`_lookup`, :func:`_default`, and :func:`_route` methods. Defining +these methods on your controller objects provides additional flexibility for +processing all or part of a URL. 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 --------------------------------------------- |