Browse Source

Fixed #27777 -- Made File.open() work with the with statement (#8310)

Fixed #27777 -- Made File.open() work with the with statement
Ingo Klöcker 8 years ago
parent
commit
c4536c4a54

+ 1 - 0
AUTHORS

@@ -324,6 +324,7 @@ answer newbie questions, and generally made Django that much better:
     Igor Kolar <ike@email.si>
     Illia Volochii <illia.volochii@gmail.com>
     Ilya Semenov <semenov@inetss.com>
+    Ingo Klöcker <djangoproject@ingo-kloecker.de>
     I.S. van Oostveen <v.oostveen@idca.nl>
     ivan.chelubeev@gmail.com
     Ivan Sagalaev (Maniac) <http://www.softwaremaniacs.org/>

+ 2 - 0
django/core/files/base.py

@@ -125,6 +125,7 @@ class File(FileProxyMixin):
             self.file = open(self.name, mode or self.mode)
         else:
             raise ValueError("The file cannot be reopened.")
+        return self
 
     def close(self):
         self.file.close()
@@ -147,6 +148,7 @@ class ContentFile(File):
 
     def open(self, mode=None):
         self.seek(0)
+        return self
 
     def close(self):
         pass

+ 1 - 0
django/core/files/uploadedfile.py

@@ -85,6 +85,7 @@ class InMemoryUploadedFile(UploadedFile):
 
     def open(self, mode=None):
         self.file.seek(0)
+        return self
 
     def chunks(self, chunk_size=None):
         self.file.seek(0)

+ 1 - 0
django/db/models/fields/files.py

@@ -75,6 +75,7 @@ class FieldFile(File):
             self.file.open(mode)
         else:
             self.file = self.storage.open(self.name, mode)
+        return self
     # open() doesn't alter the file's contents, but it does reset the pointer
     open.alters_data = True
 

+ 3 - 0
docs/ref/files/file.txt

@@ -56,6 +56,9 @@ The ``File`` class
         was originally opened with; ``None`` means to reopen with the original
         mode.
 
+        Returns ``self``, so that it can be used similar to Python's
+        built-in :func:`python:open()` with the ``with`` statement.
+
     .. method:: read(num_bytes=None)
 
         Read content from the file. The optional ``size`` is the number of

+ 35 - 1
tests/files/tests.py

@@ -10,7 +10,9 @@ from django.core.files import File
 from django.core.files.base import ContentFile
 from django.core.files.move import file_move_safe
 from django.core.files.temp import NamedTemporaryFile
-from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
+from django.core.files.uploadedfile import (
+    InMemoryUploadedFile, SimpleUploadedFile, UploadedFile,
+)
 
 try:
     from PIL import Image
@@ -38,6 +40,23 @@ class FileTests(unittest.TestCase):
         self.assertTrue(f.closed)
         self.assertTrue(orig_file.closed)
 
+    def test_open_resets_opened_file_to_start_and_returns_context_manager(self):
+        file = File(BytesIO(b'content'))
+        file.read()
+        with file.open() as f:
+            self.assertEqual(f.read(), b'content')
+
+    def test_open_reopens_closed_file_and_returns_context_manager(self):
+        temporary_file = tempfile.NamedTemporaryFile(delete=False)
+        file = File(temporary_file)
+        try:
+            file.close()
+            with file.open() as f:
+                self.assertFalse(f.closed)
+        finally:
+            # remove temporary file
+            os.unlink(file.name)
+
     def test_namedtemporaryfile_closes(self):
         """
         The symbol django.core.files.NamedTemporaryFile is assigned as
@@ -178,6 +197,21 @@ class ContentFileTestCase(unittest.TestCase):
         self.assertIsInstance(ContentFile(b"content").read(), bytes)
         self.assertIsInstance(ContentFile("español").read(), str)
 
+    def test_open_resets_file_to_start_and_returns_context_manager(self):
+        file = ContentFile(b'content')
+        with file.open() as f:
+            self.assertEqual(f.read(), b'content')
+        with file.open() as f:
+            self.assertEqual(f.read(), b'content')
+
+
+class InMemoryUploadedFileTests(unittest.TestCase):
+    def test_open_resets_file_to_start_and_returns_context_manager(self):
+        uf = InMemoryUploadedFile(StringIO('1'), '', 'test', 'text/plain', 1, 'utf8')
+        uf.read()
+        with uf.open() as f:
+            self.assertEqual(f.read(), '1')
+
 
 class DimensionClosingBug(unittest.TestCase):
     """