thirdparty/google_appengine/google/appengine/ext/db/__init__.py
changeset 2864 2e0b0af889be
parent 2413 d0b7dac5325c
child 3031 7678f72140e6
--- 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.