Merge lp:~leonardr/lazr.restful/double-your-enjoyment into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Approved by: Edwin Grubbs
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/double-your-enjoyment
Merge into: lp:lazr.restful
Diff against target: 539 lines (+330/-55)
2 files modified
src/lazr/restful/docs/multiversion.txt (+292/-0)
src/lazr/restful/docs/webservice.txt (+38/-55)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/double-your-enjoyment
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+14967@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch creates a new doctest, multiversion.txt, which defines a web service that serves three distinct services ("beta", "1.0", and "dev") from a single underlying data model. Currently there is no code that uses this data model with lazr.restful, which is good because as soon as I add that code lazr.restful is going to explode. I'm submitting this branch as a natural stopping point before I have to start tearing up lazr.restful and putting it back together.

I'm using webservice.txt as a model for multiversion.txt. This is a little tricky because webservice.txt is a very old service that reimplements many of the niceties found in lazr.restful.simple. So I'm trying to improve webservice.txt as I copy it. For instance, I removed the Traversable class from webservice.txt and replaced it with the standard TraverseWithGet class. I also got rid of an XXX that said to make a certain change once we upgraded to zope 3.4.

I also tried to get rid of webservice.txt's BaseAbsoluteURL class, but it turned out to be way too much work. I'll just use ILocation in the new service and change webservice.txt later.

97. By Leonard Richardson

Fixed the prose.

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Leonard,

This branch looks good. I have some very minor comments below.

merge-approved

-Edwin

>=== added file 'src/lazr/restful/docs/multiversion.txt'
>+ >>> from lazr.restful.simple import TraverseWithGet
>+ >>> from zope.publisher.interfaces.browser import IBrowserRequest
>+ >>> class ContactSet(TraverseWithGet):
>+ ... implements(IContactSet)
>+ ... path = "contact"

Please fix the path variable as discussed on IRC.

>+ ...
>+ ... def __init__(self):
>+ ... self.contacts = CONTACTS
>+ ...
>+ ... def get(self, name):
>+ ... contacts = [contact for contacts in self.contacts
>+ ... if pair.name == name]
>+ ... if len(contacts) == 1:
>+ ... return contacts[0]
>+ ... return None
>+ >>> sm.registerAdapter(TraverseWithGet, [ITestDataObject, IBrowserRequest])

Line too long.

>=== modified file 'src/lazr/restful/docs/webservice.txt'
>@@ -417,16 +406,15 @@
> ... def getAllAuthors(self):
> ... return self.authors
> ...
>- ... def get(self, name):
>+ ... def get(self, request, name):
> ... match = [p for p in self.authors if p.name == name]
> ... if len(match) > 0:
> ... return match[0]
> ... return None
>
>+ >>> sm.registerAdapter(TraverseWithGet, [ITestDataObject, IBrowserRequest])

