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

Thanks Tim for the review.
This commit is contained in:
Simon Charette 2016-05-22 12:43:56 -04:00
parent 6ab0d1358f
commit 4f474607de
No known key found for this signature in database
GPG Key ID: 72AF89A0B1B4EDB3
7 changed files with 69 additions and 33 deletions

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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
~~~~~~~~~~~~

View File

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

View File

@ -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):