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
=== modified file 'NEWS.rst'
--- NEWS.rst 2020-06-30 13:33:53 +0000
+++ NEWS.rst 2020-06-30 15:58:27 +0000
@@ -7,6 +7,10 @@
77
8Fix test failure with zope.interface >= 5.0.0.8Fix test failure with zope.interface >= 5.0.0.
99
10Make ``ObjectLookupFieldMarshaller`` accept URLs that redirect, provided
11that the redirected-to resource has a ``context`` attribute that evaluates
12to the appropriate model object.
13
100.22.0 (2020-06-12)140.22.0 (2020-06-12)
11===================15===================
1216
1317
=== modified file 'src/lazr/restful/_resource.py'
--- src/lazr/restful/_resource.py 2020-05-12 11:57:28 +0000
+++ src/lazr/restful/_resource.py 2020-06-30 15:58:27 +0000
@@ -122,6 +122,7 @@
122 IWebServiceVersion,122 IWebServiceVersion,
123 LAZR_WEBSERVICE_NAME,123 LAZR_WEBSERVICE_NAME,
124 )124 )
125from lazr.restful.marshallers import URLDereferencingMixin
125from lazr.restful.utils import (126from lazr.restful.utils import (
126 extract_write_portion,127 extract_write_portion,
127 get_current_web_service_request,128 get_current_web_service_request,
@@ -253,7 +254,7 @@
253254
254255
255@implementer(IHTTPResource)256@implementer(IHTTPResource)
256class RedirectResource:257class RedirectResource(URLDereferencingMixin):
257 """A resource that redirects to another URL."""258 """A resource that redirects to another URL."""
258259
259 def __init__(self, url, request):260 def __init__(self, url, request):
@@ -265,6 +266,10 @@
265 self.request.response.setStatus(301)266 self.request.response.setStatus(301)
266 self.request.response.setHeader("Location", url)267 self.request.response.setHeader("Location", url)
267268
269 @property
270 def context(self):
271 return self.dereference_url_as_object(self.url)
272
268273
269@implementer(IHTTPResource)274@implementer(IHTTPResource)
270class HTTPResource:275class HTTPResource:
271276
=== modified file 'src/lazr/restful/docs/webservice-marshallers.rst'
--- src/lazr/restful/docs/webservice-marshallers.rst 2020-02-04 11:52:59 +0000
+++ src/lazr/restful/docs/webservice-marshallers.rst 2020-06-30 15:58:27 +0000
@@ -705,6 +705,19 @@
705 >>> print(cookbook.name)705 >>> print(cookbook.name)
706 Everyday Greens706 Everyday Greens
707707
708Redirections
709~~~~~~~~~~~~
710
711Objects may have multiple URLs, with non-canonical forms redirecting to
712canonical forms. The object marshaller accepts URLs that redirect, provided
713that the redirected-to resource knows how to find the ultimate target
714object.
715
716 >>> cookbook = reference_marshaller.marshall_from_json_data(
717 ... '/cookbooks/featured')
718 >>> print(cookbook.name)
719 Mastering the Art of French Cooking
720
708Collections721Collections
709-----------722-----------
710723
711724
=== modified file 'src/lazr/restful/marshallers.py'
--- src/lazr/restful/marshallers.py 2020-02-04 13:17:32 +0000
+++ src/lazr/restful/marshallers.py 2020-06-30 15:58:27 +0000
@@ -82,10 +82,11 @@
82 else:82 else:
83 site_protocol = 'http'83 site_protocol = 'http'
84 default_port = '80'84 default_port = '80'
85 request_host = self.request.get('HTTP_HOST', 'localhost')85 full_request_host = self.request.get('HTTP_HOST', 'localhost')
86 if ':' in request_host:86 if ':' in full_request_host:
87 request_host, request_port = request_host.split(':', 2)87 request_host, request_port = full_request_host.split(':', 2)
88 else:88 else:
89 request_host = full_request_host
89 request_port = default_port90 request_port = default_port
9091
91 if not isinstance(url, basestring):92 if not isinstance(url, basestring):
@@ -116,11 +117,32 @@
116 path_parts = [unquote(part) for part in path.split('/')]117 path_parts = [unquote(part) for part in path.split('/')]
117 path_parts.pop(0)118 path_parts.pop(0)
118 path_parts.reverse()119 path_parts.reverse()
119 request = config.createRequest(StringIO(), {'PATH_INFO': path})120 environ = {'PATH_INFO': path, 'HTTP_HOST': full_request_host}
121 server_url = self.request.get('SERVER_URL')
122 if server_url is not None:
123 environ['SERVER_URL'] = server_url
124 request = config.createRequest(StringIO(), environ)
120 request.setTraversalStack(path_parts)125 request.setTraversalStack(path_parts)
121 root = request.publication.getApplication(self.request)126 root = request.publication.getApplication(self.request)
122 return request.traverse(root)127 return request.traverse(root)
123128
129 def dereference_url_as_object(self, url):
130 """Look up a data model object in the web service by URL."""
131 try:
132 resource = self.dereference_url(url)
133 except NotFound:
134 # The URL doesn't correspond to any real object.
135 raise ValueError(u'No such object "%s".' % url)
136 except InvalidURIError:
137 raise ValueError(u'"%s" is not a valid URI.' % url)
138 # We looked up the URL and got the thing at the other end of
139 # the URL: a resource. But internally, a resource isn't a
140 # valid value for any schema field. Instead we want the object
141 # that serves as a resource's context. Any time we want to get
142 # to the object underlying a resource, we need to strip its
143 # security proxy.
144 return removeSecurityProxy(resource).context
145
124146
125@implementer(IFieldMarshaller)147@implementer(IFieldMarshaller)
126class SimpleFieldMarshaller:148class SimpleFieldMarshaller:
@@ -633,17 +655,4 @@
633655
634 Look up the data model object by URL.656 Look up the data model object by URL.
635 """657 """
636 try:658 return self.dereference_url_as_object(value)
637 resource = self.dereference_url(value)
638 except NotFound:
639 # The URL doesn't correspond to any real object.
640 raise ValueError(u'No such object "%s".' % value)
641 except InvalidURIError:
642 raise ValueError(u'"%s" is not a valid URI.' % value)
643 # We looked up the URL and got the thing at the other end of
644 # the URL: a resource. But internally, a resource isn't a
645 # valid value for any schema field. Instead we want the object
646 # that serves as a resource's context. Any time we want to get
647 # to the object underlying a resource, we need to strip its
648 # security proxy.
649 return removeSecurityProxy(resource).context

Subscribers

People subscribed via source and target branches