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 | 87 | Every version must have a corresponding subclass of | 87 | Every version must have a corresponding subclass of |
6 | 88 | IWebServiceClientRequest. Each interface class is registered as a | 88 | IWebServiceClientRequest. Each interface class is registered as a |
7 | 89 | named utility implementing IWebServiceVersion. For instance, in the | 89 | named utility implementing IWebServiceVersion. For instance, in the |
9 | 90 | example below, the IWebServiceRequest10 class will be registered as | 90 | example below, the I10Version class will be registered as |
10 | 91 | the IWebServiceVersion utility with the name "1.0". | 91 | the IWebServiceVersion utility with the name "1.0". |
11 | 92 | 92 | ||
12 | 93 | When a request comes in, lazr.restful figures out which version the | 93 | When a request comes in, lazr.restful figures out which version the |
13 | @@ -99,18 +99,18 @@ | |||
14 | 99 | registered automatically. | 99 | registered automatically. |
15 | 100 | 100 | ||
16 | 101 | >>> from lazr.restful.interfaces import IWebServiceClientRequest | 101 | >>> from lazr.restful.interfaces import IWebServiceClientRequest |
29 | 102 | >>> class IWebServiceRequestBeta(IWebServiceClientRequest): | 102 | >>> class IBetaVersion(IWebServiceClientRequest): |
30 | 103 | ... pass | 103 | ... pass |
31 | 104 | 104 | ||
32 | 105 | >>> class IWebServiceRequest10(IWebServiceClientRequest): | 105 | >>> class I10Version(IWebServiceClientRequest): |
33 | 106 | ... pass | 106 | ... pass |
34 | 107 | 107 | ||
35 | 108 | >>> class IWebServiceRequestDev(IWebServiceClientRequest): | 108 | >>> class IDevVersion(IWebServiceClientRequest): |
36 | 109 | ... pass | 109 | ... pass |
37 | 110 | 110 | ||
38 | 111 | >>> versions = ((IWebServiceRequestBeta, 'beta'), | 111 | >>> versions = ((IBetaVersion, 'beta'), |
39 | 112 | ... (IWebServiceRequest10, '1.0'), | 112 | ... (I10Version, '1.0'), |
40 | 113 | ... (IWebServiceRequestDev, 'dev')) | 113 | ... (IDevVersion, 'dev')) |
41 | 114 | 114 | ||
42 | 115 | >>> from lazr.restful import register_versioned_request_utility | 115 | >>> from lazr.restful import register_versioned_request_utility |
43 | 116 | >>> for cls, version in versions: | 116 | >>> for cls, version in versions: |
44 | @@ -142,6 +142,9 @@ | |||
45 | 142 | ... def getAllContacts(): | 142 | ... def getAllContacts(): |
46 | 143 | ... "Get all contacts." | 143 | ... "Get all contacts." |
47 | 144 | ... | 144 | ... |
48 | 145 | ... def getContactsWithPhone(): | ||
49 | 146 | ... "Get all contacts that have a phone number." | ||
50 | 147 | ... | ||
51 | 145 | ... def findContacts(self, string, search_fax): | 148 | ... def findContacts(self, string, search_fax): |
52 | 146 | ... """Find contacts by name, phone number, or fax number.""" | 149 | ... """Find contacts by name, phone number, or fax number.""" |
53 | 147 | 150 | ||
54 | @@ -168,6 +171,7 @@ | |||
55 | 168 | 171 | ||
56 | 169 | Here's a simple ContactSet with a predefined list of contacts. | 172 | Here's a simple ContactSet with a predefined list of contacts. |
57 | 170 | 173 | ||
58 | 174 | >>> from operator import attrgetter | ||
59 | 171 | >>> from zope.publisher.interfaces.browser import IBrowserRequest | 175 | >>> from zope.publisher.interfaces.browser import IBrowserRequest |
60 | 172 | >>> from lazr.restful.interfaces import IServiceRootResource | 176 | >>> from lazr.restful.interfaces import IServiceRootResource |
61 | 173 | >>> from lazr.restful.simple import TraverseWithGet | 177 | >>> from lazr.restful.simple import TraverseWithGet |
62 | @@ -188,10 +192,15 @@ | |||
63 | 188 | ... def getAllContacts(self): | 192 | ... def getAllContacts(self): |
64 | 189 | ... return self.contacts | 193 | ... return self.contacts |
65 | 190 | ... | 194 | ... |
66 | 195 | ... def getContactsWithPhone(self): | ||
67 | 196 | ... return [contact for contact in self.contacts | ||
68 | 197 | ... if contact.phone is not None] | ||
69 | 198 | ... | ||
70 | 191 | ... def findContacts(self, string, search_fax=True): | 199 | ... def findContacts(self, string, search_fax=True): |
71 | 192 | ... return [contact for contact in self.contacts | 200 | ... return [contact for contact in self.contacts |
72 | 193 | ... if (string in contact.name | 201 | ... if (string in contact.name |
74 | 194 | ... or string in contact.phone | 202 | ... or (contact.phone is not None |
75 | 203 | ... and string in contact.phone) | ||
76 | 195 | ... or (search_fax and string in contact.fax))] | 204 | ... or (search_fax and string in contact.fax))] |
77 | 196 | ... | 205 | ... |
78 | 197 | ... @property | 206 | ... @property |
79 | @@ -214,7 +223,8 @@ | |||
80 | 214 | 223 | ||
81 | 215 | >>> C1 = Contact("Cleo Python", "555-1212", "111-2121") | 224 | >>> C1 = Contact("Cleo Python", "555-1212", "111-2121") |
82 | 216 | >>> C2 = Contact("Oliver Bluth", "10-1000000", "22-2222222") | 225 | >>> C2 = Contact("Oliver Bluth", "10-1000000", "22-2222222") |
84 | 217 | >>> CONTACTS = [C1, C2] | 226 | >>> C3 = Contact("Fax-your-order Pizza", None, "100-200-300") |
85 | 227 | >>> CONTACTS = [C1, C2, C3] | ||
86 | 218 | 228 | ||
87 | 219 | Defining the web service data model | 229 | Defining the web service data model |
88 | 220 | =================================== | 230 | =================================== |
89 | @@ -289,7 +299,7 @@ | |||
90 | 289 | ... self.context = context | 299 | ... self.context = context |
91 | 290 | 300 | ||
92 | 291 | >>> sm.registerAdapter( | 301 | >>> sm.registerAdapter( |
94 | 292 | ... ContactEntryBeta, [IContact, IWebServiceRequestBeta], | 302 | ... ContactEntryBeta, [IContact, IBetaVersion], |
95 | 293 | ... provided=IContactEntry) | 303 | ... provided=IContactEntry) |
96 | 294 | 304 | ||
97 | 295 | By wrapping one of our predefined Contacts in a ContactEntryBeta | 305 | By wrapping one of our predefined Contacts in a ContactEntryBeta |
98 | @@ -320,7 +330,7 @@ | |||
99 | 320 | ... def fax_number(self): | 330 | ... def fax_number(self): |
100 | 321 | ... return self.context.fax | 331 | ... return self.context.fax |
101 | 322 | >>> sm.registerAdapter( | 332 | >>> sm.registerAdapter( |
103 | 323 | ... ContactEntry10, [IContact, IWebServiceRequest10], | 333 | ... ContactEntry10, [IContact, I10Version], |
104 | 324 | ... provided=IContactEntry) | 334 | ... provided=IContactEntry) |
105 | 325 | 335 | ||
106 | 326 | >>> entry = ContactEntry10(C1, None) | 336 | >>> entry = ContactEntry10(C1, None) |
107 | @@ -343,7 +353,7 @@ | |||
108 | 343 | ... def phone_number(self): | 353 | ... def phone_number(self): |
109 | 344 | ... return self.context.phone | 354 | ... return self.context.phone |
110 | 345 | >>> sm.registerAdapter( | 355 | >>> sm.registerAdapter( |
112 | 346 | ... ContactEntryDev, [IContact, IWebServiceRequestDev], | 356 | ... ContactEntryDev, [IContact, IDevVersion], |
113 | 347 | ... provided=IContactEntry) | 357 | ... provided=IContactEntry) |
114 | 348 | 358 | ||
115 | 349 | >>> entry = ContactEntryDev(C1, None) | 359 | >>> entry = ContactEntryDev(C1, None) |
116 | @@ -378,19 +388,19 @@ | |||
117 | 378 | 388 | ||
118 | 379 | >>> from zope.component import getMultiAdapter | 389 | >>> from zope.component import getMultiAdapter |
119 | 380 | >>> request_beta = create_web_service_request('/beta/') | 390 | >>> request_beta = create_web_service_request('/beta/') |
121 | 381 | >>> alsoProvides(request_beta, IWebServiceRequestBeta) | 391 | >>> alsoProvides(request_beta, IBetaVersion) |
122 | 382 | >>> beta_entry = getMultiAdapter((C1, request_beta), IEntry) | 392 | >>> beta_entry = getMultiAdapter((C1, request_beta), IEntry) |
123 | 383 | >>> print beta_entry.fax | 393 | >>> print beta_entry.fax |
124 | 384 | 111-2121 | 394 | 111-2121 |
125 | 385 | 395 | ||
126 | 386 | >>> request_10 = create_web_service_request('/1.0/') | 396 | >>> request_10 = create_web_service_request('/1.0/') |
128 | 387 | >>> alsoProvides(request_10, IWebServiceRequest10) | 397 | >>> alsoProvides(request_10, I10Version) |
129 | 388 | >>> one_oh_entry = getMultiAdapter((C1, request_10), IEntry) | 398 | >>> one_oh_entry = getMultiAdapter((C1, request_10), IEntry) |
130 | 389 | >>> print one_oh_entry.fax_number | 399 | >>> print one_oh_entry.fax_number |
131 | 390 | 111-2121 | 400 | 111-2121 |
132 | 391 | 401 | ||
133 | 392 | >>> request_dev = create_web_service_request('/dev/') | 402 | >>> request_dev = create_web_service_request('/dev/') |
135 | 393 | >>> alsoProvides(request_dev, IWebServiceRequestDev) | 403 | >>> alsoProvides(request_dev, IDevVersion) |
136 | 394 | >>> dev_entry = getMultiAdapter((C1, request_dev), IEntry) | 404 | >>> dev_entry = getMultiAdapter((C1, request_dev), IEntry) |
137 | 395 | >>> print dev_entry.fax | 405 | >>> print dev_entry.fax |
138 | 396 | Traceback (most recent call last): | 406 | Traceback (most recent call last): |
139 | @@ -402,16 +412,20 @@ | |||
140 | 402 | 412 | ||
141 | 403 | The set of contacts publishes a slightly different named operation in | 413 | The set of contacts publishes a slightly different named operation in |
142 | 404 | every version of the web service, so in a little bit we'll be | 414 | every version of the web service, so in a little bit we'll be |
148 | 405 | implementing three different versions of the same named operation. But | 415 | implementing three different versions of the same named operation. The |
149 | 406 | the contact set itself doesn't change between versions (although it | 416 | contact set itself also changes between versions. In the 'beta' and |
150 | 407 | could). So it's sufficient to implement one ICollection implementation | 417 | '1.0' versions, the contact set serves all contcts. In the 'dev' |
151 | 408 | and register it as the implementation for every version of the web | 418 | version, the contact set omits contacts that only have a fax |
152 | 409 | service. | 419 | number. We'll implement this behavior by implementing ICollection |
153 | 420 | twice and registering each implementation for the appropriate versions | ||
154 | 421 | of the web service. | ||
155 | 410 | 422 | ||
156 | 411 | >>> from lazr.restful import Collection | 423 | >>> from lazr.restful import Collection |
157 | 412 | >>> from lazr.restful.interfaces import ICollection | 424 | >>> from lazr.restful.interfaces import ICollection |
158 | 413 | 425 | ||
160 | 414 | >>> class ContactCollection(Collection): | 426 | First we'll implement the version used in 'beta' and '1.0'. |
161 | 427 | |||
162 | 428 | >>> class ContactCollectionBeta(Collection): | ||
163 | 415 | ... """A collection of contacts, exposed through the web service.""" | 429 | ... """A collection of contacts, exposed through the web service.""" |
164 | 416 | ... adapts(IContactSet) | 430 | ... adapts(IContactSet) |
165 | 417 | ... | 431 | ... |
166 | @@ -425,24 +439,55 @@ | |||
167 | 425 | 439 | ||
168 | 426 | >>> from zope.interface.verify import verifyObject | 440 | >>> from zope.interface.verify import verifyObject |
169 | 427 | >>> contact_set = ContactSet() | 441 | >>> contact_set = ContactSet() |
171 | 428 | >>> verifyObject(ICollection, ContactCollection(contact_set, None)) | 442 | >>> verifyObject(ICollection, ContactCollectionBeta(contact_set, None)) |
172 | 429 | True | 443 | True |
173 | 430 | 444 | ||
178 | 431 | Register it as the ICollection adapter for IContactSet. We use a | 445 | Register it as the ICollection adapter for IContactSet in |
179 | 432 | generic request interface (IWebServiceClientRequest) rather than a | 446 | IBetaVersion and I10Version. |
176 | 433 | specific one like IWebServiceRequestBeta, so that the same | ||
177 | 434 | implementation will be used for every version of the web service. | ||
180 | 435 | 447 | ||
184 | 436 | >>> sm.registerAdapter( | 448 | >>> for version in [IBetaVersion, I10Version]: |
185 | 437 | ... ContactCollection, [IContactSet, IWebServiceClientRequest], | 449 | ... sm.registerAdapter( |
186 | 438 | ... provided=ICollection) | 450 | ... ContactCollectionBeta, [IContactSet, version], |
187 | 451 | ... provided=ICollection) | ||
188 | 439 | 452 | ||
189 | 440 | Make sure the functionality works properly. | 453 | Make sure the functionality works properly. |
190 | 441 | 454 | ||
191 | 442 | >>> collection = getMultiAdapter( | 455 | >>> collection = getMultiAdapter( |
192 | 443 | ... (contact_set, request_beta), ICollection) | 456 | ... (contact_set, request_beta), ICollection) |
193 | 444 | >>> len(collection.find()) | 457 | >>> len(collection.find()) |
195 | 445 | 2 | 458 | 3 |
196 | 459 | |||
197 | 460 | >>> collection = getMultiAdapter( | ||
198 | 461 | ... (contact_set, request_10), ICollection) | ||
199 | 462 | >>> len(collection.find()) | ||
200 | 463 | 3 | ||
201 | 464 | |||
202 | 465 | Now let's implement the different version used in 'dev'. | ||
203 | 466 | |||
204 | 467 | >>> class ContactCollectionDev(ContactCollectionBeta): | ||
205 | 468 | ... def find(self): | ||
206 | 469 | ... """Find all the contacts, sorted by name.""" | ||
207 | 470 | ... return self.context.getContactsWithPhone() | ||
208 | 471 | |||
209 | 472 | This class also implements ICollection. | ||
210 | 473 | |||
211 | 474 | >>> verifyObject(ICollection, ContactCollectionDev(contact_set, None)) | ||
212 | 475 | True | ||
213 | 476 | |||
214 | 477 | Register it as the ICollection adapter for IContactSet in | ||
215 | 478 | IDevVersion. | ||
216 | 479 | |||
217 | 480 | >>> sm.registerAdapter( | ||
218 | 481 | ... ContactCollectionDev, [IContactSet, IDevVersion], | ||
219 | 482 | ... provided=ICollection) | ||
220 | 483 | |||
221 | 484 | Make sure the functionality works properly. Note that the contact that | ||
222 | 485 | only has a fax number no longer shows up. | ||
223 | 486 | |||
224 | 487 | >>> collection = getMultiAdapter( | ||
225 | 488 | ... (contact_set, request_dev), ICollection) | ||
226 | 489 | >>> [contact.name for contact in collection.find()] | ||
227 | 490 | ['Cleo Python', 'Oliver Bluth'] | ||
228 | 446 | 491 | ||
229 | 447 | Implementing the named operations | 492 | Implementing the named operations |
230 | 448 | --------------------------------- | 493 | --------------------------------- |
231 | @@ -479,11 +524,11 @@ | |||
232 | 479 | 'beta' service, and the 'find' operation in the '1.0' service. | 524 | 'beta' service, and the 'find' operation in the '1.0' service. |
233 | 480 | 525 | ||
234 | 481 | >>> sm.registerAdapter( | 526 | >>> sm.registerAdapter( |
236 | 482 | ... FindContactsOperationBase, [IContactSet, IWebServiceRequestBeta], | 527 | ... FindContactsOperationBase, [IContactSet, IBetaVersion], |
237 | 483 | ... provided=IResourceGETOperation, name="findContacts") | 528 | ... provided=IResourceGETOperation, name="findContacts") |
238 | 484 | 529 | ||
239 | 485 | >>> sm.registerAdapter( | 530 | >>> sm.registerAdapter( |
241 | 486 | ... FindContactsOperationBase, [IContactSet, IWebServiceRequest10], | 531 | ... FindContactsOperationBase, [IContactSet, I10Version], |
242 | 487 | ... provided=IResourceGETOperation, name="find") | 532 | ... provided=IResourceGETOperation, name="find") |
243 | 488 | 533 | ||
244 | 489 | Here's the slightly different named operation as implemented in | 534 | Here's the slightly different named operation as implemented in |
245 | @@ -500,7 +545,7 @@ | |||
246 | 500 | ... return str(e) | 545 | ... return str(e) |
247 | 501 | 546 | ||
248 | 502 | >>> sm.registerAdapter( | 547 | >>> sm.registerAdapter( |
250 | 503 | ... FindContactsOperationNoFax, [IContactSet, IWebServiceRequestDev], | 548 | ... FindContactsOperationNoFax, [IContactSet, IDevVersion], |
251 | 504 | ... provided=IResourceGETOperation, name="find") | 549 | ... provided=IResourceGETOperation, name="find") |
252 | 505 | 550 | ||
253 | 506 | The service root resource | 551 | The service root resource |
254 | @@ -573,14 +618,14 @@ | |||
255 | 573 | ... create_web_service_request) | 618 | ... create_web_service_request) |
256 | 574 | 619 | ||
257 | 575 | >>> request_beta = create_web_service_request('/beta/') | 620 | >>> request_beta = create_web_service_request('/beta/') |
259 | 576 | >>> IWebServiceRequestBeta.providedBy(request_beta) | 621 | >>> IBetaVersion.providedBy(request_beta) |
260 | 577 | False | 622 | False |
261 | 578 | 623 | ||
262 | 579 | The traversal process associates the request with a particular version. | 624 | The traversal process associates the request with a particular version. |
263 | 580 | 625 | ||
264 | 581 | >>> request_beta.traverse(None) | 626 | >>> request_beta.traverse(None) |
265 | 582 | <BetaServiceRootResource object ...> | 627 | <BetaServiceRootResource object ...> |
267 | 583 | >>> IWebServiceRequestBeta.providedBy(request_beta) | 628 | >>> IBetaVersion.providedBy(request_beta) |
268 | 584 | True | 629 | True |
269 | 585 | >>> print request_beta.version | 630 | >>> print request_beta.version |
270 | 586 | beta | 631 | beta |
271 | @@ -620,11 +665,12 @@ | |||
272 | 620 | 665 | ||
273 | 621 | >>> body = simplejson.loads(resource()) | 666 | >>> body = simplejson.loads(resource()) |
274 | 622 | >>> body['total_size'] | 667 | >>> body['total_size'] |
276 | 623 | 2 | 668 | 3 |
277 | 624 | >>> for link in sorted( | 669 | >>> for link in sorted( |
278 | 625 | ... [contact['self_link'] for contact in body['entries']]): | 670 | ... [contact['self_link'] for contact in body['entries']]): |
279 | 626 | ... print link | 671 | ... print link |
280 | 627 | http://api.multiversion.dev/beta/contact_list/Cleo%20Python | 672 | http://api.multiversion.dev/beta/contact_list/Cleo%20Python |
281 | 673 | http://api.multiversion.dev/beta/contact_list/Fax-your-order%20Pizza | ||
282 | 628 | http://api.multiversion.dev/beta/contact_list/Oliver%20Bluth | 674 | http://api.multiversion.dev/beta/contact_list/Oliver%20Bluth |
283 | 629 | 675 | ||
284 | 630 | We can traverse through the collection to an entry. | 676 | We can traverse through the collection to an entry. |
285 | @@ -713,11 +759,12 @@ | |||
286 | 713 | 759 | ||
287 | 714 | >>> body = simplejson.loads(resource()) | 760 | >>> body = simplejson.loads(resource()) |
288 | 715 | >>> body['total_size'] | 761 | >>> body['total_size'] |
290 | 716 | 2 | 762 | 3 |
291 | 717 | >>> for link in sorted( | 763 | >>> for link in sorted( |
292 | 718 | ... [contact['self_link'] for contact in body['entries']]): | 764 | ... [contact['self_link'] for contact in body['entries']]): |
293 | 719 | ... print link | 765 | ... print link |
294 | 720 | http://api.multiversion.dev/1.0/contacts/Cleo%20Python | 766 | http://api.multiversion.dev/1.0/contacts/Cleo%20Python |
295 | 767 | http://api.multiversion.dev/1.0/contacts/Fax-your-order%20Pizza | ||
296 | 721 | http://api.multiversion.dev/1.0/contacts/Oliver%20Bluth | 768 | http://api.multiversion.dev/1.0/contacts/Oliver%20Bluth |
297 | 722 | 769 | ||
298 | 723 | We can traverse through the collection to an entry. | 770 | 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.