appengine_wrappers.py revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This will attempt to import the actual App Engine modules, and if it fails,
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# they will be replaced with fake modules. This is useful during testing.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try:
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.ext.blobstore as blobstore
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  from google.appengine.ext.blobstore.blobstore import BlobReferenceProperty
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.ext.db as db
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.ext.webapp as webapp
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.api.files as files
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.api.memcache as memcache
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import google.appengine.api.urlfetch as urlfetch
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Default to a 5 minute cache timeout.
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  CACHE_TIMEOUT = 300
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError:
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Cache for one second because zero means cache forever.
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  CACHE_TIMEOUT = 1
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import re
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  from StringIO import StringIO
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  FAKE_URL_FETCHER_CONFIGURATION = None
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ConfigureFakeUrlFetch(configuration):
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """|configuration| is a dictionary mapping strings to fake urlfetch classes.
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    A fake urlfetch class just needs to have a fetch method. The keys of the
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dictionary are treated as regex, and they are matched with the URL to
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    determine which fake urlfetch is used.
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    global FAKE_URL_FETCHER_CONFIGURATION
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    FAKE_URL_FETCHER_CONFIGURATION = dict(
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (re.compile(k), v) for k, v in configuration.iteritems())
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _GetConfiguration(key):
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not FAKE_URL_FETCHER_CONFIGURATION:
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ValueError('No fake fetch paths have been configured. '
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       'See ConfigureFakeUrlFetch in appengine_wrappers.py.')
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in FAKE_URL_FETCHER_CONFIGURATION.iteritems():
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k.match(key):
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return v
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return None
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class _RPC(object):
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, result=None):
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.result = result
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get_result(self):
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self.result
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeUrlFetch(object):
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """A fake urlfetch module that uses the current
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    |FAKE_URL_FETCHER_CONFIGURATION| to map urls to fake fetchers.
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    class DownloadError(Exception):
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      pass
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class _Response(object):
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, content):
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.content = content
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.headers = { 'content-type': 'none' }
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.status_code = 200
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def fetch(self, url, **kwargs):
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = self._Response(_GetConfiguration(url).fetch(url))
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if response.content is None:
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        response.status_code = 404
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return response
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def create_rpc(self):
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return _RPC()
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def make_fetch_call(self, rpc, url, **kwargs):
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rpc.result = self.fetch(url)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  urlfetch = FakeUrlFetch()
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class NotImplemented(object):
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __getattr__(self, attr):
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise NotImplementedError()
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _BLOBS = {}
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeBlobstore(object):
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class BlobReader(object):
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, blob_key):
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._data = _BLOBS[blob_key].getvalue()
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def read(self):
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self._data
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  blobstore = FakeBlobstore()
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeFileInterface(object):
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """This class allows a StringIO object to be used in a with block like a
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    file.
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, io):
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._io = io
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __exit__(self, *args):
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def write(self, data):
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._io.write(data)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __enter__(self, *args):
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self._io
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class FakeFiles(object):
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    _next_blobstore_key = 0
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class blobstore(object):
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      @staticmethod
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def create():
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        FakeFiles._next_blobstore_key += 1
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return FakeFiles._next_blobstore_key
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      @staticmethod
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def get_blob_key(filename):
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return filename
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def open(self, filename, mode):
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      _BLOBS[filename] = StringIO()
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return FakeFileInterface(_BLOBS[filename])
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def GetBlobKeys(self):
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return _BLOBS.keys()
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def finalize(self, filename):
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  files = FakeFiles()
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class InMemoryMemcache(object):
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """A fake memcache that does nothing.
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class Client(object):
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def set_multi_async(self, mapping, namespace='', time=0):
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def get_multi_async(self, keys, namespace='', time=0):
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return _RPC(result=dict((k, None) for k in keys))
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def set(self, key, value, namespace='', time=0):
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get(self, key, namespace='', time=0):
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def delete(self, key, namespace):
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  memcache = InMemoryMemcache()
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class webapp(object):
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class RequestHandler(object):
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """A fake webapp.RequestHandler class for Handler to extend.
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, request, response):
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.request = request
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.response = response
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.response.status = 200
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      def redirect(self, path, permanent=False):
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.response.status = 301 if permanent else 302
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.response.headers['Location'] = path
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class _Db_Result(object):
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, data):
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._data = data
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class _Result(object):
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, value):
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.value = value
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get(self):
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self._Result(self._data)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class db(object):
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    _store = {}
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class StringProperty(object):
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    class Model(object):
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def __init__(self, key_='', value=''):
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._key = key_
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._value = value
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      @staticmethod
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def gql(query, key):
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return _Db_Result(db._store.get(key, None))
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      def put(self):
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        db._store[self._key] = self._value
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class BlobReferenceProperty(object):
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pass
196