Donnerstag, 17. Februar 2011

Programmatically upload images to blobstore on AppEngine

Update: There is now a native API call to do this:



from __future__ import with_statement
from google.appengine.api import files
from google.appengine.ext import blobstore

   def get_blob_key(self, data, _type):
       # Create the file
       file_name = files.blobstore.create(mime_type = _type)

       # Open the file and write to it
       with files.open(file_name, 'a') as f:
           f.write(data)

       # Finalize the file. Do this before attempting to read it.
       files.finalize(file_name)

       # Get the file's blob key
       blob_key = files.blobstore.get_blob_key(file_name)
       return blob_key


Old way:

Let's assume you would like to programmatically store an image into blobstore on Google AppEngine in Python like this:

key = blobstore_image.uploadImage(open("test.png", "rb").read())

and then serve it like this:

self.response.out.write("<img src='" + blobstore_image.getServingUrl(key) + "'>")

Well, with the normal API, that's not easily possible.

However, I created exactly this API. :)

Prerequisites:

Libraries:
You need to have a copy of Poster's encode.py in the same directory as my library.

App.yaml:
Add this to your app.yaml:
- url: /blobstore_image(/.*)?
  script: /tools/blobstore_image.py
Special dev_appserver.py!
This is very important:
You need to run an additional dev_appserver.py instance if you want to use this library locally!

dev_appserver.py --port 8080 .
dev_appserver.py --port 8081 .

My library does a post request to your app, while handling the current request. Since the dev_appserver.py is single threaded this would not work. Therefor you have to run a second instance locally that handles all blobstore related operations. But don't worry you wont notice and in production the extra code is not even used :)


The library (blobstore_image.py):


#!/usr/bin/env python
#

DEV_APP_SERVER_BLOBSTORE_ENTITY_PORT = 8081

import os
import urllib
from tools.poster import multipart_encode, MultipartParam
from google.appengine.api import images
from google.appengine.api import urlfetch
from google.appengine.ext import blobstore
from google.appengine.ext import webapp
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app

def getServingUrl(key, size=None, crop=False):
  global DEV_APP_SERVER_BLOBSTORE_ENTITY_PORT
  url = images.get_serving_url(key)
  if development():
    url = url[7:]
    url = ("http://localhost:%d%s" %
           (DEV_APP_SERVER_BLOBSTORE_ENTITY_PORT,
            url[url.find("/"):]))
  if size:
    url += "=s%d"
    if crop:
      url += "-c"
  return url

def uploadImage(data):
  global DEV_APP_SERVER_BLOBSTORE_ENTITY_PORT        
  params = []
  params.append(MultipartParam(
      "file",
      filename='file',
      value=data))
  payloadgen, headers = multipart_encode(params)
  payload = str().join(payloadgen)
  url = None
  if development():
    url = urlfetch.fetch(
       url=("http://localhost:%d/blobstore_image/geturl" %
             DEV_APP_SERVER_BLOBSTORE_ENTITY_PORT)).content
  else:
    url = urlfetch.fetch(
           "http://%s.latest.%s.appspot.com/blobstore_image/geturl" %
           (os.environ["CURRENT_VERSION_ID"].split(".")[0],
            os.environ["APPLICATION_ID"].replace("s~"""))).content
try:
    result = urlfetch.fetch(
        url=url,
        payload=payload,
        method=urlfetch.POST,
        headers=headers,
        deadline=10,
        follow_redirects=False)
    if "location" in result.headers:
      location = result.headers["location"]
      key = location[location.rfind("/") + 1:]
      return key
    else:
      return None
  except:
    return None

def development():
  return os.environ['SERVER_SOFTWARE'].find('Development') == 0 

class GetUrl(webapp.RequestHandler):
    def get(self):
      self.response.out.write(blobstore.create_upload_url('/blobstore_image'))

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
      upload_files = self.get_uploads('file')
      blob_info = upload_files[0]
      self.redirect('%s' % blob_info.key())

def main():
    application = webapp.WSGIApplication(
          [('/blobstore_image/geturl', GetUrl),
           ('/blobstore_image', UploadHandler),
          ], debug=True)
    run_wsgi_app(application)

if __name__ == '__main__':
  main()

Kommentare:

  1. Could this be generalized for all blobstore items?

    AntwortenLöschen
  2. Absolutely. Actually it is already working for all other items. I'm just not sure if the 2 dev_appserver.py instances share the data properly. You'll have to try out...

    AntwortenLöschen