Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/deprecations/pending-removal-in-3.19.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ Pending removal in Python 3.19
supported depending on the backend implementation of hash functions.
Prefer passing the initial data as a positional argument for maximum
backwards compatibility.

* :mod:`imaplib`:

* Altering :attr:`IMAP4.file <imaplib.IMAP4.file>` is now deprecated
and slated for removal in Python 3.19. This property is now unused
and changing its value does not automatically close the current file.

Before Python 3.14, this property was used to implement the corresponding
``read()`` and ``readline()`` methods for :class:`~imaplib.IMAP4` but this
is no longer the case since then.
10 changes: 10 additions & 0 deletions Doc/library/imaplib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,16 @@ The following attributes are defined on instances of :class:`IMAP4`:
.. versionadded:: 3.5


.. property:: IMAP4.file

Internal :class:`~io.BufferedReader` associated with the underlying socket.
This property is documented for legacy purposes but not part of the public
interface. The caller is responsible to ensure that the current file is
closed before changing it.

.. deprecated-removed:: next 3.19


.. _imap4-example:

IMAP4 Example
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,12 @@ New deprecations

(Contributed by Bénédikt Tran in :gh:`134978`.)

* :mod:`imaplib`:

* Altering :attr:`IMAP4.file <imaplib.IMAP4.file>` is now deprecated
and slated for removal in Python 3.19. This property is now unused
and changing its value does *not* explicitly close the current file.

* ``__version__``

* The ``__version__`` attribute has been deprecated in these standard library
Expand Down
44 changes: 27 additions & 17 deletions Lib/imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,25 +313,34 @@ def open(self, host='', port=IMAP4_PORT, timeout=None):
self.host = host
self.port = port
self.sock = self._create_socket(timeout)
self._file = self.sock.makefile('rb')

# Since IMAP4 implements its own read() and readline() buffering,
# the '_imaplib_file' attribute is unused. Nonetheless it is kept
# and exposed solely for backward compatibility purposes.
self._imaplib_file = self.sock.makefile('rb')

@property
def file(self):
# The old 'file' attribute is no longer used now that we do our own
# read() and readline() buffering, with which it conflicts.
# As an undocumented interface, it should never have been accessed by
# external code, and therefore does not warrant deprecation.
# Nevertheless, we provide this property for now, to avoid suddenly
# breaking any code in the wild that might have been using it in a
# harmless way.
import warnings
warnings.warn(
'IMAP4.file is unsupported, can cause errors, and may be removed.',
RuntimeWarning,
stacklevel=2)
return self._file
warnings._deprecated("IMAP4.file", remove=(3, 19))
return self._imaplib_file

@file.setter
def file(self, value):
import warnings
warnings._deprecated("IMAP4.file", remove=(3, 19))
# Ideally, we would want to close the previous file,
# but since we do not know how subclasses will use
# that setter, it is probably better to leave it to
# the caller.
self._imaplib_file = value

def _close_imaplib_file(self):
file = self._imaplib_file
if file is not None:
try:
file.close()
except OSError:
pass

def read(self, size):
"""Read 'size' bytes from remote."""
Expand Down Expand Up @@ -417,7 +426,7 @@ def send(self, data):

def shutdown(self):
"""Close I/O established in "open"."""
self._file.close()
self._close_imaplib_file()
try:
self.sock.shutdown(socket.SHUT_RDWR)
except OSError as exc:
Expand Down Expand Up @@ -921,9 +930,10 @@ def starttls(self, ssl_context=None):
ssl_context = ssl._create_stdlib_context()
typ, dat = self._simple_command(name)
if typ == 'OK':
self._close_imaplib_file()
self.sock = ssl_context.wrap_socket(self.sock,
server_hostname=self.host)
self._file = self.sock.makefile('rb')
self._imaplib_file = self.sock.makefile('rb')
self._tls_established = True
self._get_capabilities()
else:
Expand Down Expand Up @@ -1678,7 +1688,7 @@ def open(self, host=None, port=None, timeout=None):
self.host = None # For compatibility with parent class
self.port = None
self.sock = None
self._file = None
self._imaplib_file = None
self.process = subprocess.Popen(self.command,
bufsize=DEFAULT_BUFFER_SIZE,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
Expand Down
30 changes: 26 additions & 4 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,11 +659,33 @@ def test_unselect(self):

# property tests

def test_file_property_should_not_be_accessed(self):
def test_file_property_getter(self):
client, _ = self._setup(SimpleIMAPHandler)
# the 'file' property replaced a private attribute that is now unsafe
with self.assertWarns(RuntimeWarning):
client.file
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(client.file.raw, socket.SocketIO)

def test_file_property_setter(self):
client, _ = self._setup(SimpleIMAPHandler)
with self.assertWarns(DeprecationWarning):
# ensure that the caller closes the existing file
client.file.close()
for new_file in [mock.Mock(), None]:
with self.assertWarns(DeprecationWarning):
client.file = new_file
with self.assertWarns(DeprecationWarning):
self.assertIs(client.file, new_file)

def test_file_property_setter_should_not_close_previous_file(self):
client, _ = self._setup(SimpleIMAPHandler)
with mock.patch.object(client, "_imaplib_file", mock.Mock()) as f:
f.close.assert_not_called()
with self.assertWarns(DeprecationWarning):
self.assertIs(client.file, f)
with self.assertWarns(DeprecationWarning):
client.file = None
with self.assertWarns(DeprecationWarning):
self.assertIsNone(client.file)
f.close.assert_not_called()


class NewIMAPTests(NewIMAPTestsMixin, unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:mod:`imaplib`: deprecate support for :attr:`IMAP4.file <imaplib.IMAP4.file>`.
This attribute was never meant to be part of the public interface and altering
its value may result in unclosed files or other synchronization issues with
the underlying socket. Patch by Bénédikt Tran.
Loading