/usr/share/doc/python-pecan-doc/html/_sources/rest.rst.txt is in python-pecan-doc 1.2.1-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | .. _rest:
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_ = str(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`:
::
from pecan import expose
from pecan.rest import RestController
from mymodel import Book
class BooksController(RestController):
@expose()
def get(self, id):
book = Book.get(id)
if not book:
abort(404)
return book.title
URL Mapping
-----------
By default, :class:`~pecan.rest.RestController` routes as follows:
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| Method | Description | Example Method(s) / URL(s) |
+=================+==============================================================+============================================+
| get_one | Display one record. | GET /books/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| get_all | Display all records in a resource. | GET /books/ |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| get | A combo of get_one and get_all. | GET /books/ |
| | +--------------------------------------------+
| | | GET /books/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| new | Display a page to create a new resource. | GET /books/new |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| edit | Display a page to edit an existing resource. | GET /books/1/edit |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| post | Create a new record. | POST /books/ |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| put | Update an existing record. | POST /books/1?_method=put |
| | +--------------------------------------------+
| | | PUT /books/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| get_delete | Display a delete confirmation page. | GET /books/1/delete |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| delete | Delete an existing record. | POST /books/1?_method=delete |
| | +--------------------------------------------+
| | | DELETE /books/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
Pecan's :class:`~pecan.rest.RestController` uses the ``?_method=`` query string
to work around the lack of support for the PUT and DELETE verbs when
submitting forms in most current browsers.
In addition to handling REST, the :class:`~pecan.rest.RestController` also
supports the :meth:`index`, :meth:`_default`, and :meth:`_lookup`
routing overrides.
.. warning::
If you need to override :meth:`_route`, make sure to call
:func:`RestController._route` at the end of your custom method so
that the REST routing described above still occurs.
Nesting ``RestController``
---------------------------
:class:`~pecan.rest.RestController` instances can be nested so that child
resources receive the parameters necessary to look up parent resources.
For example::
from pecan import expose
from pecan.rest import RestController
from mymodel import Author, Book
class BooksController(RestController):
@expose()
def get(self, author_id, id):
author = Author.get(author_id)
if not author_id:
abort(404)
book = author.get_book(id)
if not book:
abort(404)
return book.title
class AuthorsController(RestController):
books = BooksController()
@expose()
def get(self, id):
author = Author.get(id)
if not author:
abort(404)
return author.name
class RootController(object):
authors = AuthorsController()
Accessing ``/authors/1/books/2`` invokes :func:`BooksController.get` with
``author_id`` set to ``1`` and ``id`` set to ``2``.
To determine which arguments are associated with the parent resource, Pecan
looks at the :func:`get_one` then :func:`get` method signatures, in that order,
in the parent controller. If the parent resource takes a variable number of
arguments, Pecan will pass it everything up to the child resource controller
name (e.g., ``books`` in the above example).
Defining Custom Actions
-----------------------
In addition to the default methods defined above, you can add additional
behaviors to a :class:`~pecan.rest.RestController` by defining a special
:attr:`_custom_actions`
dictionary.
For example::
from pecan import expose
from pecan.rest import RestController
from mymodel import Book
class BooksController(RestController):
_custom_actions = {
'checkout': ['POST']
}
@expose()
def checkout(self, id):
book = Book.get(id)
if not book:
abort(404)
book.checkout()
:attr:`_custom_actions` maps method names to the list of valid HTTP
verbs for those custom actions. In this case :func:`checkout` supports
``POST``.
|