diff -r 27971a13089f -r 2e0b0af889be thirdparty/google_appengine/google/appengine/ext/db/__init__.py --- a/thirdparty/google_appengine/google/appengine/ext/db/__init__.py Sat Sep 05 14:04:24 2009 +0200 +++ b/thirdparty/google_appengine/google/appengine/ext/db/__init__.py Sun Sep 06 23:31:53 2009 +0200 @@ -122,6 +122,7 @@ Text = datastore_types.Text Blob = datastore_types.Blob ByteString = datastore_types.ByteString +BlobKey = datastore_types.BlobKey _kind_map = {} @@ -186,6 +187,7 @@ PhoneNumber, PostalAddress, Rating, + BlobKey, ]) _ALLOWED_EXPANDO_PROPERTY_TYPES = set(_ALLOWED_PROPERTY_TYPES) @@ -241,6 +243,49 @@ "definition." % locals()) +def query_descendants(model_instance): + """Returns a query for all the descendants of a model instance. + + Args: + model_instance: Model instance to find the descendants of. + + Returns: + Query that will retrieve all entities that have the given model instance + as an ancestor. Unlike normal ancestor queries, this does not include the + ancestor itself. + """ + + result = Query().ancestor(model_instance); + result.filter(datastore_types._KEY_SPECIAL_PROPERTY + ' >', + model_instance.key()); + return result; + + +def model_to_protobuf(model_instance, _entity_class=datastore.Entity): + """Encodes a model instance as a protocol buffer. + + Args: + model_instance: Model instance to encode. + Returns: + entity_pb.EntityProto representation of the model instance + """ + return model_instance._populate_entity(_entity_class).ToPb() + + +def model_from_protobuf(pb, _entity_class=datastore.Entity): + """Decodes a model instance from a protocol buffer. + + Args: + pb: The protocol buffer representation of the model instance. Can be an + entity_pb.EntityProto or str encoding of an entity_bp.EntityProto + + Returns: + Model instance resulting from decoding the protocol buffer + """ + entity = _entity_class.FromPb(pb) + return class_for_kind(entity.kind()).from_entity(entity) + + def _initialize_properties(model_class, name, bases, dct): """Initialize Property attributes for Model-class. @@ -248,17 +293,31 @@ model_class: Model class to initialize properties for. """ model_class._properties = {} + property_source = {} + + def get_attr_source(name, cls): + for src_cls in cls.mro(): + if name in src_cls.__dict__: + return src_cls + defined = set() for base in bases: if hasattr(base, '_properties'): - property_keys = base._properties.keys() - duplicate_properties = defined.intersection(property_keys) - if duplicate_properties: - raise DuplicatePropertyError( - 'Duplicate properties in base class %s already defined: %s' % - (base.__name__, list(duplicate_properties))) - defined.update(property_keys) - model_class._properties.update(base._properties) + property_keys = set(base._properties.keys()) + duplicate_property_keys = defined & property_keys + for dupe_prop_name in duplicate_property_keys: + old_source = property_source[dupe_prop_name] = get_attr_source( + dupe_prop_name, property_source[dupe_prop_name]) + new_source = get_attr_source(dupe_prop_name, base) + if old_source != new_source: + raise DuplicatePropertyError( + 'Duplicate property, %s, is inherited from both %s and %s.' % + (dupe_prop_name, old_source.__name__, new_source.__name__)) + property_keys -= duplicate_property_keys + if property_keys: + defined |= property_keys + property_source.update(dict.fromkeys(property_keys, base)) + model_class._properties.update(base._properties) for attr_name in dct.keys(): attr = dct[attr_name] @@ -557,6 +616,7 @@ def __init__(self, parent=None, key_name=None, + key=None, _app=None, _from_entity=False, **kwds): @@ -582,38 +642,64 @@ parent: Parent instance for this instance or None, indicating a top- level instance. key_name: Name for new model instance. - _app: Intentionally undocumented. + key: Key instance for this instance, overrides parent and key_name _from_entity: Intentionally undocumented. args: Keyword arguments mapping to properties of model. """ - if key_name == '': - raise BadKeyError('Name cannot be empty.') - elif key_name is not None and not isinstance(key_name, basestring): - raise BadKeyError('Name must be string type, not %s' % - key_name.__class__.__name__) - - if parent is not None: - if not isinstance(parent, (Model, Key)): - raise TypeError('Expected Model type; received %s (is %s)' % - (parent, parent.__class__.__name__)) - if isinstance(parent, Model) and not parent.has_key(): - raise BadValueError( - "%s instance must have a complete key before it can be used as a " - "parent." % parent.kind()) - if isinstance(parent, Key): - self._parent_key = parent + if key is not None: + if isinstance(key, (tuple, list)): + key = Key.from_path(*key) + if isinstance(key, basestring): + key = Key(encoded=key) + if not isinstance(key, Key): + raise TypeError('Expected Key type; received %s (is %s)' % + (key, key.__class__.__name__)) + if not key.has_id_or_name(): + raise BadKeyError('Key must have an id or name') + if key.kind() != self.kind(): + raise BadKeyError('Expected Key kind to be %s; received %s' % + (self.kind(), key.kind())) + if _app is not None and key.app() != _app: + raise BadKeyError('Expected Key app to be %s; received %s' % + (_app, key.app())) + if key_name is not None: + raise BadArgumentError('Cannot use key and key_name at the same time') + if parent is not None: + raise BadArgumentError('Cannot use key and parent at the same time') + self._key = key + self._key_name = None + self._parent = None + self._parent_key = None + else: + if key_name == '': + raise BadKeyError('Name cannot be empty.') + elif key_name is not None and not isinstance(key_name, basestring): + raise BadKeyError('Name must be string type, not %s' % + key_name.__class__.__name__) + + if parent is not None: + if not isinstance(parent, (Model, Key)): + raise TypeError('Expected Model type; received %s (is %s)' % + (parent, parent.__class__.__name__)) + if isinstance(parent, Model) and not parent.has_key(): + raise BadValueError( + "%s instance must have a complete key before it can be used as a " + "parent." % parent.kind()) + if isinstance(parent, Key): + self._parent_key = parent + self._parent = None + else: + self._parent_key = parent.key() + self._parent = parent + else: + self._parent_key = None self._parent = None - else: - self._parent_key = parent.key() - self._parent = parent - else: - self._parent_key = None - self._parent = None + self._key_name = key_name + self._key = None + self._entity = None - self._key_name = key_name self._app = _app - properties = self.properties() for prop in self.properties().values(): if prop.name in kwds: value = kwds[prop.name] @@ -629,8 +715,9 @@ """Unique key for this entity. This property is only available if this entity is already stored in the - datastore, so it is available if this entity was fetched returned from a - query, or after put() is called the first time for new entities. + datastore or if it has a full key, so it is available if this entity was + fetched returned from a query, or after put() is called the first time + for new entities, or if a complete key was given when constructed. Returns: Datastore key of persisted entity. @@ -640,13 +727,12 @@ """ if self.is_saved(): return self._entity.key() + elif self._key: + return self._key elif self._key_name: - if self._parent_key: - parent_key = self._parent_key - elif self._parent: - parent_key = self._parent.key() parent = self._parent_key or (self._parent and self._parent.key()) - return Key.from_path(self.kind(), self._key_name, parent=parent) + self._key = Key.from_path(self.kind(), self._key_name, parent=parent) + return self._key else: raise NotSavedError() @@ -675,8 +761,11 @@ Populated self._entity """ self._entity = self._populate_entity(_entity_class=_entity_class) - if hasattr(self, '_key_name'): - del self._key_name + for attr in ('_key_name', '_key'): + try: + delattr(self, attr) + except AttributeError: + pass return self._entity def put(self): @@ -713,13 +802,21 @@ entity = self._entity else: kwds = {'_app': self._app, - 'name': self._key_name, 'unindexed_properties': self._unindexed_properties} - - if self._parent_key is not None: - kwds['parent'] = self._parent_key - elif self._parent is not None: - kwds['parent'] = self._parent._entity + if self._key is not None: + if self._key.id(): + kwds['id'] = self._key.id() + else: + kwds['name'] = self._key.name() + if self._key.parent(): + kwds['parent'] = self._key.parent() + else: + if self._key_name is not None: + kwds['name'] = self._key_name + if self._parent_key is not None: + kwds['parent'] = self._parent_key + elif self._parent is not None: + kwds['parent'] = self._parent._entity entity = _entity_class(self.kind(), **kwds) self._to_entity(entity) @@ -749,14 +846,15 @@ def has_key(self): """Determine if this model instance has a complete key. - Ids are not assigned until the data is saved to the Datastore, but - instances with a key name always have a full key. + When not using a fully self-assigned Key, ids are not assigned until the + data is saved to the Datastore, but instances with a key name always have + a full key. Returns: - True if the object has been persisted to the datastore or has a key_name, - otherwise False. + True if the object has been persisted to the datastore or has a key + or has a key_name, otherwise False. """ - return self.is_saved() or self._key_name + return self.is_saved() or self._key or self._key_name def dynamic_properties(self): """Returns a list of all dynamic properties defined for instance.""" @@ -794,6 +892,8 @@ return self._parent.key() elif self._entity is not None: return self._entity.parent() + elif self._key is not None: + return self._key.parent() else: return None @@ -1017,8 +1117,12 @@ entity_values = cls._load_entity_values(entity) instance = cls(None, _from_entity=True, **entity_values) - instance._entity = entity - del instance._key_name + if entity.is_saved(): + instance._entity = entity + del instance._key_name + del instance._key + elif entity.key().has_id_or_name(): + instance._key = entity.key() return instance @classmethod @@ -1126,6 +1230,33 @@ keys.append(key) datastore.Delete(keys) +def allocate_ids(model, size): + """Allocates a range of IDs of size for the model_key defined by model + + Allocates a range of IDs in the datastore such that those IDs will not + be automatically assigned to new entities. You can only allocate IDs + for model keys from your app. If there is an error, raises a subclass of + datastore_errors.Error. + + Args: + model: Model, Key or string to serve as a model specifying the ID sequence + in which to allocate IDs + + Returns: + (start, end) of the allocated range, inclusive. + """ + models_or_keys, multiple = datastore.NormalizeAndTypeCheck( + model, (Model, Key, basestring)) + keys = [] + for model_or_key in models_or_keys: + if isinstance(model_or_key, Model): + key = model_or_key = model_or_key.key() + elif isinstance(model_or_key, basestring): + key = model_or_key = Key(model_or_key) + else: + key = model_or_key + keys.append(key) + return datastore.AllocateIds(keys, size) class Expando(Model): """Dynamically expandable model. @@ -1322,7 +1453,7 @@ class _BaseQuery(object): """Base class for both Query and GqlQuery.""" - def __init__(self, model_class, keys_only=False): + def __init__(self, model_class=None, keys_only=False): """Constructor. Args: @@ -1428,7 +1559,10 @@ if self._keys_only: return raw else: - return [self._model_class.from_entity(e) for e in raw] + if self._model_class is not None: + return [self._model_class.from_entity(e) for e in raw] + else: + return [class_for_kind(e.kind()).from_entity(e) for e in raw] def __getitem__(self, arg): """Support for query[index] and query[start:stop]. @@ -1505,7 +1639,11 @@ Raises: StopIteration when there are no more results in query. """ - return self.__model_class.from_entity(self.__iterator.next()) + if self.__model_class is not None: + return self.__model_class.from_entity(self.__iterator.next()) + else: + entity = self.__iterator.next() + return class_for_kind(entity.kind()).from_entity(entity) def _normalize_query_parameter(value): @@ -1569,7 +1707,7 @@ print story.title """ - def __init__(self, model_class, keys_only=False): + def __init__(self, model_class=None, keys_only=False): """Constructs a query over instances of the given Model. Args: @@ -1586,7 +1724,11 @@ _multi_query_class=datastore.MultiQuery): queries = [] for query_set in self.__query_sets: - query = _query_class(self._model_class.kind(), + if self._model_class is not None: + kind = self._model_class.kind() + else: + kind = None + query = _query_class(kind, query_set, keys_only=self._keys_only) query.Order(*self.__orderings) @@ -1665,7 +1807,12 @@ else: operator = '==' - if prop in self._model_class._unindexed_properties: + if self._model_class is None: + if prop != datastore_types._KEY_SPECIAL_PROPERTY: + raise BadQueryError( + 'Only %s filters are allowed on kindless queries.' % + datastore_types._KEY_SPECIAL_PROPERTY) + elif prop in self._model_class._unindexed_properties: raise PropertyError('Property \'%s\' is not indexed' % prop) if operator.lower() == 'in': @@ -1711,13 +1858,20 @@ else: order = datastore.Query.ASCENDING - if not issubclass(self._model_class, Expando): - if (property not in self._model_class.properties() and - property not in datastore_types._SPECIAL_PROPERTIES): - raise PropertyError('Invalid property name \'%s\'' % property) - - if property in self._model_class._unindexed_properties: - raise PropertyError('Property \'%s\' is not indexed' % property) + if self._model_class is None: + if (property != datastore_types._KEY_SPECIAL_PROPERTY or + order != datastore.Query.ASCENDING): + raise BadQueryError( + 'Only %s ascending orders are supported on kindless queries' % + datastore_types._KEY_SPECIAL_PROPERTY) + else: + if not issubclass(self._model_class, Expando): + if (property not in self._model_class.properties() and + property not in datastore_types._SPECIAL_PROPERTIES): + raise PropertyError('Invalid property name \'%s\'' % property) + + if property in self._model_class._unindexed_properties: + raise PropertyError('Property \'%s\' is not indexed' % property) self.__orderings.append((property, order)) return self @@ -1774,14 +1928,18 @@ app = kwds.pop('_app', None) self._proto_query = gql.GQL(query_string, _app=app) - model_class = class_for_kind(self._proto_query._entity) + if self._proto_query._entity is not None: + model_class = class_for_kind(self._proto_query._entity) + else: + model_class = None super(GqlQuery, self).__init__(model_class, keys_only=self._proto_query._keys_only) - for property, unused in (self._proto_query.filters().keys() + - self._proto_query.orderings()): - if property in model_class._unindexed_properties: - raise PropertyError('Property \'%s\' is not indexed' % property) + if model_class is not None: + for property, unused in (self._proto_query.filters().keys() + + self._proto_query.orderings()): + if property in model_class._unindexed_properties: + raise PropertyError('Property \'%s\' is not indexed' % property) self.bind(*args, **kwds) @@ -2404,7 +2562,6 @@ data_type = users.User - class ListProperty(Property): """A property that stores a list of things.