~james-w/wadllib/fix-doctests

« back to all changes in this revision

Viewing changes to wadllib/docs/wadllib.txt

  • Committer: Leonard Richardson
  • Date: 2008-08-01 15:07:59 UTC
  • Revision ID: leonard.richardson@canonical.com-20080801150759-7ciqu4cw992fb69n
Initial import.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
Doctest for the wadllib library.
 
2
 
 
3
An Application object represents a web service described by a WADL
 
4
file.
 
5
 
 
6
   >>> import os
 
7
   >>> import sys
 
8
   >>> import pkg_resources
 
9
   >>> from wadllib.application import Application
 
10
 
 
11
The first argument to the Application constructor is the URL at which
 
12
the WADL file was found. The second argument may be raw WADL markup.
 
13
 
 
14
   >>> wadl_string = pkg_resources.resource_string(
 
15
   ...     'wadllib.docs.testdata', 'launchpad-wadl.xml')
 
16
   >>> wadl = Application("http://api.launchpad.dev/beta/", wadl_string)
 
17
 
 
18
Or the second argument may be an open filehandle containing the markup.
 
19
 
 
20
   >>> wadl_stream = pkg_resources.resource_stream(
 
21
   ...     'wadllib.docs.testdata', 'launchpad-wadl.xml')
 
22
   >>> wadl = Application("http://api.launchpad.dev/beta/", wadl_stream)
 
23
 
 
24
 
 
25
== Link navigation ==
 
26
 
 
27
The preferred technique for finding a resource is to start at one of
 
28
the resources defined in the WADL file, and follow links. This code
 
29
retrieves the definition of the root resource.
 
30
 
 
31
   >>> service_root = wadl.get_resource_by_path('')
 
32
   >>> service_root.url
 
33
   'http://api.launchpad.dev/beta/'
 
34
   >>> service_root.type_url
 
35
   '#service-root'
 
36
 
 
37
The service root resource supports GET.
 
38
 
 
39
   >>> get_method = service_root.get_method('get')
 
40
   >>> get_method.id
 
41
   'service-root-get'
 
42
 
 
43
   >>> get_method = service_root.get_method('GET')
 
44
   >>> get_method.id
 
45
   'service-root-get'
 
46
 
 
47
If we want to invoke this method, we send a GET request to the service
 
48
root URL.
 
49
 
 
50
   >>> get_method.name
 
51
   'get'
 
52
   >>> get_method.build_request_url()
 
53
   'http://api.launchpad.dev/beta/'
 
54
 
 
55
The WADL description of a resource knows which representations are
 
56
available for that resource. In this case, the server root resource
 
57
has a a JSON representation, and it defines parameters like
 
58
'people_collection_link', a link to a list of people in Launchpad. We
 
59
should be able to use the get_parameter() method to get the WADL
 
60
definition of the 'people_collection_link' parameter and find out more
 
61
about it--for instance, is it a link to another resource?
 
62
 
 
63
   >>> link_name = 'people_collection_link'
 
64
   >>> link_parameter = service_root.get_parameter(link_name)
 
65
   Traceback (most recent call last):
 
66
   ...
 
67
   NoBoundRepresentationError: Resource is not bound to any representation.
 
68
 
 
69
Oops. In the absense of real data, there's no way to know whether
 
70
we're talking about a JSON representation or some other kind of
 
71
representation. There can be no 'people_collection_link' parameter
 
72
until there's real data to back it up.
 
73
 
 
74
The browser can use the description of the GET method to make an
 
75
actual GET request, and bind the resulting representation to the WADL
 
76
description of the resource.
 
77
 
 
78
You can't bind just any representation to a WADL resource description.
 
79
It has to be of a media type understood by the WADL description.
 
80
 
 
81
   >>> service_root.bind('<html>Some HTML</html>', 'text/html')
 
82
   Traceback (most recent call last):
 
83
   ...
 
84
   UnsupportedMediaTypeError: This resource doesn't define a representation for media type text/html
 
85
 
 
86
The WADL description of the service root resource has a JSON
 
87
representation. Here it is.
 
88
 
 
89
   >>> json_representation = service_root.get_representation_definition(
 
90
   ...     'application/json')
 
91
   >>> json_representation.media_type
 
92
   'application/json'
 
93
 
 
94
We already have a WADL representation of the service root resource, so
 
95
let's try binding it to that JSON representation. We use test JSON
 
96
data from a file to simulate the result of a GET request to the
 
