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

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: clean up 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 """
11 11
12 import logging 12 import logging
13 13
14 from google.appengine.api import datastore_errors 14 from google.appengine.api import datastore_errors
15 from google.appengine.ext import ndb 15 from google.appengine.ext import ndb
16 from google.appengine.runtime import apiproxy_errors 16 from google.appengine.runtime import apiproxy_errors
17 17
18 18
19 class _GroupRoot(ndb.Model): 19 class _GroupRoot(ndb.Model):
20 """Root entity of a group to support versioned children.""" 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 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. 22 # increasing and is 0 if no child is present.
23 current = ndb.IntegerProperty(indexed=False, default=0) 23 current = ndb.IntegerProperty(indexed=False, default=0)
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
stgao 2016/09/24 01:43:12 Update this to reflect the addition of Create belo
lijeffrey 2016/09/24 06:13:14 Done.
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 # Ndb treats key.integer_id() of 0 as None, so default to 0.
40 return self.key.integer_id() or 0 if self.key else 0
36 41
37 @classmethod 42 @classmethod
38 def GetVersion(cls, version=None): 43 def Create(cls, root_id=None):
44 """Creates an instance of cls that is to become the first version.
45
46 The calling function of Create() should be responsible first for checking
47 no previous version of the proposed entity already exists.
48
49 Args:
50 root_id: Any user-specified value that will serve as the id for the root
51 entity's key.
52
53 Returns:
54 An instance of cls meant to be the first version. Note for this instance
55 to be committed to the datastore Save() would need to be called on the
56 instance returned by this method.
57 """
58 return cls(key=ndb.Key(cls, 0, parent=cls._GetRootKey(root_id)))
59
60 @classmethod
61 def GetVersion(cls, root_id=None, version=None):
39 """Returns a version of the entity, the latest if version=None.""" 62 """Returns a version of the entity, the latest if version=None."""
40 assert not ndb.in_transaction() 63 assert not ndb.in_transaction()
41 64
42 root_key = cls._GetRootKey() 65 root_key = cls._GetRootKey(root_id)
43 root = root_key.get() 66 root = root_key.get()
67
44 if not root or not root.current: 68 if not root or not root.current:
45 return None 69 return None
46 70
47 if version is None: 71 if version is None:
48 version = root.current 72 version = root.current
49 elif version < 1: 73 elif version < 1:
50 # Return None for versions < 1, which causes exceptions in ndb.Key() 74 # Return None for versions < 1, which causes exceptions in ndb.Key()
51 return None 75 return None
52 76
53 return ndb.Key(cls, version, parent=root_key).get() 77 return ndb.Key(cls, version, parent=root_key).get()
54 78
55 @classmethod 79 @classmethod
56 def GetLatestVersionNumber(cls): 80 def GetLatestVersionNumber(cls, root_id=None):
57 root_entity = cls._GetRootKey().get() 81 root_entity = cls._GetRootKey(root_id).get()
58 if not root_entity: 82 if not root_entity:
59 return -1 83 return -1
60 return root_entity.current 84 return root_entity.current
61 85
62 def Save(self): 86 def Save(self, retry_on_conflict=True):
63 """Saves the current entity, but as a new version.""" 87 """Saves the current entity, but as a new version.
64 root_key = self._GetRootKey() 88
89 Args:
90 retry_on_conflict (bool): Whether or not the next version number should
91 automatically be tried in case another transaction writes the entity
92 first with the same proposed new version number.
93
94 Returns:
95 The key of the newly written version, and a boolean whether or not this
96 call to Save() was responsible for creating it.
97 """
98 root_key = self._GetRootKey(self._root_id)
65 root = root_key.get() or self._GetRootModel()(key=root_key) 99 root = root_key.get() or self._GetRootModel()(key=root_key)
66 100
67 def SaveData(): 101 def SaveData():
68 if self.key.get(): 102 if self.key.get():
69 return False # The entity exists, should retry. 103 return False # The entity exists, should retry.
104
70 ndb.put_multi([self, root]) 105 ndb.put_multi([self, root])
71 return True 106 return True
72 107
73 def SetNewKey(): 108 def SetNewKey():
74 root.current += 1 109 root.current += 1
75 self.key = ndb.Key(self.__class__, root.current, parent=root_key) 110 self.key = ndb.Key(self.__class__, root.current, parent=root_key)
76 111
77 SetNewKey() 112 SetNewKey()
78 while True: 113 while True:
79 while self.key.get(): 114 while self.key.get():
80 SetNewKey() 115 if retry_on_conflict:
116 SetNewKey()
117 else:
118 # Another transaction had already written the proposed new version, so
119 # return that version's key and False indicating this call to Save()
120 # was not responsible for creating it.
121 return self.key, False
81 122
82 try: 123 try:
83 if ndb.transaction(SaveData, retries=0): 124 if ndb.transaction(SaveData, retries=0):
84 return self.key 125 return self.key, True
85 except ( 126 except (
86 datastore_errors.InternalError, 127 datastore_errors.InternalError,
87 datastore_errors.Timeout, 128 datastore_errors.Timeout,
88 datastore_errors.TransactionFailedError) as e: 129 datastore_errors.TransactionFailedError) as e:
89 # https://cloud.google.com/appengine/docs/python/datastore/transactions 130 # https://cloud.google.com/appengine/docs/python/datastore/transactions
90 # states the result is ambiguous, it could have succeeded. 131 # states the result is ambiguous, it could have succeeded.
91 logging.info('Transaction likely failed: %s', e) 132 logging.info('Transaction likely failed: %s', e)
92 except ( 133 except (
93 apiproxy_errors.CancelledError, 134 apiproxy_errors.CancelledError,
94 datastore_errors.BadRequestError, 135 datastore_errors.BadRequestError,
95 RuntimeError) as e: 136 RuntimeError) as e:
96 logging.info('Transaction failure: %s', e) 137 logging.info('Transaction failure: %s', e)
97 else: 138 else:
98 SetNewKey() 139 if retry_on_conflict:
140 SetNewKey()
141 else:
142 # Another transaction had already written the proposed new version, so
143 # return that version's key and False indicating this call to Save()
144 # was not responsible for creating it.
145 return self.key, False
99 146
100 @classmethod 147 @classmethod
101 def _GetRootModel(cls): 148 def _GetRootModel(cls):
102 """Returns a root model that can be used for versioned entities.""" 149 """Returns a root model that can be used for versioned entities."""
103 root_model_name = '%sRoot' % cls.__name__ 150 root_model_name = '%sRoot' % cls.__name__
104 151
105 class _RootModel(_GroupRoot): 152 class _RootModel(_GroupRoot):
106 153
107 @classmethod 154 @classmethod
108 def _get_kind(cls): 155 def _get_kind(cls):
109 return root_model_name 156 return root_model_name
110 157
111 return _RootModel 158 return _RootModel
112 159
113 @classmethod 160 @classmethod
114 def _GetRootKey(cls): 161 def _GetRootKey(cls, root_id=None):
115 return ndb.Key(cls._GetRootModel(), 1) 162 return ndb.Key(
163 cls._GetRootModel(), root_id if root_id is not None else 1)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698