Skip to main content

Creating a very basic API using Python, Django and Piston

rshady's picture
Posted in

Ok, I'm no noob to writing client/server applications or API's to interact with them.  However, I have usually done this type of programming in C or PHP and rolled my own *EVERYTHING* to make it work.  This is obviously very time consuming, and since I've been writing more and more API's lately I decided to give 'something else' a shot.

I've been playing around with Python a lot lately, and I really like it's flexibility and ease of use, so I decided to see what I could find on writing my new API in Python.  After some careful examination, it probably wouldn't be too terribly hard to roll your own API in Python, but I wanted to see what else was out there - after all, I've already "been there, done that".

So after spending some time searching around the net, and researching various options available to me, I came up with the perfect solution... Well, in my mind at least - it looked like Django coupled with Piston was the quickly becoming the defacto standard for developing new API's in Python and it has a lot of great features right out of the box.

Piston is described as a mini framework for developing RESTful API's, here are some features out of the box:

* Ties into Django's internal mechanisms.
* Supports OAuth out of the box (as well as Basic/Digest or custom auth.)
* Doesn't require tying to models, allowing arbitrary resources.
* Speaks JSON, YAML, Python Pickle & XML (and HATEOAS.)
* Ships with a convenient reusable library in Python
* Respects and encourages proper use of HTTP (status codes, ...)
* Has built in (optional) form validation (via Django), throttling, etc.
* Supports streaming, with a small memory footprint.
* Stays out of your way.

So the next step was to try a simple API myself, but being unfamiliar with Django and even less familiar with Piston I needed an example, or a tutorial, or something to get me started.  Sure, there was SOME documentation, but even the example that was labeled "A fully functioning example" wasn't complete (at least from what I could tell, it was at least missing the data model).

So that's where this article comes in... I am going to create a fully functioning (albeit extremely simple) API so you can at least get the basics... Maybe in some future articles I will expand on this more to add data models, authorization, etc.

First you need to make sure you have Python, Django, and Piston installed properly.  I am not going to cover that here as there seems to be a lot of debate on what the "proper" way to do this is.  Seems like the most current recommendations are to use something called 'VirtualEnv' to setup your Python development library, but I'm not choosing sides. :)

 

Now make sure you are in your code directory (where-ever you normally keep your development projects), and create your Django project:

% django-admin startproject calc

 

This will create a a directory that looks like the following:

calc/
    __init__.py
    manage.py
    settings.py
    urls.py

 

This is your basic Django project directory structure.  Now we need to create our application.  Most discussions say that your API "application" should be named "api" and be kept inside of your Django project directory.  I tend to agree with this, so go into the 'calc' directory we created above with the startproject command and create an application named 'api'.

% cd calc
% ./manage.py startapp api

 

Your entire project directory will now look like this:

calc/
    __init__.py
    api/
        __init__.py
        models.py
        tests.py
        views.py
    manage.py
    settings.py
    urls.py

 

Now we need to make sure that our API is accessible, we do this using urlpatterns in the calc/urls.py file (similar to Ruby routes).

 

Edit calc/urls.py and make it look like the following:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^api/', include('calc.api.urls')),
)

 

What this is saying is anytime we receive a request for a URL that begins with api/ we need to include the python module calc.api.urls (which is found in calc/api/urls.py).

 

So let's edit calc/api/urls.py now and make it look like the following:

from django.conf.urls.defaults import *
from piston.resource import Resource
from api.handlers import CalcHandler

class CsrfExemptResource( Resource ):
    def __init__( self, handler, authentication = None ):
        super( CsrfExemptResource, self ).__init__( handler, authentication )
        self.csrf_exempt = getattr( self.handler, 'csrf_exempt', True )

calc_resource = CsrfExemptResource( CalcHandler )

urlpatterns = patterns( '',
    url( r'^calc/(?P<expression>.*)$', calc_resource )
)

 

This turns out to be a pretty important piece of code.  Apparently in Django (v1.2+?) they implemented some extra security to prevent against Cross Site Request Forgery (CSRF), which is great for most situations - but personally I don't believe it applies to API's as MOST requests are going to be from other sites... I mean, that's the whole idea, right? ;)  So essentially what this does is wrap the BaseHandler resource in our CsrfExemptResource and then disable the CSRF checking by setting csrf_exempt = True.  Technically this is something that should probably be in the base Piston code, but as of the time of this writing it was not.  If you don't use the chunk of code above, your will get a 403 error when you attempt to update something (Ie: PUT, POST, DELETE) using your new API.

