~leonardr/lazr.restful/0.9.5.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
WSGI example web service
************************

lazr.restful has two example web services. The one in
src/lazr/restful/example is designed to test all of lazr.restful's
features and Zope integrations. The one in
src/lazr/restful/example/wsgi is designed to be a simple,
understandable web service that can act as WSGI middleware.

Getting Started
===============

Here's how to set up a web server serving up the example WSGI web
service.

1. python bootstrap.py
2. bin/buildout
3. bin/run src/lazr/restful/example/wsgi/run.py localhost 8001

The web server is now running at http://localhost:8001/1.0/.

(Why "1.0"? That's defined in the web service configuration; see
below.)

Resources
=========

The resources for the WSGI example web service are:

1. A service root resource. Defined in lazr.restful.example.wsgi.root,
   published at /1.0/

2. A collection resource, defined in
   lazr.restful.example.wsgi.resources, published at /1.0/pairs

3. Two key-value pair entries. The key-value pair is defined in
   lazr.restful.example.wsgi.resources. The resources themselves are
   published at /1.0/pairs/1 and /1.0/pairs/foo.

For simplicity's sake, there are no named operations (if you're
curious, the web service in lazr/restful/example has many named
operations) and no way to create or delete key-value pairs.

Let's take a look at the resource definitions. This is the part that
will be significantly different for any web service you create. There
is code in these classes that's not explained here: it's code for
traversal or for URL generation, and it's explained below.

The root
--------

Every lazr.restful web service needs a root resource. The root must
implement the Zope interface
lazr.restful.interfaces.IServiceRootResource. The easiest way to do
this is to subclass lazr.restful.simple.RootResource
and implement _build_top_level_objects(), which advertises the main
features of your web service to your users. The service root resource
must be registered as a utility for IServiceRootResource (see
"Utilities" below).

Our service root resource is
lazr.restful.example.wsgi.root.WSGIExampleWebServiceRootResource. We
only have one top-level object, the collection of key-value pairs, so
this implementation of _build_top_level_objects() just returns
'pairs'. It maps 'pairs' to a 2-tuple (IKeyValuePair, pairset). Here,
"pairset" is the collection of key-value pairs itself, and
IKeyValuePair is a Zope interface class that explains what kind of
object is found in the collection. You'll see IKeyValuePair again in
the next section of this document.

The top-level collection
------------------------

The collection of key-value pairs is defined in
lazr.restful.example.wsgi.resources. First we define an interface
(IPairSet) that's decorated with lazr.restful decorators:

1. export_as_webservice_collection(IKeyValuePair) tells lazr.restful
   that an IPairSet should be published as a collection resource, as
   opposed to being published as an entry or not published at all. The
   "IKeyValuePair" lets lazr.restful know that this is a collection of
   key-value pairs and not some other kind of collection.

2. @collection_default_content() tells lazr.restful to call the
   getPairs() method when it wants to know what items are in the
   collection.

Then we define the implementation (PairSet), which implements
getPairs().

The entry resource
------------------

The key-value pair is also defined in
lazr.restful.example.wsgi.resources. First we define an interface
(IKeyValuePair) that's decorated with lazr.restful decorators:

1. export_as_webservice_entry() tells lazr.restful that an
   IKeyValuePair should be published as an entry, as opposed to being
   published as an entry or not published at all.

2. exported() tells lazr.restful which attributes of an IKeyValuePair
   should be published to the web service, and which ones are for
   internal use only. In this case, both 'key' and 'value' are
   published.

Then we define KeyValuePair, which simply stores a key and value.

Utilities
=========

The service root and the web service configuration object are both
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 canonical instance of one
of these classes.

Utilities are registered in lazr/restful/example/wsgi/configure.zcml,
and each one is associated with a Zope interface. So the root resource
is registered as the utility for
lazr.restful.interfaces.IServiceRootResource. Whenever you see code
that passes an interface class into getUtility(), that code is getting
the singleton utility registered for that interface.

Traversal
=========

The traversal code turns an incoming URL path like "/1.0/pairs/foo"
into an object to be published. The fragment "1.0" identifies the
IServiceRootResource, the fragment "pairs" identifies the IPairSet,
and the fragment "foo" identifies a specific IKeyValuePair within the
IPairSet.

Traversal code can get very complicated. If you're interested in the
complex stuff, look at the custom code in lazr.restful.example that
implements IPublishTraverse. But all the example resources in
lazr.restful.example.wsgi do traversal simply by subclassing
lazr.restful.publisher.TraverseWithGet. With TraverseWithGet, to
traverse from a parent resource to its children, all you have to do is
define a get() method. This method takes a path fragment like "pairs"
and returns the corresponding object.

