def _end_creation(self, token, _upload_filename): """End object upload. Args: token: upload token returned by post_start_creation. Returns: _AE_GCSFileInfo Entity for this file. Raises: ValueError: if token is invalid. Or file is corrupted during upload. Save file content to blobstore. Save blobinfo and _AE_GCSFileInfo. """ gcs_file = _AE_GCSFileInfo_.get_by_key_name(token) if not gcs_file: raise ValueError('Invalid token') if gcs_file.finalized: return gcs_file error_msg, content = self._get_content(gcs_file) if error_msg: raise ValueError(error_msg) gcs_file.etag = hashlib.md5(content).hexdigest() gcs_file.creation = datetime.datetime.utcnow() gcs_file.size = len(content) blob_info = datastore.Entity('__BlobInfo__', name=str(token), namespace='') blob_info['content_type'] = gcs_file.content_type blob_info['creation'] = gcs_file.creation blob_info['filename'] = _upload_filename blob_info['md5_hash'] = gcs_file.etag blob_info['size'] = gcs_file.size datastore.Put(blob_info) self.blob_storage.StoreBlob(token, StringIO.StringIO(content)) gcs_file.finalized = True gcs_file.next_offset = -1 gcs_file.put() return gcs_file
def CreateBlob(self, blob_key, content): """Create new blob and put in storage and Datastore. This is useful in testing where you have access to the stub. Args: blob_key: String blob-key of new blob. content: Content of new blob as a string. Returns: New Datastore entity without blob meta-data fields. """ entity = datastore.Entity(blobstore.BLOB_INFO_KIND, name=blob_key, namespace='') entity['size'] = len(content) datastore.Put(entity) self.storage.CreateBlob(blob_key, content) return entity
def test_check_missing_instance_attr(self): marker1 = "{}|name:{}".format(TestModel._meta.db_table, md5(self.i2.name).hexdigest()) marker_key = datastore.Key.from_path(UniqueMarker.kind(), marker1) marker = datastore.Get(marker_key) marker['instance'] = None datastore.Put(marker) UniqueAction.objects.create(action_type="check", model=encode_model(TestModel)) process_task_queues() a = UniqueAction.objects.get() self.assertEqual(a.status, "done") self.assertEqual(1, a.actionlog_set.count()) error = a.actionlog_set.all()[0] instance_key = datastore.Key.from_path(TestModel._meta.db_table, self.i2.pk) self.assertEqual(error.log_type, "missing_instance") self.assertEqual(error.instance_key, str(instance_key)) self.assertEqual(error.marker_key, str(marker_key))
def finalize(self, filename): """Marks file as finalized.""" upload = self.uploads[filename] self.finalized.add(filename) upload.buf.seek(0) self.blob_storage.StoreBlob(self.get_blob_key(upload.key), upload.buf) del self.sequence_keys[filename] encoded_key = blobstore.create_gs_key(upload.key) file_info = datastore.Entity(GS_INFO_KIND, name=encoded_key, namespace='') file_info['creation'] = _now_function() file_info['filename'] = upload.key file_info['size'] = upload.buf.len file_info['content_type'] = upload.content_type file_info['storage_key'] = self.get_blob_key(upload.key) datastore.Put(file_info)
def finalize(self): """Finalize a file. Copies temp file data to the blobstore. """ self.file_storage.finalize(self.filename) blob_key = dev_appserver_upload.GenerateBlobKey() self.file_storage.register_blob_key(self.ticket, blob_key) size = self.file_storage.save_blob(self.filename, blob_key) blob_entity = datastore.Entity('__BlobInfo__', name=str(blob_key), namespace='') blob_entity['content_type'] = self.mime_content_type blob_entity['creation'] = _now_function() blob_entity['filename'] = self.ticket blob_entity['size'] = size datastore.Put(blob_entity)
def save(self): """Creates or edits this page in the datastore.""" now = datetime.datetime.now() if self.entity: entity = self.entity else: entity = datastore.Entity('Page') entity['name'] = self.name entity['created'] = now entity['content'] = datastore_types.Text(self.content) entity['modified'] = now if users.get_current_user(): entity['user'] = users.get_current_user() elif entity.has_key('user'): del entity['user'] datastore.Put(entity)
def test_repair_old_style_marker(self): instance_key = datastore.Key.from_path(TestModel._meta.db_table, self.i2.pk) marker1 = "{}|name:{}".format(TestModel._meta.db_table, md5(self.i2.name).hexdigest()) marker_key = datastore.Key.from_path(UniqueMarker.kind(), marker1) marker = datastore.Get(marker_key) marker['instance'] = str(instance_key) #Make the instance a string datastore.Put(marker) UniqueAction.objects.create(action_type="repair", model=encode_model(TestModel)) process_task_queues() a = UniqueAction.objects.get() self.assertEqual(a.status, "done") self.assertEqual(0, a.actionlog_set.count()) marker = datastore.Get(marker_key) self.assertTrue(marker) self.assertEqual(marker['instance'], instance_key)
def test_empty_request_and_populated_datastore(self): entity = datastore.Entity('Kind1', id=123, _app=self.app_id) entity['intprop'] = 1 entity['listprop'] = [7, 8, 9] datastore.Put(entity) request = webapp2.Request.blank('/datastore') response = webapp2.Response() handler = datastore_viewer.DatastoreRequestHandler(request, response) admin_request_handler.AdminRequestHandler(handler).get() self.mox.ReplayAll() handler.get() self.mox.VerifyAll() self.assertEqual(302, response.status_int) self.assertEqual('http://localhost/datastore?kind=Kind1', response.location)
def store_login(self, oidrequest, kind): """Stores the details of an OpenID login in the datastore. Args: oidrequest: OpenIDRequest kind: string 'remembered', 'confirmed', or 'declined' """ assert kind in ['remembered', 'confirmed', 'declined'] user = users.get_current_user() assert user login = datastore.Entity('Login') login['relying_party'] = oidrequest.trust_root login['time'] = datetime.datetime.now() login['kind'] = kind login['user'] = user datastore.Put(login)
def create_blob(self): """Create a blob in the datastore and on disk. Returns: BlobKey of new blob. """ contents = 'a blob' blob_key = blobstore.BlobKey('blob-key-1') self.blob_storage.StoreBlob(blob_key, cStringIO.StringIO(contents)) entity = datastore.Entity(blobstore.BLOB_INFO_KIND, name=str(blob_key), namespace='') entity['content_type'] = 'image/png' entity['creation'] = datetime.datetime(1999, 10, 10, 8, 42, 0) entity['filename'] = 'largeblob.png' entity['size'] = len(contents) datastore.Put(entity) return blob_key
def LoadEntities(self, iter): entities = [] output = [] for line_num, columns in iter: if columns: try: #logging.info('\nLoading from line %d...' % line_num) new_entities = self.CreateEntity(columns) if new_entities: entities.extend(new_entities) #logging.info('done.') except: stacktrace = traceback.format_exc() logging.info('error:\n%s' % stacktrace) return output datastore.Put(entities) return output
def post(self, entity_key_string=None): super(DatastoreEditRequestHandler, self).post(entity_key_string) if self.request.get('action:delete'): if entity_key_string: datastore.Delete(datastore.Key(entity_key_string)) self.redirect(str(self.request.get('next', '/datastore'))) else: self.response.set_status(400) return if entity_key_string: entity = datastore.Get(datastore.Key(entity_key_string)) else: kind = self.request.get('kind') namespace = self.request.get('namespace', None) entity = datastore.Entity(kind, _namespace=namespace) for arg_name in self.request.arguments(): # Arguments are in <property_type>|<property_name>=<value> format. if '|' not in arg_name: continue data_type_name, property_name = arg_name.split('|') form_value = self.request.get(arg_name) data_type = DataType.get_by_name(data_type_name) if (entity and property_name in entity and data_type.format(entity[property_name]) == form_value): # If the property is unchanged then don't update it. This will prevent # empty form values from causing the property to be deleted if the # property was already empty. continue if form_value: # TODO: Handle parse exceptions. entity[property_name] = data_type.parse(form_value) elif property_name in entity: # TODO: Treating empty input as deletion is a not a good # interface. del entity[property_name] datastore.Put(entity) self.redirect(str(self.request.get('next', '/datastore')))
def apply_entity(unapplied_entity, delete_unapplied_entity=True): """Re-write an entity representing an unapplied write to apply it. Args: entity: An app engine datastore entity, typically loaded by datastore.Get. This will not work for a model instance, e.g. one loaded from db.get. delete_unapplied_entity: If true, the record of the unapplied write will be removed from the datastore. """ key = unapplied_entity.key() path = unapplied_entity.key().to_path() kind = path[-2] if not kind.startswith(UNAPPLIED_WRITE_KIND_PREFIX): logging.Error("Attempting to apply an already applied write: %r", key) return kind = kind[UNAPPLIED_WRITE_KIND_PREFIX_LEN:] id_or_name = path[-1] namespace = unapplied_entity.namespace() # You can insert code here to change id_or_name. if isinstance(id_or_name, basestring): entity_to_apply = datastore.Entity(kind, key.parent(), name=id_or_name, namespace=namespace) elif id_or_name: entity_to_apply = datastore.Entity(kind, key.parent(), id=id_or_name, namespace=namespace) else: entity_to_apply = datastore.Entity(kind, key.parent(), namespace=namespace) entity_to_apply.update(unapplied_entity) # You can insert code here to change entity_to_apply. datastore.Put(entity_to_apply) if delete_unapplied_entity: datastore.Delete(unapplied_entity)
def GetUrlBase(self, request, response): """Trivial implementation of ImagesService::GetUrlBase. Args: request: ImagesGetUrlBaseRequest, contains a blobkey to an image response: ImagesGetUrlBaseResponse, contains a url to serve the image """ if request.create_secure_url(): logging.info( "Secure URLs will not be created using the development " "application server.") entity_info = datastore.Entity(BLOB_SERVING_URL_KIND, name=request.blob_key(), namespace="") entity_info["blob_key"] = request.blob_key() datastore.Put(entity_info) response.set_url("%s/_ah/img/%s" % (self._host_prefix, request.blob_key()))
def test_check_old_style_marker(self): instance_key = datastore.Key.from_path(TestModel._meta.db_table, self.i2.pk, namespace=DEFAULT_NAMESPACE) marker1 = "{}|name:{}".format(TestModel._meta.db_table, md5(self.i2.name).hexdigest()) marker_key = datastore.Key.from_path(UniqueMarker.kind(), marker1, namespace=DEFAULT_NAMESPACE) marker = datastore.Get(marker_key) marker['instance'] = str(instance_key) #Make the instance a string datastore.Put(marker) UniqueAction.objects.create(action_type="check", model=encode_model(TestModel)) process_task_queues() a = UniqueAction.objects.get() self.assertEqual(a.status, "done") self.assertEqual(1, a.actionlog_set.count()) error = a.actionlog_set.all()[0] self.assertEqual(error.log_type, "old_instance_key") self.assertEqual(error.instance_key, str(instance_key)) self.assertEqual(error.marker_key, str(marker_key))
def test_invalid_data_in_datastore_doesnt_throw_an_error(self): """ If invalid data is found while reading the entity data, then we should silently ignore the error and just return the data as-is rather than converting to list/dict. The reason is that if we blow up on load, then there's no way to load the entity (in Django) to repair the data. This is also consistent with the behaviour of Django when (for example) you load a NULL from the database into a field that is non-nullable. The field value will still be None when read. """ entity = datastore.Entity( JSONFieldModel._meta.db_table, id=1, namespace=settings.DATABASES["default"]["NAMESPACE"]) entity["json_field"] = "bananas" datastore.Put(entity) instance = JSONFieldModel.objects.get(pk=1) self.assertEqual(instance.json_field, "bananas")
def StoreBlob(self, blob_key, blob_stream): """Store blob stream to the datastore. Args: blob_key: Blob key of blob to store. blob_stream: Stream or stream-like object that will generate blob content. """ block_count = 0 blob_key_object = self._BlobKey(blob_key) while True: block = blob_stream.read(blobstore.MAX_BLOB_FETCH_SIZE) if not block: break entity = datastore.Entity(_BLOB_CHUNK_KIND_, name=str(blob_key_object) + "__" + str(block_count), namespace='') entity.update({'block': datastore_types.Blob(block)}) datastore.Put(entity) block_count += 1
def GetUrlBase(self, request, response): """Trivial implementation of an API call. Args: request: ImagesGetUrlBaseRequest - Contains a blobkey to an image. response: ImagesGetUrlBaseResponse - Contains a URL to serve the image. """ if request.create_secure_url(): logging.info( 'Secure URLs will not be created using the development ' 'application server.') entity_info = datastore.Entity(BLOB_SERVING_URL_KIND, name=request.blob_key(), namespace='') entity_info['blob_key'] = request.blob_key() datastore.Put(entity_info) response.set_url('%s/_ah/img/%s' % (self._host_prefix, request.blob_key()))
def Put(entities, **kwargs): """ Store one or more entities in the data store. The entities may be new or previously existing. For new entities, ``Put()`` will fill in the app id and key assigned by the data store. :param entities: Entity or list of entities to be stored. :type entities: :class:`server.db.Entity` | list of :class:`server.db.Entity` :param config: Optional configuration to use for this request. This must be specified\ as a keyword argument. :type config: dict :returns: If the argument ``entities`` is a single :class:`server.db.Entity`, \ a single Key is returned. If the argument is a list of :class:`server.db.Entity`, \ a list of Keys will be returned. :rtype: Key | list of keys :raises: :exc:`TransactionFailedError`, if the action could not be committed. """ if isinstance(entities, Entity): entities._fixUnindexedProperties() elif isinstance(entities, list): for entity in entities: assert isinstance(entity, Entity) entity._fixUnindexedProperties() if conf["viur.db.caching"] > 0: if isinstance(entities, Entity): #Just one: if entities.is_saved(): #Its an update memcache.delete(str(entities.key()), namespace=__CacheKeyPrefix__, seconds=__cacheLockTime__) elif isinstance(entities, list): for entity in entities: assert isinstance(entity, Entity) if entity.is_saved(): #Its an update memcache.delete(str(entity.key()), namespace=__CacheKeyPrefix__, seconds=__cacheLockTime__) return (datastore.Put(entities, **kwargs))
def StoreBlob(self, form_item, creation): """Store form-item to blob storage. Args: form_item: FieldStorage instance that represents a specific form field. This instance should have a non-empty filename attribute, meaning that it is an uploaded blob rather than a normal form field. creation: Timestamp to associate with new blobs creation time. This parameter is provided so that all blobs in the same upload form can have the same creation date. Returns: datastore.Entity('__BlobInfo__') associated with the upload. """ main_type, sub_type = _SplitMIMEType(form_item["content_type"]) blob_key = self.__generate_blob_key() self.__blob_storage.StoreBlob(blob_key, cStringIO.StringIO(form_item["body"])) content_type_formatter = base.MIMEBase(main_type, sub_type) # **form_item.type_options) blob_entity = datastore.Entity('__BlobInfo__', name=str(blob_key), namespace='') try: blob_entity['content_type'] = ( content_type_formatter['content-type'].decode('utf-8')) blob_entity['creation'] = creation blob_entity['filename'] = form_item["filename"] #.decode('utf-8') except UnicodeDecodeError: raise InvalidMetadataError( 'The uploaded entity contained invalid UTF-8 metadata. This may be ' 'because the page containing the upload form was served with a ' 'charset other than "utf-8".') #form_item.file.seek(0, 2) #size = form_item.file.tell() #form_item.file.seek(0) blob_entity['size'] = len(form_item["body"]) datastore.Put(blob_entity) return blob_entity
def test_namespace_request(self): entity = datastore.Entity('Kind1', id=123, _app=self.app_id, _namespace='google') entity['intprop'] = 1 entity['listprop'] = [7, 8, 9] datastore.Put(entity) request = webapp2.Request.blank( '/datastore?kind=Kind1&namespace=google') response = webapp2.Response() handler = datastore_viewer.DatastoreRequestHandler(request, response) admin_request_handler.AdminRequestHandler(handler).get() handler.render( 'datastore_viewer.html', { 'entities': mox.IgnoreArg(), # Tested with _get_entity_template_data. 'headers': mox.IgnoreArg(), # Tested with _get_entity_template_data. 'kind': 'Kind1', 'kinds': ['Kind1'], 'message': None, 'namespace': 'google', 'num_pages': 1, 'order': None, 'order_base_url': '/datastore?kind=Kind1&namespace=google', 'page': 1, 'paging_base_url': '/datastore?kind=Kind1&namespace=google', 'select_namespace_url': '/datastore?kind=Kind1&namespace=google', 'show_namespace': True, 'start': 0, 'total_entities': 1 }) self.mox.ReplayAll() handler.get() self.mox.VerifyAll()
def CreateUploadSession(creation, success_path, user): """Create upload session in datastore. Creates an upload session and puts it in Datastore to be referenced by upload handler later. Args: creation: Creation timestamp. success_path: Path in users application to call upon success. user: User that initiated this upload, if any. Returns: String encoded key of new Datastore entity. """ entity = datastore.Entity(_UPLOAD_SESSION_KIND) entity.update({'creation': creation, 'success_path': success_path, 'user': user, 'state': 'init'}) datastore.Put(entity) return str(entity.key())
def finalize(self): """Finalize a file. Copies temp file data to the blobstore. """ self.file_storage.finalize(self.filename) blob_key = self.file_storage.blob_storage.GenerateBlobKey() self.file_storage.register_blob_key(self.ticket, blob_key) size = self.file_storage.save_blob(self.filename, blob_key) blob_entity = datastore.Entity('__BlobInfo__', name=str(blob_key), namespace='') blob_entity['content_type'] = self.mime_content_type blob_entity['creation'] = datetime.datetime.now() blob_entity['filename'] = self.blob_file_name blob_entity['size'] = size blob_entity['creation_handle'] = self.ticket datastore.Put(blob_entity)
def test_clean_removes_markers_with_different_values(self): marker1 = "{}|name:{}".format(TestModel._meta.db_table, md5(self.i1.name).hexdigest()) marker_key = datastore.Key.from_path(UniqueMarker.kind(), marker1) original_marker = datastore.Get(marker_key) marker2 = "{}|name:{}".format(TestModel._meta.db_table, md5("bananas").hexdigest()) new_marker = datastore.Entity(UniqueMarker.kind(), name=marker2) new_marker.update(original_marker) datastore.Put(new_marker) UniqueAction.objects.create(action_type="clean", model=encode_model(TestModel)) process_task_queues() self.assertRaises(datastore_errors.EntityNotFoundError, datastore.Get, new_marker.key()) self.assertTrue(datastore.Get(marker_key))
def testRawEntityTypeFromOtherApp(self): """Test reading from other app.""" OTHER_KIND = "bar" OTHER_APP = "foo" apiproxy_stub_map.apiproxy.GetStub("datastore_v3").SetTrusted(True) expected_keys = [str(i) for i in range(10)] for k in expected_keys: datastore.Put(datastore.Entity(OTHER_KIND, name=k, _app=OTHER_APP)) params = { "entity_kind": OTHER_KIND, "_app": OTHER_APP, } conf = map_job.JobConfig(job_name=self.TEST_JOB_NAME, mapper=map_job.Mapper, input_reader_cls=self.reader_cls, input_reader_params=params, shard_count=1) itr = conf.input_reader_cls.split_input(conf)[0] self._assertEquals_splitInput(itr, expected_keys) apiproxy_stub_map.apiproxy.GetStub("datastore_v3").SetTrusted(False)
def GetUrlBase(self, request, response): """Trivial implementation of ImagesService::GetUrlBase. Args: request: ImagesGetUrlBaseRequest, contains a blobkey to an image response: ImagesGetUrlBaseResponse, contains a url to serve the image """ if request.create_secure_url(): logging.info( "Secure URLs will not be created using the development " "application server.") entity_info = datastore.Entity(BLOB_SERVING_URL_KIND, name=request.blob_key(), namespace="") entity_info["blob_key"] = request.blob_key() datastore.Put(entity_info) # AppScale: Host prefix does not include a port, so retrieve it from the # filesystem. full_prefix = self._host_prefix if full_prefix: # CURRENT_VERSION_ID is formatted as module:major_version.minor_version. version_info = os.environ.get('CURRENT_VERSION_ID', 'v1').split('.')[0] if ':' not in version_info: version_info = 'default:' + version_info service_id, version_id = version_info.split(':') version_key = '_'.join( [os.environ['APPLICATION_ID'], service_id, version_id]) port_file_location = os.path.join( '/', 'etc', 'appscale', 'port-{}.txt'.format(version_key)) with open(port_file_location) as port_file: port = port_file.read().strip() full_prefix = '{}:{}'.format(full_prefix, port) # End AppScale response.set_url("%s/_ah/img/%s" % (full_prefix, request.blob_key()))
def convert_sqlite_data_to_emulator(app_id, filename, gcd_emulator_host): """Convert datastore sqlite stub data to cloud emulator data. Args: app_id: A String representing application ID. filename: A String representing the absolute path to SQLite data. gcd_emulator_host: A String in the format of host:port indicate the hostname and port number of gcd emulator. Raises: PersistException: if the call to emulator's /persist endpoint fails. """ previous_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3') sqlite_stub = datastore_sqlite_stub.DatastoreSqliteStub(app_id, filename, trusted=True, use_atexit=False) apiproxy_stub_map.apiproxy.ReplaceStub('datastore_v3', sqlite_stub) entities = _fetch_all_datastore_entities() if entities: logging.info('Fetched %d entities from %s', len(entities), filename) grpc_stub = datastore_grpc_stub.DatastoreGrpcStub(gcd_emulator_host) grpc_stub.get_or_set_call_handler_stub() apiproxy_stub_map.apiproxy.ReplaceStub('datastore_v3', grpc_stub) datastore.Put(entities) # persist entities to disk in emulator's data format. conn = httplib.HTTPConnection(gcd_emulator_host) conn.request('POST', '/persist') response = conn.getresponse() msg = response.read() if httplib.OK != response.status: raise PersistException(msg) logging.info('Datastore conversion complete') else: logging.warning( 'Fetched 0 entity from %s, will not create cloud ' 'datastore emulator file', filename) sqlite_stub.Close() apiproxy_stub_map.apiproxy.ReplaceStub('datastore_v3', previous_stub)
def post(self): kind = self.request.get('kind') entity_key = self.request.get('key') if entity_key: if self.request.get('action') == 'Delete': datastore.Delete(datastore.Key(entity_key)) self.redirect(self.request.get('next')) return entity = datastore.Get(datastore.Key(entity_key)) else: namespace = self.request.get('namespace') if not namespace: namespace = None entity = datastore.Entity(kind, _namespace=namespace) args = self.request.arguments() for arg in args: bar = arg.find('|') if bar > 0: data_type_name = arg[:bar] field_name = arg[bar + 1:] form_value = self.request.get(arg) data_type = DataType.get_by_name(data_type_name) if entity and entity.has_key(field_name): old_formatted_value = data_type.format(entity[field_name]) if old_formatted_value == ustr(form_value): continue if len(form_value) > 0: value = data_type.parse(form_value) entity[field_name] = value elif entity.has_key(field_name): del entity[field_name] datastore.Put(entity) self.redirect(self.request.get('next'))
def Create(cls, score_range, branching_factor): """Constructs a new Ranker and returns it. Args: score_range: A list showing the range of valid scores, in the form: [most_significant_score_min, most_significant_score_max, less_significant_score_min, less_significant_score_max, ...] Ranges are [inclusive, exclusive) branching_factor: The branching factor of the tree. The number of datastore Gets is Theta(1/log(branching_factor)), and the amount of data returned by each Get is Theta(branching_factor). Returns: A new Ranker. """ # Put the root in the datastore: root = datastore.Entity("ranker") root["score_range"] = score_range root["branching_factor"] = branching_factor datastore.Put(root) myrank = Ranker(root.key()) return myrank
def store_blob(self, content_type, filename, md5_hash, blob_file, creation): """Store a supplied form-data item to the blobstore. The appropriate metadata is stored into the datastore. Args: content_type: The MIME content type of the uploaded file. filename: The filename of the uploaded file. md5_hash: MD5 hash of the file contents, as a hashlib hash object. blob_file: A file-like object containing the contents of the file. creation: datetime.datetime instance to associate with new blobs creation time. This parameter is provided so that all blobs in the same upload form can have the same creation date. Returns: datastore.Entity('__BlobInfo__') associated with the upload. Raises: _TooManyConflictsError if there were too many name conflicts generating a blob key. """ blob_key = self._generate_blob_key() # Store the blob contents in the blobstore. self._blob_storage.StoreBlob(blob_key, blob_file) # Store the blob metadata in the datastore as a __BlobInfo__ entity. blob_entity = datastore.Entity('__BlobInfo__', name=str(blob_key), namespace='') blob_entity['content_type'] = content_type blob_entity['creation'] = creation blob_entity['filename'] = filename blob_entity['md5_hash'] = md5_hash.hexdigest() blob_entity['size'] = blob_file.tell() datastore.Put(blob_entity) return blob_entity