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

Side by Side 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: Adding test 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 unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Provides a model to support versioned entities in datastore. 5 """Provides a model to support versioned entities in datastore.
6 6
7 Idea: use a root model entity to keep track of the most recent version of a 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 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. 9 the same entity group so that they could be read and written in a transaction.
10 """ 10 """
(...skipping 13 matching lines...) Expand all
24 24
25 25
26 class VersionedModel(ndb.Model): 26 class VersionedModel(ndb.Model):
27 """A model that supports versioning. 27 """A model that supports versioning.
28 28
29 Subclass will automatically be versioned, if use GetVersion() to 29 Subclass will automatically be versioned, if use GetVersion() to
30 read and use Save() to write. 30 read and use Save() to write.
31 """ 31 """
32 32
33 @property 33 @property
34 def _root_id(self):
35 return self.key.pairs()[0][1] if self.key else None
36
37 @property
34 def version(self): 38 def version(self):
35 return self.key.integer_id() if self.key else 0 39 return self.key.integer_id() or 0 if self.key else 0
36 40
37 @classmethod 41 @classmethod
38 def GetVersion(cls, version=None): 42 def GetVersion(cls, root_id=None, version=None):
39 """Returns a version of the entity, the latest if version=None.""" 43 """Returns a version of the entity, the latest if version=None."""
40 assert not ndb.in_transaction() 44 assert not ndb.in_transaction()
41 45
42 root_key = cls._GetRootKey() 46 root_key = cls._GetRootKey(root_id)
43 root = root_key.get() 47 root = root_key.get()
48
44 if not root or not root.current: 49 if not root or not root.current:
45 return None 50 return None
46 51
47 if version is None: 52 if version is None:
48 version = root.current 53 version = root.current
49 elif version < 1: 54 elif version < 1:
50 # Return None for versions < 1, which causes exceptions in ndb.Key() 55 # Return None for versions < 1, which causes exceptions in ndb.Key()
51 return None 56 return None
52 57
53 return ndb.Key(cls, version, parent=root_key).get() 58 return ndb.Key(cls, version, parent=root_key).get()
54 59
55 @classmethod 60 @classmethod
56 def GetLatestVersionNumber(cls): 61 def GetLatestVersionNumber(cls, root_id=None):
57 root_entity = cls._GetRootKey().get() 62 root_entity = cls._GetRootKey(root_id).get()
58 if not root_entity: 63 if not root_entity:
59 return -1 64 return -1
60 return root_entity.current 65 return root_entity.current
61 66
62 def Save(self): 67 def Save(self, retry_on_conflict=True):
63 """Saves the current entity, but as a new version.""" 68 """Saves the current entity, but as a new version.
64 root_key = self._GetRootKey() 69
70 Args:
71 retry_on_conflict (bool): Whether or not the next version number should
72 automatically be tried in case another transaction writes the entity
73 first with the same proposed new version number.
74
75 Returns:
76 The key of the newly written version, and a boolean whether or not this
77 call to Save() was responsible for creating it.
78 """
79 root_key = self._GetRootKey(self._root_id)
65 root = root_key.get() or self._GetRootModel()(key=root_key) 80 root = root_key.get() or self._GetRootModel()(key=root_key)
66 81
67 def SaveData(): 82 def SaveData():
68 if self.key.get(): 83 if self.key.get():
69 return False # The entity exists, should retry. 84 return False # The entity exists, should retry.
85
70 ndb.put_multi([self, root]) 86 ndb.put_multi([self, root])
71 return True 87 return True
72 88
73 def SetNewKey(): 89 def SetNewKey():
74 root.current += 1 90 root.current += 1
75 self.key = ndb.Key(self.__class__, root.current, parent=root_key) 91 self.key = ndb.Key(self.__class__, root.current, parent=root_key)
76 92
77 SetNewKey() 93 SetNewKey()
78 while True: 94 while True:
79 while self.key.get(): 95 while self.key.get():
80 SetNewKey() 96 if retry_on_conflict:
97 SetNewKey()
98 else:
99 # Another transaction had already written the proposed new version, so
100 # return that version's key and False indicating this call to Save()
101 # was not responsible for creating it.
102 return self.key, False
81 103
82 try: 104 try:
83 if ndb.transaction(SaveData, retries=0): 105 if ndb.transaction(SaveData, retries=0):
84 return self.key 106 return self.key, True
85 except ( 107 except (
86 datastore_errors.InternalError, 108 datastore_errors.InternalError,
87 datastore_errors.Timeout, 109 datastore_errors.Timeout,
88 datastore_errors.TransactionFailedError) as e: 110 datastore_errors.TransactionFailedError) as e:
89 # https://cloud.google.com/appengine/docs/python/datastore/transactions 111 # https://cloud.google.com/appengine/docs/python/datastore/transactions
90 # states the result is ambiguous, it could have succeeded. 112 # states the result is ambiguous, it could have succeeded.
91 logging.info('Transaction likely failed: %s', e) 113 logging.info('Transaction likely failed: %s', e)
92 except ( 114 except (
93 apiproxy_errors.CancelledError, 115 apiproxy_errors.CancelledError,
94 datastore_errors.BadRequestError, 116 datastore_errors.BadRequestError,
95 RuntimeError) as e: 117 RuntimeError) as e:
96 logging.info('Transaction failure: %s', e) 118 logging.info('Transaction failure: %s', e)
97 else: 119 else:
98 SetNewKey() 120 if retry_on_conflict:
121 SetNewKey()
122 else:
123 # Another transaction had already written the proposed new version, so
124 # return that version's key and False indicating this call to Save()
125 # was not responsible for creating it.
126 return self.key, False
99 127
100 @classmethod 128 @classmethod
101 def _GetRootModel(cls): 129 def _GetRootModel(cls):
102 """Returns a root model that can be used for versioned entities.""" 130 """Returns a root model that can be used for versioned entities."""
103 root_model_name = '%sRoot' % cls.__name__ 131 root_model_name = '%sRoot' % cls.__name__
104 132
105 class _RootModel(_GroupRoot): 133 class _RootModel(_GroupRoot):
106 134
107 @classmethod 135 @classmethod
108 def _get_kind(cls): 136 def _get_kind(cls):
109 return root_model_name 137 return root_model_name
110 138
111 return _RootModel 139 return _RootModel
112 140
113 @classmethod 141 @classmethod
114 def _GetRootKey(cls): 142 def _GetRootKey(cls, root_id=None):
115 return ndb.Key(cls._GetRootModel(), 1) 143 return ndb.Key(
144 cls._GetRootModel(), root_id if root_id is not None else 1)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698