From c4536c4a54282cd89bc815b58cc3c73280712df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20Kl=C3=B6cker?= Date: Fri, 7 Apr 2017 14:21:06 +0200 Subject: [PATCH] Fixed #27777 -- Made File.open() work with the with statement (#8310) Fixed #27777 -- Made File.open() work with the with statement --- AUTHORS | 1 + django/core/files/base.py | 2 ++ django/core/files/uploadedfile.py | 1 + django/db/models/fields/files.py | 1 + docs/ref/files/file.txt | 3 +++ tests/files/tests.py | 36 ++++++++++++++++++++++++++++++- 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 0ab2376643..1817ebafa7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -324,6 +324,7 @@ answer newbie questions, and generally made Django that much better: Igor Kolar Illia Volochii Ilya Semenov + Ingo Klöcker I.S. van Oostveen ivan.chelubeev@gmail.com Ivan Sagalaev (Maniac) diff --git a/django/core/files/base.py b/django/core/files/base.py index ff0a289dea..5e6332f0b6 100644 --- a/django/core/files/base.py +++ b/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 diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py index 485bfbfd6f..60c354a9f7 100644 --- a/django/core/files/uploadedfile.py +++ b/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) diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 92e710299f..cbd0386435 100644 --- a/django/db/models/fields/files.py +++ b/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 diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt index 803f060c29..d79ae57f47 100644 --- a/docs/ref/files/file.txt +++ b/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 diff --git a/tests/files/tests.py b/tests/files/tests.py index 9b3e019f8d..1a383090f9 100644 --- a/tests/files/tests.py +++ b/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): """