Let's take an example. A request comes in for "/1.0/pairs". What happens?

1."1.0" identifies the web service root
  (lazr.restful.example.wsgi.root.WSGIExampleWebServiceRootResource)

2. "pairs" is passed into WSGIExampleWebServiceRootResource.get(), and
   the result is the lazr.restful.example.wsgi.resources.PairSet
   object. (The get() implementation is inherited from
   RootResource. It works because our implementation
   of _build_top_level_objects returned a dictionary that had a key
   called "pairs".)

3. There are no more path fragments, so traversal is complete. The
   PairSet object will be published as a collection resource. Why a
   collection? Because PairSet implements IPairSet, which is published
   as a collection. (Remember export_as_webservice_collection()?)

You don't have to write all the traversal code your web service will
ever use. There's a set of default rules that take over once your
custom traversal code returns an IEntry, the way PairSet.get() does.

Here's a more complex example. A request comes in for
"/1.0/pairs/foo/value". What happens?

1. "1.0" identifies the web service root (WSGIExampleWebServiceRootResource)

2. "pairs" is passed into WSGIExampleWebServiceRootResource.get(), and
   the result is the PairSet object.

3. "foo" is passed into PairSet.get(), and the result is the KeyValuePair
   object.

4. Because IKeyValuePair is published as an entry (remember
   export_as_webservice_entry?), the custom traversal code now
   stops. What happens next is entirely controlled by lazr.restful.
   Because of this, it doesn't make sense to have an entry subclass
   TraverseWithGet or implement IPublishTraverse.

5. "value" identifies a published field of IKeyValuePair. lazr.restful
   traverses to that field. It's a Text field.

6. There are no more path fragments, so traversal is complete. The
   Text field is published as a field resource.

URL generation
==============

URL generation is the opposite of traversal. With traversal you have a
URL and you need to find which object it refers to. With URL
generation you have an object and you need to find its URL.

URL generation can get arbitrarily complex--for each of your resource
classes, you must define a class that implements the Zope interface
IAbsoluteURL. But this web service takes a simpler approach. We
implemented IAbsoluteURL only once, for the root resource. (The
implementation is
lazr.restful.example.wsgi.root.WSGIExampleWebServiceRootAbsoluteURL.)
Then we made every other object implement a much simpler Zope
interface, ILocation.

With ILocation you just have to define two properties: __parent__ and
__name__. __parent__ points to the object's parent in the URL tree,
and __name__ is the portion of the URL space unique to this object. An
object's URL is its __parent__'s url, plus a slash, plus its
__name__. As long as you have a real IAbsoluteURL implementation at
the root, the recursion will always bottom out.

Here's an example. Consider the key-value pair at
http://localhost:8000/1.0/pairs/foo. Its __name__ is the name of the
key, "foo". Its __parent__ is the top-level collection of key-value
pairs, found with getUtility(IPairSet). The collection's __name__ is
"pairs", and its __parent__ is the service root.

The service root has its own IAbsoluteURL implementation, which gives
us "http://localhost:8000/1.0/". Put those pieces together, and you
get "http://localhost:8000/1.0/pairs/foo".

If you look in lazr/restful/example/wsgi/configure.zcml you'll see
this snippet of ZCML:

  <adapter
      for="zope.location.interfaces.ILocation
           lazr.restful.publisher.WebServiceRequestTraversal"
      provides="zope.traversing.browser.interfaces.IAbsoluteURL"
      factory="zope.traversing.browser.absoluteurl.AbsoluteURL"
      />

That's saying: "Whenever you have an ILocation and a web service
request, and you need to generate a URL (IAbsoluteURL), pass the
ILocation object and the web service request into Zope's AbsoluteURL
class, and let AbsoluteURL handle it." AbsoluteURL handles it by
checking __parent__ and __name__.

Configuration
=============

The configuration object (defined in
lazr.restful.example.wsgi.configuration) contains miscellaneous
service-specific configuration settings. It's registered as the
utility for lazr.restful.interfaces.IWebServiceConfiguration, and
lazr.restful consults it for miscellaneous things that shouldn't be
hard-coded. The most interesting configuration setting is probably
service_version_uri_prefix, which controls the version number that
shows up in URLs.

[XXX leonardr 20090803 bug=407505 miscellaneous improvements to this
doc.]