Not busy. I don't have 7 different things to get done by lunch time. I keep my schedule as simple as possible; usually it's get up, get it together, go to work, do cool stuff, come home, do family stuff, crash. Basically my policy is, if I have two things scheduled in a day, that's one too many. My brain just doesn't like that.
But occupied, yep. And that's because I changed jobs, threw out pretty much my entire existing skillset, and started from scratch.
A great and terrible thing.
(and of course some obligatory PHP support is involved, which is pure masochism).
Luckily my new job is awesome, for a little company with brilliant people and the desire to build a really first class dev team, building outstanding stuff. And a healthy hatred for Microsoft (well, you know, we'll integrate with their stuff and all that, but we wouldn't use it ourselves, bleh). The very fact that I can blog about the details of what I've been building commercially, and even release some of it, should say something positive about it.
Anyway, long story stupidly too long.
So my mind has been almost entirely occupied with this. I've been frenetic, I've been focused. Not in the "now I'm really paying attention" way, but more in the Vernor Vinge's "A Deepness In The Sky" way. Drooling and a bit broken, looked after by my darling wife.
So just as I'm making some breakthroughs, starting to Get It and feel a bit more comfortable, I got sick for a couple of days. Flu/Cold/Virusy thing. Bedridden, looked after by my darling wife.
All that was on my todo list was to rest.
And, I'm not making this up, what came to my mind was "Todo, rest, hey I could get that todo list sample app and make it talk to AppEngine via a RESTful interface".
So I built a little REST library, called Sleepy.py, while lying in my sick bed.
As I said, drooling and broken.
I've not come at Sleepy cold. Sleepy is actually a rewrite of a rewrite.
What I've been mostly doing commercially in the last few weeks has been to build a decent REST interface for AppEngine. Which of course I shouldn't be doing; just grab one that's already there. The problem is, I haven't been able to find anything serviceable. I looked, believe me, and even tried some out, but they just felt clunky; heavyweight, XML oriented (we want to use JSON), not really welcoming customization, a square peg for a round hole.
I thought we should stick with the existing options, because look how big and complex they are, we don't want to do all that work ourselves! But my good colleague protested, went away one weekend and came back with his own simple REST interface library, which was cleaner, much shorter, and really a bit of a revelation. It needed work, but that opened my eyes to his central observation, which was that this doesn't need to be that hard.
And it doesn't, of course. I mean, what is REST really?
What is REST?
Let me google that for you:
That answer sucks
Now I know that's hopelessly non-purist, but what I'm trying to achieve is this:
- We've got a rough idea for an app to build.
- I build a server side, which is largely data model.
- We'd also like the possibility of other stuff talking to the same api (iPad apps, third party code, martian mind control beams, whatever).
- I must be able to set up a RESTful web api to let the client talk to the server. It needs to give a good balance of simplicity and customizable behaviour, be quick to put in place, and just be clean and easy to work with.
Anyway enough jibber jabber, where's some code?
Yeah yeah ok.
What I decided to do here to demonstrate my approach is to take a simple sample app that demonstrates the use of backbone.js , and make it work in the context of AppEngine. The sample app, by Jérôme Gravel-Niquet, is a todo list which lets the user make a simple but nice to use todo list, and stores it in local storage in the browser (so it really has no back end).
You can check out the sample app here: http://documentcloud.github.com/backbone/#examples
Have a play with it here: http://documentcloud.github.com/backbone/examples/todos/index.html
So I started by downloading all the static files for the todo list app, sticking them in a basic appengine app, and popping that in git on github.
The git repo is here: https://github.com/emlynoregan/appenginetodos
The first version that just hosts the static app, still backed by localstorage, is available at the tag v1:
You can run that in your local appengine devserver, or upload it to appengine proper, and it'll work. But it's not touching the datastore.
The app is a simple todo list. There's no concept of users, of different lists, nothing. There's just one list, which is a list of Todo items. A todo item is composed of three elements:
- text - string, the text that the user enters describing what is to be done
- order - int. the numerical order in the list of this item; lower number comes first.
- done - bool, whether the item is done or not
The first thing we need is a datastore model to describe this. This is a simple one:
from google.appengine.ext import db class ToDo(db.Model): text = db.StringProperty() order = db.IntegerProperty() done = db.BooleanProperty() created = db.DateTimeProperty(auto_now_add = True) modified = db.DateTimeProperty(auto_now = True) def __init__(self, *args, **kwargs): db.Model.__init__(self, *args, **kwargs) if self.done is None: self.done = False
Just one class, ToDo. It's got text, order and done (and I've put a default value on "done" of false in the constructor). I've also added "created" and "modified" fields just for a bit of fun. These are managed by the server (using auto_now_add and auto_now), and wont be exposed to the client.
And now the REST!
Ok, now we want to expose this datamodel class through a REST api. For the ToDo class, we'll use a resource name of /todos. Locally this might be http://localhost:8080/todos. My version on appengine has the url http://todos.emlynhrdtest.appspot.com/todos .
The first thing to think about is the routing (kicking off the right handlers for urls) and basic infrastructure. How will we get /todos to be handled the way we want to handle it, and how would that handling be done in any case?
I figured that since the RESTful web api will be just like a normal web page, but returning JSON, why not use the standard webapp web handler mechanism already available in appengine, and the same routing mechanism? Now we'll want a little help with the routing, because we want to be able to manage /todos, but also calls like /todos/32 (where 32 is a valid id for a model instance) to address particular todo items.
So, you define the routes by providing a list of resourcename / handler pairs, which a helper function then turns into url / handler pairs as required by the wsgi application.
Here's the main.py, showing a standard route for '/', then including the calculated restRoutes.
import webapp2 import htmlui import restapi # basic route for bringing up the app lroutes = [ ('/', htmlui.ToDoHandler) ] # add api routes, see restapi/__init__.py lroutes.extend(restapi.restRoutes) # create the application with these routes app = webapp2.WSGIApplication(lroutes, debug=True)
restRoutes comes from the restapi module for this app, whose __init__.py looks like this:
from todoresthandler import * from sleepy import * restRoutes = [ ('todos', ToDoRestHandler) ] restRoutes = Sleepy.FixRoutes(restRoutes)
ToDoRestHandler is a webapp webhandler for the rest api for ToDo. Sleepy.FixRoutes turns restRoutes from a list of (resourcename, handler) pairs to a list of (url, handler) pairs.
@classmethod def FixRoutes(cls, aRoutes, aRouteBase = None): """ Modifies routes to allow specification of id aRoutes should be pairs of resourcename, resource handler. This is modified to become pairs of route source, resource handler. aRouteBase is anything you want to prepend all route sources with. eg: if you want all your route sources to begin with /api,
use aRouteBase="/api" Don't include a trailing slash in aRouteBase. """ retval =  for lroute in aRoutes: lfixedRouteSource = '/(%s)(?:/(.*))?' % lroute if aRouteBase: lfixedRouteSource = aRouteBase + lfixedRouteSource lfixedRoute = (lfixedRouteSource, lroute) retval.append(lfixedRoute) return retval
We've got the /todos route wired up to ToDoRestHandler. But what's in that? Here it is:
from google.appengine.ext import webapp from sleepy import Sleepy from datamodel import ToDo class ToDoRestHandler(webapp.RequestHandler): def get(self, aResource, aResourceArg, *args, **kwargs): Sleepy.GetHandler(self, aResource, aResourceArg, *args, **kwargs) def put(self, aResource, aResourceArg, *args, **kwargs): Sleepy.PutHandler(self, aResource, aResourceArg, *args, **kwargs) def post(self, aResource, aResourceArg, *args, **kwargs): Sleepy.PostHandler(self, aResource, aResourceArg, *args, **kwargs) def delete(self, aResource, aResourceArg, *args, **kwargs): Sleepy.DeleteHandler(self, aResource, aResourceArg, *args, **kwargs) def GetModelClass(self): return ToDo
What's going on here? Well, I've delegated all the functionality of GET, PUT, POST and DELETE to the Sleepy library. Plus, there's an extra method GetModelClass which tells us (actually Sleepy) which model class we're working with.
So basically you've shown us nothing. What's in this Sleepy?
No I haven't, have I? I'll get onto what's in Sleepy in the next post. For now, if you want to skip ahead, just check out the git repo. Otherwise, you can wait for the next installment, lazy person!