62 |
62 |
63 entity_pb.Reference.__hash__ = lambda self: hash(self.Encode()) |
63 entity_pb.Reference.__hash__ = lambda self: hash(self.Encode()) |
64 datastore_pb.Query.__hash__ = lambda self: hash(self.Encode()) |
64 datastore_pb.Query.__hash__ = lambda self: hash(self.Encode()) |
65 |
65 |
66 |
66 |
|
67 class _StoredEntity(object): |
|
68 """Simple wrapper around an entity stored by the stub. |
|
69 |
|
70 Public properties: |
|
71 native: Native protobuf Python object, entity_pb.EntityProto. |
|
72 encoded: Encoded binary representation of above protobuf. |
|
73 """ |
|
74 |
|
75 def __init__(self, entity): |
|
76 """Create a _StoredEntity object and store an entity. |
|
77 |
|
78 Args: |
|
79 entity: entity_pb.EntityProto to store. |
|
80 """ |
|
81 self.native = entity |
|
82 |
|
83 self.encoded = entity.Encode() |
|
84 |
|
85 |
67 class DatastoreFileStub(object): |
86 class DatastoreFileStub(object): |
68 """ Persistent stub for the Python datastore API. |
87 """ Persistent stub for the Python datastore API. |
69 |
88 |
70 Stores all entities in memory, and persists them to a file as pickled |
89 Stores all entities in memory, and persists them to a file as pickled |
71 protocol buffers. A DatastoreFileStub instance handles a single app's data |
90 protocol buffers. A DatastoreFileStub instance handles a single app's data |
72 and is backed by files on disk. |
91 and is backed by files on disk. |
73 """ |
92 """ |
|
93 |
|
94 _PROPERTY_TYPE_TAGS = { |
|
95 datastore_types.Blob: entity_pb.PropertyValue.kstringValue, |
|
96 bool: entity_pb.PropertyValue.kbooleanValue, |
|
97 datastore_types.Category: entity_pb.PropertyValue.kstringValue, |
|
98 datetime.datetime: entity_pb.PropertyValue.kint64Value, |
|
99 datastore_types.Email: entity_pb.PropertyValue.kstringValue, |
|
100 float: entity_pb.PropertyValue.kdoubleValue, |
|
101 datastore_types.GeoPt: entity_pb.PropertyValue.kPointValueGroup, |
|
102 datastore_types.IM: entity_pb.PropertyValue.kstringValue, |
|
103 int: entity_pb.PropertyValue.kint64Value, |
|
104 datastore_types.Key: entity_pb.PropertyValue.kReferenceValueGroup, |
|
105 datastore_types.Link: entity_pb.PropertyValue.kstringValue, |
|
106 long: entity_pb.PropertyValue.kint64Value, |
|
107 datastore_types.PhoneNumber: entity_pb.PropertyValue.kstringValue, |
|
108 datastore_types.PostalAddress: entity_pb.PropertyValue.kstringValue, |
|
109 datastore_types.Rating: entity_pb.PropertyValue.kint64Value, |
|
110 str: entity_pb.PropertyValue.kstringValue, |
|
111 datastore_types.Text: entity_pb.PropertyValue.kstringValue, |
|
112 type(None): 0, |
|
113 unicode: entity_pb.PropertyValue.kstringValue, |
|
114 users.User: entity_pb.PropertyValue.kUserValueGroup, |
|
115 } |
74 |
116 |
75 def __init__(self, app_id, datastore_file, history_file, |
117 def __init__(self, app_id, datastore_file, history_file, |
76 require_indexes=False): |
118 require_indexes=False): |
77 """Constructor. |
119 """Constructor. |
78 |
120 |
157 (self.__datastore_file, e)) |
199 (self.__datastore_file, e)) |
158 |
200 |
159 last_path = entity.key().path().element_list()[-1] |
201 last_path = entity.key().path().element_list()[-1] |
160 app_kind = (entity.key().app(), last_path.type()) |
202 app_kind = (entity.key().app(), last_path.type()) |
161 kind_dict = self.__entities.setdefault(app_kind, {}) |
203 kind_dict = self.__entities.setdefault(app_kind, {}) |
162 kind_dict[entity.key()] = entity |
204 kind_dict[entity.key()] = _StoredEntity(entity) |
163 |
205 |
164 if last_path.has_id() and last_path.id() >= self.__next_id: |
206 if last_path.has_id() and last_path.id() >= self.__next_id: |
165 self.__next_id = last_path.id() + 1 |
207 self.__next_id = last_path.id() + 1 |
166 |
208 |
167 self.__query_history = {} |
209 self.__query_history = {} |
190 """ |
232 """ |
191 if self.__datastore_file and self.__datastore_file != '/dev/null': |
233 if self.__datastore_file and self.__datastore_file != '/dev/null': |
192 encoded = [] |
234 encoded = [] |
193 for kind_dict in self.__entities.values(): |
235 for kind_dict in self.__entities.values(): |
194 for entity in kind_dict.values(): |
236 for entity in kind_dict.values(): |
195 encoded.append(entity.Encode()) |
237 encoded.append(entity.encoded) |
196 |
238 |
197 self.__WritePickled(encoded, self.__datastore_file) |
239 self.__WritePickled(encoded, self.__datastore_file) |
198 |
240 |
199 def __WriteHistory(self): |
241 def __WriteHistory(self): |
200 """ Writes out the history file. Be careful! If the file already exist, |
242 """ Writes out the history file. Be careful! If the file already exist, |
430 continue |
472 continue |
431 |
473 |
432 for filter_prop in filt.property_list(): |
474 for filter_prop in filt.property_list(): |
433 filter_val = datastore_types.FromPropertyPb(filter_prop) |
475 filter_val = datastore_types.FromPropertyPb(filter_prop) |
434 |
476 |
435 comp = u'%r %s %r' % (fixed_entity_val, op, filter_val) |
477 fixed_entity_type = self._PROPERTY_TYPE_TAGS.get( |
|
478 fixed_entity_val.__class__) |
|
479 filter_type = self._PROPERTY_TYPE_TAGS.get(filter_val.__class__) |
|
480 if fixed_entity_type == filter_type: |
|
481 comp = u'%r %s %r' % (fixed_entity_val, op, filter_val) |
|
482 elif op != '==': |
|
483 comp = '%r %s %r' % (fixed_entity_type, op, filter_type) |
|
484 else: |
|
485 continue |
436 |
486 |
437 logging.log(logging.DEBUG - 1, |
487 logging.log(logging.DEBUG - 1, |
438 'Evaling filter expression "%s"', comp) |
488 'Evaling filter expression "%s"', comp) |
439 |
489 |
440 try: |
490 try: |
461 |
511 |
462 for order in query.order_list(): |
512 for order in query.order_list(): |
463 prop = order.property().decode('utf-8') |
513 prop = order.property().decode('utf-8') |
464 results = [entity for entity in results if has_prop_indexed(entity, prop)] |
514 results = [entity for entity in results if has_prop_indexed(entity, prop)] |
465 |
515 |
466 def order_compare(a, b): |
516 def order_compare_entities(a, b): |
467 """ Return a negative, zero or positive number depending on whether |
517 """ Return a negative, zero or positive number depending on whether |
468 entity a is considered smaller than, equal to, or larger than b, |
518 entity a is considered smaller than, equal to, or larger than b, |
469 according to the query's orderings. """ |
519 according to the query's orderings. """ |
470 cmped = 0 |
520 cmped = 0 |
471 for o in query.order_list(): |
521 for o in query.order_list(): |
472 prop = o.property().decode('utf-8') |
522 prop = o.property().decode('utf-8') |
473 |
523 |
474 if o.direction() is datastore_pb.Query_Order.ASCENDING: |
524 reverse = (o.direction() is datastore_pb.Query_Order.DESCENDING) |
475 selector = min |
|
476 else: |
|
477 selector = max |
|
478 |
525 |
479 a_val = a[prop] |
526 a_val = a[prop] |
480 if isinstance(a_val, list): |
527 if isinstance(a_val, list): |
481 a_val = selector(a_val) |
528 a_val = sorted(a_val, order_compare_properties, reverse=reverse)[0] |
482 |
529 |
483 b_val = b[prop] |
530 b_val = b[prop] |
484 if isinstance(b_val, list): |
531 if isinstance(b_val, list): |
485 b_val = selector(b_val) |
532 b_val = sorted(b_val, order_compare_properties, reverse=reverse)[0] |
486 |
533 |
487 try: |
534 cmped = order_compare_properties(a_val, b_val) |
488 cmped = cmp(a_val, b_val) |
|
489 except TypeError: |
|
490 cmped = NotImplementedError |
|
491 |
|
492 if cmped == NotImplementedError: |
|
493 cmped = cmp(type(a_val), type(b_val)) |
|
494 |
535 |
495 if o.direction() is datastore_pb.Query_Order.DESCENDING: |
536 if o.direction() is datastore_pb.Query_Order.DESCENDING: |
496 cmped = -cmped |
537 cmped = -cmped |
497 |
538 |
498 if cmped != 0: |
539 if cmped != 0: |
499 return cmped |
540 return cmped |
|
541 |
500 if cmped == 0: |
542 if cmped == 0: |
501 return cmp(a.key(), b.key()) |
543 return cmp(a.key(), b.key()) |
502 |
544 |
503 results.sort(order_compare) |
545 def order_compare_properties(x, y): |
|
546 """Return a negative, zero or positive number depending on whether |
|
547 property value x is considered smaller than, equal to, or larger than |
|
548 property value y. If x and y are different types, they're compared based |
|
549 on the type ordering used in the real datastore, which is based on the |
|
550 tag numbers in the PropertyValue PB. |
|
551 """ |
|
552 if isinstance(x, datetime.datetime): |
|
553 x = datastore_types.DatetimeToTimestamp(x) |
|
554 if isinstance(y, datetime.datetime): |
|
555 y = datastore_types.DatetimeToTimestamp(y) |
|
556 |
|
557 x_type = self._PROPERTY_TYPE_TAGS.get(x.__class__) |
|
558 y_type = self._PROPERTY_TYPE_TAGS.get(y.__class__) |
|
559 |
|
560 if x_type == y_type: |
|
561 try: |
|
562 return cmp(x, y) |
|
563 except TypeError: |
|
564 return 0 |
|
565 else: |
|
566 return cmp(x_type, y_type) |
|
567 |
|
568 results.sort(order_compare_entities) |
504 |
569 |
505 offset = 0 |
570 offset = 0 |
506 limit = len(results) |
571 limit = len(results) |
507 if query.has_offset(): |
572 if query.has_offset(): |
508 offset = query.offset() |
573 offset = query.offset() |
612 kinds.append(kind_pb) |
677 kinds.append(kind_pb) |
613 |
678 |
614 props = {} |
679 props = {} |
615 |
680 |
616 for entity in self.__entities[(app, kind)].values(): |
681 for entity in self.__entities[(app, kind)].values(): |
617 for prop in entity.property_list(): |
682 for prop in entity.native.property_list(): |
618 if prop.name() not in props: |
683 if prop.name() not in props: |
619 props[prop.name()] = entity_pb.PropertyValue() |
684 props[prop.name()] = entity_pb.PropertyValue() |
620 props[prop.name()].MergeFrom(prop.value()) |
685 props[prop.name()].MergeFrom(prop.value()) |
621 |
686 |
622 for value_pb in props.values(): |
687 for value_pb in props.values(): |