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 |