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