1
2
3
4 """
5 Google App Engine adapter module.
6
7 Sets up basic type mapping and class mappings for using the Datastore API
8 in Google App Engine.
9
10 @see: U{Datastore API on Google App Engine (external)
11 <http://code.google.com/appengine/docs/datastore>}
12
13 @since: 0.3.1
14 """
15
16 from google.appengine.ext import db
17 import datetime
18
19 import pyamf
20 from pyamf.util import imports
21 from pyamf import adapters
22 from pyamf.adapters import util
23
25 """
26 This class represents a L{db.Model} or L{db.Expando} class as the typed
27 object is being read from the AMF stream. Once the attributes have been
28 read from the stream and through the magic of Python, the instance of this
29 class will be converted into the correct type.
30
31 @ivar klass: The referenced class either L{db.Model} or L{db.Expando}.
32 This is used so we can proxy some of the method calls during decoding.
33 @type klass: L{db.Model} or L{db.Expando}
34 @see: L{DataStoreClassAlias.applyAttributes}
35 """
36
39
42
45
47 """
48 This helper class holds a dict of klass to key/objects loaded from the
49 Datastore.
50
51 @since: 0.4.1
52 """
53
55 if not issubclass(klass, (db.Model, db.Expando)):
56 raise TypeError('expected db.Model/db.Expando class, got %s' % (klass,))
57
58 if klass not in self.keys():
59 self[klass] = {}
60
61 return self[klass]
62
64 """
65 Return an instance based on klass/key.
66
67 If an instance cannot be found then L{KeyError} is raised.
68
69 @param klass: The class of the instance.
70 @param key: The key of the instance.
71 @return: The instance linked to the C{klass}/C{key}.
72 @rtype: Instance of L{klass}.
73 """
74 if not isinstance(key, basestring):
75 raise TypeError('basestring type expected for test, got %s' % (repr(key),))
76
77 d = self._getClass(klass)
78
79 return d[key]
80
82 """
83 Adds an object to the collection, based on klass and key.
84
85 @param klass: The class of the object.
86 @param key: The datastore key of the object.
87 @param obj: The loaded instance from the datastore.
88 """
89 if not isinstance(key, basestring):
90 raise TypeError('basestring type expected for test, got %s' % (repr(key),))
91
92 d = self._getClass(klass)
93
94 d[key] = obj
95
97 """
98 This class contains all the business logic to interact with Google's
99 Datastore API's. Any L{db.Model} or L{db.Expando} classes will use this
100 class alias for encoding/decoding.
101
102 We also add a number of indexes to the encoder context to aggressively
103 decrease the number of Datastore API's that we need to complete.
104 """
105
106
107 KEY_ATTR = '_key'
108
122
124 static_attrs = {}
125 dynamic_attrs = {}
126 static_attrs_names, dynamic_attrs_names = self.getAttrs(obj, codec=codec)
127 gae_objects = None
128
129 if codec is not None:
130 gae_objects = getGAEObjects(codec.context)
131
132 key = str(obj.key()) if obj.is_saved() else None
133
134 static_attrs[DataStoreClassAlias.KEY_ATTR] = key
135 kd = self.klass.__dict__
136
137 for a in static_attrs_names:
138 if a == DataStoreClassAlias.KEY_ATTR:
139 continue
140
141 try:
142 prop = kd[a]
143 except KeyError:
144 prop = None
145
146 if isinstance(prop, db.ReferenceProperty):
147 if gae_objects is not None:
148 klass = prop.reference_class
149 key = prop.get_value_for_datastore(obj)
150
151 if key is not None:
152 key = str(key)
153
154 try:
155 static_attrs[a] = gae_objects.getClassKey(klass, key)
156 except KeyError:
157 ref_obj = loadInstanceFromDatastore(klass, key, codec)
158 gae_objects.addClassKey(klass, key, ref_obj)
159 static_attrs[a] = ref_obj
160
161 continue
162
163 static_attrs[a] = getattr(obj, a)
164
165 for a in dynamic_attrs_names:
166 dynamic_attrs[a] = getattr(obj, a)
167
168 return static_attrs, dynamic_attrs
169
172
174 new_obj = None
175
176
177 if DataStoreClassAlias.KEY_ATTR in attrs.keys():
178 if attrs[DataStoreClassAlias.KEY_ATTR] is not None:
179 key = attrs[DataStoreClassAlias.KEY_ATTR]
180 new_obj = loadInstanceFromDatastore(self.klass, key, codec)
181
182 del attrs[DataStoreClassAlias.KEY_ATTR]
183
184 properties = self.klass.properties()
185 p_keys = properties.keys()
186 apply_init = True
187 sa, da = self.getAttrs(obj)
188
189
190 if isinstance(obj, ModelStub) and hasattr(obj, 'klass'):
191 del obj.klass
192
193 if new_obj is not None:
194 obj.__dict__ = new_obj.__dict__.copy()
195
196 obj.__class__ = self.klass
197 kd = self.klass.__dict__
198
199 for k, v in attrs.copy().iteritems():
200 if k in p_keys:
201 prop = properties[k]
202
203 if k not in sa:
204 del attrs[k]
205 continue
206
207 if isinstance(prop, db.ListProperty) and v is None:
208 attrs[k] = []
209 elif isinstance(v, datetime.datetime):
210
211
212 if isinstance(prop, db.DateProperty):
213 attrs[k] = v.date()
214 elif isinstance(prop, db.TimeProperty):
215 attrs[k] = v.time()
216
217 if new_obj is None and isinstance(v, ModelStub) and (k in kd and isinstance(kd[k], db.ReferenceProperty) and kd[k].required is True):
218 apply_init = False
219 del attrs[k]
220 continue
221 elif k in kd:
222 kp = kd[k]
223
224
225
226
227
228 if isinstance(kp, db._ReverseReferenceProperty):
229 del attrs[k]
230
231
232
233
234 if new_obj is None and apply_init is True:
235 obj.__init__(**attrs)
236
237 for k, v in attrs.iteritems():
238 setattr(obj, k, v)
239
241 """
242 Returns a reference to the C{gae_objects} on the context. If it doesn't
243 exist then it is created.
244
245 @param context: The context to load the C{gae_objects} index from.
246 @type context: Instance of L{pyamf.BaseContext}
247 @return: The C{gae_objects} index reference.
248 @rtype: Instance of L{GAEReferenceCollection}
249 @since: 0.4.1
250 """
251 if not hasattr(context, 'gae_objects'):
252 context.gae_objects = GAEReferenceCollection()
253
254 return context.gae_objects
255
257 """
258 Attempt to load an instance from the datastore, based on C{klass}
259 and C{key}. We create an index on the codec's context (if it exists)
260 so we can check that first before accessing the datastore.
261
262 @param klass: The class that will be loaded from the datastore.
263 @type klass: Sub-class of L{db.Model} or L{db.Expando}
264 @param key: The key which is used to uniquely identify the instance in the
265 datastore.
266 @type key: C{str}
267 @param codec: The codec to reference the C{gae_objects} index. If
268 supplied,The codec must have have a context attribute.
269 @type codec: Instance of L{pyamf.BaseEncoder} or L{pyamf.BaseDecoder}
270 @return: The loaded instance from the datastore.
271 @rtype: Instance of C{klass}.
272 @since: 0.4.1
273 """
274 if not issubclass(klass, (db.Model, db.Expando)):
275 raise TypeError('expected db.Model/db.Expando class, got %s' % (klass,))
276
277 if not isinstance(key, basestring):
278 raise TypeError('string expected for key, got %s', (repr(key),))
279
280 key = str(key)
281
282 if codec is None:
283 return klass.get(key)
284
285 gae_objects = getGAEObjects(codec.context)
286
287 try:
288 return gae_objects.getClassKey(klass, key)
289 except KeyError:
290 pass
291
292 obj = klass.get(key)
293 gae_objects.addClassKey(klass, key, obj)
294
295 return obj
296
298 """
299 The GAE Datastore creates new instances of objects for each get request.
300 This is a problem for PyAMF as it uses the id(obj) of the object to do
301 reference checking.
302
303 We could just ignore the problem, but the objects are conceptually the
304 same so the effort should be made to attempt to resolve references for a
305 given object graph.
306
307 We create a new map on the encoder context object which contains a dict of
308 C{object.__class__: {key1: object1, key2: object2, .., keyn: objectn}}. We
309 use the datastore key to do the reference checking.
310
311 @since: 0.4.1
312 """
313 if not (isinstance(object, db.Model) and object.is_saved()):
314 self.writeNonGAEObject(object, *args, **kwargs)
315
316 return
317
318 context = self.context
319 kls = object.__class__
320 s = str(object.key())
321
322 gae_objects = getGAEObjects(context)
323 referenced_object = None
324
325 try:
326 referenced_object = gae_objects.getClassKey(kls, s)
327 except KeyError:
328 gae_objects.addClassKey(kls, s, object)
329 self.writeNonGAEObject(object, *args, **kwargs)
330
331 return
332
333 self.writeNonGAEObject(referenced_object, *args, **kwargs)
334
336 """
337 Called when L{pyamf.amf0} or L{pyamf.amf3} are imported. Attaches the
338 L{writeGAEObject} method to the C{Encoder} class in that module.
339
340 @param mod: The module imported.
341 @since: 0.4.1
342 """
343 if not hasattr(mod.Encoder, 'writeNonGAEObject'):
344 mod.Encoder.writeNonGAEObject = mod.Encoder.writeObject
345 mod.Encoder.writeObject = writeGAEObject
346
347
348
349 pyamf.add_type(db.Query, util.to_list)
350 pyamf.register_alias_type(DataStoreClassAlias, db.Model, db.Expando)
351
352
353 imports.whenImported('pyamf.amf0', install_gae_reference_model_hook)
354 imports.whenImported('pyamf.amf3', install_gae_reference_model_hook)
355