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.app_identity as app_identity
22  import google.appengine.api.files as files
23  import google.appengine.api.logservice as logservice
24  import google.appengine.api.memcache as memcache
25  import google.appengine.api.taskqueue as taskqueue
26  import google.appengine.api.urlfetch as urlfetch
27  import google.appengine.ext.blobstore as blobstore
28  from google.appengine.ext.blobstore.blobstore import BlobReferenceProperty
29  import google.appengine.ext.db as db
30  import webapp2
31except ImportError:
32  import re
33  from StringIO import StringIO
34
35  FAKE_URL_FETCHER_CONFIGURATION = None
36
37  def ConfigureFakeUrlFetch(configuration):
38    """|configuration| is a dictionary mapping strings to fake urlfetch classes.
39    A fake urlfetch class just needs to have a fetch method. The keys of the
40    dictionary are treated as regex, and they are matched with the URL to
41    determine which fake urlfetch is used.
42    """
43    global FAKE_URL_FETCHER_CONFIGURATION
44    FAKE_URL_FETCHER_CONFIGURATION = dict(
45        (re.compile(k), v) for k, v in configuration.iteritems())
46
47  def _GetConfiguration(key):
48    if not FAKE_URL_FETCHER_CONFIGURATION:
49      raise ValueError('No fake fetch paths have been configured. '
50                       'See ConfigureFakeUrlFetch in appengine_wrappers.py.')
51    for k, v in FAKE_URL_FETCHER_CONFIGURATION.iteritems():
52      if k.match(key):
53        return v
54    raise ValueError('No configuration found for %s' % key)
55
56  class _RPC(object):
57    def __init__(self, result=None):
58      self.result = result
59
60    def get_result(self):
61      return self.result
62
63    def wait(self):
64      pass
65
66  class FakeAppIdentity(object):
67    """A fake app_identity module that returns no access tokens."""
68    def get_access_token(self, scope):
69      return (None, None)
70  app_identity = FakeAppIdentity()
71
72  class FakeUrlFetch(object):
73    """A fake urlfetch module that uses the current
74    |FAKE_URL_FETCHER_CONFIGURATION| to map urls to fake fetchers.
75    """
76    class DownloadError(Exception):
77      pass
78
79    class _Response(object):
80      def __init__(self, content):
81        self.content = content
82        self.headers = {'Content-Type': 'none'}
83        self.status_code = 200
84
85    def fetch(self, url, **kwargs):
86      url = url.split('?', 1)[0]
87      response = self._Response(_GetConfiguration(url).fetch(url))
88      if response.content is None:
89        response.status_code = 404
90      return response
91
92    def create_rpc(self, **kwargs):
93      return _RPC()
94
95    def make_fetch_call(self, rpc, url, **kwargs):
96      rpc.result = self.fetch(url)
97  urlfetch = FakeUrlFetch()
98
99  _BLOBS = {}
100  class FakeBlobstore(object):
101    class BlobNotFoundError(Exception):
102      pass
103
104    class BlobReader(object):
105      def __init__(self, blob_key):
106        self._data = _BLOBS[blob_key].getvalue()
107
108      def read(self):
109        return self._data
110
111  blobstore = FakeBlobstore()
112
113  class FakeFileInterface(object):
114    """This class allows a StringIO object to be used in a with block like a
115    file.
116    """
117    def __init__(self, io):
118      self._io = io
119
120    def __exit__(self, *args):
121      pass
122
123    def write(self, data):
124      self._io.write(data)
125
126    def __enter__(self, *args):
127      return self._io
128
129  class FakeFiles(object):
130    _next_blobstore_key = 0
131    class blobstore(object):
132      @staticmethod
133      def create():
134        FakeFiles._next_blobstore_key += 1
135        return FakeFiles._next_blobstore_key
136
137      @staticmethod
138      def get_blob_key(filename):
139        return filename
140
141    def open(self, filename, mode):
142      _BLOBS[filename] = StringIO()
143      return FakeFileInterface(_BLOBS[filename])
144
145    def GetBlobKeys(self):
146      return _BLOBS.keys()
147
148    def finalize(self, filename):
149      pass
150
151  files = FakeFiles()
152
153  class Logservice(object):
154    AUTOFLUSH_ENABLED = True
155
156    def flush(self):
157      pass
158
159  logservice = Logservice()
160
161  class InMemoryMemcache(object):
162    """An in-memory memcache implementation.
163    """
164    def __init__(self):
165      self._namespaces = {}
166
167    class Client(object):
168      def set_multi_async(self, mapping, namespace='', time=0):
169        for k, v in mapping.iteritems():
170          memcache.set(k, v, namespace=namespace, time=time)
171
172      def get_multi_async(self, keys, namespace='', time=0):
173        return _RPC(result=dict(
174          (k, memcache.get(k, namespace=namespace, time=time)) for k in keys))
175
176    def set(self, key, value, namespace='', time=0):
177      self._GetNamespace(namespace)[key] = value
178
179    def get(self, key, namespace='', time=0):
180      return self._GetNamespace(namespace).get(key)
181
182    def delete(self, key, namespace=''):
183      self._GetNamespace(namespace).pop(key, None)
184
185    def delete_multi(self, keys, namespace=''):
186      for k in keys:
187        self.delete(k, namespace=namespace)
188
189    def _GetNamespace(self, namespace):
190      if namespace not in self._namespaces:
191        self._namespaces[namespace] = {}
192      return self._namespaces[namespace]
193
194  memcache = InMemoryMemcache()
195
196  class webapp2(object):
197    class RequestHandler(object):
198      """A fake webapp2.RequestHandler class for Handler to extend.
199      """
200      def __init__(self, request, response):
201        self.request = request
202        self.response = response
203        self.response.status = 200
204
205      def redirect(self, path, permanent=False):
206        self.response.status = 301 if permanent else 302
207        self.response.headers['Location'] = path
208
209  class _Db_Result(object):
210    def __init__(self, data):
211      self._data = data
212
213    class _Result(object):
214      def __init__(self, value):
215        self.value = value
216
217    def get(self):
218      return self._Result(self._data)
219
220  class db(object):
221    _store = {}
222
223    class StringProperty(object):
224      pass
225
226    class BlobProperty(object):
227      pass
228
229    class Key(object):
230      def __init__(self, key):
231        self._key = key
232
233      @staticmethod
234      def from_path(model_name, path):
235        return db.Key('%s/%s' % (model_name, path))
236
237      def __eq__(self, obj):
238        return self.__class__ == obj.__class__ and self._key == obj._key
239
240      def __hash__(self):
241        return hash(self._key)
242
243      def __str__(self):
244        return str(self._key)
245
246    class Model(object):
247      key = None
248
249      def __init__(self, **optargs):
250        cls = self.__class__
251        for k, v in optargs.iteritems():
252          assert hasattr(cls, k), '%s does not define property %s' % (
253              cls.__name__, k)
254          setattr(self, k, v)
255
256      @staticmethod
257      def gql(query, key):
258        return _Db_Result(db._store.get(key))
259
260      def put(self):
261        db._store[self.key_] = self.value
262
263    @staticmethod
264    def get_async(key):
265      return _RPC(result=db._store.get(key))
266
267    @staticmethod
268    def delete_async(key):
269      db._store.pop(key, None)
270      return _RPC()
271
272    @staticmethod
273    def put_async(value):
274      db._store[value.key] = value
275      return _RPC()
276
277  class BlobReferenceProperty(object):
278    pass
279
280  # Executes any queued tasks synchronously as they are queued.
281  _task_runner = None
282
283  def SetTaskRunnerForTest(task_runner):
284    global _task_runner
285    _task_runner = task_runner
286
287  class SynchronousTaskQueue(object):
288    class Task(object):
289      def __init__(self, url=None, params={}):
290        self.url_ = url
291        self.params_ = params
292
293      def GetUrl(self):
294        return self.url_
295
296      def GetCommit(self):
297        return self.params_.get('commit')
298
299    class Queue(object):
300      def __init__(self, name='default'):
301        pass
302
303      def add(self, task):
304        global _task_runner
305        if _task_runner:
306          _task_runner(task.GetUrl(), task.GetCommit())
307        return _RPC()
308
309      def purge(self):
310        return _RPC()
311
312  taskqueue = SynchronousTaskQueue()
313