diff options
Diffstat (limited to 'google-appengine/google/appengine/ext/db/__init__.py')
-rwxr-xr-x | google-appengine/google/appengine/ext/db/__init__.py | 160 |
1 files changed, 119 insertions, 41 deletions
diff --git a/google-appengine/google/appengine/ext/db/__init__.py b/google-appengine/google/appengine/ext/db/__init__.py index aea630a..7ecce9d 100755 --- a/google-appengine/google/appengine/ext/db/__init__.py +++ b/google-appengine/google/appengine/ext/db/__init__.py @@ -109,6 +109,7 @@ BadKeyError = datastore_errors.BadKeyError InternalError = datastore_errors.InternalError NeedIndexError = datastore_errors.NeedIndexError Timeout = datastore_errors.Timeout +CommittedButStillApplying = datastore_errors.CommittedButStillApplying ValidationError = BadValueError @@ -129,6 +130,9 @@ BlobKey = datastore_types.BlobKey READ_CAPABILITY = datastore.READ_CAPABILITY WRITE_CAPABILITY = datastore.WRITE_CAPABILITY +STRONG_CONSISTENCY = datastore.STRONG_CONSISTENCY +EVENTUAL_CONSISTENCY = datastore.EVENTUAL_CONSISTENCY + _kind_map = {} @@ -338,6 +342,33 @@ def _initialize_properties(model_class, name, bases, dct): name for name, prop in model_class._properties.items() if not prop.indexed) +def _coerce_to_key(value): + """Returns the value's key. + + Args: + value: a Model or Key instance or string encoded key or None + + Returns: + The corresponding key, or None if value is None. + """ + if value is None: + return None + + value, multiple = datastore.NormalizeAndTypeCheck( + value, (Model, Key, basestring)) + + if len(value) > 1: + raise datastore_errors.BadArgumentError('Expected only one model or key') + value = value[0] + + if isinstance(value, Model): + return value.key() + elif isinstance(value, basestring): + return Key(value) + else: + return value + + class PropertiedClass(type): """Meta-class for initializing Model classes properties. @@ -847,6 +878,9 @@ class Model(object): """ rpc = datastore.GetRpcFromKwargs(kwargs) datastore.Delete(self.key(), rpc=rpc) + self._key = self.key() + self._key_name = None + self._parent_key = None self._entity = None @@ -979,9 +1013,12 @@ class Model(object): key_names: A single key-name or a list of key-names. parent: Parent of instances to get. Can be a model or key. """ + try: + parent = _coerce_to_key(parent) + except BadKeyError, e: + raise BadArgumentError(str(e)) + rpc = datastore.GetRpcFromKwargs(kwargs) - if isinstance(parent, Model): - parent = parent.key() key_names, multiple = datastore.NormalizeAndTypeCheck(key_names, basestring) keys = [datastore.Key.from_path(cls.kind(), name, parent=parent) for name in key_names] @@ -1170,18 +1207,21 @@ class Model(object): return cls.properties() -def create_rpc(deadline=None, callback=None): +def create_rpc(deadline=None, callback=None, read_policy=STRONG_CONSISTENCY): """Create an rpc for use in configuring datastore calls. Args: deadline: float, deadline for calls in seconds. callback: callable, a callback triggered when this rpc completes, accepts one argument: the returned rpc. + read_policy: flag, set to EVENTUAL_CONSISTENCY to enable eventually + consistent reads Returns: A datastore.DatastoreRPC instance. """ - return datastore.CreateRPC(deadline=deadline, callback=callback) + return datastore.CreateRPC( + deadline=deadline, callback=callback, read_policy=read_policy) def get(keys, **kwargs): """Fetch the specific Model instance with the given key from the datastore. @@ -1254,21 +1294,16 @@ def delete(models, **kwargs): TransactionFailedError if the data could not be committed. """ rpc = datastore.GetRpcFromKwargs(kwargs) - models_or_keys, multiple = datastore.NormalizeAndTypeCheck( - models, (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) + + if not isinstance(models, (list, tuple)): + models = [models] + keys = [_coerce_to_key(v) for v in models] + datastore.Delete(keys, rpc=rpc) + def allocate_ids(model, size, **kwargs): - """Allocates a range of IDs of size for the model_key defined by model + """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 @@ -1276,25 +1311,15 @@ def allocate_ids(model, size, **kwargs): datastore_errors.Error. Args: - model: Model instance, Key or string to serve as a model specifying the - ID sequence in which to allocate IDs. + model: Model instance, Key or string to serve as a template specifying the + ID sequence in which to allocate IDs. Returned ids should only be used + in entities with the same parent (if any) and kind as this key. Returns: (start, end) of the allocated range, inclusive. """ - rpc = datastore.GetRpcFromKwargs(kwargs) - 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, rpc=rpc) + return datastore.AllocateIds(_coerce_to_key(model), size, **kwargs) + class Expando(Model): """Dynamically expandable model. @@ -1392,7 +1417,11 @@ class Expando(Model): ValueError on attempt to assign empty list. """ check_reserved_word(key) - if key[:1] != '_' and key not in self.properties(): + if (key[:1] != '_' and + + + + not hasattr(getattr(type(self), key, None), '__set__')): if value == []: raise ValueError('Cannot store empty list to dynamic property %s' % key) @@ -1405,6 +1434,31 @@ class Expando(Model): else: super(Expando, self).__setattr__(key, value) + def __getattribute__(self, key): + """Get attribute from expando. + + Must be overridden to allow dynamic properties to obscure class attributes. + Since all attributes are stored in self._dynamic_properties, the normal + __getattribute__ does not attempt to access it until __setattr__ is called. + By then, the static attribute being overwritten has already been located + and returned from the call. + + This method short circuits the usual __getattribute__ call when finding a + dynamic property and returns it to the user via __getattr__. __getattr__ + is called to preserve backward compatibility with older Expando models + that may have overridden the original __getattr__. + + NOTE: Access to properties defined by Python descriptors are not obscured + because setting those attributes are done through the descriptor and does + not place those attributes in self._dynamic_properties. + """ + if not key.startswith('_'): + dynamic_properties = self._dynamic_properties + if dynamic_properties is not None and key in dynamic_properties: + return self.__getattr__(key) + + return super(Expando, self).__getattribute__(key) + def __getattr__(self, key): """If no explicit attribute defined, retrieve value from entity. @@ -1418,8 +1472,9 @@ class Expando(Model): AttributeError when there is no attribute for key on object or contained entity. """ - if self._dynamic_properties and key in self._dynamic_properties: - return self._dynamic_properties[key] + _dynamic_properties = self._dynamic_properties + if _dynamic_properties is not None and key in _dynamic_properties: + return _dynamic_properties[key] else: return getattr(super(Expando, self), key) @@ -1529,6 +1584,9 @@ class _BaseQuery(object): If you know the number of results you need, consider fetch() instead, or use a GQL query with a LIMIT clause. It's more efficient. + Args: + rpc: datastore.DatastoreRPC to use for this request. + Returns: Iterator for this query. """ @@ -1597,6 +1655,7 @@ class _BaseQuery(object): Args: limit: Maximum number of results to return. offset: Optional number of results to skip first; default zero. + rpc: datastore.DatastoreRPC to use for this request. Returns: A list of db.Model instances. There may be fewer than 'limit' @@ -2081,16 +2140,35 @@ class GqlQuery(_BaseQuery): for name, arg in kwds.iteritems(): self._kwds[name] = _normalize_query_parameter(arg) - def run(self): - """Override _BaseQuery.run() so the LIMIT clause is handled properly.""" - query_run = self._proto_query.Run(*self._args, **self._kwds) - if self._keys_only: - return query_run + def run(self, **kwargs): + """Iterator for this query that handles the LIMIT clause property. + + If the GQL query string contains a LIMIT clause, this function fetches + all results before returning an iterator. Otherwise results are retrieved + in batches by the iterator. + + Args: + rpc: datastore.DatastoreRPC to use for this request. + + Returns: + Iterator for this query. + """ + if self._proto_query.limit() >= 0: + return iter(self.fetch(limit=self._proto_query.limit(), + offset=self._proto_query.offset(), + **kwargs)) else: - return _QueryIterator(self._model_class, iter(query_run)) + results = _BaseQuery.run(self, **kwargs) + try: + for _ in xrange(self._proto_query.offset()): + results.next() + except StopIteration: + pass + + return results def _get_query(self): - return self._proto_query.Bind(self._args, self._kwds) + return self._proto_query.Bind(self._args, self._kwds, self._cursor) class UnindexedProperty(Property): |