Merge lp:~leonardr/lazr.restful/test-multiversion-collection into lp:lazr.restful
- test-multiversion-collection
- Merge into trunk
Proposed by
Leonard Richardson
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~leonardr/lazr.restful/test-multiversion-collection |
Merge into: | lp:lazr.restful |
Diff against target: |
298 lines (+90/-43) 1 file modified
src/lazr/restful/docs/multiversion.txt (+90/-43) |
To merge this branch: | bzr merge lp:~leonardr/lazr.restful/test-multiversion-collection |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Review via email: mp+18089@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote : | # |
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/docs/multiversion.txt' |
2 | --- src/lazr/restful/docs/multiversion.txt 2010-01-12 15:06:15 +0000 |
3 | +++ src/lazr/restful/docs/multiversion.txt 2010-01-26 17:10:25 +0000 |
4 | @@ -87,7 +87,7 @@ |
5 | Every version must have a corresponding subclass of |
6 | IWebServiceClientRequest. Each interface class is registered as a |
7 | named utility implementing IWebServiceVersion. For instance, in the |
8 | -example below, the IWebServiceRequest10 class will be registered as |
9 | +example below, the I10Version class will be registered as |
10 | the IWebServiceVersion utility with the name "1.0". |
11 | |
12 | When a request comes in, lazr.restful figures out which version the |
13 | @@ -99,18 +99,18 @@ |
14 | registered automatically. |
15 | |
16 | >>> from lazr.restful.interfaces import IWebServiceClientRequest |
17 | - >>> class IWebServiceRequestBeta(IWebServiceClientRequest): |
18 | - ... pass |
19 | - |
20 | - >>> class IWebServiceRequest10(IWebServiceClientRequest): |
21 | - ... pass |
22 | - |
23 | - >>> class IWebServiceRequestDev(IWebServiceClientRequest): |
24 | - ... pass |
25 | - |
26 | - >>> versions = ((IWebServiceRequestBeta, 'beta'), |
27 | - ... (IWebServiceRequest10, '1.0'), |
28 | - ... (IWebServiceRequestDev, 'dev')) |
29 | + >>> class IBetaVersion(IWebServiceClientRequest): |
30 | + ... pass |
31 | + |
32 | + >>> class I10Version(IWebServiceClientRequest): |
33 | + ... pass |
34 | + |
35 | + >>> class IDevVersion(IWebServiceClientRequest): |
36 | + ... pass |
37 | + |
38 | + >>> versions = ((IBetaVersion, 'beta'), |
39 | + ... (I10Version, '1.0'), |
40 | + ... (IDevVersion, 'dev')) |
41 | |
42 | >>> from lazr.restful import register_versioned_request_utility |
43 | >>> for cls, version in versions: |
44 | @@ -142,6 +142,9 @@ |
45 | ... def getAllContacts(): |
46 | ... "Get all contacts." |
47 | ... |
48 | + ... def getContactsWithPhone(): |
49 | + ... "Get all contacts that have a phone number." |
50 | + ... |
51 | ... def findContacts(self, string, search_fax): |
52 | ... """Find contacts by name, phone number, or fax number.""" |
53 | |
54 | @@ -168,6 +171,7 @@ |
55 | |
56 | Here's a simple ContactSet with a predefined list of contacts. |
57 | |
58 | + >>> from operator import attrgetter |
59 | >>> from zope.publisher.interfaces.browser import IBrowserRequest |
60 | >>> from lazr.restful.interfaces import IServiceRootResource |
61 | >>> from lazr.restful.simple import TraverseWithGet |
62 | @@ -188,10 +192,15 @@ |
63 | ... def getAllContacts(self): |
64 | ... return self.contacts |
65 | ... |
66 | + ... def getContactsWithPhone(self): |
67 | + ... return [contact for contact in self.contacts |
68 | + ... if contact.phone is not None] |
69 | + ... |
70 | ... def findContacts(self, string, search_fax=True): |
71 | ... return [contact for contact in self.contacts |
72 | ... if (string in contact.name |
73 | - ... or string in contact.phone |
74 | + ... or (contact.phone is not None |
75 | + ... and string in contact.phone) |
76 | ... or (search_fax and string in contact.fax))] |
77 | ... |
78 | ... @property |
79 | @@ -214,7 +223,8 @@ |
80 | |
81 | >>> C1 = Contact("Cleo Python", "555-1212", "111-2121") |
82 | >>> C2 = Contact("Oliver Bluth", "10-1000000", "22-2222222") |
83 | - >>> CONTACTS = [C1, C2] |
84 | + >>> C3 = Contact("Fax-your-order Pizza", None, "100-200-300") |
85 | + >>> CONTACTS = [C1, C2, C3] |
86 | |
87 | Defining the web service data model |
88 | =================================== |
89 | @@ -289,7 +299,7 @@ |
90 | ... self.context = context |
91 | |
92 | >>> sm.registerAdapter( |
93 | - ... ContactEntryBeta, [IContact, IWebServiceRequestBeta], |
94 | + ... ContactEntryBeta, [IContact, IBetaVersion], |
95 | ... provided=IContactEntry) |
96 | |
97 | By wrapping one of our predefined Contacts in a ContactEntryBeta |
98 | @@ -320,7 +330,7 @@ |
99 | ... def fax_number(self): |
100 | ... return self.context.fax |
101 | >>> sm.registerAdapter( |
102 | - ... ContactEntry10, [IContact, IWebServiceRequest10], |
103 | + ... ContactEntry10, [IContact, I10Version], |
104 | ... provided=IContactEntry) |
105 | |
106 | >>> entry = ContactEntry10(C1, None) |
107 | @@ -343,7 +353,7 @@ |
108 | ... def phone_number(self): |
109 | ... return self.context.phone |
110 | >>> sm.registerAdapter( |
111 | - ... ContactEntryDev, [IContact, IWebServiceRequestDev], |
112 | + ... ContactEntryDev, [IContact, IDevVersion], |
113 | ... provided=IContactEntry) |
114 | |
115 | >>> entry = ContactEntryDev(C1, None) |
116 | @@ -378,19 +388,19 @@ |
117 | |
118 | >>> from zope.component import getMultiAdapter |
119 | >>> request_beta = create_web_service_request('/beta/') |
120 | - >>> alsoProvides(request_beta, IWebServiceRequestBeta) |
121 | + >>> alsoProvides(request_beta, IBetaVersion) |
122 | >>> beta_entry = getMultiAdapter((C1, request_beta), IEntry) |
123 | >>> print beta_entry.fax |
124 | 111-2121 |
125 | |
126 | >>> request_10 = create_web_service_request('/1.0/') |
127 | - >>> alsoProvides(request_10, IWebServiceRequest10) |
128 | + >>> alsoProvides(request_10, I10Version) |
129 | >>> one_oh_entry = getMultiAdapter((C1, request_10), IEntry) |
130 | >>> print one_oh_entry.fax_number |
131 | 111-2121 |
132 | |
133 | >>> request_dev = create_web_service_request('/dev/') |
134 | - >>> alsoProvides(request_dev, IWebServiceRequestDev) |
135 | + >>> alsoProvides(request_dev, IDevVersion) |
136 | >>> dev_entry = getMultiAdapter((C1, request_dev), IEntry) |
137 | >>> print dev_entry.fax |
138 | Traceback (most recent call last): |
139 | @@ -402,16 +412,20 @@ |
140 | |
141 | The set of contacts publishes a slightly different named operation in |
142 | every version of the web service, so in a little bit we'll be |
143 | -implementing three different versions of the same named operation. But |
144 | -the contact set itself doesn't change between versions (although it |
145 | -could). So it's sufficient to implement one ICollection implementation |
146 | -and register it as the implementation for every version of the web |
147 | -service. |
148 | +implementing three different versions of the same named operation. The |
149 | +contact set itself also changes between versions. In the 'beta' and |
150 | +'1.0' versions, the contact set serves all contcts. In the 'dev' |
151 | +version, the contact set omits contacts that only have a fax |
152 | +number. We'll implement this behavior by implementing ICollection |
153 | +twice and registering each implementation for the appropriate versions |
154 | +of the web service. |
155 | |
156 | >>> from lazr.restful import Collection |
157 | >>> from lazr.restful.interfaces import ICollection |
158 | |
159 | - >>> class ContactCollection(Collection): |
160 | +First we'll implement the version used in 'beta' and '1.0'. |
161 | + |
162 | + >>> class ContactCollectionBeta(Collection): |
163 | ... """A collection of contacts, exposed through the web service.""" |
164 | ... adapts(IContactSet) |
165 | ... |
166 | @@ -425,24 +439,55 @@ |
167 | |
168 | >>> from zope.interface.verify import verifyObject |
169 | >>> contact_set = ContactSet() |
170 | - >>> verifyObject(ICollection, ContactCollection(contact_set, None)) |
171 | + >>> verifyObject(ICollection, ContactCollectionBeta(contact_set, None)) |
172 | True |
173 | |
174 | -Register it as the ICollection adapter for IContactSet. We use a |
175 | -generic request interface (IWebServiceClientRequest) rather than a |
176 | -specific one like IWebServiceRequestBeta, so that the same |
177 | -implementation will be used for every version of the web service. |
178 | +Register it as the ICollection adapter for IContactSet in |
179 | +IBetaVersion and I10Version. |
180 | |
181 | - >>> sm.registerAdapter( |
182 | - ... ContactCollection, [IContactSet, IWebServiceClientRequest], |
183 | - ... provided=ICollection) |
184 | + >>> for version in [IBetaVersion, I10Version]: |
185 | + ... sm.registerAdapter( |
186 | + ... ContactCollectionBeta, [IContactSet, version], |
187 | + ... provided=ICollection) |
188 | |
189 | Make sure the functionality works properly. |
190 | |
191 | >>> collection = getMultiAdapter( |
192 | ... (contact_set, request_beta), ICollection) |
193 | >>> len(collection.find()) |
194 | - 2 |
195 | + 3 |
196 | + |
197 | + >>> collection = getMultiAdapter( |
198 | + ... (contact_set, request_10), ICollection) |
199 | + >>> len(collection.find()) |
200 | + 3 |
201 | + |
202 | +Now let's implement the different version used in 'dev'. |
203 | + |
204 | + >>> class ContactCollectionDev(ContactCollectionBeta): |
205 | + ... def find(self): |
206 | + ... """Find all the contacts, sorted by name.""" |
207 | + ... return self.context.getContactsWithPhone() |
208 | + |
209 | +This class also implements ICollection. |
210 | + |
211 | + >>> verifyObject(ICollection, ContactCollectionDev(contact_set, None)) |
212 | + True |
213 | + |
214 | +Register it as the ICollection adapter for IContactSet in |
215 | +IDevVersion. |
216 | + |
217 | + >>> sm.registerAdapter( |
218 | + ... ContactCollectionDev, [IContactSet, IDevVersion], |
219 | + ... provided=ICollection) |
220 | + |
221 | +Make sure the functionality works properly. Note that the contact that |
222 | +only has a fax number no longer shows up. |
223 | + |
224 | + >>> collection = getMultiAdapter( |
225 | + ... (contact_set, request_dev), ICollection) |
226 | + >>> [contact.name for contact in collection.find()] |
227 | + ['Cleo Python', 'Oliver Bluth'] |
228 | |
229 | Implementing the named operations |
230 | --------------------------------- |
231 | @@ -479,11 +524,11 @@ |
232 | 'beta' service, and the 'find' operation in the '1.0' service. |
233 | |
234 | >>> sm.registerAdapter( |
235 | - ... FindContactsOperationBase, [IContactSet, IWebServiceRequestBeta], |
236 | + ... FindContactsOperationBase, [IContactSet, IBetaVersion], |
237 | ... provided=IResourceGETOperation, name="findContacts") |
238 | |
239 | >>> sm.registerAdapter( |
240 | - ... FindContactsOperationBase, [IContactSet, IWebServiceRequest10], |
241 | + ... FindContactsOperationBase, [IContactSet, I10Version], |
242 | ... provided=IResourceGETOperation, name="find") |
243 | |
244 | Here's the slightly different named operation as implemented in |
245 | @@ -500,7 +545,7 @@ |
246 | ... return str(e) |
247 | |
248 | >>> sm.registerAdapter( |
249 | - ... FindContactsOperationNoFax, [IContactSet, IWebServiceRequestDev], |
250 | + ... FindContactsOperationNoFax, [IContactSet, IDevVersion], |
251 | ... provided=IResourceGETOperation, name="find") |
252 | |
253 | The service root resource |
254 | @@ -573,14 +618,14 @@ |
255 | ... create_web_service_request) |
256 | |
257 | >>> request_beta = create_web_service_request('/beta/') |
258 | - >>> IWebServiceRequestBeta.providedBy(request_beta) |
259 | + >>> IBetaVersion.providedBy(request_beta) |
260 | False |
261 | |
262 | The traversal process associates the request with a particular version. |
263 | |
264 | >>> request_beta.traverse(None) |
265 | <BetaServiceRootResource object ...> |
266 | - >>> IWebServiceRequestBeta.providedBy(request_beta) |
267 | + >>> IBetaVersion.providedBy(request_beta) |
268 | True |
269 | >>> print request_beta.version |
270 | beta |
271 | @@ -620,11 +665,12 @@ |
272 | |
273 | >>> body = simplejson.loads(resource()) |
274 | >>> body['total_size'] |
275 | - 2 |
276 | + 3 |
277 | >>> for link in sorted( |
278 | ... [contact['self_link'] for contact in body['entries']]): |
279 | ... print link |
280 | http://api.multiversion.dev/beta/contact_list/Cleo%20Python |
281 | + http://api.multiversion.dev/beta/contact_list/Fax-your-order%20Pizza |
282 | http://api.multiversion.dev/beta/contact_list/Oliver%20Bluth |
283 | |
284 | We can traverse through the collection to an entry. |
285 | @@ -713,11 +759,12 @@ |
286 | |
287 | >>> body = simplejson.loads(resource()) |
288 | >>> body['total_size'] |
289 | - 2 |
290 | + 3 |
291 | >>> for link in sorted( |
292 | ... [contact['self_link'] for contact in body['entries']]): |
293 | ... print link |
294 | http://api.multiversion.dev/1.0/contacts/Cleo%20Python |
295 | + http://api.multiversion.dev/1.0/contacts/Fax-your-order%20Pizza |
296 | http://api.multiversion.dev/1.0/contacts/Oliver%20Bluth |
297 | |
298 | We can traverse through the collection to an entry. |
This branch changes the multiversion.txt doctest so that there are two different implementations of the 'contact' collection; one used in the 'beta' and '1.0' versions, one used in the 'dev' version. There is no new code; the test merely illustrates something that was already possible.
I also changed the overly long names of the marker interfaces used to designate different versions of the web service. IWebServiceRequ estBeta now becomes simply IBetaVersion, and so on.