3XtkfLWeUNaXHv1JE7qWva changeset

Changeset303866353263 (b)
ParentNone (a)
ab
0+    def update_fields(self, record_id, fields, cached_record=None):
0+        """Safely update a number of fields. 'fields' being a
0+        dictionary with path_tuple: new_value for only the fields we
0+        want to change the value of, where path_tuple is a tuple of
0+        fieldnames indicating the path to the possibly nested field
0+        we're interested in. old_record a the copy of the record we
0+        most recently read from the database.
0+
0+        In the case the underlying document was changed, we try to
0+        merge, but only if none of the old values have changed. (i.e.,
0+        do not overwrite changes originating elsewhere.)
0+
0+        This is slightly hairy, so that other code won't have to be.
0+        """
0+        # Initially, the record in memory and in the db are the same
0+        # as far as we know. (If they're not, we'll get a
0+        # ResourceConflict later on, from which we can recover.)
0+        if cached_record is None:
0+            cached_record = self.db[record_id]
0+        if isinstance(cached_record, Record):
0+            cached_record = cached_record._data
0+        record = copy.deepcopy(cached_record)
0+        # Loop until either failure or success has been determined
0+        while True:
0+            modified = False
0+            conflicts = {}
0+            # loop through all the fields that need to be modified
0+            for path, new_value in fields.items():
0+                if not isinstance(path, tuple):
0+                    path = (path,)
0+                # Walk down in both copies of the record to the leaf
0+                # node that represents the field, creating the path in
0+                # the in memory record if necessary.
0+                db_parent = record
0+                cached_parent = cached_record
0+                for field in path[:-1]:
0+                    db_parent = db_parent.setdefault(field, {})
0+                    cached_parent = cached_parent.get(field, {})
0+                # Get the values of the fields in the two copies.
0+                db_value = db_parent.get(path[-1])
0+                cached_value = cached_parent.get(path[-1])
0+                # If the value we intend to store is already in the
0+                # database, we need do nothing, which is our favorite.
0+                if db_value == new_value:
0+                    continue
0+                # If the value in the db is different than the value
0+                # our copy holds, we have a conflict. We could bail
0+                # here, but we can give better feedback if we gather
0+                # all the conflicts, so we continue the for loop
0+                if db_value != cached_value:
0+                    conflicts[path] = (db_value, new_value)
0+                    continue
0+                # Otherwise, it is safe to update the field with the
0+                # new value.
0+                modified = True
0+                db_parent[path[-1]] = new_value
0+            # If we had conflicts, we can bail.
0+            if conflicts:
0+                raise FieldsConflict(conflicts)
0+            # If we made changes to the document, we'll need to save
0+            # it.
0+            if modified:
0+                try:
0+                    self.db[record_id] = record
0+                except ResourceConflict:
0+                    # We got a conflict, meaning the record has
0+                    # changed in the database since we last loaded it
0+                    # into memory. Let's get a fresh copy and try
0+                    # again.
0+                    record = self.db[record_id]
0+                    continue
0+            # If we get here, nothing remains to be done, and we can
0+            # take a well deserved break.
0+            break
0+
...
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
75
76
77
78
--- Revision None
+++ Revision 303866353263
@@ -0,0 +1,75 @@
+ 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
+