36 |
36 |
37 |
37 |
38 import datetime |
38 import datetime |
39 import logging |
39 import logging |
40 import os |
40 import os |
41 import pickle |
|
42 import struct |
41 import struct |
43 import sys |
42 import sys |
44 import tempfile |
43 import tempfile |
45 import threading |
44 import threading |
46 import warnings |
45 import warnings |
47 |
46 |
|
47 import cPickle as pickle |
|
48 |
48 from google.appengine.api import api_base_pb |
49 from google.appengine.api import api_base_pb |
|
50 from google.appengine.api import apiproxy_stub |
49 from google.appengine.api import datastore |
51 from google.appengine.api import datastore |
50 from google.appengine.api import datastore_admin |
52 from google.appengine.api import datastore_admin |
51 from google.appengine.api import datastore_errors |
53 from google.appengine.api import datastore_errors |
52 from google.appengine.api import datastore_types |
54 from google.appengine.api import datastore_types |
53 from google.appengine.api import users |
55 from google.appengine.api import users |
62 |
64 |
63 entity_pb.Reference.__hash__ = lambda self: hash(self.Encode()) |
65 entity_pb.Reference.__hash__ = lambda self: hash(self.Encode()) |
64 datastore_pb.Query.__hash__ = lambda self: hash(self.Encode()) |
66 datastore_pb.Query.__hash__ = lambda self: hash(self.Encode()) |
65 |
67 |
66 |
68 |
|
69 _MAXIMUM_RESULTS = 1000 |
|
70 |
|
71 |
|
72 _MAX_QUERY_OFFSET = 4000 |
|
73 |
67 class _StoredEntity(object): |
74 class _StoredEntity(object): |
68 """Simple wrapper around an entity stored by the stub. |
75 """Simple wrapper around an entity stored by the stub. |
69 |
76 |
70 Public properties: |
77 Public properties: |
71 native: Native protobuf Python object, entity_pb.EntityProto. |
78 protobuf: Native protobuf Python object, entity_pb.EntityProto. |
72 encoded: Encoded binary representation of above protobuf. |
79 encoded_protobuf: Encoded binary representation of above protobuf. |
|
80 native: datastore.Entity instance. |
73 """ |
81 """ |
74 |
82 |
75 def __init__(self, entity): |
83 def __init__(self, entity): |
76 """Create a _StoredEntity object and store an entity. |
84 """Create a _StoredEntity object and store an entity. |
77 |
85 |
78 Args: |
86 Args: |
79 entity: entity_pb.EntityProto to store. |
87 entity: entity_pb.EntityProto to store. |
80 """ |
88 """ |
81 self.native = entity |
89 self.protobuf = entity |
82 |
90 |
83 self.encoded = entity.Encode() |
91 self.encoded_protobuf = entity.Encode() |
84 |
92 |
85 |
93 self.native = datastore.Entity._FromPb(entity) |
86 class DatastoreFileStub(object): |
94 |
|
95 |
|
96 class DatastoreFileStub(apiproxy_stub.APIProxyStub): |
87 """ Persistent stub for the Python datastore API. |
97 """ Persistent stub for the Python datastore API. |
88 |
98 |
89 Stores all entities in memory, and persists them to a file as pickled |
99 Stores all entities in memory, and persists them to a file as pickled |
90 protocol buffers. A DatastoreFileStub instance handles a single app's data |
100 protocol buffers. A DatastoreFileStub instance handles a single app's data |
91 and is backed by files on disk. |
101 and is backed by files on disk. |
112 type(None): 0, |
122 type(None): 0, |
113 unicode: entity_pb.PropertyValue.kstringValue, |
123 unicode: entity_pb.PropertyValue.kstringValue, |
114 users.User: entity_pb.PropertyValue.kUserValueGroup, |
124 users.User: entity_pb.PropertyValue.kUserValueGroup, |
115 } |
125 } |
116 |
126 |
117 def __init__(self, app_id, datastore_file, history_file, |
127 def __init__(self, |
118 require_indexes=False): |
128 app_id, |
|
129 datastore_file, |
|
130 history_file, |
|
131 require_indexes=False, |
|
132 service_name='datastore_v3'): |
119 """Constructor. |
133 """Constructor. |
120 |
134 |
121 Initializes and loads the datastore from the backing files, if they exist. |
135 Initializes and loads the datastore from the backing files, if they exist. |
122 |
136 |
123 Args: |
137 Args: |
126 not to use a file. |
140 not to use a file. |
127 history_file: string, stores query history. Use None as with |
141 history_file: string, stores query history. Use None as with |
128 datastore_file. |
142 datastore_file. |
129 require_indexes: bool, default False. If True, composite indexes must |
143 require_indexes: bool, default False. If True, composite indexes must |
130 exist in index.yaml for queries that need them. |
144 exist in index.yaml for queries that need them. |
131 """ |
145 service_name: Service name expected for all calls. |
|
146 """ |
|
147 super(DatastoreFileStub, self).__init__(service_name) |
|
148 |
132 |
149 |
133 assert isinstance(app_id, basestring) and app_id != '' |
150 assert isinstance(app_id, basestring) and app_id != '' |
134 self.__app_id = app_id |
151 self.__app_id = app_id |
135 self.__datastore_file = datastore_file |
152 self.__datastore_file = datastore_file |
136 self.__history_file = history_file |
153 self.__history_file = history_file |
137 |
154 |
138 self.__entities = {} |
155 self.__entities = {} |
|
156 |
|
157 self.__schema_cache = {} |
139 |
158 |
140 self.__tx_snapshot = {} |
159 self.__tx_snapshot = {} |
141 |
160 |
142 self.__queries = {} |
161 self.__queries = {} |
143 |
162 |
168 queries. """ |
187 queries. """ |
169 self.__entities = {} |
188 self.__entities = {} |
170 self.__queries = {} |
189 self.__queries = {} |
171 self.__transactions = {} |
190 self.__transactions = {} |
172 self.__query_history = {} |
191 self.__query_history = {} |
|
192 self.__schema_cache = {} |
|
193 |
|
194 def _AppKindForKey(self, key): |
|
195 """ Get (app, kind) tuple from given key. |
|
196 |
|
197 The (app, kind) tuple is used as an index into several internal |
|
198 dictionaries, e.g. __entities. |
|
199 |
|
200 Args: |
|
201 key: entity_pb.Reference |
|
202 |
|
203 Returns: |
|
204 Tuple (app, kind), both are unicode strings. |
|
205 """ |
|
206 last_path = key.path().element_list()[-1] |
|
207 return key.app(), last_path.type() |
|
208 |
|
209 def _StoreEntity(self, entity): |
|
210 """ Store the given entity. |
|
211 |
|
212 Args: |
|
213 entity: entity_pb.EntityProto |
|
214 """ |
|
215 key = entity.key() |
|
216 app_kind = self._AppKindForKey(key) |
|
217 if app_kind not in self.__entities: |
|
218 self.__entities[app_kind] = {} |
|
219 self.__entities[app_kind][key] = _StoredEntity(entity) |
|
220 |
|
221 if app_kind in self.__schema_cache: |
|
222 del self.__schema_cache[app_kind] |
173 |
223 |
174 def Read(self): |
224 def Read(self): |
175 """ Reads the datastore and history files into memory. |
225 """ Reads the datastore and history files into memory. |
176 |
226 |
177 The in-memory query history is cleared, but the datastore is *not* |
227 The in-memory query history is cleared, but the datastore is *not* |
196 entity = entity_pb.EntityProto(encoded_entity) |
246 entity = entity_pb.EntityProto(encoded_entity) |
197 except pb_exceptions, e: |
247 except pb_exceptions, e: |
198 raise datastore_errors.InternalError(error_msg % |
248 raise datastore_errors.InternalError(error_msg % |
199 (self.__datastore_file, e)) |
249 (self.__datastore_file, e)) |
200 |
250 |
|
251 self._StoreEntity(entity) |
|
252 |
201 last_path = entity.key().path().element_list()[-1] |
253 last_path = entity.key().path().element_list()[-1] |
202 app_kind = (entity.key().app(), last_path.type()) |
|
203 kind_dict = self.__entities.setdefault(app_kind, {}) |
|
204 kind_dict[entity.key()] = _StoredEntity(entity) |
|
205 |
|
206 if last_path.has_id() and last_path.id() >= self.__next_id: |
254 if last_path.has_id() and last_path.id() >= self.__next_id: |
207 self.__next_id = last_path.id() + 1 |
255 self.__next_id = last_path.id() + 1 |
208 |
256 |
209 self.__query_history = {} |
257 self.__query_history = {} |
210 for encoded_query, count in self.__ReadPickled(self.__history_file): |
258 for encoded_query, count in self.__ReadPickled(self.__history_file): |
232 """ |
280 """ |
233 if self.__datastore_file and self.__datastore_file != '/dev/null': |
281 if self.__datastore_file and self.__datastore_file != '/dev/null': |
234 encoded = [] |
282 encoded = [] |
235 for kind_dict in self.__entities.values(): |
283 for kind_dict in self.__entities.values(): |
236 for entity in kind_dict.values(): |
284 for entity in kind_dict.values(): |
237 encoded.append(entity.encoded) |
285 encoded.append(entity.encoded_protobuf) |
238 |
286 |
239 self.__WritePickled(encoded, self.__datastore_file) |
287 self.__WritePickled(encoded, self.__datastore_file) |
240 |
288 |
241 def __WriteHistory(self): |
289 def __WriteHistory(self): |
242 """ Writes out the history file. Be careful! If the file already exist, |
290 """ Writes out the history file. Be careful! If the file already exist, |
294 |
346 |
295 def MakeSyncCall(self, service, call, request, response): |
347 def MakeSyncCall(self, service, call, request, response): |
296 """ The main RPC entry point. service must be 'datastore_v3'. So far, the |
348 """ The main RPC entry point. service must be 'datastore_v3'. So far, the |
297 supported calls are 'Get', 'Put', 'RunQuery', 'Next', and 'Count'. |
349 supported calls are 'Get', 'Put', 'RunQuery', 'Next', and 'Count'. |
298 """ |
350 """ |
299 |
351 super(DatastoreFileStub, self).MakeSyncCall(service, |
300 assert service == 'datastore_v3' |
352 call, |
|
353 request, |
|
354 response) |
301 |
355 |
302 explanation = [] |
356 explanation = [] |
303 assert request.IsInitialized(explanation), explanation |
|
304 |
|
305 (getattr(self, "_Dynamic_" + call))(request, response) |
|
306 |
|
307 assert response.IsInitialized(explanation), explanation |
357 assert response.IsInitialized(explanation), explanation |
308 |
358 |
309 def QueryHistory(self): |
359 def QueryHistory(self): |
310 """Returns a dict that maps Query PBs to times they've been run. |
360 """Returns a dict that maps Query PBs to times they've been run. |
311 """ |
361 """ |
355 put_response.key_list().extend([c.key() for c in clones]) |
402 put_response.key_list().extend([c.key() for c in clones]) |
356 |
403 |
357 |
404 |
358 def _Dynamic_Get(self, get_request, get_response): |
405 def _Dynamic_Get(self, get_request, get_response): |
359 for key in get_request.key_list(): |
406 for key in get_request.key_list(): |
360 app = key.app() |
407 app_kind = self._AppKindForKey(key) |
361 last_path = key.path().element_list()[-1] |
|
362 |
408 |
363 group = get_response.add_entity() |
409 group = get_response.add_entity() |
364 try: |
410 try: |
365 entity = self.__entities[app, last_path.type()][key].native |
411 entity = self.__entities[app_kind][key].protobuf |
366 except KeyError: |
412 except KeyError: |
367 entity = None |
413 entity = None |
368 |
414 |
369 if entity: |
415 if entity: |
370 group.mutable_entity().CopyFrom(entity) |
416 group.mutable_entity().CopyFrom(entity) |
372 |
418 |
373 def _Dynamic_Delete(self, delete_request, delete_response): |
419 def _Dynamic_Delete(self, delete_request, delete_response): |
374 self.__entities_lock.acquire() |
420 self.__entities_lock.acquire() |
375 try: |
421 try: |
376 for key in delete_request.key_list(): |
422 for key in delete_request.key_list(): |
|
423 app_kind = self._AppKindForKey(key) |
377 try: |
424 try: |
378 app = key.app() |
425 del self.__entities[app_kind][key] |
379 kind = key.path().element_list()[-1].type() |
426 if not self.__entities[app_kind]: |
380 del self.__entities[app, kind][key] |
427 del self.__entities[app_kind] |
381 if not self.__entities[app, kind]: |
428 |
382 del self.__entities[app, kind] |
429 del self.__schema_cache[app_kind] |
383 except KeyError: |
430 except KeyError: |
384 pass |
431 pass |
385 |
432 |
386 if not delete_request.has_transaction(): |
433 if not delete_request.has_transaction(): |
387 self.__WriteDatastore() |
434 self.__WriteDatastore() |
394 raise apiproxy_errors.ApplicationError( |
441 raise apiproxy_errors.ApplicationError( |
395 datastore_pb.Error.BAD_REQUEST, "Can't query inside a transaction.") |
442 datastore_pb.Error.BAD_REQUEST, "Can't query inside a transaction.") |
396 else: |
443 else: |
397 self.__tx_lock.release() |
444 self.__tx_lock.release() |
398 |
445 |
|
446 if query.has_offset() and query.offset() > _MAX_QUERY_OFFSET: |
|
447 raise apiproxy_errors.ApplicationError( |
|
448 datastore_pb.Error.BAD_REQUEST, "Too big query offset.") |
|
449 |
399 app = query.app() |
450 app = query.app() |
400 |
451 |
401 if self.__require_indexes: |
452 if self.__require_indexes: |
402 required_index = datastore_index.CompositeIndexForQuery(query) |
453 required, kind, ancestor, props, num_eq_filters = datastore_index.CompositeIndexForQuery(query) |
403 if required_index is not None: |
454 if required: |
404 kind, ancestor, props, num_eq_filters = required_index |
|
405 required_key = kind, ancestor, props |
455 required_key = kind, ancestor, props |
406 indexes = self.__indexes.get(app) |
456 indexes = self.__indexes.get(app) |
407 if not indexes: |
457 if not indexes: |
408 raise apiproxy_errors.ApplicationError( |
458 raise apiproxy_errors.ApplicationError( |
409 datastore_pb.Error.NEED_INDEX, |
459 datastore_pb.Error.NEED_INDEX, |
454 assert filt.op() != datastore_pb.Query_Filter.IN |
504 assert filt.op() != datastore_pb.Query_Filter.IN |
455 |
505 |
456 prop = filt.property(0).name().decode('utf-8') |
506 prop = filt.property(0).name().decode('utf-8') |
457 op = operators[filt.op()] |
507 op = operators[filt.op()] |
458 |
508 |
|
509 filter_val_list = [datastore_types.FromPropertyPb(filter_prop) |
|
510 for filter_prop in filt.property_list()] |
|
511 |
459 def passes(entity): |
512 def passes(entity): |
460 """ Returns True if the entity passes the filter, False otherwise. """ |
513 """ Returns True if the entity passes the filter, False otherwise. """ |
461 entity_vals = entity.get(prop, []) |
514 if prop in datastore_types._SPECIAL_PROPERTIES: |
|
515 entity_vals = self.__GetSpecialPropertyValue(entity, prop) |
|
516 else: |
|
517 entity_vals = entity.get(prop, []) |
|
518 |
462 if not isinstance(entity_vals, list): |
519 if not isinstance(entity_vals, list): |
463 entity_vals = [entity_vals] |
520 entity_vals = [entity_vals] |
464 |
521 |
465 entity_property_list = [datastore_types.ToPropertyPb(prop, value) |
522 for fixed_entity_val in entity_vals: |
466 for value in entity_vals] |
|
467 |
|
468 for entity_prop in entity_property_list: |
|
469 fixed_entity_val = datastore_types.FromPropertyPb(entity_prop) |
|
470 |
|
471 if type(fixed_entity_val) in datastore_types._RAW_PROPERTY_TYPES: |
523 if type(fixed_entity_val) in datastore_types._RAW_PROPERTY_TYPES: |
472 continue |
524 continue |
473 |
525 |
474 for filter_prop in filt.property_list(): |
526 for filter_val in filter_val_list: |
475 filter_val = datastore_types.FromPropertyPb(filter_prop) |
|
476 |
|
477 fixed_entity_type = self._PROPERTY_TYPE_TAGS.get( |
527 fixed_entity_type = self._PROPERTY_TYPE_TAGS.get( |
478 fixed_entity_val.__class__) |
528 fixed_entity_val.__class__) |
479 filter_type = self._PROPERTY_TYPE_TAGS.get(filter_val.__class__) |
529 filter_type = self._PROPERTY_TYPE_TAGS.get(filter_val.__class__) |
480 if fixed_entity_type == filter_type: |
530 if fixed_entity_type == filter_type: |
481 comp = u'%r %s %r' % (fixed_entity_val, op, filter_val) |
531 comp = u'%r %s %r' % (fixed_entity_val, op, filter_val) |
497 return False |
547 return False |
498 |
548 |
499 results = filter(passes, results) |
549 results = filter(passes, results) |
500 |
550 |
501 def has_prop_indexed(entity, prop): |
551 def has_prop_indexed(entity, prop): |
502 """Returns True if prop is in the entity and is not a raw property.""" |
552 """Returns True if prop is in the entity and is not a raw property, or |
|
553 is a special property.""" |
|
554 if prop in datastore_types._SPECIAL_PROPERTIES: |
|
555 return True |
|
556 |
503 values = entity.get(prop, []) |
557 values = entity.get(prop, []) |
504 if not isinstance(values, (tuple, list)): |
558 if not isinstance(values, (tuple, list)): |
505 values = [values] |
559 values = [values] |
506 |
560 |
507 for value in values: |
561 for value in values: |
521 for o in query.order_list(): |
575 for o in query.order_list(): |
522 prop = o.property().decode('utf-8') |
576 prop = o.property().decode('utf-8') |
523 |
577 |
524 reverse = (o.direction() is datastore_pb.Query_Order.DESCENDING) |
578 reverse = (o.direction() is datastore_pb.Query_Order.DESCENDING) |
525 |
579 |
526 a_val = a[prop] |
580 if prop in datastore_types._SPECIAL_PROPERTIES: |
527 if isinstance(a_val, list): |
581 a_val = self.__GetSpecialPropertyValue(a, prop) |
528 a_val = sorted(a_val, order_compare_properties, reverse=reverse)[0] |
582 b_val = self.__GetSpecialPropertyValue(b, prop) |
529 |
583 else: |
530 b_val = b[prop] |
584 a_val = a[prop] |
531 if isinstance(b_val, list): |
585 if isinstance(a_val, list): |
532 b_val = sorted(b_val, order_compare_properties, reverse=reverse)[0] |
586 a_val = sorted(a_val, order_compare_properties, reverse=reverse)[0] |
|
587 |
|
588 b_val = b[prop] |
|
589 if isinstance(b_val, list): |
|
590 b_val = sorted(b_val, order_compare_properties, reverse=reverse)[0] |
533 |
591 |
534 cmped = order_compare_properties(a_val, b_val) |
592 cmped = order_compare_properties(a_val, b_val) |
535 |
593 |
536 if o.direction() is datastore_pb.Query_Order.DESCENDING: |
594 if o.direction() is datastore_pb.Query_Order.DESCENDING: |
537 cmped = -cmped |
595 cmped = -cmped |
571 limit = len(results) |
629 limit = len(results) |
572 if query.has_offset(): |
630 if query.has_offset(): |
573 offset = query.offset() |
631 offset = query.offset() |
574 if query.has_limit(): |
632 if query.has_limit(): |
575 limit = query.limit() |
633 limit = query.limit() |
|
634 if limit > _MAXIMUM_RESULTS: |
|
635 limit = _MAXIMUM_RESULTS |
576 results = results[offset:limit + offset] |
636 results = results[offset:limit + offset] |
577 |
637 |
578 clone = datastore_pb.Query() |
638 clone = datastore_pb.Query() |
579 clone.CopyFrom(query) |
639 clone.CopyFrom(query) |
580 clone.clear_hint() |
640 clone.clear_hint() |
582 self.__query_history[clone] += 1 |
642 self.__query_history[clone] += 1 |
583 else: |
643 else: |
584 self.__query_history[clone] = 1 |
644 self.__query_history[clone] = 1 |
585 self.__WriteHistory() |
645 self.__WriteHistory() |
586 |
646 |
587 results = [e._ToPb() for e in results] |
|
588 self.__cursor_lock.acquire() |
647 self.__cursor_lock.acquire() |
589 cursor = self.__next_cursor |
648 cursor = self.__next_cursor |
590 self.__next_cursor += 1 |
649 self.__next_cursor += 1 |
591 self.__cursor_lock.release() |
650 self.__cursor_lock.release() |
592 self.__queries[cursor] = (results, len(results)) |
651 self.__queries[cursor] = (results, len(results)) |
593 |
652 |
594 query_result.mutable_cursor().set_cursor(cursor) |
653 query_result.mutable_cursor().set_cursor(cursor) |
595 query_result.set_more_results(len(results) > 0) |
654 query_result.set_more_results(len(results) > 0) |
596 |
655 |
597 |
|
598 def _Dynamic_Next(self, next_request, query_result): |
656 def _Dynamic_Next(self, next_request, query_result): |
599 cursor = next_request.cursor().cursor() |
657 cursor = next_request.cursor().cursor() |
600 |
658 |
601 try: |
659 try: |
602 results, orig_count = self.__queries[cursor] |
660 results, orig_count = self.__queries[cursor] |
603 except KeyError: |
661 except KeyError: |
604 raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, |
662 raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, |
605 'Cursor %d not found' % cursor) |
663 'Cursor %d not found' % cursor) |
606 |
664 |
607 count = next_request.count() |
665 count = next_request.count() |
608 for r in results[:count]: |
666 results_pb = [r._ToPb() for r in results[:count]] |
609 query_result.add_result().CopyFrom(r) |
667 query_result.result_list().extend(results_pb) |
610 del results[:count] |
668 del results[:count] |
611 |
669 |
612 query_result.set_more_results(len(results) > 0) |
670 query_result.set_more_results(len(results) > 0) |
613 |
|
614 |
671 |
615 def _Dynamic_Count(self, query, integer64proto): |
672 def _Dynamic_Count(self, query, integer64proto): |
616 query_result = datastore_pb.QueryResult() |
673 query_result = datastore_pb.QueryResult() |
617 self._Dynamic_RunQuery(query, query_result) |
674 self._Dynamic_RunQuery(query, query_result) |
618 cursor = query_result.cursor().cursor() |
675 cursor = query_result.cursor().cursor() |
619 results, count = self.__queries[cursor] |
676 results, count = self.__queries[cursor] |
620 integer64proto.set_value(count) |
677 integer64proto.set_value(count) |
621 del self.__queries[cursor] |
678 del self.__queries[cursor] |
622 |
679 |
623 |
|
624 def _Dynamic_BeginTransaction(self, request, transaction): |
680 def _Dynamic_BeginTransaction(self, request, transaction): |
625 self.__tx_handle_lock.acquire() |
681 self.__tx_handle_lock.acquire() |
626 handle = self.__next_tx_handle |
682 handle = self.__next_tx_handle |
627 self.__next_tx_handle += 1 |
683 self.__next_tx_handle += 1 |
628 self.__tx_handle_lock.release() |
684 self.__tx_handle_lock.release() |
668 |
724 |
669 kinds = [] |
725 kinds = [] |
670 |
726 |
671 for app, kind in self.__entities: |
727 for app, kind in self.__entities: |
672 if app == app_str: |
728 if app == app_str: |
|
729 app_kind = (app, kind) |
|
730 if app_kind in self.__schema_cache: |
|
731 kinds.append(self.__schema_cache[app_kind]) |
|
732 continue |
|
733 |
673 kind_pb = entity_pb.EntityProto() |
734 kind_pb = entity_pb.EntityProto() |
674 kind_pb.mutable_key().set_app('') |
735 kind_pb.mutable_key().set_app('') |
675 kind_pb.mutable_key().mutable_path().add_element().set_type(kind) |
736 kind_pb.mutable_key().mutable_path().add_element().set_type(kind) |
676 kind_pb.mutable_entity_group() |
737 kind_pb.mutable_entity_group() |
677 kinds.append(kind_pb) |
|
678 |
738 |
679 props = {} |
739 props = {} |
680 |
740 |
681 for entity in self.__entities[(app, kind)].values(): |
741 for entity in self.__entities[app_kind].values(): |
682 for prop in entity.native.property_list(): |
742 for prop in entity.protobuf.property_list(): |
683 if prop.name() not in props: |
743 if prop.name() not in props: |
684 props[prop.name()] = entity_pb.PropertyValue() |
744 props[prop.name()] = entity_pb.PropertyValue() |
685 props[prop.name()].MergeFrom(prop.value()) |
745 props[prop.name()].MergeFrom(prop.value()) |
686 |
746 |
687 for value_pb in props.values(): |
747 for value_pb in props.values(): |
708 for name, value_pb in props.items(): |
768 for name, value_pb in props.items(): |
709 prop_pb = kind_pb.add_property() |
769 prop_pb = kind_pb.add_property() |
710 prop_pb.set_name(name) |
770 prop_pb.set_name(name) |
711 prop_pb.mutable_value().CopyFrom(value_pb) |
771 prop_pb.mutable_value().CopyFrom(value_pb) |
712 |
772 |
713 schema.kind_list().extend(kinds) |
773 kinds.append(kind_pb) |
|
774 self.__schema_cache[app_kind] = kind_pb |
|
775 |
|
776 for kind_pb in kinds: |
|
777 schema.add_kind().CopyFrom(kind_pb) |
714 |
778 |
715 def _Dynamic_CreateIndex(self, index, id_response): |
779 def _Dynamic_CreateIndex(self, index, id_response): |
716 if index.id() != 0: |
780 if index.id() != 0: |
717 raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, |
781 raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, |
718 'New index id must be 0.') |
782 'New index id must be 0.') |
788 for stored_index in self.__indexes[app]: |
852 for stored_index in self.__indexes[app]: |
789 if index.definition() == stored_index.definition(): |
853 if index.definition() == stored_index.definition(): |
790 return stored_index |
854 return stored_index |
791 |
855 |
792 return None |
856 return None |
|
857 |
|
858 @classmethod |
|
859 def __GetSpecialPropertyValue(cls, entity, property): |
|
860 """Returns an entity's value for a special property. |
|
861 |
|
862 Right now, the only special property is __key__, whose value is the |
|
863 entity's key. |
|
864 |
|
865 Args: |
|
866 entity: datastore.Entity |
|
867 |
|
868 Returns: |
|
869 property value. For __key__, a datastore_types.Key. |
|
870 |
|
871 Raises: |
|
872 AssertionError, if the given property is not special. |
|
873 """ |
|
874 assert property in datastore_types._SPECIAL_PROPERTIES |
|
875 if property == datastore_types._KEY_SPECIAL_PROPERTY: |
|
876 return entity.key() |