14a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Copyright 2015 The Chromium Authors. All rights reserved.
24a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Use of this source code is governed by a BSD-style license that can be
34a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# found in the LICENSE file.
44a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair"""A module for storing and getting objects from datastore.
64a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan SinclairThis module provides Get, Set and Delete functions for storing pickleable
8edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairobjects in datastore, with support for large objects greater than 1 MB.
9edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
10edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan SinclairAlthough this module contains ndb.Model classes, these are not intended
11edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairto be used directly by other modules.
12edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
13edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan SinclairApp Engine datastore limits entity size to less than 1 MB; this module
14edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairsupports storing larger objects by splitting the data and using multiple
15edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairdatastore entities and multiple memcache keys. Using ndb.get and pickle, a
16edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclaircomplex data structure can be retrieved more quickly than datastore fetch.
174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairExample:
194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  john = Account()
204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  john.username = 'John'
214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  john.userid = 123
22edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  stored_object.Set(john.userid, john)
234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair"""
244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport cPickle as pickle
264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport logging
274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom google.appengine.api import memcache
294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom google.appengine.ext import ndb
304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair_MULTIPART_ENTITY_MEMCACHE_KEY = 'multipart_entity_'
324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
33edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair# Maximum number of entities and memcache to save a value.
34edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair# The limit for data stored in one datastore entity is 1 MB,
35edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair# and the limit for memcache batch operations is 32 MB. See:
36edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair# https://cloud.google.com/appengine/docs/python/memcache/#Python_Limits
374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair_MAX_NUM_PARTS = 16
384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Max bytes per entity or value cached with memcache.
404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair_CHUNK_SIZE = 1000 * 1000
414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
43edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairdef Get(key):
44edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  """Gets the value.
45edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
46edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  Args:
47edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key: String key value.
48edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
49edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  Returns:
50edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    A value for key.
51edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  """
52edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  results = MultipartCache.Get(key)
53edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  if not results:
54edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    results = _GetValueFromDatastore(key)
55edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    MultipartCache.Set(key, results)
56edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  return results
57edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
58edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
59edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairdef Set(key, value):
60edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  """Sets the value in datastore and memcache with limit of '_MAX_NUM_PARTS' MB.
61edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
62edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  Args:
63edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key: String key value.
64edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    value: A pickleable value to be stored limited at '_MAX_NUM_PARTS' MB.
65edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  """
66edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  entity = ndb.Key(MultipartEntity, key).get()
67edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  if not entity:
68edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    entity = MultipartEntity(id=key)
69edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  entity.SetData(value)
70edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  entity.Save()
71edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  MultipartCache.Set(key, value)
72edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
73edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
74edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairdef Delete(key):
75edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  """Deletes the value in datastore and memcache."""
76edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  ndb.Key(MultipartEntity, key).delete()
77edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  MultipartCache.Delete(key)
78edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
79edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MultipartEntity(ndb.Model):
814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Container for PartEntity."""
824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  # Number of entities use to store serialized.
844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  size = ndb.IntegerProperty(default=0, indexed=False)
854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _post_get_hook(cls, key, future):  # pylint: disable=unused-argument
884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Deserializes data from multiple PartEntity."""
894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    entity = future.get_result()
904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if entity is None or not entity.size:
914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return
924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    string_id = entity.key.string_id()
944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    part_keys = [ndb.Key(MultipartEntity, string_id, PartEntity, i + 1)
954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                 for i in xrange(entity.size)]
964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    part_entities = ndb.get_multi(part_keys)
974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    serialized = ''.join(p.value for p in part_entities if p is not None)
984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    entity.SetData(pickle.loads(serialized))
994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
1014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _pre_delete_hook(cls, key):
1024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Deletes PartEntity entities."""
1034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    part_keys = PartEntity.query(ancestor=key).fetch(keys_only=True)
1044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    ndb.delete_multi(part_keys)
1054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def Save(self):
1074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Stores serialized data over multiple PartEntity."""
1084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    serialized_parts = _Serialize(self.GetData())
1094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if len(serialized_parts) > _MAX_NUM_PARTS:
1104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      logging.error('Max number of parts reached.')
1114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return
1124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    part_list = []
1134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    num_parts = len(serialized_parts)
1144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for i in xrange(num_parts):
1154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if serialized_parts[i] is not None:
116cef7893435aa41160dd1255c43cb8498279738ccChris Craik        part = PartEntity(id=i + 1, parent=self.key, value=serialized_parts[i])
1174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        part_list.append(part)
1184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.size = num_parts
1194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    ndb.put_multi(part_list + [self])
1204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def GetData(self):
1224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return getattr(self, '_data', None)
1234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def SetData(self, data):
1254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    setattr(self, '_data', data)
1264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass PartEntity(ndb.Model):
1294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Holds a part of serialized data for MultipartEntity.
1304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  This entity key has the form:
1324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    ndb.Key('MultipartEntity', multipart_entity_id, 'PartEntity', part_index)
1334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
1344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  value = ndb.BlobProperty()
1354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MultipartCache(object):
1384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Contains operations for storing values over multiple memcache keys.
1394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Values are serialized, split, and stored over multiple memcache keys.  The
1414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  head cache stores the expected size.
1424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
1434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
1454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def Get(cls, key):
1464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Gets value in memcache."""
1474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    keys = cls._GetCacheKeyList(key)
1484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    head_key = cls._GetCacheKey(key)
1494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    cache_values = memcache.get_multi(keys)
1504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Whether we have all the memcache values.
1514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if len(keys) != len(cache_values) or head_key not in cache_values:
1524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return None
1534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    serialized = ''
1554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    cache_size = cache_values[head_key]
1564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    keys.remove(head_key)
1574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for key in keys[:cache_size]:
1584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if key not in cache_values:
1594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        return None
1604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if cache_values[key] is not None:
1614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        serialized += cache_values[key]
1624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return pickle.loads(serialized)
1634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
1654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def Set(cls, key, value):
1664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Sets a value in memcache."""
1674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    serialized_parts = _Serialize(value)
1684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if len(serialized_parts) > _MAX_NUM_PARTS:
1694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      logging.error('Max number of parts reached.')
1704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return
1714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    cached_values = {}
1734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    cached_values[cls._GetCacheKey(key)] = len(serialized_parts)
1744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for i in xrange(len(serialized_parts)):
1754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      cached_values[cls._GetCacheKey(key, i)] = serialized_parts[i]
1764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    memcache.set_multi(cached_values)
1774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
1794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def Delete(cls, key):
1804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Deletes all cached values for key."""
1814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    memcache.delete_multi(cls._GetCacheKeyList(key))
1824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
1844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _GetCacheKeyList(cls, key):
1854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Gets a list of head cache key and cache key parts."""
1864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    keys = [cls._GetCacheKey(key, i) for i in xrange(_MAX_NUM_PARTS)]
1874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    keys.append(cls._GetCacheKey(key))
1884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return keys
1894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
1914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _GetCacheKey(cls, key, index=None):
1924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Returns either head cache key or cache key part."""
1934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if index is not None:
1944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return _MULTIPART_ENTITY_MEMCACHE_KEY + '%s.%s' % (key, index)
1954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return _MULTIPART_ENTITY_MEMCACHE_KEY + key
1964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _GetValueFromDatastore(key):
1994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  entity = ndb.Key(MultipartEntity, key).get()
2004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  if not entity:
2014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return None
2024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  return entity.GetData()
2034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _Serialize(value):
2064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Serializes value and returns a list of its parts.
2074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Args:
2094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    value: A pickleable value.
2104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Returns:
2124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    A list of string representation of the value that has been pickled and split
2134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    into _CHUNK_SIZE.
2144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
2154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  serialized = pickle.dumps(value, 2)
2164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  length = len(serialized)
2174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  values = []
2184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  for i in xrange(0, length, _CHUNK_SIZE):
219cef7893435aa41160dd1255c43cb8498279738ccChris Craik    values.append(serialized[i:i + _CHUNK_SIZE])
2204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  for i in xrange(len(values), _MAX_NUM_PARTS):
2214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    values.append(None)
2224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  return values
223