--- 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.