15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
53551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)def IsDeadlineExceededError(error):
63551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''A general way of determining whether |error| is a DeadlineExceededError,
73551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  since there are 3 different types thrown by AppEngine and we might as well
83551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  handle them all the same way. For more info see:
93551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  https://developers.google.com/appengine/articles/deadlineexceedederrors
103551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  '''
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return type(error).__name__ == 'DeadlineExceededError'
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def IsDownloadError(error):
15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return type(error).__name__ == 'DownloadError'
163551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This will attempt to import the actual App Engine modules, and if it fails,
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# they will be replaced with fake modules. This is useful during testing.
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try:
211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  import google.appengine.api.app_identity as app_identity
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  import google.appengine.api.files as files
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  import google.appengine.api.logservice as logservice
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  import google.appengine.api.memcache as memcache
251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  import google.appengine.api.taskqueue as taskqueue
26c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  import google.appengine.api.urlfetch as urlfetch
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.ext.blobstore as blobstore
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  from google.appengine.ext.blobstore.blobstore import BlobReferenceProperty
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.ext.db as db
30f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  import webapp2
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError:
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import re
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  from StringIO import StringIO
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  FAKE_URL_FETCHER_CONFIGURATION = None
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ConfigureFakeUrlFetch(configuration):
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """|configuration| is a dictionary mapping strings to fake urlfetch classes.
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A fake urlfetch class just needs to have a fetch method. The keys of the
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dictionary are treated as regex, and they are matched with the URL to
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    determine which fake urlfetch is used.
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    global FAKE_URL_FETCHER_CONFIGURATION
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    FAKE_URL_FETCHER_CONFIGURATION = dict(
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (re.compile(k), v) for k, v in configuration.iteritems())
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _GetConfiguration(key):
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not FAKE_URL_FETCHER_CONFIGURATION:
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ValueError('No fake fetch paths have been configured. '
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       'See ConfigureFakeUrlFetch in appengine_wrappers.py.')
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in FAKE_URL_FETCHER_CONFIGURATION.iteritems():
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k.match(key):
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return v
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise ValueError('No configuration found for %s' % key)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class _RPC(object):
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, result=None):
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.result = result
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get_result(self):
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self.result
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
63c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def wait(self):
64c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pass
65c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  class FakeAppIdentity(object):
671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """A fake app_identity module that returns no access tokens."""
681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    def get_access_token(self, scope):
691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return (None, None)
701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  app_identity = FakeAppIdentity()
711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeUrlFetch(object):
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """A fake urlfetch module that uses the current
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    |FAKE_URL_FETCHER_CONFIGURATION| to map urls to fake fetchers.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    class DownloadError(Exception):
772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      pass
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class _Response(object):
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, content):
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.content = content
82f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        self.headers = {'Content-Type': 'none'}
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.status_code = 200
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def fetch(self, url, **kwargs):
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      url = url.split('?', 1)[0]
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = self._Response(_GetConfiguration(url).fetch(url))
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if response.content is None:
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        response.status_code = 404
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return response
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    def create_rpc(self, **kwargs):
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return _RPC()
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def make_fetch_call(self, rpc, url, **kwargs):
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rpc.result = self.fetch(url)
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  urlfetch = FakeUrlFetch()
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _BLOBS = {}
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeBlobstore(object):
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    class BlobNotFoundError(Exception):
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      pass
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class BlobReader(object):
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, blob_key):
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._data = _BLOBS[blob_key].getvalue()
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def read(self):
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self._data
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  blobstore = FakeBlobstore()
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeFileInterface(object):
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """This class allows a StringIO object to be used in a with block like a
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    file.
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, io):
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._io = io
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __exit__(self, *args):
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def write(self, data):
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._io.write(data)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __enter__(self, *args):
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self._io
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeFiles(object):
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    _next_blobstore_key = 0
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class blobstore(object):
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      @staticmethod
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def create():
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        FakeFiles._next_blobstore_key += 1
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return FakeFiles._next_blobstore_key
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      @staticmethod
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def get_blob_key(filename):
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return filename
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def open(self, filename, mode):
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      _BLOBS[filename] = StringIO()
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return FakeFileInterface(_BLOBS[filename])
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def GetBlobKeys(self):
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return _BLOBS.keys()
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def finalize(self, filename):
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  files = FakeFiles()
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
153c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  class Logservice(object):
154c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    AUTOFLUSH_ENABLED = True
155c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
156c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def flush(self):
157c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pass
158c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
159c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  logservice = Logservice()
160c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class InMemoryMemcache(object):
162c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """An in-memory memcache implementation.
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
164c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def __init__(self):
165c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self._namespaces = {}
166c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class Client(object):
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def set_multi_async(self, mapping, namespace='', time=0):
169c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        for k, v in mapping.iteritems():
170c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          memcache.set(k, v, namespace=namespace, time=time)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def get_multi_async(self, keys, namespace='', time=0):
173c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return _RPC(result=dict(
174c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          (k, memcache.get(k, namespace=namespace, time=time)) for k in keys))
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def set(self, key, value, namespace='', time=0):
177c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self._GetNamespace(namespace)[key] = value
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get(self, key, namespace='', time=0):
180c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return self._GetNamespace(namespace).get(key)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def delete(self, key, namespace=''):
183c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self._GetNamespace(namespace).pop(key, None)
184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
185c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def delete_multi(self, keys, namespace=''):
186c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      for k in keys:
187c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self.delete(k, namespace=namespace)
188c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
189c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def _GetNamespace(self, namespace):
190c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      if namespace not in self._namespaces:
191c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self._namespaces[namespace] = {}
192c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return self._namespaces[namespace]
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  memcache = InMemoryMemcache()
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
196f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  class webapp2(object):
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class RequestHandler(object):
198f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      """A fake webapp2.RequestHandler class for Handler to extend.
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, request, response):
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.request = request
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.response = response
2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.response.status = 200
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      def redirect(self, path, permanent=False):
2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.response.status = 301 if permanent else 302
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.response.headers['Location'] = path
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class _Db_Result(object):
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, data):
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._data = data
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class _Result(object):
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, value):
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.value = value
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get(self):
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self._Result(self._data)
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class db(object):
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    _store = {}
222c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class StringProperty(object):
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
226c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    class BlobProperty(object):
227c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pass
228c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
229c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    class Key(object):
230c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      def __init__(self, key):
231c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self._key = key
232c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
233c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      @staticmethod
234c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      def from_path(model_name, path):
235c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return db.Key('%s/%s' % (model_name, path))
236c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
237c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      def __eq__(self, obj):
238c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return self.__class__ == obj.__class__ and self._key == obj._key
239c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
240c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      def __hash__(self):
241c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return hash(self._key)
242c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
243c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      def __str__(self):
244c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return str(self._key)
245c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class Model(object):
247c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      key = None
248c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
249c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      def __init__(self, **optargs):
250c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        cls = self.__class__
251c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        for k, v in optargs.iteritems():
252c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          assert hasattr(cls, k), '%s does not define property %s' % (
253c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)              cls.__name__, k)
254c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          setattr(self, k, v)
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      @staticmethod
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def gql(query, key):
258c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        return _Db_Result(db._store.get(key))
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def put(self):
261c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        db._store[self.key_] = self.value
262c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
263c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    @staticmethod
264c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def get_async(key):
265c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return _RPC(result=db._store.get(key))
266c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
267c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    @staticmethod
268c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def delete_async(key):
269c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      db._store.pop(key, None)
270c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return _RPC()
271c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
272c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    @staticmethod
273c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    def put_async(value):
274c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      db._store[value.key] = value
275c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return _RPC()
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class BlobReferenceProperty(object):
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pass
2791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  # Executes any queued tasks synchronously as they are queued.
2811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  _task_runner = None
2821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def SetTaskRunnerForTest(task_runner):
2841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    global _task_runner
2851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    _task_runner = task_runner
2861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  class SynchronousTaskQueue(object):
2881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    class Task(object):
2891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      def __init__(self, url=None, params={}):
2901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        self.url_ = url
2911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        self.params_ = params
2921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      def GetUrl(self):
2941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return self.url_
2951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      def GetCommit(self):
2971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return self.params_.get('commit')
2981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    class Queue(object):
3001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      def __init__(self, name='default'):
3011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        pass
3021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      def add(self, task):
3041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        global _task_runner
3051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        if _task_runner:
3061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          _task_runner(task.GetUrl(), task.GetCommit())
3071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return _RPC()
3081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      def purge(self):
3101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return _RPC()
3111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
3121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  taskqueue = SynchronousTaskQueue()
313