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

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: Changing versioning mechanism to use integer for version number and fixing concurrency issue 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
« no previous file with comments | « appengine/findit/model/versioned_config.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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_string_id(self):
stgao 2016/09/22 00:44:01 Why do we care whether the root id is a string or
stgao 2016/09/22 00:44:01 Also not make it a public property.
lijeffrey 2016/09/22 17:36:15 Done.
35 if not self.key:
36 return None
37
38 key_pairs = self.key.pairs()
39 root_string_id = key_pairs[0][1]
40
41 if len(key_pairs) > 1:
42 # The key is expected to have format Key(root, string_id, kind, version)
43 return root_string_id
44
45 if isinstance(root_string_id, basestring):
46 # The key has format Key(kind, string_id)
47 return root_string_id
48
49 # Otherwise, the key has format Key(root, version_number).
50 return None
51
52 @property
34 def version(self): 53 def version(self):
35 return self.key.integer_id() if self.key else 0 54 if not self.key:
55 return 0
56
57 key_pairs = self.key.pairs()
58 if len(key_pairs) == 1:
59 # Key(root, version_number) or Key(kind, string_id). The latter is used
stgao 2016/09/22 00:44:01 This is the child entity of the root. Per discuss
lijeffrey 2016/09/22 17:36:14 A slight change is actually needed: When creating
stgao 2016/09/23 06:27:14 Should we fix the problem in the subclasses (MFA h
stgao 2016/09/23 16:35:53 Another option is to add a function here for insta
60 # only when an entity has been created but not yet written to ndb.
61 return self.key.integer_id() or 0
62
63 # Key(root, string_id, type, version_number).
64 return key_pairs[-1][-1]
36 65
37 @classmethod 66 @classmethod
38 def GetVersion(cls, version=None): 67 def GetVersion(cls, root_string_id=None, version=None):
39 """Returns a version of the entity, the latest if version=None.""" 68 """Returns a version of the entity, the latest if version=None."""
40 assert not ndb.in_transaction() 69 assert not ndb.in_transaction()
41 70
42 root_key = cls._GetRootKey() 71 root_key = cls._GetRootKey(root_string_id)
43 root = root_key.get() 72 root = root_key.get()
73
44 if not root or not root.current: 74 if not root or not root.current:
45 return None 75 return None
46 76
47 if version is None: 77 if version is None:
48 version = root.current 78 version = root.current
49 elif version < 1: 79 elif version < 1:
50 # Return None for versions < 1, which causes exceptions in ndb.Key() 80 # Return None for versions < 1, which causes exceptions in ndb.Key()
51 return None 81 return None
52 82
53 return ndb.Key(cls, version, parent=root_key).get() 83 return ndb.Key(cls, version, parent=root_key).get()
54 84
55 @classmethod 85 @classmethod
56 def GetLatestVersionNumber(cls): 86 def GetLatestVersionNumber(cls, root_string_id=None):
57 root_entity = cls._GetRootKey().get() 87 root_entity = cls._GetRootKey(root_string_id).get()
58 if not root_entity: 88 if not root_entity:
59 return -1 89 return -1
60 return root_entity.current 90 return root_entity.current
61 91
62 def Save(self): 92 def Save(self, should_try_next_version_number=True):
stgao 2016/09/22 00:44:01 name nit: too long. How about retry_on_conflict or
lijeffrey 2016/09/22 17:36:15 Done.
63 """Saves the current entity, but as a new version.""" 93 """Saves the current entity, but as a new version.
64 root_key = self._GetRootKey() 94
95 Args:
96 should_try_next_version_number: Boolean whether or not the next version
stgao 2016/09/22 00:44:01 Format nit: Args: param (type): comment.
lijeffrey 2016/09/22 17:36:14 Done.
97 number should automatically be tried in case another transaction writes
98 the entity first with the same proposed new version number.
99
100 Returns:
101 The key of the newly written version, and a boolean whether or not this
102 call to Save() was responsible for creating it.
103 """
104 root_string_id = self.root_string_id
stgao 2016/09/22 00:44:01 nit: root_string_id is used only once.
lijeffrey 2016/09/22 17:36:14 Done.
105 root_key = self._GetRootKey(root_string_id)
65 root = root_key.get() or self._GetRootModel()(key=root_key) 106 root = root_key.get() or self._GetRootModel()(key=root_key)
66 107
67 def SaveData(): 108 def SaveData():
68 if self.key.get(): 109 if self.key.get():
69 return False # The entity exists, should retry. 110 return False # The entity exists, should retry.
111
70 ndb.put_multi([self, root]) 112 ndb.put_multi([self, root])
71 return True 113 return True
72 114
73 def SetNewKey(): 115 def SetNewKey():
74 root.current += 1 116 root.current += 1
75 self.key = ndb.Key(self.__class__, root.current, parent=root_key) 117 self.key = ndb.Key(self.__class__, root.current, parent=root_key)
76 118
77 SetNewKey() 119 SetNewKey()
78 while True: 120 while True:
79 while self.key.get(): 121 while self.key.get():
80 SetNewKey() 122 if should_try_next_version_number:
123 SetNewKey()
124 else:
125 # Another transaction had already written the proposed new version, so
126 # return that version's key and False indicating this call to Save()
127 # was not responsible for creating it.
128 return self.key, False
81 129
82 try: 130 try:
83 if ndb.transaction(SaveData, retries=0): 131 if ndb.transaction(SaveData, retries=0):
84 return self.key 132 return self.key, True
85 except ( 133 except (
86 datastore_errors.InternalError, 134 datastore_errors.InternalError,
87 datastore_errors.Timeout, 135 datastore_errors.Timeout,
88 datastore_errors.TransactionFailedError) as e: 136 datastore_errors.TransactionFailedError) as e:
89 # https://cloud.google.com/appengine/docs/python/datastore/transactions 137 # https://cloud.google.com/appengine/docs/python/datastore/transactions
90 # states the result is ambiguous, it could have succeeded. 138 # states the result is ambiguous, it could have succeeded.
91 logging.info('Transaction likely failed: %s', e) 139 logging.info('Transaction likely failed: %s', e)
92 except ( 140 except (
93 apiproxy_errors.CancelledError, 141 apiproxy_errors.CancelledError,
94 datastore_errors.BadRequestError, 142 datastore_errors.BadRequestError,
95 RuntimeError) as e: 143 RuntimeError) as e:
96 logging.info('Transaction failure: %s', e) 144 logging.info('Transaction failure: %s', e)
97 else: 145 else:
98 SetNewKey() 146 if should_try_next_version_number:
147 SetNewKey()
148 else:
149 # Another transaction had already written the proposed new version, so
150 # return that version's key and False indicating this call to Save()
151 # was not responsible for creating it.
152 return self.key, False
chanli 2016/09/22 00:11:25 Based on https://docs.python.org/3/tutorial/errors
stgao 2016/09/22 00:44:01 There is a if in line #131. We return only when we
99 153
100 @classmethod 154 @classmethod
101 def _GetRootModel(cls): 155 def _GetRootModel(cls):
102 """Returns a root model that can be used for versioned entities.""" 156 """Returns a root model that can be used for versioned entities."""
103 root_model_name = '%sRoot' % cls.__name__ 157 root_model_name = '%sRoot' % cls.__name__
104 158
105 class _RootModel(_GroupRoot): 159 class _RootModel(_GroupRoot):
106 160
107 @classmethod 161 @classmethod
108 def _get_kind(cls): 162 def _get_kind(cls):
109 return root_model_name 163 return root_model_name
110 164
111 return _RootModel 165 return _RootModel
112 166
113 @classmethod 167 @classmethod
114 def _GetRootKey(cls): 168 def _GetRootKey(cls, string_id=None):
stgao 2016/09/22 00:44:01 name nit: root_id to be consistent.
lijeffrey 2016/09/22 17:36:15 Done.
115 return ndb.Key(cls._GetRootModel(), 1) 169 return ndb.Key(
170 cls._GetRootModel(), string_id if string_id is not None else 1)
OLDNEW
« no previous file with comments | « appengine/findit/model/versioned_config.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698