Blobstore API for Python 3

This page describes how to use the Blobstore API, one of the legacy bundled services, with the Python 3 runtime for the standard environment. Your app can access the bundled services through the App Engine services SDK for Python 3.

Overview

Since webapp is not supported in Python 3, you need to make some minimal changes when migrating Blobstore handler code from Python 2 to Python 3. To use the Blobstore API for Python 3, keep in mind the following:

  • Blobstore handler classes are utility classes. This means that the handler classes are no longer webapp-based, and you cannot use the blobstore_handlers module provided by the webapp package (google.appengine.ext.webapp) or the webapp2.RequestHandler parameters in subclasses of these handlers.

  • All of the methods in Blobstore handler classes require the WSGI environ dictionary as an input parameter.

The following sections show how to use BlobstoreUploadHandler and BlobstoreDownloadHandler classes for Python 3 in a Flask app and a WSGI app that does not use a Python framework. You can compare the Python 3 examples with the Python 2 example code to learn more about code change differences.

Example: Flask app

In Python 3, the Blobstore handler classes are part of module google.appengine.ext.blobstore. For a Flask app, all calls made to methods in BlobstoreUploadHandler and BlobstoreDownloadHandler classes require the request.environ dictionary (request being imported from the flask module).

Compare the code changes made from Python 2 (webapp2) to Python 3 (Flask). Notice how the Flask app uses the request.environ parameter in the methods get_uploads() and send_blob():

Python 2 (webapp2)

class PhotoUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        upload = self.get_uploads()[0]
        user_photo = UserPhoto(
            user=users.get_current_user().user_id(),
            blob_key=upload.key())
        user_photo.put()

        self.redirect('/view_photo/%s' % upload.key())

class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):
    def get(self, photo_key):
        if not blobstore.get(photo_key):
            self.error(404)
        else:
            self.send_blob(photo_key)

app = webapp2.WSGIApplication([
    ('/', PhotoUploadFormHandler),
    ('/upload_photo', PhotoUploadHandler),
    ('/view_photo/([^/]+)?', ViewPhotoHandler),
], debug=True)

Python 3 (Flask)

class PhotoUploadHandler(blobstore.BlobstoreUploadHandler):
    def post(self):
        upload = self.get_uploads(request.environ)[0]
        photo = PhotoUpload(blob_key=upload.key())
        photo.put()

        return redirect("/view_photo/%s" % upload.key())


class ViewPhotoHandler(blobstore.BlobstoreDownloadHandler):
    def get(self, photo_key):
        if not blobstore.get(photo_key):
            return "Photo key not found", 404
        else:
            headers = self.send_blob(request.environ, photo_key)

            # Prevent Flask from setting a default content-type.
            # GAE sets it to a guessed type if the header is not set.
            headers["Content-Type"] = None
            return "", headers


@app.route("/view_photo/<photo_key>")
def view_photo(photo_key):
    """View photo given a key."""
    return ViewPhotoHandler().get(photo_key)


@app.route("/upload_photo", methods=["POST"])
def upload_photo():
    """Upload handler called by blobstore when a blob is uploaded in the test."""
    return PhotoUploadHandler().post()

To view the complete code sample for Python 3 (Flask), see GitHub.

Example: WSGI app without a web framework

The following Python 3 (WSGI app) code shows how to add the environ parameter when using Blobstore handler classes for a WSGI app without a web framework. Notice how the environ parameter is used in the get_uploads() and send_blob() methods, and compare it with the Python 2 version:

Python 2

class PhotoUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        upload = self.get_uploads()[0]
        user_photo = UserPhoto(
            user=users.get_current_user().user_id(),
            blob_key=upload.key())
        user_photo.put()

        self.redirect('/view_photo/%s' % upload.key())

class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):
    def get(self, photo_key):
        if not blobstore.get(photo_key):
            self.error(404)
        else:
            self.send_blob(photo_key)

app = webapp2.WSGIApplication([
    ('/', PhotoUploadFormHandler),
    ('/upload_photo', PhotoUploadHandler),
    ('/view_photo/([^/]+)?', ViewPhotoHandler),
], debug=True)

Python 3

class UploadPhotoHandler(blobstore.BlobstoreUploadHandler):
    """Upload handler called by blobstore when a blob is uploaded in the test."""

    def post(self, environ):
        upload = self.get_uploads(environ)[0]
        user_photo = UserPhoto(blob_key=upload.key())
        user_photo.put()

        # Redirect to the '/view_photo/<Photo Key>' URL
        return (
            "",
            http.HTTPStatus.FOUND,
            [("Location", "/view_photo/%s" % upload.key())],
        )


class ViewPhotoHandler(blobstore.BlobstoreDownloadHandler):
    def get_photo(self, environ, photo_key):
        if not blobstore.get(photo_key):
            return "Photo key not found", http.HTTPStatus.NOT_FOUND, []
        else:
            return (
                "",
                http.HTTPStatus.OK,
                list(self.send_blob(environ, photo_key).items()),
            )

    def get(self, environ):
        photo_key = (environ["app.url_args"])[0]
        return self.get_photo(environ, photo_key)


# map urls to functions
urls = [
    (r"^$", UploadFormHandler),
    (r"upload_photo/?$", UploadPhotoHandler),
    (r"view_photo/(.+)$", ViewPhotoHandler),
]

To view the complete code sample for Python 3, see GitHub.