97
service root.
 
98
 
 
99
   >>> def get_testdata(filename):
 
100
   ...     return pkg_resources.resource_string(
 
101
   ...         'wadllib.docs.testdata', filename + '.json')
 
102
 
 
103
   >>> def bind_to_testdata(resource, filename):
 
104
   ...     return resource.bind(get_testdata(filename), 'application/json')
 
105
 
 
106
The return value is a new Resource object that's "bound" to that JSON
 
107
test data.
 
108
 
 
109
   >>> bound_service_root = bind_to_testdata(service_root, 'root')
 
110
 
 
111
Now the bound resource object has a JSON representation, and now
 
112
'people_collection_link' makes sense. We can follow the
 
113
'people_collection_link' to a new Resource object.
 
114
 
 
115
   >>> link_parameter = bound_service_root.get_parameter(link_name)
 
116
   >>> link_parameter.style
 
117
   'plain'
 
118
   >>> link_parameter.get_value()
 
119
   u'http://api.launchpad.dev/beta/people'
 
120
   >>> personset_resource = link_parameter.linked_resource
 
121
   >>> personset_resource.__class__
 
122
   <class 'wadllib.application.Resource'>
 
123
   >>> personset_resource.url
 
124
   u'http://api.launchpad.dev/beta/people'
 
125
   >>> personset_resource.type_url
 
126
   'http://api.launchpad.dev/beta/#people'
 
127
 
 
128
This new resource is a collection of people.
 
129
 
 
130
   >>> personset_resource.id
 
131
   'people'
 
132
 
 
133
The "collection of people" resource supports a standard GET request as
 
134
well as a special GET and an overloaded POST. The get_method() method
 
135
is used to retrieve WADL definitions of the possible HTTP requests you
 
136
might make. Here's how to get the WADL definition of the standard GET
 
137
request.
 
138
 
 
139
   >>> get_method = personset_resource.get_method('get')
 
140
   >>> get_method.id
 
141
   'people-get'
 
142
 
 
143
The method name passed into get_method() is treated case-insensitively.
 
144
 
 
145
   >>> personset_resource.get_method('GET').id
 
146
   'people-get'
 
147
 
 
148
To invoke the special GET request, the client sets the 'ws.op' query
 
149
parameter to the fixed string 'find'.
 
150
 
 
151
   >>> find_method = personset_resource.get_method(
 
152
   ...     query_params={'ws.op' : 'findPerson'})
 
153
   >>> find_method.id
 
154
   'people-findPerson'
 
155
 
 
156
Given an end-user's values for the non-fixed parameters, it's possible
 
157
to get the URL that should be used to invoke the method.
 
158
 
 
159
   >>> find_method.build_request_url(text='foo')
 
160
   u'http://api.launchpad.dev/beta/people?text=foo&ws.op=findPerson'
 
161
 
 
162
   >>> find_method.build_request_url({'ws.op' : 'findPerson', 'text' : 'bar'})
 
163
   u'http://api.launchpad.dev/beta/people?text=bar&ws.op=findPerson'
 
164
 
 
165
An error occurs if the end-user gives an incorrect value for a fixed
 
166
parameter value, or omits a required parameter.
 
167
 
 
168
   >>> find_method.build_request_url()
 
169
   Traceback (most recent call last):
 
170
   ...
 
171
   ValueError: No value for required parameter 'text'
 
172
 
 
173
   >>> find_method.build_request_url(
 
174
   ...     {'ws.op' : 'findAPerson', 'text' : 'foo'})
 
175
   ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
 
176
   Traceback (most recent call last):
 
177
   ...
 
178
   ValueError: Value 'findAPerson' for parameter 'ws.op' conflicts
 
179
   with fixed value 'findPerson'
 
180
 
 
181
To invoke the overloaded POST request, the client sets the 'ws.op'
 
182
query variable to the fixed string 'newTeam':
 
183
 
 
184
   >>> create_team_method = personset_resource.get_method(
 
185
   ...     'post', representation_params={'ws.op' : 'newTeam'})
 
186
   >>> create_team_method.id
 
187
   'people-newTeam'
 
188
 
 
189
findMethod() returns None when there's no WADL method matching the
 
190
name or the fixed parameters.
 
191
 
 
192
   >>> print personset_resource.get_method('nosuchmethod')
 
193
   None
 
194
 
 
195
   >>> print personset_resource.get_method(
 
196
   ...     'post', query_params={'ws_op' : 'nosuchparam'})
 
