Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """Provides a model to support versioned entities in datastore. | |
| 6 | |
| 7 Idea: use a root model entity to keep track of the most recent version of a | |
| 8 versioned entity, and make the versioned entities and the root model entity in | |
| 9 the same entity group so that they could be read and written in a transaction. | |
| 10 """ | |
| 11 | |
| 12 import logging | |
| 13 | |
| 14 from google.appengine.api import datastore_errors | |
| 15 from google.appengine.ext import ndb | |
| 16 from google.appengine.runtime import apiproxy_errors | |
| 17 | |
| 18 | |
| 19 class _GroupRoot(ndb.Model): | |
| 20 """Root entity of a group to support versioned children.""" | |
| 21 # Key id of the most recent child entity in the datastore. It is monotonically | |
| 22 # increasing and is 0 if no child is present. | |
| 23 current = ndb.IntegerProperty(indexed=False, default=0) | |
| 24 | |
| 25 | |
| 26 class VersionedModel(ndb.Model): | |
| 27 """A model that supports versioning. | |
| 28 | |
| 29 Subclass will automatically be versioned, if use GetMostRecentVersion() to | |
| 30 read and use Save() to write. | |
| 31 """ | |
| 32 | |
| 33 @property | |
| 34 def version(self): | |
| 35 return self.key.integer_id() | |
| 36 | |
| 37 @classmethod | |
| 38 def GetMostRecentVersion(cls): | |
| 39 """Returns the most recent version of the entity.""" | |
| 40 assert not ndb.in_transaction() | |
| 41 | |
| 42 root_key = cls._GetRootKey() | |
| 43 root = root_key.get() | |
| 44 if not root or not root.current: | |
| 45 return None | |
| 46 | |
| 47 return ndb.Key(cls, root.current, parent=root_key).get() | |
| 48 | |
| 49 def Save(self): | |
| 50 """Saves the current entity, but as a new version.""" | |
| 51 root_key = self._GetRootKey() | |
| 52 root = root_key.get() or self._GetRootModel()(key=root_key) | |
| 53 | |
| 54 def SaveData(): | |
| 55 if self.key.get(): | |
| 56 return False # The entity exists, should retry. | |
| 57 ndb.put_multi([self, root]) | |
| 58 return True | |
| 59 | |
| 60 def SetNewKey(): | |
| 61 root.current += 1 | |
| 62 self.key = ndb.Key(self.__class__, root.current, parent=root_key) | |
| 63 | |
| 64 SetNewKey() | |
| 65 while True: | |
|
lijeffrey1
2015/12/03 18:51:37
so is this while True used to perform a retry if a
stgao
2015/12/03 19:43:56
Yes, this is to retry for exception or key already
| |
| 66 while self.key.get(): | |
| 67 SetNewKey() | |
| 68 | |
| 69 try: | |
| 70 if ndb.transaction(SaveData, retries=0): | |
| 71 return self.key | |
| 72 except ( | |
| 73 datastore_errors.InternalError, | |
| 74 datastore_errors.Timeout, | |
| 75 datastore_errors.TransactionFailedError) as e: | |
| 76 # https://cloud.google.com/appengine/docs/python/datastore/transactions | |
| 77 # states the result is ambiguous, it could have succeeded. | |
| 78 logging.info('Transaction likely failed: %s', e) | |
| 79 except ( | |
| 80 apiproxy_errors.CancelledError, | |
| 81 datastore_errors.BadRequestError, | |
| 82 RuntimeError) as e: | |
| 83 logging.info('Transaction failure: %s', e) | |
| 84 else: | |
| 85 SetNewKey() | |
| 86 | |
| 87 @classmethod | |
| 88 def _GetRootModel(cls): | |
| 89 """Returns a root model that can be used for versioned entities.""" | |
| 90 root_model_name = '%sRoot' % cls.__name__ | |
| 91 | |
| 92 class _RootModel(_GroupRoot): | |
| 93 @classmethod | |
| 94 def _get_kind(cls): | |
| 95 return root_model_name | |
| 96 | |
| 97 return _RootModel | |
| 98 | |
| 99 @classmethod | |
| 100 def _GetRootKey(cls): | |
| 101 return ndb.Key(cls._GetRootModel(), 1) | |
| OLD | NEW |