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