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:
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:
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('',
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:
And the only files we needed to touch were:
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:
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.