Browse Source

Fixed #26646 -- Added IOBase methods required by TextIOWrapper to File.

Thanks Tim for the review.
Simon Charette 8 years ago
parent
commit
4f474607de

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

@@ -64,10 +64,6 @@ class File(FileProxyMixin):
 
     size = property(_get_size, _set_size)
 
-    def _get_closed(self):
-        return not self.file or self.file.closed
-    closed = property(_get_closed)
-
     def chunks(self, chunk_size=None):
         """
         Read the file and yield chunks of ``chunk_size`` bytes (defaults to

+ 0 - 9
django/core/files/temp.py

@@ -58,15 +58,6 @@ if os.name == 'nt':
                 except (OSError):
                     pass
 
-        @property
-        def closed(self):
-            """
-            This attribute needs to be accessible in certain situations,
-            because this class is supposed to mock the API of the class
-            tempfile.NamedTemporaryFile in the Python standard library.
-            """
-            return self.file.closed
-
         def __del__(self):
             self.close()
 

+ 25 - 5
django/core/files/utils.py

@@ -1,6 +1,3 @@
-from django.utils import six
-
-
 class FileProxyMixin(object):
     """
     A mixin class used to forward file methods to an underlaying file
@@ -27,8 +24,31 @@ class FileProxyMixin(object):
     write = property(lambda self: self.file.write)
     writelines = property(lambda self: self.file.writelines)
     xreadlines = property(lambda self: self.file.xreadlines)
-    if six.PY3:
-        seekable = property(lambda self: self.file.seekable)
+
+    @property
+    def closed(self):
+        return not self.file or self.file.closed
+
+    def readable(self):
+        if self.closed:
+            return False
+        if hasattr(self.file, 'readable'):
+            return self.file.readable()
+        return True
+
+    def writable(self):
+        if self.closed:
+            return False
+        if hasattr(self.file, 'writable'):
+            return self.file.writable()
+        return 'w' in getattr(self.file, 'mode', '')
+
+    def seekable(self):
+        if self.closed:
+            return False
+        if hasattr(self.file, 'seekable'):
+            return self.file.seekable()
+        return True
 
     def __iter__(self):
         return iter(self.file)

+ 7 - 2
docs/ref/files/file.txt

@@ -92,8 +92,13 @@ The ``File`` class
     the following attributes and methods of its ``file`` object:
     ``encoding``, ``fileno``, ``flush``, ``isatty``, ``newlines``,
     ``read``, ``readinto``, ``readlines``, ``seek``, ``softspace``, ``tell``,
-    ``truncate``, ``writelines``, ``xreadlines``. If you are using
-    Python 3, the ``seekable`` method is also available.
+    ``truncate``, ``writelines``, ``xreadlines``, ``readable()``,
+    ``writable()``, and ``seekable()``.
+
+    .. versionchanged:: 1.11
+
+        The ``readable()`` and ``writable()`` methods were added and the
+        ``seekable()`` method was made available on Python 2.
 
 .. currentmodule:: django.core.files.base
 

+ 3 - 1
docs/releases/1.11.txt

@@ -122,7 +122,9 @@ Email
 File Storage
 ~~~~~~~~~~~~
 
-* ...
+* To make it wrappable by :class:`io.TextIOWrapper`,
+  :class:`~django.core.files.File` now has the ``readable()``, ``writable()``,
+  and ``seekable()`` methods.
 
 File Uploads
 ~~~~~~~~~~~~

+ 1 - 0
docs/spelling_wordlist

@@ -972,6 +972,7 @@ wordcount
 wordwrap
 workflow
 worksforme
+wrappable
 wsgi
 www
 xe

+ 33 - 12
tests/files/tests.py

@@ -6,7 +6,7 @@ import os
 import struct
 import tempfile
 import unittest
-from io import BytesIO, StringIO
+from io import BytesIO, StringIO, TextIOWrapper
 
 from django.core.files import File
 from django.core.files.base import ContentFile
@@ -120,18 +120,39 @@ class FileTests(unittest.TestCase):
         f = File(StringIO('one\ntwo\nthree'))
         self.assertEqual(list(f), ['one\n', 'two\n', 'three'])
 
+    def test_readable(self):
+        with tempfile.TemporaryFile() as temp, File(temp, name='something.txt') as test_file:
+            self.assertTrue(test_file.readable())
+        self.assertFalse(test_file.readable())
+
+    def test_writable(self):
+        with tempfile.TemporaryFile() as temp, File(temp, name='something.txt') as test_file:
+            self.assertTrue(test_file.writable())
+        self.assertFalse(test_file.writable())
+        with tempfile.TemporaryFile('rb') as temp, File(temp, name='something.txt') as test_file:
+            self.assertFalse(test_file.writable())
+
     def test_seekable(self):
-        """
-        File.seekable() should be available on Python 3.
-        """
-        with tempfile.TemporaryFile() as temp:
-            temp.write(b"contents\n")
-            test_file = File(temp, name="something.txt")
-            if six.PY2:
-                self.assertFalse(hasattr(test_file, 'seekable'))
-            if six.PY3:
-                self.assertTrue(hasattr(test_file, 'seekable'))
-                self.assertTrue(test_file.seekable())
+        with tempfile.TemporaryFile() as temp, File(temp, name='something.txt') as test_file:
+            self.assertTrue(test_file.seekable())
+        self.assertFalse(test_file.seekable())
+
+    def test_io_wrapper(self):
+        content = "vive l'été\n"
+        with tempfile.TemporaryFile() as temp, File(temp, name='something.txt') as test_file:
+            test_file.write(content.encode('utf-8'))
+            test_file.seek(0)
+            wrapper = TextIOWrapper(test_file, 'utf-8', newline='\n')
+            self.assertEqual(wrapper.read(), content)
+            # The following seek() call is required on Windows Python 2 when
+            # switching from reading to writing.
+            wrapper.seek(0, 2)
+            wrapper.write(content)
+            wrapper.seek(0)
+            self.assertEqual(wrapper.read(), content * 2)
+            test_file = wrapper.detach()
+            test_file.seek(0)
+            self.assertEqual(test_file.read(), (content * 2).encode('utf-8'))
 
 
 class NoNameFileTestCase(unittest.TestCase):