appengine_wrappers.py revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
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
5import os
6
7from app_yaml_helper import AppYamlHelper
8
9def 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
18def 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.
23try:
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
33except 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