appengine_wrappers.py revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5def IsDeadlineExceededError(error):
6  '''A general way of determining whether |error| is a DeadlineExceededError,
7  since there are 3 different types thrown by AppEngine and we might as well
8  handle them all the same way. For more info see:
9  https://developers.google.com/appengine/articles/deadlineexceedederrors
10  '''
11  return type(error).__name__ == 'DeadlineExceededError'
12
13
14def IsDownloadError(error):
15  return type(error).__name__ == 'DownloadError'
16
17
18# This will attempt to import the actual App Engine modules, and if it fails,
19# they will be replaced with fake modules. This is useful during testing.
20try:
21  import google.appengine.api.files as files
22  import google.appengine.api.logservice as logservice
23  import google.appengine.api.memcache as memcache
24  import google.appengine.api.urlfetch as urlfetch
25  import google.appengine.ext.blobstore as blobstore
26  from google.appengine.ext.blobstore.blobstore import BlobReferenceProperty
27  import google.appengine.ext.db as db
28  import webapp2
29except ImportError:
30  import re
31  from StringIO import StringIO
32
33  FAKE_URL_FETCHER_CONFIGURATION = None
34
35  def ConfigureFakeUrlFetch(configuration):
36    """|configuration| is a dictionary mapping strings to fake urlfetch classes.
37    A fake urlfetch class just needs to have a fetch method. The keys of the
38    dictionary are treated as regex, and they are matched with the URL to
39    determine which fake urlfetch is used.
40    """
41    global FAKE_URL_FETCHER_CONFIGURATION
42    FAKE_URL_FETCHER_CONFIGURATION = dict(
43        (re.compile(k), v) for k, v in configuration.iteritems())
44
45  def _GetConfiguration(key):
46    if not FAKE_URL_FETCHER_CONFIGURATION:
47      raise ValueError('No fake fetch paths have been configured. '
48                       'See ConfigureFakeUrlFetch in appengine_wrappers.py.')
49    for k, v in FAKE_URL_FETCHER_CONFIGURATION.iteritems():
50      if k.match(key):
51        return v
52    raise ValueError('No configuration found for %s' % key)
53
54  class _RPC(object):
55    def __init__(self, result=None):
56      self.result = result
57
58    def get_result(self):
59      return self.result
60
61    def wait(self):
62      pass
63
64  class FakeUrlFetch(object):
65    """A fake urlfetch module that uses the current
66    |FAKE_URL_FETCHER_CONFIGURATION| to map urls to fake fetchers.
67    """
68    class DownloadError(Exception):
69      pass
70
71    class _Response(object):
72      def __init__(self, content):
73        self.content = content
74        self.headers = {'Content-Type': 'none'}
75        self.status_code = 200
76
77    def fetch(self, url, **kwargs):
78      url = url.split('?', 1)[0]
79      response = self._Response(_GetConfiguration(url).fetch(url))
80      if response.content is None:
81        response.status_code = 404
82      return response
83
84    def create_rpc(self):
85      return _RPC()
86
87    def make_fetch_call(self, rpc, url, **kwargs):
88      rpc.result = self.fetch(url)
89  urlfetch = FakeUrlFetch()
90
91  _BLOBS = {}
92  class FakeBlobstore(object):
93    class BlobNotFoundError(Exception):
94      pass
95
96    class BlobReader(object):
97      def __init__(self, blob_key):
98        self._data = _BLOBS[blob_key].getvalue()
99
100      def read(self):
101        return self._data
102
103  blobstore = FakeBlobstore()
104
105  class FakeFileInterface(object):
106    """This class allows a StringIO object to be used in a with block like a
107    file.
108    """
109    def __init__(self, io):
110      self._io = io
111
112    def __exit__(self, *args):
113      pass
114
115    def write(self, data):
116      self._io.write(data)
117
118    def __enter__(self, *args):
119      return self._io
120
121  class FakeFiles(object):
122    _next_blobstore_key = 0
123    class blobstore(object):
124      @staticmethod
125      def create():
126        FakeFiles._next_blobstore_key += 1
127        return FakeFiles._next_blobstore_key
128
129      @staticmethod
130      def get_blob_key(filename):
131        return filename
132
133    def open(self, filename, mode):
134      _BLOBS[filename] = StringIO()
135      return FakeFileInterface(_BLOBS[filename])
136
137    def GetBlobKeys(self):
138      return _BLOBS.keys()
139
140    def finalize(self, filename):
141      pass
142
143  files = FakeFiles()
144
145  class Logservice(object):
146    AUTOFLUSH_ENABLED = True
147
148    def flush(self):
149      pass
150
151  logservice = Logservice()
152
153  class InMemoryMemcache(object):
154    """An in-memory memcache implementation.
155    """
156    def __init__(self):
157      self._namespaces = {}
158
159    class Client(object):
160      def set_multi_async(self, mapping, namespace='', time=0):
161        for k, v in mapping.iteritems():
162          memcache.set(k, v, namespace=namespace, time=time)
163
164      def get_multi_async(self, keys, namespace='', time=0):
165        return _RPC(result=dict(
166          (k, memcache.get(k, namespace=namespace, time=time)) for k in keys))
167
168    def set(self, key, value, namespace='', time=0):
169      self._GetNamespace(namespace)[key] = value
170
171    def get(self, key, namespace='', time=0):
172      return self._GetNamespace(namespace).get(key)
173
174    def delete(self, key, namespace=''):
175      self._GetNamespace(namespace).pop(key, None)
176
177    def delete_multi(self, keys, namespace=''):
178      for k in keys:
179        self.delete(k, namespace=namespace)
180
181    def _GetNamespace(self, namespace):
182      if namespace not in self._namespaces:
183        self._namespaces[namespace] = {}
184      return self._namespaces[namespace]
185
186  memcache = InMemoryMemcache()
187
188  class webapp2(object):
189    class RequestHandler(object):
190      """A fake webapp2.RequestHandler class for Handler to extend.
191      """
192      def __init__(self, request, response):
193        self.request = request
194        self.response = response
195        self.response.status = 200
196
197      def redirect(self, path, permanent=False):
198        self.response.status = 301 if permanent else 302
199        self.response.headers['Location'] = path
200
201  class _Db_Result(object):
202    def __init__(self, data):
203      self._data = data
204
205    class _Result(object):
206      def __init__(self, value):
207        self.value = value
208
209    def get(self):
210      return self._Result(self._data)
211
212  class db(object):
213    _store = {}
214
215    class StringProperty(object):
216      pass
217
218    class BlobProperty(object):
219      pass
220
221    class Key(object):
222      def __init__(self, key):
223        self._key = key
224
225      @staticmethod
226      def from_path(model_name, path):
227        return db.Key('%s/%s' % (model_name, path))
228
229      def __eq__(self, obj):
230        return self.__class__ == obj.__class__ and self._key == obj._key
231
232      def __hash__(self):
233        return hash(self._key)
234
235      def __str__(self):
236        return str(self._key)
237
238    class Model(object):
239      key = None
240
241      def __init__(self, **optargs):
242        cls = self.__class__
243        for k, v in optargs.iteritems():
244          assert hasattr(cls, k), '%s does not define property %s' % (
245              cls.__name__, k)
246          setattr(self, k, v)
247
248      @staticmethod
249      def gql(query, key):
250        return _Db_Result(db._store.get(key))
251
252      def put(self):
253        db._store[self.key_] = self.value
254
255    @staticmethod
256    def get_async(key):
257      return _RPC(result=db._store.get(key))
258
259    @staticmethod
260    def delete_async(key):
261      db._store.pop(key, None)
262      return _RPC()
263
264    @staticmethod
265    def put_async(value):
266      db._store[value.key] = value
267      return _RPC()
268
269  class BlobReferenceProperty(object):
270    pass
271