Piston Documentation
-
-
Piston Documentation
- Getting Started
- Resources
- Emitters
-
Mapping URLs
- Anonymous Resources
- Working with Models
-
Configuring Handlers
- Model
- Fields/Exclude
- Anonymous
-
Authentication
- OAuth
- Form Validation
- Helpers, utils & @decorators
- Throttling
-
Generating Documentation
- Resource URIs
- Tests
- Receiving data
- Streaming
- Configuration variables
-
Piston Documentation
Getting Started
Getting started with Piston is easy. Your API code will look and behave just like any other Django application. It will have an URL mapping and handlers defining resources.
To get started, it is recommended that you place your API code in a separate folder, e.g. 'api'.
Your application layout could look like this:
urls.py settings.py myapp/ __init__.py views.py models.py api/ __init__.py urls.py handlers.py
Then, define a "namespace" where your API will live in your top-level urls.py, like so:
urlpatterns = patterns('',
# all my other url mappings
(r'^api/', include('mysite.api.urls')),
)
This will include the API's urls.py for anything beginning with 'api/'.
Next up we'll look at how we can create resources and how to map URLs to them.
Resources
A "Resource" is an entity mapping some kind of resource in code. This could be a blog post, a forum or even something completely arbitrary.
Let's start out by creating a simple handler in handlers.py:
from piston.handler import BaseHandler
from myapp.models import Blogpost
class BlogpostHandler(BaseHandler):
allowed_methods = ('GET',)
model = Blogpost
def read(self, request, post_slug):
...
Piston lets you map resource to models, and by doing so, it will do a lot of the heavy lifting for you.
A resource can be just a class, but usually you would want to define at least 1 of 4 methods:
read
is called on GET requests, and should never modify data (idempotent.)
create
is called on POST, and creates new objects, and should return them (or rc.CREATED.)
update
is called on PUT, and should update an existing product and return them (or rc.ALL_OK.)
delete
is called on DELETE, and should delete an existing object. Should not return anything, just rc.DELETED.
In addition to these, you may define any other methods you want. You can use these by including their names in the fields
directive, and by doing so, the function will be called with a single argument: The instance of the model
. It can then return anything, and the return value will be used as the value for that key.
NB: These "resource methods" should be decorated with the @classmethod decorator, as they will not always receive an instance of itself. For example, if you have a UserHandler defined, and you return a User from another handler, you will not receive an instance of that handler, but rather the UserHandler.
Since a single handler can be responsible for both single- and multiple-object data sets, you can differentiate between them in the read() method like so:
from piston.handler import BaseHandler
from myapp.models import Blogpost
class BlogpostHandler(BaseHandler):
allowed_methods = ('GET',)
model = Blogpost
def read(self, request, blogpost_id=None):
"""
Returns a single post if `blogpost_id` is given,
otherwise a subset.
"""
base = Blogpost.objects
if blogpost_id:
return base.get(pk=blogpost_id)
else:
return base.all() # Or base.filter(...)
Emitters
Emitters are what spews out the data, and are the things responsible for speaking YAML, JSON, XML, Pickle and Django. They currently reside in emitters.py
as XMLEmitter
, JSONEmitter
, YAMLEmitter
, PickleEmitter
and DjangoEmitter
.
Writing your own emitters is easy, all you have to do is create a class that subclasses Emitter
and has a render
method. The render method will receive 1 argument, 'request', which is a copy of the request object, which is useful if you need to look at request.GET (like defining callbacks, like the JSON emitter does.)
To get the data to serialize/render, you can call self.construct()
which always returns a dictionary. From there, you can do whatever you want with the data and return it (as a unicode string.)
NB: New in 23ebc37c78e8
: Emitters can now be registered with the Emitter.register
function, and can be removed (in case you want to remove a built-in emitter) via the Emitter.unregister
function.
The built-in emitters are registered like so:
class JSONEmitter(Emitter):
...
Emitter.register('json', JSONEmitter, 'application/json; charset=utf-8')
If you write your own emitters, you can import Emitter and call 'register' on it to put your emitter into action. You can also overwrite built-in, or existing emitters, by using the same name (the first argument.)
This makes it very easy to add support for extended formats, like protocol buffers or CSV.
Emitters are accessed via the ?format GET argument, e.g. '/api/blogposts/?format=yaml', but since 23ebc37c78e8
, it is now possible to access them via a special keyword argument in your URL mapping. This keyword is called 'emitter_format' (to not clash with your own 'format' keyword), and can be used like so:
urlpatterns = patterns('',
url(r'^blogposts(\.(?P<emitter_format>.+))