thirdparty/google_appengine/google/appengine/ext/db/__init__.py
changeset 1278 a7766286a7be
parent 828 f5fd65cc3bf3
child 2309 be1b94099f2d
--- a/thirdparty/google_appengine/google/appengine/ext/db/__init__.py	Thu Feb 12 10:24:37 2009 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/db/__init__.py	Thu Feb 12 12:30:36 2009 +0000
@@ -77,10 +77,13 @@
 
 
 
+import copy
 import datetime
 import logging
+import re
 import time
 import urlparse
+import warnings
 
 from google.appengine.api import datastore
 from google.appengine.api import datastore_errors
@@ -187,6 +190,11 @@
 _ALLOWED_EXPANDO_PROPERTY_TYPES = set(_ALLOWED_PROPERTY_TYPES)
 _ALLOWED_EXPANDO_PROPERTY_TYPES.update((list, tuple, type(None)))
 
+_OPERATORS = ['<', '<=', '>', '>=', '=', '==', '!=', 'in']
+_FILTER_REGEX = re.compile(
+    '^\s*([^\s]+)(\s+(%s)\s*)?$' % '|'.join(_OPERATORS),
+    re.IGNORECASE | re.UNICODE)
+
 
 def class_for_kind(kind):
   """Return base-class responsible for implementing kind.
@@ -527,12 +535,12 @@
                **kwds):
     """Creates a new instance of this model.
 
-    To create a new entity, you instantiate a model and then call save(),
+    To create a new entity, you instantiate a model and then call put(),
     which saves the entity to the datastore:
 
        person = Person()
        person.name = 'Bret'
-       person.save()
+       person.put()
 
     You can initialize properties in the model in the constructor with keyword
     arguments:
@@ -595,7 +603,7 @@
 
     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 save() is called the first time for new entities.
+    query, or after put() is called the first time for new entities.
 
     Returns:
       Datastore key of persisted entity.
@@ -606,7 +614,11 @@
     if self.is_saved():
       return self._entity.key()
     elif self._key_name:
-      parent = self._parent and self._parent.key()
+      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)
     else:
       raise NotSavedError()
@@ -1143,7 +1155,7 @@
 
     1 - Because it is not possible for the datastore to know what kind of
         property to store on an undefined expando value, setting a property to
-        None is the same as deleting it form the expando.
+        None is the same as deleting it from the expando.
 
     2 - Persistent variables on Expando must not begin with '_'.  These
         variables considered to be 'protected' in Python, and are used
@@ -1524,16 +1536,71 @@
       model_class: Model class to build query for.
     """
     super(Query, self).__init__(model_class)
-    self.__query_set = {}
+    self.__query_sets = [{}]
     self.__orderings = []
     self.__ancestor = None
 
-  def _get_query(self, _query_class=datastore.Query):
-    query = _query_class(self._model_class.kind(), self.__query_set)
-    if self.__ancestor is not None:
-      query.Ancestor(self.__ancestor)
-    query.Order(*self.__orderings)
-    return query
+  def _get_query(self,
+                 _query_class=datastore.Query,
+                 _multi_query_class=datastore.MultiQuery):
+    queries = []
+    for query_set in self.__query_sets:
+      query = _query_class(self._model_class.kind(), query_set)
+      if self.__ancestor is not None:
+        query.Ancestor(self.__ancestor)
+      queries.append(query)
+
+    if (_query_class != datastore.Query and
+        _multi_query_class == datastore.MultiQuery):
+      warnings.warn(
+          'Custom _query_class specified without corresponding custom'
+          ' _query_multi_class. Things will break if you use queries with'
+          ' the "IN" or "!=" operators.', RuntimeWarning)
+      if len(queries) > 1:
+        raise datastore_errors.BadArgumentError(
+            'Query requires multiple subqueries to satisfy. If _query_class'
+            ' is overridden, _multi_query_class must also be overridden.')
+    elif (_query_class == datastore.Query and
+          _multi_query_class != datastore.MultiQuery):
+      raise BadArgumentError('_query_class must also be overridden if'
+                             ' _multi_query_class is overridden.')
+
+    if len(queries) == 1:
+      queries[0].Order(*self.__orderings)
+      return queries[0]
+    else:
+      return _multi_query_class(queries, self.__orderings)
+
+  def __filter_disjunction(self, operations, values):
+    """Add a disjunction of several filters and several values to the query.
+
+    This is implemented by duplicating queries and combining the
+    results later.
+
+    Args:
+      operations: a string or list of strings. Each string contains a
+        property name and an operator to filter by. The operators
+        themselves must not require multiple queries to evaluate
+        (currently, this means that 'in' and '!=' are invalid).
+
+      values: a value or list of filter values, normalized by
+        _normalize_query_parameter.
+    """
+    if not isinstance(operations, (list, tuple)):
+      operations = [operations]
+    if not isinstance(values, (list, tuple)):
+      values = [values]
+
+    new_query_sets = []
+    for operation in operations:
+      if operation.lower().endswith('in') or operation.endswith('!='):
+        raise BadQueryError('Cannot use "in" or "!=" in a disjunction.')
+      for query_set in self.__query_sets:
+        for value in values:
+          new_query_set = copy.copy(query_set)
+          datastore._AddOrAppend(new_query_set, operation, value)
+          new_query_sets.append(new_query_set)
+    self.__query_sets = new_query_sets
 
   def filter(self, property_operator, value):
     """Add filter to query.
@@ -1545,11 +1612,29 @@
     Returns:
       Self to support method chaining.
     """
-    if isinstance(value, (list, tuple)):
-      raise BadValueError('Filtering on lists is not supported')
-
-    value = _normalize_query_parameter(value)
-    datastore._AddOrAppend(self.__query_set, property_operator, value)
+    match = _FILTER_REGEX.match(property_operator)
+    prop = match.group(1)
+    if match.group(3) is not None:
+      operator = match.group(3)
+    else:
+      operator = '=='
+
+    if operator.lower() == 'in':
+      if not isinstance(value, (list, tuple)):
+        raise BadValueError('Argument to the "in" operator must be a list')
+      values = [_normalize_query_parameter(v) for v in value]
+      self.__filter_disjunction(prop + ' =', values)
+    else:
+      if isinstance(value, (list, tuple)):
+        raise BadValueError('Filtering on lists is not supported')
+      if operator == '!=':
+        self.__filter_disjunction([prop + ' <', prop + ' >'],
+                                  _normalize_query_parameter(value))
+      else:
+        value = _normalize_query_parameter(value)
+        for query_set in self.__query_sets:
+          datastore._AddOrAppend(query_set, property_operator, value)
+
     return self
 
   def order(self, property):
@@ -2359,8 +2444,11 @@
     Returns:
       validated list appropriate to save in the datastore.
     """
-    return self.validate_list_contents(
+    value = self.validate_list_contents(
         super(ListProperty, self).get_value_for_datastore(model_instance))
+    if self.validator:
+      self.validator(value)
+    return value
 
 
 class StringListProperty(ListProperty):
@@ -2622,5 +2710,7 @@
 
 
 run_in_transaction = datastore.RunInTransaction
+run_in_transaction_custom_retries = datastore.RunInTransactionCustomRetries
 
 RunInTransaction = run_in_transaction
+RunInTransactionCustomRetries = run_in_transaction_custom_retries