The urlpatterns here are what get used after the other url pattern is already stripped off, so even though the pattern matches against the beginning of the line (^calc/), since we've already parsed ^api/ to get here, the full path is actually going to be api/calc/<expression>.  A call to that URL will be forwarded along to our 'calc_handler' which is a Piston Resource 'CalcHandler' wrapped in our CsrfExemptResource class.  Clear as mud, right? ;)

 

Now we need to write our CalcHandler class, we will do this in handlers.py file (which doesn't exist yet) in our api directory.

 

Let's edit calc/api/handlers.py and make it look like the following:

from piston.handler import BaseHandler

class CalcHandler( BaseHandler ):
    def read( self, request, expression ):
        return eval( expression )

 

I'm not even going to go into the security implications of this, because that's not what this article is about, but suffice it to say YOU SHOULD NEVER RUN THIS ON A LIVE SERVER!  The 'eval( expression )' is just like handing hackers the key to your new Ferrari, it's a wide-open door into your server.  The "read" method is what will get called when we attempt to READ from this handler (Ie: a GET request).

 

Now our directory structure should look like this:

calc/
    __init__.py
    api/
        __init__.py
        handlers.py
        models.py
        tests.py
        urls.py
        views.py
    manage.py
    settings.py
    urls.py

 

And the only files we needed to touch were:

calc/urls.py
calc/api/handlers.py
calc/api/urls.py

 

That's it!  Now let's fire up Django's built in web server to test out our new API.
From the calc/ directory:

% ./manage.py runserver localhost:8000 &

 

That will start a very simple webserver (designed for testing, not for production use) on your localhost at port 8000.

 

Now let's use curl to make a call to our new API:

% curl http://localhost:8000/api/calc/1+2
3

 

What this did is connect to our localhost at port 8000 and request the URL api/calc/1+2 from our API.  Django saw the api/... and based on our calc/urls.py knew that it had to include our urlpatterns from calc/api/urls.py.  Once it did that, it matched the 2nd part of our URL 'calc' and then passed the rest of our URL '1+2' along to our CalcHandler which did an eval on it and returned the result (which was 3 in this case).

So there you go, a VERY basic API setup in Python with Django and Piston.  I know it's not very impressive, that'll come later - but even getting this far without any documentation proved to be extremely difficult, so I hope this helps the next person. 

The one last thing you should do is add your new API application to the list of INSTALED_APPS in the calc/settings.py file.  It's not necessary in this example, but once you get into models it will make some things happen automagically.  For this example, I would add 'calc.api' to the end of INSTALLED_APPS.

 

5
Your rating: None Average: 5 (2 votes)

Nice and Simple

I like the simplistic idea of this example. But I was wondering if there was a way you could demonstrate another example with just plain old Django without adding another framework.

That would be very awesome.

A little help!!!

Would it be possible to have the same example but considering the new Django version 1.5? because when I created  a project the folders and file organization is different. There are just small differences but still I can not make the example run =(

 

I will really appreciate your help!!!

 

Jose

Excellent!

Thanks for writing a simple, easy to understand example for api creation, this was very helpful!

Hooray!

It is very refreshing to come across a tutorial which actually works.  Thanks!

Really very helpful tutorial

Really very helpful tutorial

Thank you for your article

Thank you very much for your article which has made me know the basic mechanism of Piston. :-)

Thanks man

This is very helpful. Thanks man

CSRF

The CSRF thing was killing me because no amount of debug would tell me what was happening.  Thanks!

Very awesome!

Thank you very much! This is such a useful tutorial :)

Extremely helpful

Extremely helpful

 

I can't thank you enough

 

 

Do you have any example of

Do you have any example of Piston using authentication? 

Nice, simple walkthrough

Thanks for posting your experience.  The walkthrough was simple and easy to follow.  It definitly saved me some time!

thanks

just what I was looking for to help me get going with Piston.

well written, concise!

 

Simple and Effective

I have been searching for a while to a simple tutorial for implementing a Web Service in Django.

They (Piston guys), should include this example in theirs documentation.

Thank you very much.