Merge lp:~gary/lazr.restful/bug871944 into lp:lazr.restful

Proposed by Gary Poster
Status: Merged
Approved by: Graham Binns
Approved revision: 199
Merged at revision: 197
Proposed branch: lp:~gary/lazr.restful/bug871944
Merge into: lp:lazr.restful
Diff against target: 158 lines (+62/-10)
5 files modified
src/lazr/restful/NEWS.txt (+6/-0)
src/lazr/restful/_resource.py (+14/-7)
src/lazr/restful/tests/test_etag.py (+1/-1)
src/lazr/restful/tests/test_webservice.py (+40/-1)
src/lazr/restful/version.txt (+1/-1)
To merge this branch: bzr merge lp:~gary/lazr.restful/bug871944
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+78976@code.launchpad.net

Commit message

Be more careful in obtaining old and new values when determining cache invalidation.

Description of the change

The related bug describes the problem that this branch solves. The solution I chose is fairly straightforward and involves fairly isolated changes to the codebase: keep the field and original value around in the cache too, so we can get the new value with the expected name, and compare it against the real original value. I added a test which failed before my change, and passed with it.

Launchpad appears to be happy with these changes.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/restful/NEWS.txt'
2--- src/lazr/restful/NEWS.txt 2011-09-20 18:26:06 +0000
3+++ src/lazr/restful/NEWS.txt 2011-10-11 14:10:35 +0000
4@@ -2,6 +2,12 @@
5 NEWS for lazr.restful
6 =====================
7
8+0.19.4 (2011-10-11)
9+===================
10+
11+Fixed bug 871944: a successful write with an If-Match would sometimes
12+return stale values.
13+
14 0.19.3 (2011-09-20)
15 ===================
16
17
18=== modified file 'src/lazr/restful/_resource.py'
19--- src/lazr/restful/_resource.py 2011-09-20 14:36:24 +0000
20+++ src/lazr/restful/_resource.py 2011-10-11 14:10:35 +0000
21@@ -797,15 +797,17 @@
22 cached_value = self._unmarshalled_field_cache.get(
23 field_name, MISSING)
24 if cached_value is not MISSING:
25- return cached_value
26+ return cached_value[0]
27
28 field = field.bind(self.context)
29 marshaller = getMultiAdapter((field, self.request), IFieldMarshaller)
30 try:
31 if IUnmarshallingDoesntNeedValue.providedBy(marshaller):
32 value = None
33+ flag = IUnmarshallingDoesntNeedValue
34 else:
35 value = getattr(self.entry, field.__name__)
36+ flag = None
37 if detail is NORMAL_DETAIL:
38 repr_value = marshaller.unmarshall(self.entry, value)
39 elif detail is CLOSEUP_DETAIL:
40@@ -820,11 +822,14 @@
41 # access to the resource altogether, use our special
42 # 'redacted' tag: URI for the field's value.
43 repr_value = self.REDACTED_VALUE
44+ value = None
45+ flag = Unauthorized
46
47 unmarshalled = (marshaller.representation_name, repr_value)
48
49 if detail is NORMAL_DETAIL:
50- self._unmarshalled_field_cache[field_name] = unmarshalled
51+ self._unmarshalled_field_cache[field_name] = (
52+ unmarshalled, (field, value, flag))
53 return unmarshalled
54
55 def _field_with_html_renderer(self, field_name, field):
56@@ -1215,14 +1220,16 @@
57 # The changeset contained new values for some of this object's
58 # fields, and the notification event may have changed others.
59 # Clear out any fields that changed.
60- for name, ignored in self._unmarshalled_field_cache.items():
61+ for name, cached in self._unmarshalled_field_cache.items():
62+ cached, (field, old_value, flag) = cached
63+ if flag is IUnmarshallingDoesntNeedValue:
64+ continue
65 force_clear = False
66 try:
67- old_value = getattr(entry_before_modification, name, None)
68- new_value = getattr(self.entry.context, name, None)
69+ new_value = getattr(self.entry, field.__name__)
70 except Unauthorized:
71- # Clear the cache, just to be safe.
72- force_clear = True
73+ if flag is not Unauthorized:
74+ force_clear = True
75 if force_clear or new_value != old_value:
76 del(self._unmarshalled_field_cache[name])
77
78
79=== modified file 'src/lazr/restful/tests/test_etag.py'
80--- src/lazr/restful/tests/test_etag.py 2010-10-04 15:42:42 +0000
81+++ src/lazr/restful/tests/test_etag.py 2011-10-11 14:10:35 +0000
82@@ -121,7 +121,7 @@
83 """Set the value of the fake field the EntryFieldResource references.
84 """
85 self.resource._unmarshalled_field_cache['field_name'] = (
86- 'field_name', value)
87+ ('field_name', value), (FauxEntryField(), value, None))
88 # We have to clear the etag cache for a new value to be generated.
89 # XXX benji 2010-09-30 [bug=652459] Does this mean there is an error
90 # condition that occurs when something other than applyChanges (which
91
92=== modified file 'src/lazr/restful/tests/test_webservice.py'
93--- src/lazr/restful/tests/test_webservice.py 2011-09-08 20:18:30 +0000
94+++ src/lazr/restful/tests/test_webservice.py 2011-10-11 14:10:35 +0000
95@@ -374,9 +374,24 @@
96 self.a_field = value
97
98
99+class IHasFieldExportedAsDifferentName(Interface):
100+ """An entry with a field exported as a different name."""
101+ export_as_webservice_entry()
102+ a_field = exported(TextLine(title=u"A field."), exported_as='field')
103+
104+
105+class HasFieldExportedAsDifferentName:
106+ """An implementation of IHasFieldExportedAsDifferentName."""
107+ implements(IHasFieldExportedAsDifferentName)
108+ def __init__(self, value):
109+ self.a_field = value
110+
111+
112 class TestEntryWrite(EntryTestCase):
113
114- testmodule_objects = [IHasOneField, HasOneField]
115+ testmodule_objects = [
116+ IHasOneField, HasOneField,
117+ IHasFieldExportedAsDifferentName, HasFieldExportedAsDifferentName]
118
119 def test_applyChanges_rejects_nonexistent_web_link(self):
120 # If web_link is not published, applyChanges rejects a request
121@@ -418,6 +433,30 @@
122 representation = simplejson.loads(resource.applyChanges({}))
123 self.assertEquals(representation, existing_representation)
124
125+ def test_applyChanges_returns_modified_representation_on_change(self):
126+ # If a change is made, the representation that is returned
127+ # represents the new state. Bug fix: this should happen even
128+ # if the resource and its context have different names for the
129+ # same value.
130+ self._register_website_url_space(IHasFieldExportedAsDifferentName)
131+
132+ with self.entry_resource(
133+ IHasFieldExportedAsDifferentName,
134+ HasFieldExportedAsDifferentName,
135+ u"initial value") as resource:
136+ # We have an entry that has a different name on the
137+ # entry than on its context.
138+ entry = resource.entry
139+ self.assertEquals(entry.field, entry.context.a_field)
140+ # This populates the cache.
141+ self.assertEquals(
142+ u'initial value', resource.toDataForJSON()['field'])
143+ # This returns the changed value.
144+ representation = simplejson.loads(
145+ resource.applyChanges(dict(field=u'new value')))
146+ self.assertEquals(
147+ u'new value', representation['field'])
148+
149
150 class TestEntryWriteForRestrictedField(EntryTestCase):
151
152
153=== modified file 'src/lazr/restful/version.txt'
154--- src/lazr/restful/version.txt 2011-09-20 18:26:06 +0000
155+++ src/lazr/restful/version.txt 2011-10-11 14:10:35 +0000
156@@ -1,1 +1,1 @@
157-0.19.3
158+0.19.4

Subscribers

People subscribed via source and target branches