summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2016-05-22 12:43:56 -0400
committerSimon Charette <charette.s@gmail.com>2016-05-27 21:05:58 -0400
commit4f474607de9b470f977a734bdd47590ab202e778 (patch)
treece05cd13e229c7086362703d84c9dedd8a0d6fb2
parent6ab0d1358fc78077064aab88a4fb0a47ca116391 (diff)
Fixed #26646 -- Added IOBase methods required by TextIOWrapper to File.
Thanks Tim for the review.
-rw-r--r--django/core/files/base.py4
-rw-r--r--django/core/files/temp.py9
-rw-r--r--django/core/files/utils.py30
-rw-r--r--docs/ref/files/file.txt9
-rw-r--r--docs/releases/1.11.txt4
-rw-r--r--docs/spelling_wordlist1
-rw-r--r--tests/files/tests.py45
7 files changed, 69 insertions, 33 deletions
diff --git a/django/core/files/base.py b/django/core/files/base.py
index a27d363658..11049a45de 100644
--- a/django/core/files/base.py
+++ b/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
diff --git a/django/core/files/temp.py b/django/core/files/temp.py
index f70d975dbe..39b61fd52e 100644
--- a/django/core/files/temp.py
+++ b/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()
diff --git a/django/core/files/utils.py b/django/core/files/utils.py
index 2656fa7188..8e891bf23f 100644
--- a/django/core/files/utils.py
+++ b/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)
diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt
index e88032ab66..6c218e38b3 100644
--- a/docs/ref/files/file.txt
+++ b/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
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index e2583a4dc4..6a1f1e0241 100644
--- a/docs/releases/1.11.txt
+++ b/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
~~~~~~~~~~~~
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index c9fb05f5ea..4355bbd065 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -972,6 +972,7 @@ wordcount
wordwrap
workflow
worksforme
+wrappable
wsgi
www
xe
diff --git a/tests/files/tests.py b/tests/files/tests.py
index 6eb7f30d61..d22020cb8c 100644
--- a/tests/files/tests.py
+++ b/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):