Revision 303866353263 () - Diff

Link to this snippet: https://friendpaste.com/3XtkfLWeUNaXHv1JE7qWva
Embed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def update_fields(self, record_id, fields, cached_record=None):
"""Safely update a number of fields. 'fields' being a
dictionary with path_tuple: new_value for only the fields we
want to change the value of, where path_tuple is a tuple of
fieldnames indicating the path to the possibly nested field
we're interested in. old_record a the copy of the record we
most recently read from the database.

In the case the underlying document was changed, we try to
merge, but only if none of the old values have changed. (i.e.,
do not overwrite changes originating elsewhere.)

This is slightly hairy, so that other code won't have to be.
"""
# Initially, the record in memory and in the db are the same
# as far as we know. (If they're not, we'll get a
# ResourceConflict later on, from which we can recover.)
if cached_record is None:
cached_record = self.db[record_id]
if isinstance(cached_record, Record):
cached_record = cached_record._data
record = copy.deepcopy(cached_record)
# Loop until either failure or success has been determined
while True:
modified = False
conflicts = {}
# loop through all the fields that need to be modified
for path, new_value in fields.items():
if not isinstance(path, tuple):
path = (path,)
# Walk down in both copies of the record to the leaf
# node that represents the field, creating the path in
# the in memory record if necessary.
db_parent = record
cached_parent = cached_record
for field in path[:-1]:
db_parent = db_parent.setdefault(field, {})
cached_parent = cached_parent.get(field, {})
# Get the values of the fields in the two copies.
db_value = db_parent.get(path[-1])
cached_value = cached_parent.get(path[-1])
# If the value we intend to store is already in the
# database, we need do nothing, which is our favorite.
if db_value == new_value:
continue
# If the value in the db is different than the value
# our copy holds, we have a conflict. We could bail
# here, but we can give better feedback if we gather
# all the conflicts, so we continue the for loop
if db_value != cached_value:
conflicts[path] = (db_value, new_value)
continue
# Otherwise, it is safe to update the field with the
# new value.
modified = True
db_parent[path[-1]] = new_value
# If we had conflicts, we can bail.
if conflicts:
raise FieldsConflict(conflicts)
# If we made changes to the document, we'll need to save
# it.
if modified:
try:
self.db[record_id] = record
except ResourceConflict:
# We got a conflict, meaning the record has
# changed in the database since we last loaded it
# into memory. Let's get a fresh copy and try
# again.
record = self.db[record_id]
continue
# If we get here, nothing remains to be done, and we can
# take a well deserved break.
break