Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Unified Diff: appengine/findit/model/versioned_model.py

Issue 2345093002: [Findit] Extending versioned_model.py to support versioning multiple entities of the same class. (Closed)
Patch Set: addressing comments Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: appengine/findit/model/versioned_model.py
diff --git a/appengine/findit/model/versioned_model.py b/appengine/findit/model/versioned_model.py
index f45a10ad3ddabe23fe969ef739c3e600b6ea82be..452d2143725ab3431933b3b70c75244ba223b0bb 100644
--- a/appengine/findit/model/versioned_model.py
+++ b/appengine/findit/model/versioned_model.py
@@ -26,21 +26,47 @@ class _GroupRoot(ndb.Model):
class VersionedModel(ndb.Model):
"""A model that supports versioning.
- Subclass will automatically be versioned, if use GetVersion() to
- read and use Save() to write.
+ Subclasses will automatically be versioned. To create the first instance of a
+ versioned entity, use Create(root_id) with optional root_id to differentiate
+ between multiple unique entities of the same subclass. Use GetVersion() to
+ read to read and Save() to write.
stgao 2016/09/24 18:00:37 typo: "to read to read"
lijeffrey 2016/09/26 19:03:53 Done.
"""
@property
+ def _root_id(self):
+ return self.key.pairs()[0][1] if self.key else None
+
+ @property
def version(self):
- return self.key.integer_id() if self.key else 0
+ # 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 GetVersion(cls, version=None):
+ def Create(cls, root_id=None):
stgao 2016/09/24 18:00:37 After a second thought, root_id is more of a conce
lijeffrey 2016/09/26 19:03:53 Done.
+ """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:
+ root_id: 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(root_id)))
+
+ @classmethod
+ def GetVersion(cls, root_id=None, version=None):
"""Returns a version of the entity, the latest if version=None."""
assert not ndb.in_transaction()
- root_key = cls._GetRootKey()
+ root_key = cls._GetRootKey(root_id)
root = root_key.get()
+
if not root or not root.current:
return None
@@ -53,20 +79,31 @@ class VersionedModel(ndb.Model):
return ndb.Key(cls, version, parent=root_key).get()
@classmethod
- def GetLatestVersionNumber(cls):
- root_entity = cls._GetRootKey().get()
+ def GetLatestVersionNumber(cls, root_id=None):
+ root_entity = cls._GetRootKey(root_id).get()
if not root_entity:
return -1
return root_entity.current
- def Save(self):
- """Saves the current entity, but as a new version."""
- root_key = self._GetRootKey()
+ 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
@@ -77,11 +114,17 @@ class VersionedModel(ndb.Model):
SetNewKey()
while True:
while self.key.get():
- SetNewKey()
+ 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
+ return self.key, True
except (
datastore_errors.InternalError,
datastore_errors.Timeout,
@@ -95,7 +138,13 @@ class VersionedModel(ndb.Model):
RuntimeError) as e:
logging.info('Transaction failure: %s', e)
else:
- SetNewKey()
+ 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):
@@ -111,5 +160,6 @@ class VersionedModel(ndb.Model):
return _RootModel
@classmethod
- def _GetRootKey(cls):
- return ndb.Key(cls._GetRootModel(), 1)
+ def _GetRootKey(cls, root_id=None):
+ return ndb.Key(
+ cls._GetRootModel(), root_id if root_id is not None else 1)

Powered by Google App Engine
This is Rietveld 408576698