Merge lp:~leonardr/lazr.restful/fix-unicode-error into lp:lazr.restful

Proposed by Leonard Richardson
Status: Merged
Approved by: Aaron Bentley
Approved revision: 158
Merged at revision: 155
Proposed branch: lp:~leonardr/lazr.restful/fix-unicode-error
Merge into: lp:lazr.restful
Diff against target: 290 lines (+102/-44)
6 files modified
src/lazr/restful/NEWS.txt (+1/-1)
src/lazr/restful/_resource.py (+13/-13)
src/lazr/restful/docs/webservice.txt (+22/-22)
src/lazr/restful/marshallers.py (+4/-4)
src/lazr/restful/tests/test_webservice.py (+61/-3)
src/lazr/restful/version.txt (+1/-1)
To merge this branch: bzr merge lp:~leonardr/lazr.restful/fix-unicode-error
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+39279@code.launchpad.net

Description of the change

This branch changes a number of related places where Python error strings are merged with strings that may be Unicode strings. If this happens, it can cause encoding errors.

I've added a test to verify that the one case that actually causes encoding errors right now is fixed by this change.

To post a comment you must log in.
157. By Leonard Richardson

Revved version.

158. By Leonard Richardson

Changed the doctest to a unit test.

Revision history for this message
Aaron Bentley (abentley) :
review: Approve

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-10-25 13:24:52 +0000
3+++ src/lazr/restful/NEWS.txt 2010-10-25 16:32:51 +0000
4@@ -2,7 +2,7 @@
5 NEWS for lazr.restful
6 =====================
7
8-0.15.0 (2010-10-22)
9+0.14.1 (2010-10-24)
10 ===================
11
12 Fixed a unicode encoding bug that precluded reporting exceptions with
13
14=== modified file 'src/lazr/restful/_resource.py'
15--- src/lazr/restful/_resource.py 2010-10-25 13:24:52 +0000
16+++ src/lazr/restful/_resource.py 2010-10-25 16:32:51 +0000
17@@ -993,8 +993,8 @@
18
19 # The self link and resource type link aren't part of the
20 # schema, so they're handled separately.
21- modified_read_only_attribute = ("%s: You tried to modify a "
22- "read-only attribute.")
23+ modified_read_only_attribute = (u"%s: You tried to modify a "
24+ "read-only attribute.")
25 if 'self_link' in changeset:
26 if changeset['self_link'] != absoluteURL(self.entry.context,
27 self.request):
28@@ -1059,8 +1059,8 @@
29 # error message if the new value is not identical to the
30 # current one.
31 if value != current_value:
32- errors.append("%s: You tried to modify a collection "
33- "attribute." % repr_name)
34+ errors.append(u"%s: You tried to modify a collection "
35+ "attribute." % repr_name)
36 continue
37
38 if IBytes.providedBy(field):
39@@ -1073,8 +1073,8 @@
40 % repr_name)
41 else:
42 errors.append(
43- "%s: To modify this field you need to send a PUT "
44- "request to its URI (%s)."
45+ u"%s: To modify this field you need to send a PUT "
46+ "request to its URI (%s)."
47 % (repr_name, current_value))
48 continue
49
50@@ -1087,8 +1087,8 @@
51 # possible for Vocabulary fields to specify a schema
52 # class the way IObject fields can.
53 if value != None and not field.schema.providedBy(value):
54- errors.append("%s: Your value points to the "
55- "wrong kind of object" % repr_name)
56+ errors.append(u"%s: Your value points to the "
57+ "wrong kind of object" % repr_name)
58 continue
59
60 # Obtain the current value of the field. This gives us an easy
61@@ -1130,24 +1130,24 @@
62 error = e.args[0]
63 else:
64 error = "Constraint not satisfied."
65- errors.append("%s: %s" % (repr_name, error))
66+ errors.append(u"%s: %s" % (repr_name, error))
67 continue
68 except RequiredMissing, e:
69 error = "Missing required value."
70- errors.append("%s: %s" % (repr_name, error))
71+ errors.append(u"%s: %s" % (repr_name, error))
72 except (ValueError, ValidationError), e:
73 error = str(e)
74 if error == "":
75 error = "Validation error"
76- errors.append("%s: %s" % (repr_name, error))
77+ errors.append(u"%s: %s" % (repr_name, error))
78 continue
79 validated_changeset.append((field, value))
80 # If there are any fields left in the changeset, they're
81 # fields that don't correspond to some field in the
82 # schema. They're all errors.
83 for invalid_field in changeset.keys():
84- errors.append("%s: You tried to modify a nonexistent "
85- "attribute." % invalid_field)
86+ errors.append(u"%s: You tried to modify a nonexistent "
87+ "attribute." % invalid_field)
88
89 # If there were errors, display them and send a status of 400.
90 if len(errors) > 0:
91
92=== modified file 'src/lazr/restful/docs/webservice.txt'
93--- src/lazr/restful/docs/webservice.txt 2010-08-06 15:28:40 +0000
94+++ src/lazr/restful/docs/webservice.txt 2010-10-25 16:32:51 +0000
95@@ -2026,40 +2026,40 @@
96 matches the hostname you're requesting. If they don't match, your
97 request will fail.
98
99- >>> change_joy_author(u'http://not.the.same.host' + path)
100- 'author_link: No such object...'
101+ >>> print change_joy_author(u'http://not.the.same.host' + path)
102+ author_link: No such object...
103
104 One possible source of hostname mismatches is the HTTP port. If the
105 web service is served from a strange port, you'll need to specify that
106 port in the URLs you send.
107
108- >>> change_joy_author(u'http://api.cookbooks.dev' + path,
109- ... host='api.cookbooks.dev:9000')
110- 'author_link: No such object...'
111+ >>> print change_joy_author(u'http://api.cookbooks.dev' + path,
112+ ... host='api.cookbooks.dev:9000')
113+ author_link: No such object...
114
115- >>> change_joy_author(u'http://api.cookbooks.dev:9000' + path,
116- ... host='api.cookbooks.dev:9000')
117- '{...}'
118+ >>> print change_joy_author(u'http://api.cookbooks.dev:9000' + path,
119+ ... host='api.cookbooks.dev:9000')
120+ {...}
121
122 You don't have to specify the default port in the URLs you send, even
123 if you specified it when you made the request.
124
125- >>> change_joy_author(u'http://api.cookbooks.dev' + path,
126- ... host='api.cookbooks.dev:80')
127- '{...}'
128-
129- >>> change_joy_author(u'http://api.cookbooks.dev:80' + path,
130- ... host='api.cookbooks.dev')
131- '{...}'
132-
133- >>> change_joy_author(u'https://api.cookbooks.dev' + path,
134- ... host='api.cookbooks.dev:443')
135- 'author_link: No such object...'
136+ >>> print change_joy_author(u'http://api.cookbooks.dev' + path,
137+ ... host='api.cookbooks.dev:80')
138+ {...}
139+
140+ >>> print change_joy_author(u'http://api.cookbooks.dev:80' + path,
141+ ... host='api.cookbooks.dev')
142+ {...}
143+
144+ >>> print change_joy_author(u'https://api.cookbooks.dev' + path,
145+ ... host='api.cookbooks.dev:443')
146+ author_link: No such object...
147
148 >>> webservice_configuration.use_https = True
149- >>> change_joy_author(u'https://api.cookbooks.dev' + path,
150- ... host='api.cookbooks.dev:443')
151- '{...}'
152+ >>> print change_joy_author(u'https://api.cookbooks.dev' + path,
153+ ... host='api.cookbooks.dev:443')
154+ {...}
155 >>> webservice_configuration.use_https = False
156
157 If an entry has an IResourceDELETEOperation registered for it, you can
158
159=== modified file 'src/lazr/restful/marshallers.py'
160--- src/lazr/restful/marshallers.py 2010-10-25 13:24:52 +0000
161+++ src/lazr/restful/marshallers.py 2010-10-25 16:32:51 +0000
162@@ -85,7 +85,7 @@
163 request_port = default_port
164
165 if not isinstance(url, basestring):
166- raise ValueError("got '%s', expected string: %r" % (
167+ raise ValueError(u"got '%s', expected string: %r" % (
168 type(url).__name__, url))
169 if url.startswith('/'):
170 # It's a relative URI. Resolve it relative to the root of this
171@@ -349,7 +349,7 @@
172 try:
173 return self.field.vocabulary.getTermByToken(str(value)).value
174 except LookupError:
175- raise ValueError("%r isn't a valid token" % value)
176+ raise ValueError(u"%r isn't a valid token" % value)
177
178
179 class DateTimeFieldMarshaller(SimpleFieldMarshaller):
180@@ -559,9 +559,9 @@
181 resource = self.dereference_url(value)
182 except NotFound:
183 # The URL doesn't correspond to any real object.
184- raise ValueError('No such object "%s".' % value)
185+ raise ValueError(u'No such object "%s".' % value)
186 except InvalidURIError:
187- raise ValueError('"%s" is not a valid URI.' % value)
188+ raise ValueError(u'"%s" is not a valid URI.' % value)
189 # We looked up the URL and got the thing at the other end of
190 # the URL: a resource. But internally, a resource isn't a
191 # valid value for any schema field. Instead we want the object
192
193=== modified file 'src/lazr/restful/tests/test_webservice.py'
194--- src/lazr/restful/tests/test_webservice.py 2010-08-25 13:21:39 +0000
195+++ src/lazr/restful/tests/test_webservice.py 2010-10-25 16:32:51 +0000
196@@ -15,10 +15,11 @@
197 from zope.interface import implements, Interface
198 from zope.interface.interface import InterfaceClass
199 from zope.publisher.browser import TestRequest
200-from zope.schema import Date, Datetime, TextLine
201+from zope.schema import Choice, Date, Datetime, TextLine
202 from zope.security.management import newInteraction, queryInteraction
203 from zope.traversing.browser.interfaces import IAbsoluteURL
204
205+from lazr.enum import EnumeratedType, Item
206 from lazr.restful import ResourceOperation
207 from lazr.restful.fields import Reference
208 from lazr.restful.interfaces import (
209@@ -158,8 +159,9 @@
210 request = getUtility(IWebServiceConfiguration).createRequest(
211 StringIO(""), {})
212
213- entry = entry_class(HasRestrictedField(""), request)
214- resource = EntryResource(HasRestrictedField(""), request)
215+ data_object = HasRestrictedField("")
216+ entry = entry_class(data_object, request)
217+ resource = EntryResource(data_object, request)
218 entry.schema['a_field'].restrict_to_interface = IHasRestrictedField
219 self.assertEquals(resource.entry.a_field, '')
220 resource.applyChanges({'a_field': u'a_value'})
221@@ -174,6 +176,62 @@
222 {'a_field': u'a_new_value'})
223 self.assertEquals(resource.entry.a_field, 'a_value')
224
225+
226+class UnicodeChoice(EnumeratedType):
227+ """A choice between an ASCII value and a Unicode value."""
228+ ASCII = Item("Ascii", "Ascii choice")
229+ UNICODE = Item(u"Uni\u00e7ode", "Uni\u00e7ode choice")
230+
231+
232+class ICanBeSetToUnicodeValue(Interface):
233+ """An entry with an InterfaceRestrictedField."""
234+ export_as_webservice_entry()
235+ a_field = exported(Choice(
236+ vocabulary=UnicodeChoice,
237+ title=u"A value that might be ASCII or Unicode.",
238+ required=False, default=None))
239+
240+
241+class CanBeSetToUnicodeValue:
242+ """An implementation of ICanBeSetToUnicodeValue."""
243+ implements(ICanBeSetToUnicodeValue)
244+ def __init__(self, value):
245+ self.a_field = value
246+
247+
248+class UnicodeErrorTestCase(WebServiceTestCase):
249+ """Test that Unicode error strings are properly passed through."""
250+
251+ testmodule_objects = [CanBeSetToUnicodeValue, ICanBeSetToUnicodeValue]
252+
253+ def setUp(self):
254+ super(UnicodeErrorTestCase, self).setUp()
255+ getGlobalSiteManager().registerAdapter(
256+ DummyAbsoluteURL,
257+ [ICanBeSetToUnicodeValue, IWebServiceClientRequest], IAbsoluteURL)
258+
259+ def test_unicode_error(self):
260+ entry_class = get_resource_factory(ICanBeSetToUnicodeValue, IEntry)
261+ request = getUtility(IWebServiceConfiguration).createRequest(
262+ StringIO(""), {})
263+
264+ data_object = CanBeSetToUnicodeValue("")
265+ entry = entry_class(data_object, request)
266+ resource = EntryResource(data_object, request)
267+
268+ # This will raise an exception, which will cause the request
269+ # to fail with a 400 error code.
270+ error = resource.applyChanges({'a_field': u'No such value'})
271+ self.assertEqual(request.response.getStatus(), 400)
272+
273+ # The error message is a Unicode string which mentions both
274+ # the ASCII value and the Unicode value,
275+ expected_error = (
276+ u'a_field: Invalid value "No such value". Acceptable values '
277+ u'are: Ascii, Uni\u00e7ode')
278+ self.assertEquals(error, expected_error)
279+
280+
281 class WadlAPITestCase(WebServiceTestCase):
282 """Test the docstring generation."""
283
284
285=== modified file 'src/lazr/restful/version.txt'
286--- src/lazr/restful/version.txt 2010-10-25 13:24:52 +0000
287+++ src/lazr/restful/version.txt 2010-10-25 16:32:51 +0000
288@@ -1,1 +1,1 @@
289-0.15.0
290+0.14.1

Subscribers

People subscribed via source and target branches