197
   None
 
198
 
 
199
Let's say the browser makes a GET request to the person set resource
 
200
and gets back a representation. We can bind that representation to our
 
201
description of the person set resource.
 
202
 
 
203
   >>> bound_personset = bind_to_testdata(personset_resource, 'personset')
 
204
   >>> bound_personset.get_parameter("start").get_value()
 
205
   0
 
206
   >>> bound_personset.get_parameter("total_size").get_value()
 
207
   63
 
208
 
 
209
We can keep following links indefinitely, so long as we bind to a
 
210
representation to each resource as we get it, and use the
 
211
representation to find the next link.
 
212
 
 
213
   >>> next_page_link = bound_personset.get_parameter("next_collection_link")
 
214
   >>> next_page_link.get_value()
 
215
   u'http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5'
 
216
   >>> page_two = next_page_link.linked_resource
 
217
   >>> bound_page_two = bind_to_testdata(page_two, 'personset-page2')
 
218
   >>> bound_page_two.url
 
219
   u'http://api.launchpad.dev/beta/people?ws.start=5&ws.size=5'
 
220
   >>> bound_page_two.get_parameter("start").get_value()
 
221
   5
 
222
   >>> bound_page_two.get_parameter("next_collection_link").get_value()
 
223
   u'http://api.launchpad.dev/beta/people?ws.start=10&ws.size=5'
 
224
 
 
225
Let's say the browser makes a POST request that invokes the 'newTeam'
 
226
named operation. The response will include a number of HTTP headers,
 
227
including 'Location', which points the way to the newly created team.
 
228
 
 
229
   >>> headers = { 'Location' : 'http://api.launchpad.dev/~newteam' }
 
230
   >>> response = create_team_method.response.bind(headers)
 
231
   >>> location_parameter = response.get_parameter('Location')
 
232
   >>> location_parameter.get_value()
 
233
   'http://api.launchpad.dev/~newteam'
 
234
   >>> new_team = location_parameter.linked_resource
 
235
   >>> new_team.url
 
236
   'http://api.launchpad.dev/~newteam'
 
237
   >>> new_team.type_url
 
238
   'http://api.launchpad.dev/beta/#team'
 
239
 
 
240
 
 
241
== Resource instantiation ==
 
242
 
 
243
If you happen to have the URL to an object lying around, and you know
 
244
its type, you can construct a Resource object directly instead of
 
245
by following links.
 
246
 
 
247
   >>> from wadllib.application import Resource
 
248
   >>> limi_person = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
 
249
   ...     "http://api.launchpad.dev/beta/#person")
 
250
 
 
251
   >>> bound_limi = bind_to_testdata(limi_person, 'person-limi')
 
252
   >>> languages_link = bound_limi.get_parameter("languages_collection_link")
 
253
   >>> languages_link.get_value()
 
254
   u'http://api.launchpad.dev/beta/~limi/languages'
 
255
 
 
256
You can bind a Resource to a representation when you create it.
 
257
 
 
258
   >>> limi_data = get_testdata('person-limi')
 
259
   >>> bound_limi = Resource(
 
260
   ...     wadl, "http://api.launchpad.dev/beta/~limi",
 
261
   ...     "http://api.launchpad.dev/beta/#person", limi_data,
 
262
   ...     "application/json")
 
263
   >>> bound_limi.get_parameter("languages_collection_link").get_value()
 
264
   u'http://api.launchpad.dev/beta/~limi/languages'
 
265
 
 
266
By default the representation is treated as a string and processed
 
267
according to the media type you pass into the Resource constructor. If
 
268
you've already processed the representation, pass in False for the
 
269
'representation_needs_processing' argument.
 
270
 
 
271
   >>> import simplejson
 
272
   >>> processed_limi_data = simplejson.loads(limi_data)
 
273
   >>> bound_limi = Resource(wadl, "http://api.launchpad.dev/beta/~limi",
 
274
   ...     "http://api.launchpad.dev/beta/#person", processed_limi_data,
 
275
   ...     "application/json", False)
 
276
   >>> bound_limi.get_parameter("languages_collection_link").get_value()
 
277
   u'http://api.launchpad.dev/beta/~limi/languages'
 
278
 
 
279
Most of the time, the representation of a resource is of the type
 
280
you'd get by sending a standard GET to that resource. If that's not
 
281
the case, you can specify a RepresentationDefinition as the
 
282
'representation_definition' argument to bind() or the Resource
 
