Merge lp:~leonardr/lazr.restful/multiversion-pagetest into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/multiversion-pagetest
Merge into: lp:lazr.restful
Diff against target: 467 lines (+238/-69)
5 files modified
src/lazr/restful/declarations.py (+32/-46)
src/lazr/restful/docs/webservice-declarations.txt (+3/-13)
src/lazr/restful/example/multiversion/resources.py (+57/-4)
src/lazr/restful/example/multiversion/root.py (+3/-1)
src/lazr/restful/example/multiversion/tests/introduction.txt (+143/-5)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/multiversion-pagetest
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Review via email: mp+18868@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch adds more multi-version capabilities to the example multiversion web service (fields that are not published in all versions, a field that's manipulated with a mutator, and two destructor methods for different versions), and adds pagetests to make sure all the features I've been writing work in a real multi-version web service.

I found one part of the system which did _not_ work in a real web service. If you define a field's mutator method for version 1.0, that works fine for version 1.0, but the mutator is not inherited in version 2.0--the field goes back to being read-only. There was no inheritance mechanism to make sure that version 2.0's mutator defaults to version 1.0's mutator.

Fortunately, I've already dealt with this situation. I had the exact same problem on the field level, where version 2.0's field was not published under version 1.0's name, and I resolved it with _normalize_field_annotations. In this branch, I removed the _get_by_version helper method which I was using to retrieve mutator information from the special mutator dictionary. Instead, I changed _normalize_field_annotations to merge the special mutator dictionary into the standard annotation dictionary as it normalizes that dictionary. This is where inheritance is implemented for fields, and now that's also where it's implemented for field mutators.

Revision history for this message
Paul Hummer (rockstar) wrote :

<rockstar> leonardr, hi. Something is fishy about this diff.
<rockstar> leonardr, is it not based on one of the previous branches I reviewed?
<rockstar> leonardr, nevermind. I plead temporary insanity.
<rockstar> leonardr, so, if there's no mutator specified for 2.0, it uses the mutator specified from 1.0?
<leonardr> rockstar: right
<leonardr> same as other named operations
<rockstar> leonardr, so is "beta" the most recent version, and then 3.0, 2.0, and 1.0?
<leonardr> rockstar: right, according to the list of versions defined in the iwebserviceconfiguration

review: Approve (code)
Revision history for this message
Leonard Richardson (leonardr) wrote :

Wait, actually, the list of versions goes beta, 1.0, 2.0, 3.0. Not beta, 3.0, 2.0, 1.0. I hope you haven't found a problem.

Revision history for this message
Paul Hummer (rockstar) wrote :

<rockstar> leonardr, actually, that would be a problem, wouldn't it?
<rockstar> beta should be newer than 3.0, which is newer than 2.0, etc.
<leonardr> rockstar: actually beta is the _earliest_ version, but 3.0 is newer than 2.0, etc
<rockstar> leonardr, what is meant by "earliest" though.
<rockstar> It shouldn't mean that 1.0 overrides it.
<leonardr> rockstar: well, right now launchpad has a 'beta' web service and nothing else
<leonardr> now that i have multi-version working i'm going to create a '1.0' version that's different
<rockstar> leonardr, yes, but then we'll go 1.0, and beta will be what 2.0 is created from.
<leonardr> that's what i mean by saying beta is the earliest
<leonardr> rockstar: no, we do have a 'floating development version', but in launchpad's case that is called 'trunk'
<leonardr> 'beta' is a specific version
<leonardr> that will become obsolete and stop changing
<rockstar> leonardr, huh, that's odd.
<rockstar> leonardr, okay, I guess it makes sense to have 1.0 supersede beta then.
<leonardr> rockstar: if you are confused it's likely others will be confused and think that 'beta' is the permanent cutting edge
<leonardr> but i don't know what we can do about that
<leonardr> it's more like gmail, which was in 'beta' for 5 years and now it's not
<leonardr> the only differnece is that we have to keep 'beta' around for a while
<rockstar> leonardr, yeah. Having worked on the other side of the REST API (on Tarmac), when the API changes now, baby Jesus cries.
<leonardr> thus the multi-version project
<rockstar> leonardr, I think "trunk" for bleeding edge is probably misnamed, since it's likely it could be in production as "trunk"
<leonardr> rockstar: it's not too late to change it
<leonardr> if you have a better idea
<rockstar> leonardr, well, just what I was assuming where beta is the floating development version.
<leonardr> my only alternative is 'dev' or 'development'
<rockstar> leonardr, are we going to release an API version on every release of Launchpad?
<leonardr> rockstar: no, API versions will correspond roughly with ubuntu releases
<leonardr> and will be retired at the same rate as ubuntu releases
<rockstar> Ah, okay.
<rockstar> So maybe "dev" would be appropriate, since (at this point in time), it would correspond to the scripts in Lucid.
<leonardr> flacoste: ^- what do you think?
<leonardr> incidentally, flacoste, the multiversion lazr.restful work is complete as of yesterday
<flacoste> leonardr: i saw good progress on that, great! but complete? i guess we still need some changes in the client support, no?
<flacoste> leonardr: btw, i don't have an opinion on dev vs trunk
<flacoste> we could call it 'unstable'
<leonardr> flacoste: yes, we need to add multiversion to lazr.restfulclient

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/lazr/restful/declarations.py'
--- src/lazr/restful/declarations.py 2010-02-05 14:30:30 +0000
+++ src/lazr/restful/declarations.py 2010-02-08 19:15:22 +0000
@@ -894,14 +894,8 @@
894 tags = tags[0]894 tags = tags[0]
895 if tags.get('exported') is False:895 if tags.get('exported') is False:
896 continue896 continue
897 mutator_annotations = field.queryTaggedValue(897 mutated_by, mutated_by_annotations = tags.get(
898 LAZR_WEBSERVICE_MUTATORS)898 'mutator_annotations', (None, {}))
899 error_prefix = (
900 'Duplicate mutator for interface "%s", field "%s":' % (
901 interface.__name__, field.__name__))
902 mutated_by, mutated_by_annotations = _get_by_version(
903 mutator_annotations, version, versions[0],
904 error_prefix, (None, {}))
905 readonly = (field.readonly and mutated_by is None)899 readonly = (field.readonly and mutated_by is None)
906 attrs[tags['as']] = copy_field(900 attrs[tags['as']] = copy_field(
907 field, __name__=tags['as'], readonly=readonly)901 field, __name__=tags['as'], readonly=readonly)
@@ -941,7 +935,6 @@
941 adapters_by_version = {}935 adapters_by_version = {}
942 for name, field in getFields(content_interface).items():936 for name, field in getFields(content_interface).items():
943 tag_stack = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)937 tag_stack = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
944 mutator_annotations = field.queryTaggedValue(LAZR_WEBSERVICE_MUTATORS)
945 if tag_stack is None:938 if tag_stack is None:
946 continue939 continue
947 for tags_version, tags in tag_stack.stack:940 for tags_version, tags in tag_stack.stack:
@@ -954,12 +947,8 @@
954 # this version's adapter class dictionary.947 # this version's adapter class dictionary.
955 if tags.get('exported') is False:948 if tags.get('exported') is False:
956 continue949 continue
957 error_prefix = (950 mutator, annotations = tags.get(
958 'Duplicate mutator for interface "%s", field "%s":' % (951 'mutator_annotations', (None, {}))
959 content_interface.__name__, field.__name__))
960 mutator, annotations = _get_by_version(
961 mutator_annotations, tags_version, webservice_interfaces[0][0],
962 error_prefix, (None, {}))
963952
964 if mutator is None:953 if mutator is None:
965 property = Passthrough(name, 'context')954 property = Passthrough(name, 'context')
@@ -1222,13 +1211,18 @@
1222 Since we have the list of versions available, this is also a good1211 Since we have the list of versions available, this is also a good
1223 time to do some error checking: make sure that version annotations1212 time to do some error checking: make sure that version annotations
1224 are not duplicated or in the wrong order.1213 are not duplicated or in the wrong order.
1214
1215 Finally, this is a good time to integrate the mutator annotations
1216 into the field annotations.
1225 """1217 """
1226 versioned_dict = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)1218 versioned_dict = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
1219 mutator_annotations = field.queryTaggedValue(LAZR_WEBSERVICE_MUTATORS)
12271220
1228 # If the first version is None and the second version is the1221 # If the first version is None and the second version is the
1229 # earliest version, consolidate the two versions.1222 # earliest version, consolidate the two versions.
1230 earliest_version = versions[0]1223 earliest_version = versions[0]
1231 stack = versioned_dict.stack1224 stack = versioned_dict.stack
1225
1232 if (len(stack) >= 21226 if (len(stack) >= 2
1233 and stack[0][0] is None and stack[1][0] == earliest_version):1227 and stack[0][0] is None and stack[1][0] == earliest_version):
12341228
@@ -1253,6 +1247,20 @@
1253 if stack[0][0] is None:1247 if stack[0][0] is None:
1254 stack[0] = (earliest_version, stack[0][1])1248 stack[0] = (earliest_version, stack[0][1])
12551249
1250 # Make sure there is at most one mutator for the earliest version.
1251 # If there is one, move it from the mutator-specific dictionary to
1252 # the normal tag stack.
1253 implicit_earliest_mutator = mutator_annotations.get(None, None)
1254 explicit_earliest_mutator = mutator_annotations.get(earliest_version, None)
1255 if (implicit_earliest_mutator is not None
1256 and explicit_earliest_mutator is not None):
1257 raise ValueError(
1258 error_prefix + " Both implicit and explicit mutator definitions "
1259 "found for earliest version %s." % earliest_version)
1260 earliest_mutator = implicit_earliest_mutator or explicit_earliest_mutator
1261 if earliest_mutator is not None:
1262 stack[0][1]['mutator_annotations'] = earliest_mutator
1263
1256 # Do some error checking.1264 # Do some error checking.
1257 max_index = -11265 max_index = -1
1258 for version, tags in versioned_dict.stack:1266 for version, tags in versioned_dict.stack:
@@ -1279,7 +1287,10 @@
1279 # The field is not initially published.1287 # The field is not initially published.
1280 new_stack = (earliest_version, dict(published=False))1288 new_stack = (earliest_version, dict(published=False))
1281 most_recent_tags = new_stack[0][1]1289 most_recent_tags = new_stack[0][1]
1290 most_recent_mutator_tags = earliest_mutator
1282 for version in versions[1:]:1291 for version in versions[1:]:
1292 most_recent_mutator_tags = mutator_annotations.get(
1293 version, most_recent_mutator_tags)
1283 match = [(stack_version, stack_tags)1294 match = [(stack_version, stack_tags)
1284 for stack_version, stack_tags in stack1295 for stack_version, stack_tags in stack
1285 if stack_version == version]1296 if stack_version == version]
@@ -1293,6 +1304,12 @@
1293 new_stack.append(match[0])1304 new_stack.append(match[0])
1294 most_recent_tags = match[0][1]1305 most_recent_tags = match[0][1]
12951306
1307 # Install a (possibly inherited) mutator for this field in
1308 # this version.
1309 if most_recent_mutator_tags is not None:
1310 new_stack[-1][1]['mutator_annotations'] = copy.deepcopy(
1311 most_recent_mutator_tags)
1312
1296 versioned_dict.stack = new_stack1313 versioned_dict.stack = new_stack
1297 return field1314 return field
12981315
@@ -1319,34 +1336,3 @@
1319 version = "__Earliest"1336 version = "__Earliest"
1320 name = "%s_%s" % (base_name, version.encode('utf8'))1337 name = "%s_%s" % (base_name, version.encode('utf8'))
1321 return make_identifier_safe(name)1338 return make_identifier_safe(name)
1322
1323
1324def _get_by_version(dictionary, version, earliest_version,
1325 error_prefix, default=None):
1326 """Pull from a dictionary using `version` as the key.
1327
1328 Under normal circumstances, this will be an ordinary lookup. But
1329 some values for the earliest version are stored under None
1330 (because the name of the earliest version wasn't known at the
1331 time). This helper function knows to look again under None if
1332 a lookup for the earliest version fails.
1333
1334 It also knows to raise an exception signaling a conflict if a
1335 value is present under the earliest version *and* under None.
1336
1337 :param dictionary: The dictionary to use for the lookup.
1338 :param version: The dictionary key.
1339 :param earliest_version: The version that might also be filed under None.
1340 :param default: A default value to use if the lookup fails.
1341 """
1342 value = dictionary.get(version, None)
1343 if version == earliest_version:
1344 earliest_value = dictionary.get(None, None)
1345 if value is not None and earliest_value is not None:
1346 raise ValueError(
1347 error_prefix + " Both implicit and explicit definitions found "
1348 "for earliest version %s." % version)
1349 value = value or earliest_value
1350 if value is None:
1351 value = default
1352 return value
13531339
=== modified file 'src/lazr/restful/docs/webservice-declarations.txt'
--- src/lazr/restful/docs/webservice-declarations.txt 2010-02-05 14:30:30 +0000
+++ src/lazr/restful/docs/webservice-declarations.txt 2010-02-08 19:15:22 +0000
@@ -1808,16 +1808,6 @@
1808 AttributeError: 'MultiVersionEntryEntry_3_0Adapter' object has no1808 AttributeError: 'MultiVersionEntryEntry_3_0Adapter' object has no
1809 attribute 'new_in_10'1809 attribute 'new_in_10'
18101810
1811[XXX leonardr These tests need to be done in pagetests, since they
1812 test the behavior of the webservice rather than whether certain
1813 combinations of annotations are valid.
1814
1815 If a field has a mutator in version n, later versions will inherit the
1816 same mutator.
1817
1818 An entry can have different operations marked as the destructor for
1819 different versions.]
1820
1821Why the list of version strings?1811Why the list of version strings?
1822********************************1812********************************
18231813
@@ -2327,9 +2317,9 @@
2327 ... IImplicitAndExplicitMutator, 'beta', '1.0')2317 ... IImplicitAndExplicitMutator, 'beta', '1.0')
2328 Traceback (most recent call last):2318 Traceback (most recent call last):
2329 ...2319 ...
2330 ValueError: Duplicate mutator for interface2320 ValueError: Field "field" in interface
2331 "IImplicitAndExplicitMutator", field "field": Both implicit and2321 "IImplicitAndExplicitMutator": Both implicit and explicit mutator
2332 explicit definitions found for earliest version beta.2322 definitions found for earliest version beta.
23332323
2334This error isn't detected until you try to generate the entry2324This error isn't detected until you try to generate the entry
2335interfaces, because until that point lazr.restful doesn't know that2325interfaces, because until that point lazr.restful doesn't know that
23362326
=== modified file 'src/lazr/restful/example/multiversion/resources.py'
--- src/lazr/restful/example/multiversion/resources.py 2010-01-27 15:05:56 +0000
+++ src/lazr/restful/example/multiversion/resources.py 2010-02-08 19:15:22 +0000
@@ -6,26 +6,57 @@
6 'PairSet']6 'PairSet']
77
8from zope.interface import implements8from zope.interface import implements
9from zope.schema import Text9from zope.schema import Bool, Text
10from zope.location.interfaces import ILocation10from zope.location.interfaces import ILocation
1111
12from lazr.restful.declarations import (12from lazr.restful.declarations import (
13 collection_default_content, export_as_webservice_collection,13 collection_default_content, export_as_webservice_collection,
14 export_as_webservice_entry, export_operation_as,14 export_as_webservice_entry, export_destructor_operation,
15 export_read_operation, exported, operation_for_version,15 export_operation_as, export_read_operation, export_write_operation,
16 operation_parameters, operation_removed_in_version)16 exported, mutator_for, operation_for_version, operation_parameters,
17 operation_removed_in_version)
1718
18# Our implementations of these classes can be based on the19# Our implementations of these classes can be based on the
19# implementations from the WSGI example.20# implementations from the WSGI example.
20from lazr.restful.example.wsgi.resources import (21from lazr.restful.example.wsgi.resources import (
21 PairSet as BasicPairSet, KeyValuePair as BasicKeyValuePair)22 PairSet as BasicPairSet, KeyValuePair as BasicKeyValuePair)
2223
24
23# Our interfaces _will_ diverge from the WSGI example interfaces, so25# Our interfaces _will_ diverge from the WSGI example interfaces, so
24# define them separately.26# define them separately.
25class IKeyValuePair(ILocation):27class IKeyValuePair(ILocation):
26 export_as_webservice_entry()28 export_as_webservice_entry()
27 key = exported(Text(title=u"The key"))29 key = exported(Text(title=u"The key"))
28 value = exported(Text(title=u"The value"))30 value = exported(Text(title=u"The value"))
31 a_comment = exported(Text(title=u"A comment on this key-value pair.",
32 readonly=True),
33 ('1.0', dict(exported=True, exported_as='comment')))
34 deleted = exported(Bool(title=u"Whether this key-value pair has been "
35 "deleted"),
36 ('3.0', dict(exported=True)), exported=False)
37 @mutator_for(a_comment)
38 @export_write_operation()
39 @operation_parameters(comment=Text())
40 @operation_for_version('1.0')
41 def comment_mutator_1(comment):
42 """A comment mutator that adds some junk on the end."""
43
44 @mutator_for(a_comment)
45 @export_write_operation()
46 @operation_parameters(comment=Text())
47 @operation_for_version('3.0')
48 def comment_mutator_2(comment):
49 """A comment mutator that adds different junk on the end."""
50
51 @export_destructor_operation()
52 @operation_for_version('1.0')
53 def total_destruction():
54 """A destructor that removes the key-value pair altogether."""
55
56 @export_destructor_operation()
57 @operation_for_version('3.0')
58 def mark_as_deleted():
59 """A destructor that simply sets .deleted to True."""
2960
3061
31class IPairSet(ILocation):62class IPairSet(ILocation):
@@ -69,5 +100,27 @@
69 def getNonEmptyPairs(self):100 def getNonEmptyPairs(self):
70 return [pair for pair in self.pairs if pair.value is not None]101 return [pair for pair in self.pairs if pair.value is not None]
71102
103
72class KeyValuePair(BasicKeyValuePair):104class KeyValuePair(BasicKeyValuePair):
73 implements(IKeyValuePair)105 implements(IKeyValuePair)
106
107 def __init__(self, pairset, key, value):
108 super(KeyValuePair, self).__init__(pairset, key, value)
109 self.a_comment = ''
110 self.deleted = False
111
112 def comment_mutator_1(self, comment):
113 """A comment mutator."""
114 self.a_comment = comment + " (modified by mutator #1)"
115
116 def comment_mutator_2(self, comment):
117 """A comment mutator."""
118 self.a_comment = comment + " (modified by mutator #2)"
119
120 def total_destruction(self):
121 """Remove the pair from the pairset."""
122 self.set.pairs.remove(self)
123
124 def mark_as_deleted(self):
125 """Set .deleted to True."""
126 self.deleted = True
74127
=== modified file 'src/lazr/restful/example/multiversion/root.py'
--- src/lazr/restful/example/multiversion/root.py 2010-01-27 15:05:56 +0000
+++ src/lazr/restful/example/multiversion/root.py 2010-02-08 19:15:22 +0000
@@ -47,7 +47,9 @@
47 pairset.pairs = [47 pairset.pairs = [
48 KeyValuePair(pairset, "foo", "bar"),48 KeyValuePair(pairset, "foo", "bar"),
49 KeyValuePair(pairset, "1", "2"),49 KeyValuePair(pairset, "1", "2"),
50 KeyValuePair(pairset, "Some", None)50 KeyValuePair(pairset, "Some", None),
51 KeyValuePair(pairset, "Delete", "me"),
52 KeyValuePair(pairset, "Also delete", "me")
51 ]53 ]
52 collections = dict(pairs=(IKeyValuePair, pairset))54 collections = dict(pairs=(IKeyValuePair, pairset))
53 return collections, {}55 return collections, {}
5456
=== modified file 'src/lazr/restful/example/multiversion/tests/introduction.txt'
--- src/lazr/restful/example/multiversion/tests/introduction.txt 2010-01-27 15:05:56 +0000
+++ src/lazr/restful/example/multiversion/tests/introduction.txt 2010-02-08 19:15:22 +0000
@@ -68,8 +68,8 @@
68 HTTP/1.1 404 Not Found68 HTTP/1.1 404 Not Found
69 ...69 ...
7070
71Collections and entries71Collections
72=======================72===========
7373
74The web service presents a single collection of key-value pairs. In74The web service presents a single collection of key-value pairs. In
75versions previous to 2.0, the collection omits key-value pairs where75versions previous to 2.0, the collection omits key-value pairs where
@@ -83,25 +83,163 @@
8383
84 >>> show_pairs('beta')84 >>> show_pairs('beta')
85 1: 285 1: 2
86 Also delete: me
87 Delete: me
86 foo: bar88 foo: bar
8789
88 >>> show_pairs('1.0')90 >>> show_pairs('1.0')
89 1: 291 1: 2
92 Also delete: me
93 Delete: me
90 foo: bar94 foo: bar
9195
92 >>> show_pairs('2.0')96 >>> show_pairs('2.0')
93 1: 297 1: 2
98 Also delete: me
99 Delete: me
94 Some: None100 Some: None
95 foo: bar101 foo: bar
96102
97 >>> show_pairs('3.0')103 >>> show_pairs('3.0')
98 1: 2104 1: 2
105 Also delete: me
106 Delete: me
99 Some: None107 Some: None
100 foo: bar108 foo: bar
101109
102 >>> body = webservice.get('/pairs/foo').jsonBody()110Entries
103 >>> print body['key'], body['value']111=======
104 foo bar112
113Let's take a look at 'comment' and 'deleted', two fields with
114interesting properties.
115
116The 'comment' field is not modified directly, but by internal mutator
117methods which append some useless text to your comment.
118
119 >>> import simplejson
120 >>> def get_comment(version):
121 ... response = webservice.get("/pairs/foo", api_version=version)
122 ... return response.jsonBody()['comment']
123
124 >>> def change_comment(comment, version, get_comment_afterwards=True):
125 ... ignored = webservice.patch(
126 ... "/pairs/foo/", 'application/json',
127 ... simplejson.dumps({"comment": comment}),
128 ... api_version=version)
129 ... if get_comment_afterwards:
130 ... return get_comment(version)
131 ... return None
132
133 >>> get_comment('1.0')
134 u''
135 >>> print change_comment('I changed 1.0', '1.0')
136 I changed 1.0 (modified by mutator #1)
137
138 >>> print change_comment('I changed 2.0', '2.0')
139 I changed 2.0 (modified by mutator #1)
140
141 >>> print change_comment('I changed 3.0', '3.0')
142 I changed 3.0 (modified by mutator #2)
143
144You can try to modify the 'comment' field from a version that doesn't
145publish that field, but lazr.restful will ignore your request.
146
147 >>> change_comment('I changed beta', 'beta', False)
148
149 >>> print get_comment('1.0')
150 I changed 3.0 (modified by mutator #2)
151
152A field called 'deleted' is published starting in version '3.0'. A
153comment field is called 'a_comment' in version 'beta' and 'comment' in
154all later versions.
155
156 >>> def show_fields(version):
157 ... entry_body = webservice.get(
158 ... '/pairs/foo', api_version=version).jsonBody()
159 ... for key in sorted(entry_body.keys()):
160 ... print key
161
162 >>> show_fields('beta')
163 a_comment
164 http_etag
165 key
166 resource_type_link
167 self_link
168 value
169
170 >>> show_fields('1.0')
171 comment
172 http_etag
173 key
174 resource_type_link
175 self_link
176 value
177
178 >>> show_fields('3.0')
179 comment
180 deleted
181 http_etag
182 key
183 resource_type_link
184 self_link
185 value
186
187In the 'beta' version, attempting to delete a key-value pair will
188result in a status code of 405 ("Method Not Available").
189
190 >>> response = webservice.delete('/pairs/Delete', api_version='beta')
191 >>> response.status
192 405
193
194As of '1.0', attempting to delete a key-value pair results in the
195key-value pair being totally removed from the web service.
196
197 >>> ignore = webservice.delete('/pairs/Delete', api_version='1.0')
198 >>> show_pairs('beta')
199 1: 2
200 Also delete: me
201 foo: bar
202
203In '3.0', deleting a key-value pair simply sets its 'deleted' field
204to True. (This is an abuse of the HTTP DELETE method, but it makes a
205good demo.)
206
207 >>> body = webservice.get(
208 ... '/pairs/Also%20delete', api_version='3.0').jsonBody()
209 >>> body['deleted']
210 False
211
212 >>> ignore = webservice.delete('/pairs/Also%20delete', api_version='3.0')
213
214The "deleted" key-value pair is still visible in all versions:
215
216 >>> show_pairs('beta')
217 1: 2
218 Also delete: me
219 foo: bar
220
221And in a version which publishes the 'delete' field, we can check the
222key-value pair's value for that field.
223
224 >>> body = webservice.get(
225 ... '/pairs/Also%20delete', api_version='3.0').jsonBody()
226 >>> body['deleted']
227 True
228
229Fields
230======
231
232If an entry field is not published in a certain version, the
233corresponding field resource does not exist for that version.
234
235 >>> webservice.get('/pairs/foo/deleted', api_version='beta')
236 Traceback (most recent call last):
237 ...
238 NotFound: ... name: u'deleted'
239
240 >>> print webservice.get(
241 ... '/pairs/foo/deleted', api_version='3.0').jsonBody()
242 False
105243
106Named operations244Named operations
107================245================

Subscribers

People subscribed via source and target branches