Merge lp:~leonardr/lazr.restful/simple-root into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Approved by: Barry Warsaw
Approved revision: 53
Merged at revision: not available
Proposed branch: lp:~leonardr/lazr.restful/simple-root
Merge into: lp:lazr.restful
Diff against target: None lines
To merge this branch: bzr merge lp:~leonardr/lazr.restful/simple-root
Reviewer Review Type Date Requested Status
Barry Warsaw Approve
Review via email: mp+9988@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

The default ServiceRootResource has a very complicated implementation of getTopLevelPublications(). It goes through all the registered adapters and sees which ones are adapters for utilities, and which ones are adapters for ITopLevelEntryLink. The most obvious effect of this is that you have to register all your collections as Zope utilities.

This is fine for Launchpad, because Launchpad's collections are already registered as Zope utilities. But other web services don't do this. To take an extreme example, the WSGI example web service defines its entire object model within the root resource. So why not

I created a class called SimpleRootResource which asks its subclasses for two data structures: one for top-level collections and one for top-level entry links. It uses this data structure to drive both getTopLevelPublications() and the get() method used by TraverseWithGet. The data structure is a little bit complicated, but a SimpleRootResource subclass is significantly less complicated than the corresponding ServiceRootResource. The WSGI sample application now has much less code and a little bit less ZCML.

To get this to work without circular references, I had to move some classes around. I ended up moving all the "simple implementation of complex zope/lazr.restful feature x" classes into lazr.restful.simple. It now contains SimplePublicationMixin, SimplePublication, TraverseWithGet, SimpleRequest, and SimpleWebServiceRootResource. Most of the code in this branch is this new file + changes to the import statements in other files.

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

"So why not" should have been "So why not create an implementation of IWebServiceRootResource that caters to simpler cases?"

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

At Gary's suggestion, I removed 'Simple' from the name of all classes now found in lazr.restful.simple.

52. By Leonard Richardson

Removed 'Simple' from the class names of classes in lazr.restful.simple.

Revision history for this message
Barry Warsaw (barry) wrote :

On Aug 11, 2009, at 06:37 PM, Leonard Richardson wrote:

>At Gary's suggestion, I removed 'Simple' from the name of all classes now found in lazr.restful.simple.

Great suggestion. I'm still reviewing the branch but will keep this in mind.
Thanks for making this change.

53. By Leonard Richardson

Renamed SimpleWebServiceRootResource to RootResource.

Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (15.1 KiB)

On Aug 11, 2009, at 06:15 PM, Leonard Richardson wrote:

>Leonard Richardson has proposed merging lp:~leonardr/lazr.restful/simple-root into lp:lazr.restful.
>
>Requested reviews:
> LAZR Developers (lazr-developers)
>
>The default ServiceRootResource has a very complicated implementation of getTopLevelPublications(). It goes through all the registered adapters and sees which ones are adapters for utilities, and which ones are adapters for ITopLevelEntryLink. The most obvious effect of this is that you have to register all your collections as Zope utilities.
>
>This is fine for Launchpad, because Launchpad's collections are already registered as Zope utilities. But other web services don't do this. To take an extreme example, the WSGI example web service defines its entire object model within the root resource. So why not
>
>I created a class called SimpleRootResource which asks its subclasses for two data structures: one for top-level collections and one for top-level entry links. It uses this data structure to drive both getTopLevelPublications() and the get() method used by TraverseWithGet. The data structure is a little bit complicated, but a SimpleRootResource subclass is significantly less complicated than the corresponding ServiceRootResource. The WSGI sample application now has much less code and a little bit less ZCML.
>
>To get this to work without circular references, I had to move some classes around. I ended up moving all the "simple implementation of complex zope/lazr.restful feature x" classes into lazr.restful.simple. It now contains SimplePublicationMixin, SimplePublication, TraverseWithGet, SimpleRequest, and SimpleWebServiceRootResource. Most of the code in this branch is this new file + changes to the import statements in other files.

Hi Leonard,

It's great to see the work you're doing on simplifying lazr.restful for simple
applications. The more I use lazr.restful the more I like it, and I think
it's really going to help drive up adoption by making it easier to use. I'm
going to be attending my local Python users group meeting and overwhelmingly,
people want me to do a presentation on lazr.restful.