283
constructor, to show what the representation really looks like. Here's
 
284
an example.
 
285
 
 
286
There's a method on a person resource such as bound_limi that's
 
287
identified by a distinctive query argument: ws.op=findPathToTeam.
 
288
 
 
289
   >>> method = bound_limi.get_method(
 
290
   ...     query_params={'ws.op' : 'findPathToTeam'})
 
291
 
 
292
Invoke this method with a GET request and you'll get back a page from
 
293
a list of people.
 
294
 
 
295
   >>> people_page_repr_definition = (
 
296
   ...     method.response.get_representation_definition('application/json'))
 
297
   >>> people_page_repr_definition.tag.attrib['href']
 
298
   'http://api.launchpad.dev/beta/#person-page'
 
299
 
 
300
As it happens, we have a page from a list of people to use as test data.
 
301
 
 
302
   >>> people_page_repr = get_testdata('personset')
 
303
 
 
304
If we bind the resource to the result of the method invocation as
 
305
happened above, we don't be able to access any of the parameters we'd
 
306
expect. wadllib will think the representation is of type
 
307
'person-full', the default GET type for bound_limi.
 
308
 
 
309
   >>> bad_people_page = bound_limi.bind(people_page_repr)
 
310
   >>> print bad_people_page.get_parameter('total_size')
 
311
   None
 
312
 
 
313
Since we don't actually have a 'person-full' representation, we won't
 
314
be able to get values for the parameters of that kind of
 
315
representation.
 
316
 
 
317
   >>> bad_people_page.get_parameter('name').get_value()
 
318
   Traceback (most recent call last):
 
319
   ...
 
320
   KeyError: 'name'
 
321
 
 
322
So that's a dead end. *But*, if we pass the correct representation
 
323
type into bind(), we can access the parameters associated with a
 
324
'person-page' representation.
 
325
 
 
326
   >>> people_page = bound_limi.bind(
 
327
   ...     people_page_repr,
 
328
   ...     representation_definition=people_page_repr_definition)
 
329
   >>> people_page.get_parameter('total_size').get_value()
 
330
   63
 
331
 
 
332
If you invoke the method and ask for a media type other than JSON, you
 
333
won't get anything.
 
334
 
 
335
   >>> print method.response.get_representation_definition('text/html')
 
336
   None
 
337
 
 
338
 
 
339
== Representation creation ==
 
340
 
 
341
You must provide a representation when invoking certain methods. The
 
342
representation() method helps you build one without knowing the
 
343
details of how a representation is put together.
 
344
 
 
345
   >>> create_team_method.build_representation(
 
346
   ...     display_name='Joe Bloggs', name='joebloggs')
 
347
   ('application/x-www-form-urlencoded', 'display_name=Joe+Bloggs&ws.op=newTeam&name=joebloggs')
 
348
 
 
349
The return value of build_representation is a 2-tuple containing the
 
350
media type of the built representation, and the string representation
 
351
itself. Along with the resource's URL, this is all you need to send
 
352
the representation to a web server.
 
353
 
 
354
   >>> bound_limi.get_method('patch').build_representation(name='limi2')
 
355
   ('application/json', '{"name": "limi2"}')
 
356
 
 
357
Representations may require values for certain parameters.
 
358
 
 
359
   >>> create_team_method.build_representation()
 
360
   Traceback (most recent call last):
 
361
   ...
 
362
   ValueError: No value for required parameter 'display_name'
 
363
 
 
364
   >>> bound_limi.get_method('put').build_representation(name='limi2')
 
365
   Traceback (most recent call last):
 
366
   ...
 
367
   ValueError: No value for required parameter 'mugshot_link'
 
368
 
 
369
 
 
370
== Error conditions ==
 
371
 
 
372
You'll get None if you try to look up a nonexistent resource.
 
373
 
 
374
   >>> print wadl.get_resource_by_path('nosuchresource')
 
375
   None
 
376
 
 
377
You'll get an exception if you try to look up a nonexistent resource
 
378
type.
 
379
 
 
380
   >>> print wadl.get_resource_type('#nosuchtype')
 
381
   Traceback (most recent call last):
 
382
   KeyError: 'No such XML ID: "#nosuchtype"'
 
383
 
 
384
You'll get None if you try to look up a method whose parameters don't
 
385
match any defined method.
 
386
 
 
387
   >>> print bound_limi.get_method(
 
388
   ...     'post', representation_params={ 'foo' : 'bar' })
 
389
   None