Things I learned about Python and Google AppEngine
So this last cloudspokes challenge was particularily challenging for me, as it required the use of the python programming language, and the google appengine hosting platform. I am not sure if it’s just me, but I found solutions to seemingly simple problems hard to find, so I’ll outline a few things I found here in hopes to save you time.
1) With google appengine, use python 2.5. Nothing else. Don’t even think about it.
2) You have to enable appengine for your google apps account. Do so by by going to the users and groups link, then services. Find app engine and enable it. Otherwise you will get an unauthorized to use this application error.
3) Of course when using google appEngine you are not allowed access to the file system. So any file operations are not going to happen. You have to use the datastore or some other method.
4) When you use the GetDocumentListFeed() method to get data about your documents using the client library, the resource id returned (resourceId.text) is not just the resource ID. it is the type of file, and the resource ID. You have to split them to get each separate. For example.
feed = self.gd_client.GetDocumentListFeed()
for entry in feed.entry:
resource = entry.resourceId.text
doc_type = resource[:resource.find(‘:’)]
resource_Id = resource[resource.index(‘:’)+1:]
5) Not all files in google docs have extensions in the file names/titles, so don’t count on them being there. You have to figure out the file extension for yourself.
6) When calling a google API, error 401 Unauthorized may likely mean that there is something wrong with the header you have sent for the authorization. You don’t just send the auth token, you also send a short pre string. For example
docs_token = gd_client.GetClientLoginToken()
request.add_header(‘Authorization’, ‘GoogleLogin auth=’+docs_token)
7) The content type header of your HTTP request can change how the body content is sent. The correct content type for most requests against the google docs API is
request.add_header(‘Content-Type’, ‘application/atom+xml; charset=\”UTF-8\”‘)
You might be tempted to change it to text/xml since that seems like what you are sending, but don’t. You’ll get an error 415 media type unrecognized or some shit like that.
8) If you are debugging your post request, and you send it to a page that prints the content, your content may come back base64 encoded. This is fine. Don’t worry about it. Google knows what to do with the base64 shit. I spend a while trying to figure why this was happening, and it is because of the content type that you set in the headers. Yes changing the content type to text/xml makes the content come through as regular text but again you get that error 415. So don’t worry, your data is fine.
9) The one is probably obvious to most, but just in the spirit of clearing up confusion, you must include a content-length header. It must be the amount of characters in the body of your request. Nothing fancy, just run a len() on the body of your content and set that as the content-length header.
10) You must also include the header GData-Version, and it should be set to 3.0. Again, I know most of the pros probably know this, but it’s just one of those things that can slip by and screw you up.
11) Trim your XML content, and unicode escape it as well before inserting it into the body of your post request. Otherwise you could get some freak errors. Something like
body = body.encode(‘unicode-escape’).strip()
should do the trick.
12) HTTP status 201 means you finally did it and you’re request went through. However the urllib2 library still sees that as an error. It’s okay, just have a special handler set up for it. EX
response = urllib2.urlopen(request) except urllib2.HTTPError, e: if e.code == 201: message = 'w00t success' else: message = 'There was an error processing your request. ' +e.read() except: # catch *all* exceptions e = sys.exc_info() message = 'There was an error processing your request.'
13) For any other python n00bz like me, when creating your post request to send to google, urllib2 is the way to go. You can set your headers, and include your body content, everything you need to do. Here is an example of how I put together my post request for my most recent app.
url = 'http://docs.google.com/feeds/default/private/archive' body = '''<entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007"> <docs:archiveConversion source='application/vnd.google-apps.document' target='application/msword'/> <docs:archiveConversion source='application/vnd.google-apps.spreadsheet' target='text/csv'/> <docs:archiveConversion source='application/pdf' target='application/pdf'/> <docs:archiveConversion source='application/vnd.google-apps.presentation' target='application/pdf'/> <docs:archiveNotify>'''+username+'''</docs:archiveNotify> </entry>''' body = body.encode('unicode-escape').strip() request = urllib2.Request(url,body) request.add_header('GData-Version', '3.0') request.add_header('Authorization', 'GoogleLogin auth='+docs_token) request.add_header('Content-length', str(len(body))) request.add_header('Content-Type', 'application/atom+xml; charset=\"UTF-8\"') response = urllib2.urlopen(request)
So there you have it. Some handy tips for working with Python and AppEngine that should make you hate life a little less. Hope this helps someone out there!