I've deleted the chunks that look fine to me. All my other comments are
fairly trivial. Great branch, r=me.

 review approve
 status approve

=== modified file 'src/lazr/restful/example/wsgi/README.txt'
--- src/lazr/restful/example/wsgi/README.txt 2009-08-03 13:12:20 +0000
+++ src/lazr/restful/example/wsgi/README.txt 2009-08-11 17:47:07 +0000
> @@ -109,12 +109,11 @@
> Utilities
> =========
>
> -The service root, the top-level collection of key-value pairs, and the
> -the web service configuration object are all registered as Zope
> -utilities. A Zope utility is basically a singleton. These three
> -classes are registered as singletons because from time to time,
> -lazr.restful needs access to a canonical instance of one of these
> -classes.
> +The service root and the the web service configuration object are both

s/the the/the/

> +registered as Zope utilities. A Zope utility is basically a
> +singleton. These two classes are registered as singletons because
> +from time to time, lazr.restful needs access to a c...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/restful/docs/webservice-marshallers.txt'
2--- src/lazr/restful/docs/webservice-marshallers.txt 2009-08-04 19:27:13 +0000
3+++ src/lazr/restful/docs/webservice-marshallers.txt 2009-08-11 16:40:43 +0000
4@@ -9,7 +9,7 @@
5 application root.
6
7 >>> from lazr.restful.testing.webservice import WebServiceTestPublication
8- >>> from lazr.restful.publisher import SimpleRequest
9+ >>> from lazr.restful.simple import SimpleRequest
10 >>> from lazr.restful.example.root import (
11 ... CookbookServiceRootResource)
12 >>> request = SimpleRequest("", {'HTTP_HOST': 'cookbooks.dev'})
13
14=== modified file 'src/lazr/restful/docs/webservice-request.txt'
15--- src/lazr/restful/docs/webservice-request.txt 2009-08-04 18:23:18 +0000
16+++ src/lazr/restful/docs/webservice-request.txt 2009-08-11 16:40:43 +0000
17@@ -14,7 +14,7 @@
18 >>> from lazr.restful.publisher import (
19 ... browser_request_to_web_service_request)
20 >>> from lazr.restful.testing.webservice import WebServiceTestPublication
21- >>> from lazr.restful.publisher import SimpleRequest
22+ >>> from lazr.restful.simple import SimpleRequest
23 >>> from zope.interface import implements
24 >>> from zope.component import getSiteManager
25 >>> from zope.publisher.browser import TestRequest
26
27=== modified file 'src/lazr/restful/docs/webservice.txt'
28--- src/lazr/restful/docs/webservice.txt 2009-08-04 20:21:42 +0000
29+++ src/lazr/restful/docs/webservice.txt 2009-08-11 16:40:43 +0000
30@@ -512,7 +512,7 @@
31
32 >>> from lazr.restful.interfaces import IWebServiceConfiguration
33 >>> from lazr.restful.testing.webservice import WebServiceTestPublication
34- >>> from lazr.restful.publisher import SimpleRequest
35+ >>> from lazr.restful.simple import SimpleRequest
36
37 >>> from cStringIO import StringIO
38
39
40=== modified file 'src/lazr/restful/example/configuration.py'
41--- src/lazr/restful/example/configuration.py 2009-08-04 18:23:18 +0000
42+++ src/lazr/restful/example/configuration.py 2009-08-11 16:40:43 +0000
43@@ -12,7 +12,7 @@
44
45 from lazr.restful.interfaces import (
46 IServiceRootResource, IWebServiceConfiguration)
47-from lazr.restful.publisher import SimpleRequest
48+from lazr.restful.simple import SimpleRequest
49 from lazr.restful.testing.webservice import WebServiceTestPublication
50
51 class CookbookWebServiceConfiguration:
52
53=== modified file 'src/lazr/restful/example/wsgi/README.txt'
54--- src/lazr/restful/example/wsgi/README.txt 2009-08-03 13:12:20 +0000
55+++ src/lazr/restful/example/wsgi/README.txt 2009-08-11 17:47:07 +0000
56@@ -52,17 +52,21 @@
57 Every lazr.restful web service needs a root resource. The root must
58 implement the Zope interface
59 lazr.restful.interfaces.IServiceRootResource. The easiest way to do
60-this is to subclass lazr.restful.ServiceRootResource and implement
61-top_level_names, which advertises the main features of your web
62-service to your users. The service root resource must be registered as
63-a utility for IServiceRootResource (see "Utilities" below).
64+this is to subclass lazr.restful.simple.SimpleWebServiceRootResource
65+and implement _build_top_level_objects(), which advertises the main
66+features of your web service to your users. The service root resource
67+must be registered as a utility for IServiceRootResource (see
68+"Utilities" below).
69
70 Our service root resource is
71-lazr.restful.example.wsgi.root.WSGIExampleWebServiceRootResource.
72-
73-We only have one top-level object, the collection of key-value pairs, so
74-this implementation of top_level_names ends up returning
75-['pairs'].
76+lazr.restful.example.wsgi.root.WSGIExampleWebServiceRootResource. We
77+only have one top-level object, the collection of key-value pairs, so
78+this implementation of _build_top_level_objects() just returns
79+'pairs'. It maps 'pairs' to a 2-tuple (IKeyValuePair, pairset). Here,
80+"pairset" is the collection of key-value pairs itself, and
81+IKeyValuePair is a Zope interface class that explains what kind of
82+object is found in the collection. You'll see IKeyValuePair again in
83+the next section of this document.
84
85 The top-level collection
86 ------------------------
87@@ -84,10 +88,6 @@
88 Then we define the implementation (PairSet), which implements
89 getPairs().
90
91-Like all top-level collections, the collection of key-value pairs is
92-registered as a utility. In this case, PairSet is registered as the
93-utility for IPairSet (see "Utilities" below).
94-
95 The entry resource
96 ------------------
97
98@@ -109,12 +109,11 @@
99 Utilities
100 =========
101
102-The service root, the top-level collection of key-value pairs, and the
103-the web service configuration object are all registered as Zope
104-utilities. A Zope utility is basically a singleton. These three
105-classes are registered as singletons because from time to time,
106-lazr.restful needs access to a canonical instance of one of these
107-classes.
108+The service root and the the web service configuration object are both
109+registered as Zope utilities. A Zope utility is basically a
110+singleton. These two classes are registered as singletons because
111+from time to time, lazr.restful needs access to a canonical instance
112+of one of these classes.
113
114 Utilities are registered in lazr/restful/example/wsgi/configure.zcml,
115 and each one is associated with a Zope interface. So the root resource
116@@ -123,10 +122,6 @@
117 that passes an interface class into getUtility(), that code is getting
118 the singleton utility registered for that interface.
119
120-All top-level objects published by a lazr.restful web service must be
121-registered as utilities. This is why the set of key-value pairs is
122-registered as a utility for IPairSet.
123-
124 Traversal
125 =========
126
127@@ -151,7 +146,11 @@
128 (lazr.restful.example.wsgi.root.WSGIExampleWebServiceRootResource)
129
130 2. "pairs" is passed into WSGIExampleWebServiceRootResource.get(), and
131- the result is the lazr.restful.example.wsgi.resources.PairSet object.
132+ the result is the lazr.restful.example.wsgi.resources.PairSet
133+ object. (The get() implementation is inherited from
134+ SimpleWebServiceRootResource. It works because our implementation
135+ of _build_top_level_objects returned a dictionary that had a key
136+ called "pairs".)
137
138 3. There are no more path fragments, so traversal is complete. The
139 PairSet object will be published as a collection resource. Why a
140
141=== modified file 'src/lazr/restful/example/wsgi/configure.zcml'
142--- src/lazr/restful/example/wsgi/configure.zcml 2009-08-04 19:27:13 +0000
143+++ src/lazr/restful/example/wsgi/configure.zcml 2009-08-11 15:26:14 +0000
144@@ -22,11 +22,6 @@
145 provides="lazr.restful.interfaces.IServiceRootResource"
146 factory="lazr.restful.example.wsgi.root.WSGIExampleWebServiceRootResource" />
147
148- <!--All top-level objects need to be registered as utilities.-->
149- <utility
150- provides="lazr.restful.example.wsgi.resources.IPairSet"
151- factory="lazr.restful.example.wsgi.resources.PairSet" />
152-
153 <!--The configuration object also needs to be registered as a utility.-->
154 <utility
155 factory="lazr.restful.example.wsgi.configuration.WSGIExampleWebServiceConfiguration"
156
157=== modified file 'src/lazr/restful/example/wsgi/resources.py'
158--- src/lazr/restful/example/wsgi/resources.py 2009-08-03 13:12:20 +0000
159+++ src/lazr/restful/example/wsgi/resources.py 2009-08-11 16:40:43 +0000
160@@ -14,7 +14,8 @@
161 collection_default_content, export_as_webservice_collection,
162 export_as_webservice_entry, exported)
163 from lazr.restful.interfaces import IServiceRootResource
164-from lazr.restful.publisher import TraverseWithGet
165+from lazr.restful.simple import TraverseWithGet
166+
167
168 class IKeyValuePair(ILocation):
169 export_as_webservice_entry()
170
171=== modified file 'src/lazr/restful/example/wsgi/root.py'
172--- src/lazr/restful/example/wsgi/root.py 2009-07-21 15:21:35 +0000
173+++ src/lazr/restful/example/wsgi/root.py 2009-08-11 16:40:43 +0000
174@@ -9,41 +9,24 @@
175
176 from zope.component import adapts, getUtility
177 from zope.interface import implements
178-from zope.publisher.interfaces.browser import IDefaultBrowserLayer
179 from zope.traversing.browser.interfaces import IAbsoluteURL
180
181-from lazr.restful import ServiceRootResource
182-from lazr.restful.example.wsgi.resources import IPairSet, KeyValuePair
183-from lazr.restful.interfaces import (
184- ICollection, IServiceRootResource, IWebServiceConfiguration)
185-from lazr.restful.publisher import TraverseWithGet, WebServiceRequestTraversal
186-
187-
188-class WSGIExampleWebServiceRootResource(ServiceRootResource, TraverseWithGet):
189+from lazr.restful.example.wsgi.resources import (
190+ IKeyValuePair, PairSet, KeyValuePair)
191+from lazr.restful.interfaces import IWebServiceConfiguration
192+from lazr.restful.simple import SimpleServiceRootResource
193+from lazr.restful.publisher import WebServiceRequestTraversal
194+
195+
196+class WSGIExampleWebServiceRootResource(SimpleServiceRootResource):
197 """The root resource for the WSGI example web service."""
198- implements (IServiceRootResource)
199-
200- @property
201- def top_level_names(self):
202- return self.top_level_objects.keys()
203-
204- def get(self, name):
205- """Traverse to a top-level object."""
206- return self.top_level_objects.get(name)
207-
208- @property
209- def top_level_objects(self):
210- """Access or create the list of top-level objects."""
211-
212- if getattr(self, '_top_level_objects', None) is None:
213- pairs = [KeyValuePair(self, "foo", "bar"),
214- KeyValuePair(self, "1", "2")]
215- pairset = getUtility(IPairSet)
216- pairset.pairs = pairs
217- self._top_level_objects = {
218- 'pairs': pairset,
219- }
220- return self._top_level_objects
221+
222+ def _build_top_level_objects(self):
223+ pairset = PairSet()
224+ pairset.pairs = [KeyValuePair(self, "foo", "bar"),
225+ KeyValuePair(self, "1", "2")]
226+ collections = {'pairs': (IKeyValuePair, pairset)}
227+ return collections, {}
228
229
230 class WSGIExampleWebServiceRootAbsoluteURL:
231
232=== modified file 'src/lazr/restful/example/wsgi/tests/test_integration.py'
233--- src/lazr/restful/example/wsgi/tests/test_integration.py 2009-08-04 15:59:15 +0000
234+++ src/lazr/restful/example/wsgi/tests/test_integration.py 2009-08-11 16:40:43 +0000
235@@ -16,7 +16,7 @@
236
237 from lazr.restful.example.wsgi.root import WSGIExampleWebServiceRootResource
238 from lazr.restful.interfaces import IWebServiceConfiguration
239-from lazr.restful.publisher import SimplePublication
240+from lazr.restful.simple import SimplePublication
241 from lazr.restful.testing.webservice import WebServiceApplication
242
243
244
245=== modified file 'src/lazr/restful/example/wsgi/webservice.py'
246--- src/lazr/restful/example/wsgi/webservice.py 2009-08-04 18:23:18 +0000
247+++ src/lazr/restful/example/wsgi/webservice.py 2009-08-11 16:40:43 +0000
248@@ -17,7 +17,7 @@
249
250 from lazr.restful.interfaces import IWebServiceConfiguration
251
252-from lazr.restful.publisher import SimplePublication, SimpleRequest
253+from lazr.restful.simple import SimplePublication, SimpleRequest
254 from lazr.restful.example.wsgi.root import WSGIExampleWebServiceRootResource
255
256
257
258=== modified file 'src/lazr/restful/publisher.py'
259--- src/lazr/restful/publisher.py 2009-08-04 22:26:53 +0000
260+++ src/lazr/restful/publisher.py 2009-08-11 16:40:43 +0000
261@@ -9,8 +9,6 @@
262 __metaclass__ = type
263 __all__ = [
264 'browser_request_to_web_service_request',
265- 'SimplePublication',
266- 'SimpleRequest',
267 'TraverseWithGet',
268 'WebServicePublicationMixin',
269 'WebServiceRequestTraversal',
270@@ -23,23 +21,20 @@
271 from zope.component import (
272 adapter, getMultiAdapter, getUtility, queryAdapter, queryMultiAdapter)
273 from zope.interface import alsoProvides, implementer, implements
274-from zope.publisher.browser import BrowserRequest
275-from zope.publisher.interfaces import IPublication, IPublishTraverse, NotFound
276+from zope.publisher.interfaces import NotFound
277 from zope.publisher.interfaces.browser import IBrowserRequest
278-from zope.publisher.publish import mapply
279 from zope.schema.interfaces import IBytes, IObject
280 from zope.security.checker import ProxyFactory
281-from zope.security.management import endInteraction, newInteraction
282
283 from lazr.uri import URI
284
285 from lazr.restful import (
286- CollectionResource, EntryField, EntryFieldResource, EntryResource,
287- ScopedCollection)
288+ CollectionResource, EntryField, EntryFieldResource,
289+ EntryResource, ScopedCollection, ServiceRootResource)
290 from lazr.restful.interfaces import (
291 IByteStorage, ICollection, ICollectionField, IEntry, IEntryField,
292- IHTTPResource, ITraverseWithGet, IWebBrowserInitiatedRequest,
293- IWebServiceClientRequest, IWebServiceConfiguration, IWebServiceLayer)
294+ IHTTPResource, IWebBrowserInitiatedRequest,
295+ IWebServiceClientRequest, IWebServiceConfiguration)
296
297
298 class WebServicePublicationMixin:
299@@ -190,131 +185,6 @@
300 return value
301
302
303-class SimplePublicationMixin(object):
304- """A very simple implementation of `IPublication`.
305-
306- The object passed to the constructor is returned by getApplication().
307- """
308- implements(IPublication)
309-
310- def __init__(self, application):
311- """Create the test publication.
312-
313- The object at which traversal should start is passed as parameter.
314- """
315- self.application = application
316-
317- def beforeTraversal(self, request):
318- """Sets the request as the current interaction.
319-
320- (It also ends any previous interaction, that's convenient when
321- tests don't go through the whole request.)
322- """
323- endInteraction()
324- newInteraction(request)
325-
326- def getApplication(self, request):
327- """Returns the application passed to the constructor."""
328- return self.application
329-
330- def callTraversalHooks(self, request, ob):
331- """Does nothing."""
332-
333- def traverseName(self, request, ob, name):
334- """Traverse by looking for an `IPublishTraverse` adapter."""
335- # XXX flacoste 2009/03/06 bug=338831. This is copied from
336- # zope.app.publication.publicationtraverse.PublicationTraverse.
337- # This should really live in zope.publisher, we are copying because
338- # we don't want to depend on zope.app stuff.
339- # Namespace support was dropped.
340- if name == '.':
341- return ob
342-
343- if IPublishTraverse.providedBy(ob):
344- ob2 = ob.publishTraverse(request, name)
345- else:
346- # self is marker.
347- adapter = queryMultiAdapter(
348- (ob, request), IPublishTraverse, default=self)
349- if adapter is not self:
350- ob2 = adapter.publishTraverse(request, name)
351- else:
352- raise NotFound(ob, name, request)
353-
354- return self.wrapTraversedObject(ob2)
355-
356- def wrapTraversedObject(self, ob):
357- """Wrap the traversed object, for instance in a security proxy.
358-
359- By default, does nothing."""
360- return ob
361-
362- def afterTraversal(self, request, ob):
363- """Does nothing."""
364-
365- def callObject(self, request, ob):
366- """Call the object, returning the result."""
367- return mapply(ob, request.getPositionalArguments(), request)
368-
369- def afterCall(self, request, ob):
370- """Does nothing."""
371-
372- def handleException(self, object, request, exc_info, retry_allowed=1):
373- """Prints the exception."""
374- # Reproduce the behavior of ZopePublication by looking up a view
375- # for this exception.
376- exception = exc_info[1]
377- view = queryMultiAdapter((exception, request), name='index.html')
378- if view is not None:
379- exc_info = None
380- request.response.reset()
381- request.response.setResult(view())
382- else:
383- traceback.print_exception(*exc_info)
384-
385- def endRequest(self, request, ob):
386- """Ends the interaction."""
387- endInteraction()
388-
389-
390-class SimplePublication(WebServicePublicationMixin, SimplePublicationMixin):
391- """A simple publication.
392-
393- Combines the IPublication implementation of SimplePublicationMixin
394- with the web service implementation of WebServicePublicationMixin,
395- """
396- pass
397-
398-
399-class TraverseWithGet(object):
400- """An implementation of `IPublishTraverse` that uses the get() method.
401-
402- This is a simple traversal technique that works with any object
403- that defines a lookup method called get().
404-
405- This class should not be confused with
406- WebServiceRequestTraversal. This class (or any other class that
407- implements IPublishTraverse) controls traversal in the web
408- application towards an object that implements IEntry. Once an
409- IEntry has been found, further traversal (eg. to scoped
410- collections or fields) always happens with
411- WebServiceRequestTraversal.
412- """
413- implements(ITraverseWithGet)
414-
415- def publishTraverse(self, request, name):
416- """See `IPublishTraverse`."""
417- name = urllib.unquote(name)
418- value = self.get(name)
419- if value is None:
420- raise NotFound(self, name)
421- return value
422-
423- def get(self, name):
424- """See `ITraverseWithGet`."""
425- raise NotImplementedError
426-
427-
428 class WebServiceRequestTraversal(object):
429 """Mixin providing web-service resource wrapping in traversal.
430
431@@ -370,11 +240,6 @@
432 return None
433
434
435-class SimpleRequest(WebServiceRequestTraversal, BrowserRequest):
436- """A web service request with no special features."""
437- implements(IWebServiceLayer)
438-
439-
440 @implementer(IWebServiceClientRequest)
441 @adapter(IBrowserRequest)
442 def browser_request_to_web_service_request(website_request):
443
444=== added file 'src/lazr/restful/simple.py'
445--- src/lazr/restful/simple.py 1970-01-01 00:00:00 +0000
446+++ src/lazr/restful/simple.py 2009-08-11 17:55:34 +0000
447@@ -0,0 +1,226 @@
448+"""Simple implementations of various Zope and lazr.restful interfaces."""
449+
450+__metaclass__ = type
451+__all__ = [
452+ 'SimplePublicationMixin',
453+ 'SimplePublication',
454+ 'TraverseWithGet',
455+ 'SimpleRequest',
456+ 'SimpleWebServiceRootResource'
457+ ]
458+
459+import urllib
460+
461+from zope.component import queryMultiAdapter
462+from zope.interface import implements
463+from zope.publisher.browser import BrowserRequest
464+from zope.publisher.interfaces import IPublication, IPublishTraverse, NotFound
465+from zope.publisher.publish import mapply
466+from zope.security.management import endInteraction, newInteraction
467+
468+from lazr.restful import EntryAdapterUtility, ServiceRootResource
469+from lazr.restful.interfaces import ITraverseWithGet, IWebServiceLayer
470+from lazr.restful.publisher import (
471+ WebServicePublicationMixin, WebServiceRequestTraversal)
472+
473+class SimplePublicationMixin(object):
474+ """A very simple implementation of `IPublication`.
475+
476+ The object passed to the constructor is returned by getApplication().
477+ """
478+ implements(IPublication)
479+
480+ def __init__(self, application):
481+ """Create the test publication.
482+
483+ The object at which traversal should start is passed as parameter.
484+ """
485+ self.application = application
486+
487+ def beforeTraversal(self, request):
488+ """Sets the request as the current interaction.
489+
490+ (It also ends any previous interaction, that's convenient when
491+ tests don't go through the whole request.)
492+ """
493+ endInteraction()
494+ newInteraction(request)
495+
496+ def getApplication(self, request):
497+ """Returns the application passed to the constructor."""
498+ return self.application
499+
500+ def callTraversalHooks(self, request, ob):
501+ """Does nothing."""
502+
503+ def traverseName(self, request, ob, name):
504+ """Traverse by looking for an `IPublishTraverse` adapter."""
505+ # XXX flacoste 2009/03/06 bug=338831. This is copied from
506+ # zope.app.publication.publicationtraverse.PublicationTraverse.
507+ # This should really live in zope.publisher, we are copying because
508+ # we don't want to depend on zope.app stuff.
509+ # Namespace support was dropped.
510+ if name == '.':
511+ return ob
512+
513+ if IPublishTraverse.providedBy(ob):
514+ ob2 = ob.publishTraverse(request, name)
515+ else:
516+ # self is marker.
517+ adapter = queryMultiAdapter(
518+ (ob, request), IPublishTraverse, default=self)
519+ if adapter is not self:
520+ ob2 = adapter.publishTraverse(request, name)
521+ else:
522+ raise NotFound(ob, name, request)
523+
524+ return self.wrapTraversedObject(ob2)
525+
526+ def wrapTraversedObject(self, ob):
527+ """Wrap the traversed object, for instance in a security proxy.
528+
529+ By default, does nothing."""
530+ return ob
531+
532+ def afterTraversal(self, request, ob):
533+ """Does nothing."""
534+
535+ def callObject(self, request, ob):
536+ """Call the object, returning the result."""
537+ return mapply(ob, request.getPositionalArguments(), request)
538+
539+ def afterCall(self, request, ob):
540+ """Does nothing."""
541+
542+ def handleException(self, object, request, exc_info, retry_allowed=1):
543+ """Prints the exception."""
544+ # Reproduce the behavior of ZopePublication by looking up a view
545+ # for this exception.
546+ exception = exc_info[1]
547+ view = queryMultiAdapter((exception, request), name='index.html')
548+ if view is not None:
549+ exc_info = None
550+ request.response.reset()
551+ request.response.setResult(view())
552+ else:
553+ traceback.print_exception(*exc_info)
554+
555+ def endRequest(self, request, ob):
556+ """Ends the interaction."""
557+ endInteraction()
558+
559+
560+class SimplePublication(WebServicePublicationMixin, SimplePublicationMixin):
561+ """A simple publication.
562+
563+ Combines the IPublication implementation of SimplePublicationMixin
564+ with the web service implementation of WebServicePublicationMixin,
565+ """
566+ pass
567+
568+
569+class TraverseWithGet(object):
570+ """An implementation of `IPublishTraverse` that uses the get() method.
571+
572+ This is a simple traversal technique that works with any object
573+ that defines a lookup method called get().
574+
575+ This class should not be confused with
576+ WebServiceRequestTraversal. This class (or any other class that
577+ implements IPublishTraverse) controls traversal in the web
578+ application towards an object that implements IEntry. Once an
579+ IEntry has been found, further traversal (eg. to scoped
580+ collections or fields) always happens with
581+ WebServiceRequestTraversal.
582+ """
583+ implements(ITraverseWithGet)
584+
585+ def publishTraverse(self, request, name):
586+ """See `IPublishTraverse`."""
587+ name = urllib.unquote(name)
588+ value = self.get(name)
589+ if value is None:
590+ raise NotFound(self, name)
591+ return value
592+
593+ def get(self, name):
594+ """See `ITraverseWithGet`."""
595+ raise NotImplementedError
596+
597+
598+class SimpleRequest(WebServiceRequestTraversal, BrowserRequest):
599+ """A web service request with no special features."""
600+ implements(IWebServiceLayer)
601+
602+
603+class SimpleServiceRootResource(ServiceRootResource, TraverseWithGet):
604+ """A service root that expects top-level objects to be defined in code.
605+
606+ ServiceRootResource expects top-level objects to be registered as
607+ Zope interfaces.
608+ """
609+
610+ @property
611+ def top_level_names(self):
612+ return self.top_level_objects.keys()
613+
614+ def get(self, name):
615+ """Traverse to a top-level object."""
616+ return self.top_level_objects.get(name)
617+
618+ def getTopLevelPublications(self):
619+ """Return a mapping of top-level link names to published objects."""
620+ top_level_resources = {}
621+ # First collect the top-level collections.
622+ for name, (schema_interface, obj) in (
623+ self.top_level_collections.items()):
624+ adapter = EntryAdapterUtility.forSchemaInterface(schema_interface)
625+ link_name = ("%s_collection_link" % adapter.plural_type)
626+ top_level_resources[link_name] = obj
627+ # Then collect the top-level entries.
628+ for name, entry_link in self.top_level_entry_links.items():
629+ link_name = ("%s_link" % ITopLevelEntryLink(entry_link).link_name)
630+ top_level_resources[link_name] = entry_link
631+ return top_level_resources
632+
633+ @property
634+ def top_level_objects(self):
635+ """Return this web service's top-level objects."""
636+ objects = {}
637+ for name, (schema_interface, obj) in (
638+ self.top_level_collections.items()):
639+ objects[name] = obj
640+ objects.update(self.top_level_entry_links)
641+ return objects
642+
643+ @property
644+ def top_level_collections(self):
645+ """Return this web service's top-level collections.
646+
647+ :return: A hash mapping a name to a 2-tuple (interface, collection).
648+ The interface is the kind of thing contained in the collection.
649+ """
650+ if not hasattr(self, '_top_level_collections'):
651+ self._top_level_collections, self._top_level_entry_links = (
652+ self._build_top_level_objects())
653+ return self._top_level_collections
654+
655+ @property
656+ def top_level_entry_links(self):
657+ """Return this web service's top-level entry links."""
658+ if not hasattr(self, '_top_level_entry_links'):
659+ self._top_level_collections, self._top_level_entry_links = (
660+ self._build_top_level_objects())
661+ return self._top_level_entry_links
662+
663+ def _build_top_level_objects(self):
664+ """Create the list of top-level objects.
665+
666+ :return: A 2-tuple of hashes (collections, entry_links). The
667+ 'collections' hash maps a name to a 2-tuple (interface, object).
668+ The interface is the kind of thing contained in the collection.
669+ For instance, 'users': (IUser, UserSet())
670+
671+ The 'entry_links' hash maps a name to an object.
672+ """
673+ return ({}, {})
674
675=== modified file 'src/lazr/restful/testing/webservice.py'
676--- src/lazr/restful/testing/webservice.py 2009-08-04 18:23:18 +0000
677+++ src/lazr/restful/testing/webservice.py 2009-08-11 16:40:43 +0000
678@@ -33,8 +33,8 @@
679 from lazr.restful.interfaces import (
680 IWebServiceConfiguration, IWebServiceLayer)
681 from lazr.restful.publisher import (
682- SimplePublication, SimpleRequest, WebServicePublicationMixin,
683- WebServiceRequestTraversal)
684+ WebServicePublicationMixin, WebServiceRequestTraversal)
685+from lazr.restful.simple import SimplePublication, SimpleRequest
686
687 from lazr.uri import URI
688
689
690=== modified file 'src/lazr/restful/tests/test_webservice.py'
691--- src/lazr/restful/tests/test_webservice.py 2009-08-04 18:23:18 +0000
692+++ src/lazr/restful/tests/test_webservice.py 2009-08-11 16:40:43 +0000
693@@ -20,7 +20,7 @@
694 ICollection, IEntry, IEntryResource, IResourceGETOperation,
695 IWebServiceClientRequest)
696 from lazr.restful import EntryResource, ServiceRootResource, ResourceGETOperation
697-from lazr.restful.publisher import SimpleRequest
698+from lazr.restful.simple import SimpleRequest
699 from lazr.restful.declarations import (
700 collection_default_content, exported, export_as_webservice_collection,
701 export_as_webservice_entry, export_read_operation, operation_parameters)

Subscribers

People subscribed via source and target branches