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

Side by Side Diff: appengine/findit/model/versioned_model.py

Issue 2488113005: [Findit] Re-org code. (Closed)
Patch Set: Rebase. Created 4 years, 1 month 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') | appengine/findit/model/wf_config.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 Subclasses will automatically be versioned. To create the first instance of a
30 versioned entity, use Create(key) with optional key to differentiate between
31 multiple unique entities of the same subclass. Use GetVersion() to read and
32 Save() to write.
33 """
34
35 @property
36 def _root_id(self):
37 return self.key.pairs()[0][1] if self.key else None
38
39 @property
40 def version_number(self):
41 # Ndb treats key.integer_id() of 0 as None, so default to 0.
42 return self.key.integer_id() or 0 if self.key else 0
43
44 @classmethod
45 def Create(cls, key=None):
46 """Creates an instance of cls that is to become the first version.
47
48 The calling function of Create() should be responsible first for checking
49 no previous version of the proposed entity already exists.
50
51 Args:
52 key: Any user-specified value that will serve as the id for the root
53 entity's key.
54
55 Returns:
56 An instance of cls meant to be the first version. Note for this instance
57 to be committed to the datastore Save() would need to be called on the
58 instance returned by this method.
59 """
60 return cls(key=ndb.Key(cls, 0, parent=cls._GetRootKey(key)))
61
62 @classmethod
63 def GetVersion(cls, key=None, version=None):
64 """Returns a version of the entity, the latest if version=None."""
65 assert not ndb.in_transaction()
66
67 root_key = cls._GetRootKey(key)
68 root = root_key.get()
69
70 if not root or not root.current:
71 return None
72
73 if version is None:
74 version = root.current
75 elif version < 1:
76 # Return None for versions < 1, which causes exceptions in ndb.Key()
77 return None
78
79 return ndb.Key(cls, version, parent=root_key).get()
80
81 @classmethod
82 def GetLatestVersionNumber(cls, key=None):
83 root_entity = cls._GetRootKey(key).get()
84 if not root_entity:
85 return -1
86 return root_entity.current
87
88 def Save(self, retry_on_conflict=True):
89 """Saves the current entity, but as a new version.
90
91 Args:
92 retry_on_conflict (bool): Whether or not the next version number should
93 automatically be tried in case another transaction writes the entity
94 first with the same proposed new version number.
95
96 Returns:
97 The key of the newly written version, and a boolean whether or not this
98 call to Save() was responsible for creating it.
99 """
100 root_key = self._GetRootKey(self._root_id)
101 root = root_key.get() or self._GetRootModel()(key=root_key)
102
103 def SaveData():
104 if self.key.get():
105 return False # The entity exists, should retry.
106
107 ndb.put_multi([self, root])
108 return True
109
110 def SetNewKey():
111 root.current += 1
112 self.key = ndb.Key(self.__class__, root.current, parent=root_key)
113
114 SetNewKey()
115 while True:
116 while self.key.get():
117 if retry_on_conflict:
118 SetNewKey()
119 else:
120 # Another transaction had already written the proposed new version, so
121 # return that version's key and False indicating this call to Save()
122 # was not responsible for creating it.
123 return self.key, False
124
125 try:
126 if ndb.transaction(SaveData, retries=0):
127 return self.key, True
128 except (
129 datastore_errors.InternalError,
130 datastore_errors.Timeout,
131 datastore_errors.TransactionFailedError) as e:
132 # https://cloud.google.com/appengine/docs/python/datastore/transactions
133 # states the result is ambiguous, it could have succeeded.
134 logging.info('Transaction likely failed: %s', e)
135 except (
136 apiproxy_errors.CancelledError,
137 datastore_errors.BadRequestError,
138 RuntimeError) as e:
139 logging.info('Transaction failure: %s', e)
140 else:
141 if retry_on_conflict:
142 SetNewKey()
143 else:
144 # Another transaction had already written the proposed new version, so
145 # return that version's key and False indicating this call to Save()
146 # was not responsible for creating it.
147 return self.key, False
148
149 @classmethod
150 def _GetRootModel(cls):
151 """Returns a root model that can be used for versioned entities."""
152 root_model_name = '%sRoot' % cls.__name__
153
154 class _RootModel(_GroupRoot):
155
156 @classmethod
157 def _get_kind(cls):
158 return root_model_name
159
160 return _RootModel
161
162 @classmethod
163 def _GetRootKey(cls, key=None):
164 return ndb.Key(cls._GetRootModel(), key if key is not None else 1)
OLDNEW
« no previous file with comments | « appengine/findit/model/versioned_config.py ('k') | appengine/findit/model/wf_config.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698