Merge lp:~leonardr/lazr.restful/474522-type-error into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Merged at revision: 149
Proposed branch: lp:~leonardr/lazr.restful/474522-type-error
Merge into: lp:lazr.restful
Diff against target: 191 lines (+99/-15)
5 files modified
src/lazr/restful/NEWS.txt (+7/-0)
src/lazr/restful/example/base/tests/entry.txt (+65/-9)
src/lazr/restful/example/base/tests/redirect.txt (+1/-0)
src/lazr/restful/marshallers.py (+25/-5)
src/lazr/restful/version.txt (+1/-1)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/474522-type-error
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+37018@code.launchpad.net

Description of the change

Ignore the name of this branch; it refers to a backlog bug that turned out to be invalid.

This branch fixes a different backlog bug, bug 497602. It makes lazr.restful accept relative URLs when dereferencing URLs. This happens when you pass a URL into a named operation or try to change an entry's link. The URLs are relative to the versioned service root (so "/foo" might actually be "http://server/1.0/foo").

I also updated some imports and fixed a trivial test failure in the previous revision.

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Leonard,

This branch looks great. Thanks for fixing this issue.

-Edwin

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/NEWS.txt'
2--- src/lazr/restful/NEWS.txt 2010-09-27 18:31:14 +0000
3+++ src/lazr/restful/NEWS.txt 2010-09-29 16:47:51 +0000
4@@ -2,6 +2,13 @@
5 NEWS for lazr.restful
6 =====================
7
8+0.13.3 (2010-09-29)
9+===================
10+
11+Named operations that take URLs as arguments will now accept URLs
12+relative to the versioned service root. Previously they would only
13+accept absolute URLs. This fixes bug 497602.
14+
15 0.13.2 (2010-09-27)
16 ===================
17
18
19=== modified file 'src/lazr/restful/example/base/tests/entry.txt'
20--- src/lazr/restful/example/base/tests/entry.txt 2010-08-26 18:22:19 +0000
21+++ src/lazr/restful/example/base/tests/entry.txt 2010-09-29 16:47:51 +0000
22@@ -222,11 +222,33 @@
23 object. Here the 'dish' argument is the URL to a dish, and the named
24 operation finds a recipe for making that dish.
25
26+ >>> def find_recipe_in_joy(dish_url):
27+ ... """Look up a dish in 'The Joy of Cooking'."""
28+ ... return webservice.get("%s?ws.op=find_recipe_for&dish=%s" %
29+ ... (joy_url, quote(dish_url))).jsonBody()
30+
31 >>> dish_url = webservice.get("/recipes/2").jsonBody()['dish_link']
32- >>> recipe = webservice.get("%s?ws.op=find_recipe_for&dish=%s" %
33- ... (joy_url, quote(dish_url))).jsonBody()
34- >>> recipe['instructions']
35- u'Draw, singe, stuff, and truss...'
36+ >>> find_recipe_in_joy(dish_url)['instructions']
37+ u'Draw, singe, stuff, and truss...'
38+
39+The URL passed in to a named operation may be an absolute URL, or it
40+may be relative to the versioned service root. This is for developer
41+convenience only, as lazr.restful never serves relative URLs.
42+
43+ >>> print dish_url
44+ http://cookbooks.dev/devel/dishes/Roast%20chicken
45+ >>> relative_url = quote("/dishes/Roast chicken")
46+ >>> find_recipe_in_joy(relative_url)['instructions']
47+ u'Draw, singe, stuff, and truss...'
48+
49+A URL relative to the unversioned service root will not work.
50+
51+ >>> relative_url = quote("/devel/dishes/Roast chicken")
52+ >>> find_recipe_in_joy(relative_url)
53+ Traceback (most recent call last):
54+ ...
55+ ValueError: dish: No such object "/devel/dishes/Roast%20chicken".
56+
57
58 Some entries support custom operations through POST. You can invoke a
59 custom operation to modify a cookbook's name, making it seem more
60@@ -274,14 +296,19 @@
61 should use the PATCH HTTP method. Or it may completely describe the
62 entry's state, in which case the client should use PUT.
63
64- >>> def modify_cookbook(cookbook, representation, method, headers=None):
65- ... "A helper function to PUT or PATCH a cookbook."
66+ >>> def modify_entry(url, representation, method, headers=None):
67+ ... "A helper function to PUT or PATCH an entry."
68 ... new_headers = {'Content-type': 'application/json'}
69 ... if headers is not None:
70 ... new_headers.update(headers)
71- ... return webservice('/cookbooks/' + quote(cookbook), method,
72- ... simplejson.dumps(representation),
73- ... headers)
74+ ... return webservice(
75+ ... url, method, simplejson.dumps(representation), headers)
76+
77+ >>> def modify_cookbook(cookbook, representation, method, headers=None):
78+ ... "A helper function to PUT or PATCH a cookbook."
79+ ... return modify_entry(
80+ ... '/cookbooks/' + quote(cookbook), representation,
81+ ... method, headers)
82
83 Here we use the web service to change the cuisine of the "Everyday
84 Greens" cookbook. The data returned is the new JSON representation of
85@@ -309,6 +336,35 @@
86 >>> print greens['revision_number']
87 1
88
89+A modification may cause one of en entry's links to point to another
90+object. Here, we change the 'dish_link' field of a roast chicken
91+recipe, turning it into a recipe for baked beans.
92+
93+ >>> old_dish = webservice.get("/recipes/1").jsonBody()['dish_link']
94+ >>> print old_dish
95+ http://.../dishes/Roast%20chicken
96+
97+ >>> new_dish = webservice.get("/recipes/4").jsonBody()['dish_link']
98+ >>> print new_dish
99+ http://.../dishes/Baked%20beans
100+
101+ >>> new_entry = modify_entry(
102+ ... "/recipes/2", {'dish_link' : new_dish}, 'PATCH').jsonBody()
103+ >>> print new_entry['dish_link']
104+ http://.../dishes/Baked%20beans
105+
106+When changing one of an entry's links, you can use an absolute URL (as
107+seen above) or a URL relative to the versioned service root. Let's use
108+a relative URL to change the baked beans recipe back to a roast
109+chicken recipe.
110+
111+ >>> relative_old_dish = quote('/dishes/Roast chicken')
112+ >>> new_entry = modify_entry(
113+ ... "/recipes/2", {'dish_link' : relative_old_dish},
114+ ... 'PATCH').jsonBody()
115+ >>> print new_entry['dish_link']
116+ http://.../dishes/Roast%20chicken
117+
118 A modification might cause an entry's address to change. Here we use
119 the web service to change the cookbook's name to 'Everyday Greens 2'.
120
121
122=== modified file 'src/lazr/restful/example/base/tests/redirect.txt'
123--- src/lazr/restful/example/base/tests/redirect.txt 2010-09-27 16:40:28 +0000
124+++ src/lazr/restful/example/base/tests/redirect.txt 2010-09-29 16:47:51 +0000
125@@ -42,3 +42,4 @@
126 HTTP/1.1 301 Moved Permanently
127 ...
128 Location: http://.../Mastering%20the%20Art%20of%20French%20Cooking{invalid}?ws.accept=application/json
129+ ...
130
131=== modified file 'src/lazr/restful/marshallers.py'
132--- src/lazr/restful/marshallers.py 2009-11-10 13:58:22 +0000
133+++ src/lazr/restful/marshallers.py 2010-09-29 16:47:51 +0000
134@@ -28,17 +28,30 @@
135
136 import simplejson
137
138-from zope.datetime import DateTimeError, DateTimeParser
139-from zope.component import getMultiAdapter, getUtility
140+from zope.datetime import (
141+ DateTimeError,
142+ DateTimeParser,
143+ )
144+from zope.component import (
145+ getMultiAdapter,
146+ getUtility,
147+ )
148 from zope.interface import implements
149 from zope.publisher.interfaces import NotFound
150 from zope.security.proxy import removeSecurityProxy
151 from zope.traversing.browser import absoluteURL
152
153-from lazr.uri import URI, InvalidURIError
154+from lazr.uri import (
155+ URI,
156+ InvalidURIError,
157+ )
158
159 from lazr.restful.interfaces import (
160- IFieldMarshaller, IUnmarshallingDoesntNeedValue, IWebServiceConfiguration)
161+ IFieldMarshaller,
162+ IUnmarshallingDoesntNeedValue,
163+ IServiceRootResource,
164+ IWebServiceConfiguration,
165+ )
166 from lazr.restful.utils import safe_hasattr
167
168
169@@ -74,7 +87,14 @@
170 if not isinstance(url, basestring):
171 raise ValueError("got '%s', expected string: %r" % (
172 type(url).__name__, url))
173- uri = URI(url)
174+ if url.startswith('/'):
175+ # It's a relative URI. Resolve it relative to the root of this
176+ # version of the web service.
177+ service_root = getUtility(IServiceRootResource)
178+ root_uri = absoluteURL(service_root, self.request)
179+ uri = URI(root_uri).append(url[1:])
180+ else:
181+ uri = URI(url)
182 protocol = uri.scheme
183 host = uri.host
184 port = uri.port or default_port
185
186=== modified file 'src/lazr/restful/version.txt'
187--- src/lazr/restful/version.txt 2010-09-27 18:31:14 +0000
188+++ src/lazr/restful/version.txt 2010-09-29 16:47:51 +0000
189@@ -1,1 +1,1 @@
190-0.13.2
191+0.13.3

Subscribers

People subscribed via source and target branches