commit be117094f7d66a716e6ca61809441bbe6d0cefbe
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Wed Oct 24 23:41:45 2012 +0200
Fixed #18796 -- Refactored conversion to bytes in HttpResponse
diff --git a/django/http/response.py b/django/http/response.py
index 6bbadd7..68c3ca9 100644
a
|
b
|
from django.http.cookie import SimpleCookie
|
16 | 16 | from django.utils import six, timezone |
17 | 17 | from django.utils.encoding import force_bytes, iri_to_uri |
18 | 18 | from django.utils.http import cookie_date |
| 19 | from django.utils.six.moves import map |
19 | 20 | |
20 | 21 | |
21 | 22 | class BadHeaderError(ValueError): |
… |
… |
class HttpResponseBase(object):
|
191 | 192 | |
192 | 193 | def make_bytes(self, value): |
193 | 194 | """Turn a value into a bytestring encoded in the output charset.""" |
194 | | # For backwards compatibility, this method supports values that are |
195 | | # unlikely to occur in real applications. It has grown complex and |
196 | | # should be refactored. It also overlaps __next__. See #18796. |
| 195 | # Per PEP 3333, this response body must be bytes. To avoid returning |
| 196 | # an instance of a subclass, this function returns `bytes(value)`. |
| 197 | # This doesn't make a copy when `value` already contains bytes. |
| 198 | |
| 199 | # If content is already encoded (eg. gzip), assume bytes. |
197 | 200 | if self.has_header('Content-Encoding'): |
198 | | if isinstance(value, int): |
199 | | value = six.text_type(value) |
200 | | if isinstance(value, six.text_type): |
201 | | value = value.encode('ascii') |
202 | | # force conversion to bytes in case chunk is a subclass |
| 201 | if isinstance(value, bytes): |
| 202 | return bytes(value) |
| 203 | raise TypeError("Unsupported data in response: %s" % type(value)) |
| 204 | |
| 205 | # Handle string types |
| 206 | if isinstance(value, bytes): |
203 | 207 | return bytes(value) |
204 | | else: |
205 | | return force_bytes(value, self._charset) |
| 208 | if isinstance(value, six.text_type): |
| 209 | return bytes(value.encode(self._charset)) |
| 210 | |
| 211 | # Handle non-string types - not ovbiously useful but supported (#16494) |
| 212 | return force_bytes(value, self._charset) |
| 213 | |
| 214 | def __iter__(self): |
| 215 | return self |
| 216 | |
| 217 | def __next__(self): |
| 218 | # Subclasses must define self._iterator for this function. |
| 219 | return self.make_bytes(next(self._iterator)) |
| 220 | |
| 221 | next = __next__ # Python 2 compatibility |
206 | 222 | |
207 | 223 | # These methods partially implement the file-like object interface. |
208 | 224 | # See http://docs.python.org/lib/bltin-file-objects.html |
… |
… |
class HttpResponse(HttpResponseBase):
|
287 | 303 | self._iterator = iter(self._container) |
288 | 304 | return self |
289 | 305 | |
290 | | def __next__(self): |
291 | | chunk = next(self._iterator) |
292 | | if isinstance(chunk, int): |
293 | | chunk = six.text_type(chunk) |
294 | | if isinstance(chunk, six.text_type): |
295 | | chunk = chunk.encode(self._charset) |
296 | | # force conversion to bytes in case chunk is a subclass |
297 | | return bytes(chunk) |
298 | | |
299 | | next = __next__ # Python 2 compatibility |
300 | | |
301 | 306 | def write(self, content): |
302 | 307 | self._consume_content() |
303 | 308 | self._container.append(content) |
… |
… |
class StreamingHttpResponse(HttpResponseBase):
|
331 | 336 | |
332 | 337 | @property |
333 | 338 | def streaming_content(self): |
334 | | return self._iterator |
| 339 | return map(self.make_bytes, self._iterator) |
335 | 340 | |
336 | 341 | @streaming_content.setter |
337 | 342 | def streaming_content(self, value): |
… |
… |
class StreamingHttpResponse(HttpResponseBase):
|
340 | 345 | if hasattr(value, 'close'): |
341 | 346 | self._closable_objects.append(value) |
342 | 347 | |
343 | | def __iter__(self): |
344 | | return self |
345 | | |
346 | | def __next__(self): |
347 | | return self.make_bytes(next(self._iterator)) |
348 | | |
349 | | next = __next__ # Python 2 compatibility |
350 | | |
351 | 348 | |
352 | 349 | class CompatibleStreamingHttpResponse(StreamingHttpResponse): |
353 | 350 | """ |
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
index 8e20c80..b57c102 100644
a
|
b
|
class HttpResponseTests(unittest.TestCase):
|
330 | 330 | self.assertEqual(r.content, b'123\xde\x9e') |
331 | 331 | |
332 | 332 | #with Content-Encoding header |
333 | | r = HttpResponse([1,1,2,4,8]) |
| 333 | r = HttpResponse() |
334 | 334 | r['Content-Encoding'] = 'winning' |
335 | | self.assertEqual(r.content, b'11248') |
336 | | r.content = ['\u079e',] |
337 | | self.assertRaises(UnicodeEncodeError, |
338 | | getattr, r, 'content') |
| 335 | r.content = [b'abc', b'def'] |
| 336 | self.assertEqual(r.content, b'abcdef') |
| 337 | r.content = ['\u079e'] |
| 338 | self.assertRaises(TypeError, getattr, r, 'content') |
339 | 339 | |
340 | 340 | # .content can safely be accessed multiple times. |
341 | 341 | r = HttpResponse(iter(['hello', 'world'])) |