Skip to content

Instantly share code, notes, and snippets.

@voscausa
Last active July 24, 2018 12:01
Show Gist options
  • Save voscausa/9222732 to your computer and use it in GitHub Desktop.
Save voscausa/9222732 to your computer and use it in GitHub Desktop.

App Engine update blob

Writing to the blostore using the Files API is deprecated. But we can upload a blob to update the blob

This way we do not need GCS (Google Cloud Storage) To use GCS you have to pay for the application

UPDATE: GCS, using a default bucket is now FREE (1.9.0)

From the docs: An application can use the default GCS bucket, which provides an already configured bucket with free quota. https://developers.google.com/appengine/docs/python/googlecloudstorageclient/

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, with_statement
import webapp2
import mimetypes
from google.appengine.ext.ndb import blobstore
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.api import urlfetch
from google.appengine.runtime import DeadlineExceededError
import logging
CRLF = b'\r\n'
BOUNDERY = b'--Th15-Is-ThE-BoUnDeRy--'
max_buffer_size = 1048576
def create_upload_url(for_url):
return blobstore.create_upload_url(for_url, max_bytes_per_blob=max_buffer_size)
def _fetch_blob_update(payload):
upload_url = create_upload_url(webapp2.uri_for('blob_update', _full=True))
try:
response = urlfetch.fetch(
url=upload_url,
payload=payload,
method=urlfetch.POST,
deadline=40,
follow_redirects=False,
headers={b'Content-Type': str('multipart/form-data; boundary=%s' % BOUNDERY)}
)
if response.status_code != 200:
logging.error('URL : %s fetch ERROR : %d' % (upload_url, response.status_code))
return None
else:
return response.content
except (urlfetch.DownloadError, DeadlineExceededError), e:
logging.error('fetch %s download or deadline error, exception : %s'
% (upload_url, str(e)))
return None
def _encode_multipart(_fields=None, _files=None):
""" Generator inserts fields and files to create a multipart payload """
for (field_name, field_value) in _fields or []:
yield b'--' + BOUNDERY + CRLF
yield b'Content-Disposition: form-data; name="%s"' % str(field_name) + CRLF
yield CRLF
yield str(field_value) + CRLF
for (file_name, file_data) in _files or []:
yield b'--' + BOUNDERY + CRLF
yield b'Content-Disposition: form-data; name="file"; filename="%s"' \
% str(file_name) + CRLF
yield b'Content-Type: %s' % mimetypes.guess_type(file_name)[0] + CRLF
yield CRLF
yield file_data + CRLF
yield b'--%s--' % BOUNDERY + CRLF
def blob_update_by_upload(ndb_key, blob):
""" update blob using blobstore upload """
result = _fetch_blob_update(b''.join(_encode_multipart(_files=[(ndb_key.id(), blob)])))
return blobstore.BlobKey(result)
class BlobUpdate(blobstore_handlers.BlobstoreUploadHandler):
""" has named route : blob_update :
webapp2.Route(r'/blob_update', handler='....BlobUpdate', name='blob_update') """
def post(self):
""" blob upload handler returns the new blobkey"""
blob_info = self.get_uploads('file')[0]
logging.info('BlobUpdate : ' + blob_info.filename)
self.response.headers[b'Content-Type'] = b'text/plain'
self.response.write(str(blob_info.key()))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment