Merge lp:~cjwatson/lazr.restful/url-dereferencing-better-environ into lp:lazr.restful

Proposed by Colin Watson
Status: Merged
Merged at revision: 246
Proposed branch: lp:~cjwatson/lazr.restful/url-dereferencing-better-environ
Merge into: lp:lazr.restful
Diff against target: 142 lines (+50/-19)
4 files modified
NEWS.rst (+4/-0)
src/lazr/restful/_resource.py (+6/-1)
src/lazr/restful/docs/webservice-marshallers.rst (+13/-0)
src/lazr/restful/marshallers.py (+27/-18)
To merge this branch: bzr merge lp:~cjwatson/lazr.restful/url-dereferencing-better-environ
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+386612@code.launchpad.net

Commit message

Make ObjectLookupFieldMarshaller accept URLs that redirect.

Description of the change

The redirected-to resource must have a context attribute that evaluates to the appropriate model object.

Among other things, this makes it more feasible to adjust the URL layout of exported resources.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS.rst'
2--- NEWS.rst 2020-06-30 13:33:53 +0000
3+++ NEWS.rst 2020-06-30 15:58:27 +0000
4@@ -7,6 +7,10 @@
5
6 Fix test failure with zope.interface >= 5.0.0.
7
8+Make ``ObjectLookupFieldMarshaller`` accept URLs that redirect, provided
9+that the redirected-to resource has a ``context`` attribute that evaluates
10+to the appropriate model object.
11+
12 0.22.0 (2020-06-12)
13 ===================
14
15
16=== modified file 'src/lazr/restful/_resource.py'
17--- src/lazr/restful/_resource.py 2020-05-12 11:57:28 +0000
18+++ src/lazr/restful/_resource.py 2020-06-30 15:58:27 +0000
19@@ -122,6 +122,7 @@
20 IWebServiceVersion,
21 LAZR_WEBSERVICE_NAME,
22 )
23+from lazr.restful.marshallers import URLDereferencingMixin
24 from lazr.restful.utils import (
25 extract_write_portion,
26 get_current_web_service_request,
27@@ -253,7 +254,7 @@
28
29
30 @implementer(IHTTPResource)
31-class RedirectResource:
32+class RedirectResource(URLDereferencingMixin):
33 """A resource that redirects to another URL."""
34
35 def __init__(self, url, request):
36@@ -265,6 +266,10 @@
37 self.request.response.setStatus(301)
38 self.request.response.setHeader("Location", url)
39
40+ @property
41+ def context(self):
42+ return self.dereference_url_as_object(self.url)
43+
44
45 @implementer(IHTTPResource)
46 class HTTPResource:
47
48=== modified file 'src/lazr/restful/docs/webservice-marshallers.rst'
49--- src/lazr/restful/docs/webservice-marshallers.rst 2020-02-04 11:52:59 +0000
50+++ src/lazr/restful/docs/webservice-marshallers.rst 2020-06-30 15:58:27 +0000
51@@ -705,6 +705,19 @@
52 >>> print(cookbook.name)
53 Everyday Greens
54
55+Redirections
56+~~~~~~~~~~~~
57+
58+Objects may have multiple URLs, with non-canonical forms redirecting to
59+canonical forms. The object marshaller accepts URLs that redirect, provided
60+that the redirected-to resource knows how to find the ultimate target
61+object.
62+
63+ >>> cookbook = reference_marshaller.marshall_from_json_data(
64+ ... '/cookbooks/featured')
65+ >>> print(cookbook.name)
66+ Mastering the Art of French Cooking
67+
68 Collections
69 -----------
70
71
72=== modified file 'src/lazr/restful/marshallers.py'
73--- src/lazr/restful/marshallers.py 2020-02-04 13:17:32 +0000
74+++ src/lazr/restful/marshallers.py 2020-06-30 15:58:27 +0000
75@@ -82,10 +82,11 @@
76 else:
77 site_protocol = 'http'
78 default_port = '80'
79- request_host = self.request.get('HTTP_HOST', 'localhost')
80- if ':' in request_host:
81- request_host, request_port = request_host.split(':', 2)
82+ full_request_host = self.request.get('HTTP_HOST', 'localhost')
83+ if ':' in full_request_host:
84+ request_host, request_port = full_request_host.split(':', 2)
85 else:
86+ request_host = full_request_host
87 request_port = default_port
88
89 if not isinstance(url, basestring):
90@@ -116,11 +117,32 @@
91 path_parts = [unquote(part) for part in path.split('/')]
92 path_parts.pop(0)
93 path_parts.reverse()
94- request = config.createRequest(StringIO(), {'PATH_INFO': path})
95+ environ = {'PATH_INFO': path, 'HTTP_HOST': full_request_host}
96+ server_url = self.request.get('SERVER_URL')
97+ if server_url is not None:
98+ environ['SERVER_URL'] = server_url
99+ request = config.createRequest(StringIO(), environ)
100 request.setTraversalStack(path_parts)
101 root = request.publication.getApplication(self.request)
102 return request.traverse(root)
103
104+ def dereference_url_as_object(self, url):
105+ """Look up a data model object in the web service by URL."""
106+ try:
107+ resource = self.dereference_url(url)
108+ except NotFound:
109+ # The URL doesn't correspond to any real object.
110+ raise ValueError(u'No such object "%s".' % url)
111+ except InvalidURIError:
112+ raise ValueError(u'"%s" is not a valid URI.' % url)
113+ # We looked up the URL and got the thing at the other end of
114+ # the URL: a resource. But internally, a resource isn't a
115+ # valid value for any schema field. Instead we want the object
116+ # that serves as a resource's context. Any time we want to get
117+ # to the object underlying a resource, we need to strip its
118+ # security proxy.
119+ return removeSecurityProxy(resource).context
120+
121
122 @implementer(IFieldMarshaller)
123 class SimpleFieldMarshaller:
124@@ -633,17 +655,4 @@
125
126 Look up the data model object by URL.
127 """
128- try:
129- resource = self.dereference_url(value)
130- except NotFound:
131- # The URL doesn't correspond to any real object.
132- raise ValueError(u'No such object "%s".' % value)
133- except InvalidURIError:
134- raise ValueError(u'"%s" is not a valid URI.' % value)
135- # We looked up the URL and got the thing at the other end of
136- # the URL: a resource. But internally, a resource isn't a
137- # valid value for any schema field. Instead we want the object
138- # that serves as a resource's context. Any time we want to get
139- # to the object underlying a resource, we need to strip its
140- # security proxy.
141- return removeSecurityProxy(resource).context
142+ return self.dereference_url_as_object(value)

Subscribers

People subscribed via source and target branches