| Index: appengine/findit/model/versioned_model.py
|
| diff --git a/appengine/findit/model/versioned_model.py b/appengine/findit/model/versioned_model.py
|
| deleted file mode 100644
|
| index d4d614488f960cd164cbc7c343f2b5320f695b0e..0000000000000000000000000000000000000000
|
| --- a/appengine/findit/model/versioned_model.py
|
| +++ /dev/null
|
| @@ -1,164 +0,0 @@
|
| -# Copyright 2015 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -"""Provides a model to support versioned entities in datastore.
|
| -
|
| -Idea: use a root model entity to keep track of the most recent version of a
|
| -versioned entity, and make the versioned entities and the root model entity in
|
| -the same entity group so that they could be read and written in a transaction.
|
| -"""
|
| -
|
| -import logging
|
| -
|
| -from google.appengine.api import datastore_errors
|
| -from google.appengine.ext import ndb
|
| -from google.appengine.runtime import apiproxy_errors
|
| -
|
| -
|
| -class _GroupRoot(ndb.Model):
|
| - """Root entity of a group to support versioned children."""
|
| - # Key id of the most recent child entity in the datastore. It is monotonically
|
| - # increasing and is 0 if no child is present.
|
| - current = ndb.IntegerProperty(indexed=False, default=0)
|
| -
|
| -
|
| -class VersionedModel(ndb.Model):
|
| - """A model that supports versioning.
|
| -
|
| - Subclasses will automatically be versioned. To create the first instance of a
|
| - versioned entity, use Create(key) with optional key to differentiate between
|
| - multiple unique entities of the same subclass. Use GetVersion() to read and
|
| - Save() to write.
|
| - """
|
| -
|
| - @property
|
| - def _root_id(self):
|
| - return self.key.pairs()[0][1] if self.key else None
|
| -
|
| - @property
|
| - def version_number(self):
|
| - # Ndb treats key.integer_id() of 0 as None, so default to 0.
|
| - return self.key.integer_id() or 0 if self.key else 0
|
| -
|
| - @classmethod
|
| - def Create(cls, key=None):
|
| - """Creates an instance of cls that is to become the first version.
|
| -
|
| - The calling function of Create() should be responsible first for checking
|
| - no previous version of the proposed entity already exists.
|
| -
|
| - Args:
|
| - key: Any user-specified value that will serve as the id for the root
|
| - entity's key.
|
| -
|
| - Returns:
|
| - An instance of cls meant to be the first version. Note for this instance
|
| - to be committed to the datastore Save() would need to be called on the
|
| - instance returned by this method.
|
| - """
|
| - return cls(key=ndb.Key(cls, 0, parent=cls._GetRootKey(key)))
|
| -
|
| - @classmethod
|
| - def GetVersion(cls, key=None, version=None):
|
| - """Returns a version of the entity, the latest if version=None."""
|
| - assert not ndb.in_transaction()
|
| -
|
| - root_key = cls._GetRootKey(key)
|
| - root = root_key.get()
|
| -
|
| - if not root or not root.current:
|
| - return None
|
| -
|
| - if version is None:
|
| - version = root.current
|
| - elif version < 1:
|
| - # Return None for versions < 1, which causes exceptions in ndb.Key()
|
| - return None
|
| -
|
| - return ndb.Key(cls, version, parent=root_key).get()
|
| -
|
| - @classmethod
|
| - def GetLatestVersionNumber(cls, key=None):
|
| - root_entity = cls._GetRootKey(key).get()
|
| - if not root_entity:
|
| - return -1
|
| - return root_entity.current
|
| -
|
| - def Save(self, retry_on_conflict=True):
|
| - """Saves the current entity, but as a new version.
|
| -
|
| - Args:
|
| - retry_on_conflict (bool): Whether or not the next version number should
|
| - automatically be tried in case another transaction writes the entity
|
| - first with the same proposed new version number.
|
| -
|
| - Returns:
|
| - The key of the newly written version, and a boolean whether or not this
|
| - call to Save() was responsible for creating it.
|
| - """
|
| - root_key = self._GetRootKey(self._root_id)
|
| - root = root_key.get() or self._GetRootModel()(key=root_key)
|
| -
|
| - def SaveData():
|
| - if self.key.get():
|
| - return False # The entity exists, should retry.
|
| -
|
| - ndb.put_multi([self, root])
|
| - return True
|
| -
|
| - def SetNewKey():
|
| - root.current += 1
|
| - self.key = ndb.Key(self.__class__, root.current, parent=root_key)
|
| -
|
| - SetNewKey()
|
| - while True:
|
| - while self.key.get():
|
| - if retry_on_conflict:
|
| - SetNewKey()
|
| - else:
|
| - # Another transaction had already written the proposed new version, so
|
| - # return that version's key and False indicating this call to Save()
|
| - # was not responsible for creating it.
|
| - return self.key, False
|
| -
|
| - try:
|
| - if ndb.transaction(SaveData, retries=0):
|
| - return self.key, True
|
| - except (
|
| - datastore_errors.InternalError,
|
| - datastore_errors.Timeout,
|
| - datastore_errors.TransactionFailedError) as e:
|
| - # https://cloud.google.com/appengine/docs/python/datastore/transactions
|
| - # states the result is ambiguous, it could have succeeded.
|
| - logging.info('Transaction likely failed: %s', e)
|
| - except (
|
| - apiproxy_errors.CancelledError,
|
| - datastore_errors.BadRequestError,
|
| - RuntimeError) as e:
|
| - logging.info('Transaction failure: %s', e)
|
| - else:
|
| - if retry_on_conflict:
|
| - SetNewKey()
|
| - else:
|
| - # Another transaction had already written the proposed new version, so
|
| - # return that version's key and False indicating this call to Save()
|
| - # was not responsible for creating it.
|
| - return self.key, False
|
| -
|
| - @classmethod
|
| - def _GetRootModel(cls):
|
| - """Returns a root model that can be used for versioned entities."""
|
| - root_model_name = '%sRoot' % cls.__name__
|
| -
|
| - class _RootModel(_GroupRoot):
|
| -
|
| - @classmethod
|
| - def _get_kind(cls):
|
| - return root_model_name
|
| -
|
| - return _RootModel
|
| -
|
| - @classmethod
|
| - def _GetRootKey(cls, key=None):
|
| - return ndb.Key(cls._GetRootModel(), key if key is not None else 1)
|
|
|