From a480f8320a5b36501bfd0e8cd70b8dc04adf2d08 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 23 Nov 2013 17:03:43 +0100 Subject: [PATCH] Simplified iteration in HTTP response objects. Fixed #20187 -- Allowed repeated iteration of HttpResponse. All this became possible when support for old-style streaming responses was finally removed. --- django/http/response.py | 24 +++++++++++------------- tests/httpwrappers/tests.py | 6 +++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/django/http/response.py b/django/http/response.py index 532f8ee1e7..c590172a9c 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -282,13 +282,6 @@ class HttpResponseBase(six.Iterator): # Handle non-string types (#16494) return force_bytes(value, self._charset) - def __iter__(self): - return self - - def __next__(self): - # Subclasses must define self._iterator for this function. - return self.make_bytes(next(self._iterator)) - # These methods partially implement the file-like object interface. # See http://docs.python.org/lib/bltin-file-objects.html @@ -337,23 +330,25 @@ class HttpResponse(HttpResponseBase): @property def content(self): - return b''.join(self.make_bytes(e) for e in self._container) + return b''.join(self._container) @content.setter def content(self, value): + # Consume iterators upon assignment to allow repeated iteration. if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)): if hasattr(value, 'close'): self._closable_objects.append(value) - value = b''.join(self.make_bytes(e) for e in value) + value = b''.join(self.make_bytes(chunk) for chunk in value) + else: + value = self.make_bytes(value) + # Create a list of properly encoded bytestrings to support write(). self._container = [value] def __iter__(self): - if not hasattr(self, '_iterator'): - self._iterator = iter(self._container) - return self + return iter(self._container) def write(self, content): - self._container.append(content) + self._container.append(self.make_bytes(content)) def tell(self): return len(self.content) @@ -392,6 +387,9 @@ class StreamingHttpResponse(HttpResponseBase): if hasattr(value, 'close'): self._closable_objects.append(value) + def __iter__(self): + return self.streaming_content + class HttpResponseRedirectBase(HttpResponse): allowed_schemes = ['http', 'https', 'ftp'] diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index ccea20f9c5..06b29f89b9 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -356,10 +356,10 @@ class HttpResponseTests(unittest.TestCase): r = HttpResponse(iter(['hello', 'world'])) self.assertEqual(r.content, r.content) self.assertEqual(r.content, b'helloworld') - # accessing the iterator works (once) after accessing .content + # __iter__ can safely be called multiple times (#20187). self.assertEqual(b''.join(r), b'helloworld') - self.assertEqual(b''.join(r), b'') - # accessing .content still works + self.assertEqual(b''.join(r), b'helloworld') + # Accessing .content still works. self.assertEqual(r.content, b'helloworld') # Accessing .content also works if the response was iterated first.