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