Line too long.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'src/lazr/restful/docs/multiversion.txt'
--- src/lazr/restful/docs/multiversion.txt 1970-01-01 00:00:00 +0000
+++ src/lazr/restful/docs/multiversion.txt 2009-11-18 16:46:09 +0000
@@ -0,0 +1,292 @@
1Publishing multiple versions of a web service
2*********************************************
3
4A single data model can yield many different lazr.restful web
5services. Typically these different services represent successive
6versions of a single web service, improved over time.
7
8Example model objects
9=====================
10
11First let's define the data model. The model in webservice.txt is
12pretty complicated; this model will be just complicated enough to
13illustrate how to publish multiple versions of a web service.
14
15 # All classes defined in this test are new-style classes.
16 >>> __metaclass__ = type
17
18 >>> from zope.interface import Interface, Attribute
19 >>> from zope.schema import Bool, Bytes, Int, Text, TextLine, Object
20
21 >>> class ITestDataObject(Interface):
22 ... """A marker interface for data objects."""
23 ... path = Attribute("The path portion of this object's URL. "
24 ... "Defined here for simplicity of testing.")
25
26 >>> class IContact(ITestDataObject):
27 ... name = TextLine(title=u"Name", required=True)
28 ... phone = TextLine(title=u"Phone number", required=True)
29 ... fax = TextLine(title=u"Fax number", required=False)
30
31Here's the interface for the 'set' object that manages the contacts.
32
33 >>> from lazr.restful.interfaces import ITraverseWithGet
34 >>> class IContactSet(ITestDataObject, ITraverseWithGet):
35 ... def getAll(self):
36 ... "Get all contacts."
37 ...
38 ... def get(self, name):
39 ... "Retrieve a single contact by name."
40
41Before we can define any classes, a bit of web service setup. Let's
42make all component lookups use the global site manager.
43
44 >>> from zope.component import getSiteManager
45 >>> sm = getSiteManager()
46
47 >>> from zope.component import adapter
48 >>> from zope.component.interfaces import IComponentLookup
49 >>> from zope.interface import implementer, Interface
50 >>> @implementer(IComponentLookup)
51 ... @adapter(Interface)
52 ... def everything_uses_the_global_site_manager(context):
53 ... return sm
54 >>> sm.registerAdapter(everything_uses_the_global_site_manager)
55
56Here's a simple implementation of IContact.
57
58 >>> from urllib import quote
59 >>> from zope.interface import implements
60 >>> from lazr.restful.security import protect_schema
61 >>> class Contact:
62 ... implements(IContact)
63 ... def __init__(self, name, phone, fax):
64 ... self.name = name
65 ... self.phone = phone
66 ... self.fax = fax
67 ...
68 ... @property
69 ... def path(self):
70 ... return 'contacts/' + quote(self.name)
71 >>> protect_schema(Contact, IContact)
72
73Here's a simple ContactSet with a predefined list of contacts.
74
75 >>> from lazr.restful.simple import TraverseWithGet
76 >>> from zope.publisher.interfaces.browser import IBrowserRequest
77 >>> class ContactSet(TraverseWithGet):
78 ... implements(IContactSet)
79 ... path = "contact"
80 ...
81 ... def __init__(self):
82 ... self.contacts = CONTACTS
83 ...
84 ... def get(self, name):
85 ... contacts = [contact for contacts in self.contacts
86 ... if pair.name == name]
87 ... if len(contacts) == 1:
88 ... return contacts[0]
89 ... return None
90 >>> sm.registerAdapter(TraverseWithGet, [ITestDataObject, IBrowserRequest])
91
92Here are the "model objects" themselves:
93
94 >>> C1 = Contact("Cleo Python", "555-1212", "111-2121")
95 >>> C2 = Contact("Oliver Bluth", "10-1000000", "22-2222222")
96 >>> CONTACTS = [C1, C2]
97
98Web service infrastructure initialization
99=========================================
100
101The lazr.restful package contains a set of default adapters and
102definitions to implement the web service.
103
104 >>> from zope.configuration import xmlconfig
105 >>> zcmlcontext = xmlconfig.string("""
106 ... <configure xmlns="http://namespaces.zope.org/zope">
107 ... <include package="lazr.restful" file="basic-site.zcml"/>
108 ... <utility
109 ... factory="lazr.restful.example.base.filemanager.FileManager" />
110 ... </configure>
111 ... """)
112
113A IWebServiceConfiguration utility is also expected to be defined which
114defines common configuration option for the webservice.
115
116 >>> from lazr.restful import directives
117 >>> from lazr.restful.interfaces import IWebServiceConfiguration
118 >>> from lazr.restful.simple import BaseWebServiceConfiguration
119 >>> from lazr.restful.testing.webservice import WebServiceTestPublication
120
121 >>> class WebServiceConfiguration(BaseWebServiceConfiguration):
122 ... use_https = False
123 ... active_versions = ['beta', '1.0']
124 ... latest_version_uri_prefix = 'dev'
125 ... code_revision = 'test'
126 ... max_batch_size = 100
127 ... directives.publication_class(WebServiceTestPublication)
128
129 >>> from grokcore.component.testing import grok_component
130 >>> ignore = grok_component(
131 ... 'WebServiceConfiguration', WebServiceConfiguration)
132
133 >>> from zope.component import getUtility
134 >>> webservice_configuration = getUtility(IWebServiceConfiguration)
135
136Defining the web service data model
137===================================
138
139We've defined an underlying data model (IContact), and now we're going
140to define the evolution of a web service through three versions, by
141defining three derivative data models. In a real application, these
142IEntry subclasses would be generated from lazr.restful decorators
143present in IContact but for testing purposes we're going to just
144define the three IEntry subclasses manually.
145
146The "beta" version of the web service publishes the IContact interface
147exactly as it is defined.
148
149 >>> from lazr.restful.interfaces import IEntry
150 >>> class IContactEntry(IEntry):
151 ... """Marker for a contact published through the web service."""
152
153 >>> from zope.interface import taggedValue
154 >>> from lazr.restful.interfaces import IEntry, LAZR_WEBSERVICE_NAME
155 >>> class IContactEntryBeta(IContactEntry, IContact):
156 ... """The part of an author we expose through the web service."""
157 ... taggedValue(LAZR_WEBSERVICE_NAME,
158 ... dict(singular="contact", plural="contacts"))
159
160The "1.0" version publishes the IContact interface as is, but renames
161two of the fields.
162
163 >>> class IContactEntry10(IContactEntry):
164 ... name = TextLine(title=u"Name", required=True)
165 ... phone_number = TextLine(title=u"Phone number", required=True)
166 ... fax_number = TextLine(title=u"Fax number", required=False)
167 ... taggedValue(LAZR_WEBSERVICE_NAME,
168 ... dict(singular="contact", plural="contacts"))
169
170The "dev" version drops the "fax_number" field because fax machines
171are obsolete.
172
173 >>> class IContactEntryDev(IContactEntry):
174 ... name = TextLine(title=u"Name", required=True)
175 ... phone_number = TextLine(title=u"Phone number", required=True)
176 ... taggedValue(LAZR_WEBSERVICE_NAME,
177 ... dict(singular="contact", plural="contacts"))
178
179Implementing the resources
180==========================
181
182The Contact class defined above implements the IContact interface, but
183IContact just describes the data model, not any particular web
184service. The IContactEntry subclasses above -- IContactEntryBeta,
185IContactEntry10, IContactEntryDev -- describe the three versions of
186the web service. Each of these interfaces must be implemented by a
187subclass of Entry.
188
189In a real application, the Entry subclasses would be generated from
190lazr.restful decorators present in IContact (just like the IEntry
191subclasses), but for testing purposes we're going to define the three
192Entry subclasses manually.
193
194 >>> from zope.component import adapts
195 >>> from zope.interface import implements
196 >>> from lazr.delegates import delegates
197 >>> from lazr.restful import Entry
198
199 >>> class ContactEntryBeta(Entry):
200 ... """A contact, as exposed through the 'beta' web service."""
201 ... adapts(IContact)
202 ... implements(IContactEntryBeta)
203 ... delegates(IContactEntryBeta)
204 ... schema = IContactEntryBeta
205 >>> sm.registerAdapter(
206 ... ContactEntryBeta, provided=IContactEntry, name="beta")
207
208By wrapping one of our predefined Contacts in a ContactEntryBeta
209object, we can verify that it implements IContactEntryBeta and
210IContactEntry.
211
212 >>> entry = ContactEntryBeta(C1)
213 >>> IContactEntry.validateInvariants(entry)
214 >>> IContactEntryBeta.validateInvariants(entry)
215
216Here's the implemenation of IContactEntry10, which defines Python
217properties to implement the different field names.
218
219 >>> class ContactEntry10(Entry):
220 ... adapts(IContact)
221 ... implements(IContactEntry10)
222 ... schema = IContactEntry10
223 ...
224 ... def __init__(self, contact):
225 ... self.contact = contact
226 ...
227 ... @property
228 ... def phone_number(self):
229 ... return self.contact.phone
230 ...
231 ... @property
232 ... def fax_number(self):
233 ... return self.contact.fax
234 >>> sm.registerAdapter(
235 ... ContactEntry10, provided=IContactEntry, name="1.0")
236
237 >>> entry = ContactEntry10(C1)
238 >>> IContactEntry.validateInvariants(entry)
239 >>> IContactEntry10.validateInvariants(entry)
240
241Finally, here's the implementation of IContactEntry for the "dev" version of
242the web service.
243
244 >>> class ContactEntryDev(Entry):
245 ... adapts(IContact)
246 ... implements(IContactEntryDev)
247 ... schema = IContactEntryDev
248 ...
249 ... def __init__(self, contact):
250 ... self.contact = contact
251 ...
252 ... @property
253 ... def phone_number(self):
254 ... return self.contact.phone
255 >>> sm.registerAdapter(
256 ... ContactEntryDev, provided=IContactEntry, name="dev")
257
258 >>> entry = ContactEntryDev(C1)
259 >>> IContactEntry.validateInvariants(entry)
260 >>> IContactEntryDev.validateInvariants(entry)
261
262Looking up the appropriate implementation
263=========================================
264
265Because there is no single IEntry implementation for Contact objects,
266you can't just adapt Contact to IEntry.
267
268 >>> from zope.component import getAdapter
269
270 >>> getAdapter(C1, IEntry)
271 Traceback (most recent call last):
272 ...
273 ComponentLookupError: ...
274
275When adapting Contact to IEntry you must specify a version number as
276the name of the adapter. The object you get back will implement the
277appropriate version of the web service.
278
279 >>> beta_entry = getAdapter(C1, IEntry, name="beta")
280 >>> print beta_entry.fax
281 111-2121
282
283 >>> one_oh_entry = getAdapter(C1, IEntry, name="1.0")
284 >>> print one_oh_entry.fax_number
285 111-2121
286
287 >>> dev_entry = getAdapter(C1, IEntry, name="dev")
288 >>> print dev_entry.fax
289 Traceback (most recent call last):
290 ...
291 AttributeError: 'ContactEntryDev' object has no attribute 'fax'
292
0293
=== modified file 'src/lazr/restful/docs/webservice.txt'
--- src/lazr/restful/docs/webservice.txt 2009-11-12 19:08:10 +0000
+++ src/lazr/restful/docs/webservice.txt 2009-11-18 16:46:09 +0000
@@ -74,28 +74,32 @@
74Here's the interface for the 'set' objects that manage the authors,74Here's the interface for the 'set' objects that manage the authors,
75cookbooks, and dishes. The inconsistent naming is intentional.75cookbooks, and dishes. The inconsistent naming is intentional.
7676
77 >>> class IAuthorSet(ITestDataObject):77 >>> from lazr.restful.interfaces import ITraverseWithGet
78 >>> class ITestDataSet(ITestDataObject, ITraverseWithGet):
79 ... """A marker interface."""
80
81 >>> class IAuthorSet(ITestDataSet):
78 ... def getAllAuthors(self):82 ... def getAllAuthors(self):
79 ... "Get all authors."83 ... "Get all authors."
80 ...84 ...
81 ... def get(self, name):85 ... def get(self, request, name):
82 ... "Retrieve a single author by name."86 ... "Retrieve a single author by name."
8387
84 >>> class ICookbookSet(ITestDataObject):88 >>> class ICookbookSet(ITestDataSet):
85 ... def getAll(self):89 ... def getAll(self):
86 ... "Get all cookbooks."90 ... "Get all cookbooks."
87 ...91 ...
88 ... def get(self, name):92 ... def get(self, request, name):
89 ... "Retrieve a single cookbook by name."93 ... "Retrieve a single cookbook by name."
90 ...94 ...
91 ... def findRecipes(self, name):95 ... def findRecipes(self, name):
92 ... "Find recipes with a given name, across cookbooks."96 ... "Find recipes with a given name, across cookbooks."
9397
94 >>> class IDishSet(ITestDataObject):98 >>> class IDishSet(ITestDataSet):
95 ... def getAll(self):99 ... def getAll(self):
96 ... "Get all dishes."100 ... "Get all dishes."
97 ...101 ...
98 ... def get(self, name):102 ... def get(self, request, name):
99 ... "Retrieve a single dish by name."103 ... "Retrieve a single dish by name."
100104
101105
@@ -143,23 +147,6 @@
143 ... BaseAbsoluteURL, [ITestDataObject, IBrowserRequest],147 ... BaseAbsoluteURL, [ITestDataObject, IBrowserRequest],
144 ... IAbsoluteURL)148 ... IAbsoluteURL)
145149
146 >>> from urllib import unquote
147 >>> class Traversable:
148 ... """A default IPublishTraverse that uses the get() method."""
149 ... implements(IPublishTraverse)
150 ... adapts(ITestDataObject, IBrowserRequest)
151 ...
152 ... def __init__(self, context, request):
153 ... self.context = context
154 ...
155 ... def publishTraverse(self, request, name):
156 ... name = unquote(name)
157 ... value = self.context.get(name)
158 ... if value is None:
159 ... raise NotFound(self, name)
160 ... return value
161 >>> sm.registerAdapter(Traversable)
162
163 >>> class Author:150 >>> class Author:
164 ... implements(IAuthor)151 ... implements(IAuthor)
165 ... def __init__(self, name):152 ... def __init__(self, name):
@@ -219,6 +206,7 @@
219206
220 >>> protect_schema(Cookbook, ICookbook, write_permission=CheckerPublic)207 >>> protect_schema(Cookbook, ICookbook, write_permission=CheckerPublic)
221208
209 >>> from urllib import unquote
222 >>> class CookbookTraversal:210 >>> class CookbookTraversal:
223 ... implements(IPublishTraverse)211 ... implements(IPublishTraverse)
224 ... adapts(ICookbook, IBrowserRequest)212 ... adapts(ICookbook, IBrowserRequest)
@@ -358,7 +346,8 @@
358346
359Here's a simple CookbookSet with a predefined list of cookbooks.347Here's a simple CookbookSet with a predefined list of cookbooks.
360348
361 >>> class CookbookSet(BaseAbsoluteURL):349 >>> from lazr.restful.simple import TraverseWithGet
350 >>> class CookbookSet(BaseAbsoluteURL, TraverseWithGet):
362 ... implements(ICookbookSet)351 ... implements(ICookbookSet)
363 ... path = 'cookbooks'352 ... path = 'cookbooks'
364 ...353 ...
@@ -367,7 +356,7 @@
367 ...356 ...
368 ... def newCookbook(self, author_name, title, cuisine):357 ... def newCookbook(self, author_name, title, cuisine):
369 ... authors = AuthorSet()358 ... authors = AuthorSet()
370 ... author = authors.get(author_name)359 ... author = authors.get(None, author_name)
371 ... if author is None:360 ... if author is None:
372 ... author = authors.newAuthor(author_name)361 ... author = authors.newAuthor(author_name)
373 ... cookbook = Cookbook(title, author, cuisine)362 ... cookbook = Cookbook(title, author, cuisine)
@@ -377,7 +366,7 @@
377 ... def getAll(self):366 ... def getAll(self):
378 ... return self.cookbooks367 ... return self.cookbooks
379 ...368 ...
380 ... def get(self, name):369 ... def get(self, request, name):
381 ... match = [c for c in self.cookbooks if c.name == name]370 ... match = [c for c in self.cookbooks if c.name == name]
382 ... if len(match) > 0:371 ... if len(match) > 0:
383 ... return match[0]372 ... return match[0]
@@ -402,7 +391,7 @@
402391
403Here's a simple AuthorSet with predefined authors.392Here's a simple AuthorSet with predefined authors.
404393
405 >>> class AuthorSet(BaseAbsoluteURL):394 >>> class AuthorSet(BaseAbsoluteURL, TraverseWithGet):
406 ... implements(IAuthorSet)395 ... implements(IAuthorSet)
407 ... path = 'authors'396 ... path = 'authors'
408 ...397 ...
@@ -417,16 +406,15 @@
417 ... def getAllAuthors(self):406 ... def getAllAuthors(self):
418 ... return self.authors407 ... return self.authors
419 ...408 ...
420 ... def get(self, name):409 ... def get(self, request, name):
421 ... match = [p for p in self.authors if p.name == name]410 ... match = [p for p in self.authors if p.name == name]
422 ... if len(match) > 0:411 ... if len(match) > 0:
423 ... return match[0]412 ... return match[0]
424 ... return None413 ... return None
425414
415 >>> sm.registerAdapter(TraverseWithGet, [ITestDataObject, IBrowserRequest])
426 >>> protect_schema(AuthorSet, IAuthorSet)416 >>> protect_schema(AuthorSet, IAuthorSet)
427 >>> sm.registerUtility(AuthorSet(), IAuthorSet)417 >>> sm.registerUtility(AuthorSet(), IAuthorSet)
428 >>> sm.registerAdapter(
429 ... Traversable, [IAuthorSet, IBrowserRequest])
430418
431Here's a vocabulary of authors, for a field that presents a Choice419Here's a vocabulary of authors, for a field that presents a Choice
432among authors.420among authors.
@@ -448,7 +436,7 @@
448436
449Finally, a simple DishSet with predefined dishes.437Finally, a simple DishSet with predefined dishes.
450438
451 >>> class DishSet(BaseAbsoluteURL):439 >>> class DishSet(BaseAbsoluteURL, TraverseWithGet):
452 ... implements(IDishSet)440 ... implements(IDishSet)
453 ... path = 'dishes'441 ... path = 'dishes'
454 ... def __init__(self):442 ... def __init__(self):
@@ -457,7 +445,7 @@
457 ... def getAll(self):445 ... def getAll(self):
458 ... return self.dishes446 ... return self.dishes
459 ...447 ...
460 ... def get(self, name):448 ... def get(self, request, name):
461 ... match = [d for d in self.dishes if d.name == name]449 ... match = [d for d in self.dishes if d.name == name]
462 ... if len(match) > 0:450 ... if len(match) > 0:
463 ... return match[0]451 ... return match[0]
@@ -546,27 +534,20 @@
546interface and ``IEntry``. Since ``IAuthor`` and ``IComment`` are so simple, we534interface and ``IEntry``. Since ``IAuthor`` and ``IComment`` are so simple, we
547can define ``IAuthorEntry`` and ``ICommentEntry`` very simply.535can define ``IAuthorEntry`` and ``ICommentEntry`` very simply.
548536
549 >>> from lazr.restful.interfaces import IEntry537The only extra and unusual step we have to take is to annotate the interfaces
538with human-readable names for the objects we're exposing.
539
540 >>> from zope.interface import taggedValue
541 >>> from lazr.restful.interfaces import IEntry, LAZR_WEBSERVICE_NAME
550 >>> class IAuthorEntry(IAuthor, IEntry):542 >>> class IAuthorEntry(IAuthor, IEntry):
551 ... """The part of an author we expose through the web service."""543 ... """The part of an author we expose through the web service."""
544 ... taggedValue(LAZR_WEBSERVICE_NAME, dict(singular="author",
545 ... plural="authors"))
552546
553 >>> class ICommentEntry(IComment, IEntry):547 >>> class ICommentEntry(IComment, IEntry):
554 ... """The part of a comment we expose through the web service."""548 ... """The part of a comment we expose through the web service."""
555549 ... taggedValue(LAZR_WEBSERVICE_NAME,
556The only extra and unusual step we have to take is to annotate the interfaces550 ... dict(singular="comment", plural="comments"))
557with human-readable names for the objects we're exposing.
558
559 >>> from lazr.restful.interfaces import LAZR_WEBSERVICE_NAME
560
561 # XXX leonardr 2008-07-09 bug=145746
562 # Once we're using Zope 3.4 we can call
563 # zope.interface.taggedValue(LAZR_WEBSERVICE_NAME, dict(...))
564 # from within the interface declaration.
565 >>> def tag_with_names(interface, singular, plural):
566 ... interface.setTaggedValue(
567 ... LAZR_WEBSERVICE_NAME, dict(singular=singular, plural=plural))
568 >>> tag_with_names(IAuthorEntry, 'author', 'authors')
569 >>> tag_with_names(ICommentEntry, 'comment', 'comments')
570551
571Most of the time, it doesn't work to expose to the web service the same data552Most of the time, it doesn't work to expose to the web service the same data
572model we expose internally. Usually there are fields we don't want to expose,553model we expose internally. Usually there are fields we don't want to expose,
@@ -586,7 +567,8 @@
586 >>> class IDishEntry(IEntry):567 >>> class IDishEntry(IEntry):
587 ... "The part of a dish that we expose through the web service."568 ... "The part of a dish that we expose through the web service."
588 ... recipes = CollectionField(value_type=Object(schema=IRecipe))569 ... recipes = CollectionField(value_type=Object(schema=IRecipe))
589 >>> tag_with_names(IDishEntry, 'dish', 'dishes')570 ... taggedValue(LAZR_WEBSERVICE_NAME,
571 ... dict(singular="dish", plural="dishes"))
590572
591In the following code block we define an interface that exposes the underlying573In the following code block we define an interface that exposes the underlying
592``Recipe``'s name but not its ID. References to associated objects (like the574``Recipe``'s name but not its ID. References to associated objects (like the
@@ -599,7 +581,8 @@
599 ... dish = Object(schema=IDish)581 ... dish = Object(schema=IDish)
600 ... instructions = Text(title=u"Name", required=True)582 ... instructions = Text(title=u"Name", required=True)
601 ... comments = CollectionField(value_type=Object(schema=IComment))583 ... comments = CollectionField(value_type=Object(schema=IComment))
602 >>> tag_with_names(IRecipeEntry, 'recipe', 'recipes')584 ... taggedValue(LAZR_WEBSERVICE_NAME,
585 ... dict(singular="recipe", plural="recipes"))
603586
604 >>> from lazr.restful.fields import ReferenceChoice587 >>> from lazr.restful.fields import ReferenceChoice
605 >>> class ICookbookEntry(IEntry):588 >>> class ICookbookEntry(IEntry):
@@ -610,7 +593,8 @@
610 ... recipes = CollectionField(value_type=Object(schema=IRecipe))593 ... recipes = CollectionField(value_type=Object(schema=IRecipe))
611 ... comments = CollectionField(value_type=Object(schema=IComment))594 ... comments = CollectionField(value_type=Object(schema=IComment))
612 ... cover = Bytes(0, 5000, title=u"An image of the cookbook's cover.")595 ... cover = Bytes(0, 5000, title=u"An image of the cookbook's cover.")
613 >>> tag_with_names(ICookbookEntry, 'cookbook', 'cookbooks')596 ... taggedValue(LAZR_WEBSERVICE_NAME,
597 ... dict(singular="cookbook", plural="cookbooks"))
614598
615The ``author`` field is a choice between ``Author`` objects. To make sure599The ``author`` field is a choice between ``Author`` objects. To make sure
616that the ``Author`` objects are properly marshalled to JSON, we need to600that the ``Author`` objects are properly marshalled to JSON, we need to
@@ -947,7 +931,7 @@
947 >>> from lazr.restful import ServiceRootResource931 >>> from lazr.restful import ServiceRootResource
948 >>> from zope.traversing.browser.interfaces import IAbsoluteURL932 >>> from zope.traversing.browser.interfaces import IAbsoluteURL
949933
950 >>> class MyServiceRootResource(ServiceRootResource):934 >>> class MyServiceRootResource(ServiceRootResource, TraverseWithGet):
951 ... implements(IAbsoluteURL)935 ... implements(IAbsoluteURL)
952 ... path = ''936 ... path = ''
953 ...937 ...
@@ -956,7 +940,7 @@
956 ... 'cookbooks': CookbookSet(),940 ... 'cookbooks': CookbookSet(),
957 ... 'authors': AuthorSet()}941 ... 'authors': AuthorSet()}
958 ...942 ...
959 ... def get(self, name):943 ... def get(self, request, name):
960 ... return self.top_level_names.get(name)944 ... return self.top_level_names.get(name)
961945
962It's the responsibility of each web service to provide an implementation of946It's the responsibility of each web service to provide an implementation of
@@ -964,8 +948,7 @@
964948
965 >>> sm.registerAdapter(949 >>> sm.registerAdapter(
966 ... BaseAbsoluteURL, [MyServiceRootResource, IBrowserRequest])950 ... BaseAbsoluteURL, [MyServiceRootResource, IBrowserRequest])
967 >>> sm.registerAdapter(951
968 ... Traversable, [IServiceRootResource, IBrowserRequest])
969 >>> app = MyServiceRootResource()952 >>> app = MyServiceRootResource()
970 >>> sm.registerUtility(app, IServiceRootResource)953 >>> sm.registerUtility(app, IServiceRootResource)
971954

Subscribers

People subscribed via source and target branches