Skip to content

Commit 5dcd4f4

Browse files
committed
gh-142307: restore legacy support for altering IMAP4.file
1 parent 5a639ca commit 5dcd4f4

3 files changed

Lines changed: 46 additions & 23 deletions

File tree

Lib/imaplib.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -316,25 +316,30 @@ def open(self, host='', port=IMAP4_PORT, timeout=None):
316316
self.host = host
317317
self.port = port
318318
self.sock = self._create_socket(timeout)
319-
self._file = self.sock.makefile('rb')
320-
319+
# Since IMAP4 implements its own read() and readline() buffering,
320+
# the '_imaplib_file' attribute is unused. Nonetheless it is kept
321+
# and exposed solely for backward compatibility purposes.
322+
self._imaplib_file = self.sock.makefile('rb')
321323

322324
@property
323325
def file(self):
324-
# The old 'file' attribute is no longer used now that we do our own
325-
# read() and readline() buffering, with which it conflicts.
326-
# As an undocumented interface, it should never have been accessed by
327-
# external code, and therefore does not warrant deprecation.
328-
# Nevertheless, we provide this property for now, to avoid suddenly
329-
# breaking any code in the wild that might have been using it in a
330-
# harmless way.
331-
import warnings
332-
warnings.warn(
333-
'IMAP4.file is unsupported, can cause errors, and may be removed.',
334-
RuntimeWarning,
335-
stacklevel=2)
336-
return self._file
337-
326+
return self._imaplib_file
327+
328+
@file.setter
329+
def file(self, value):
330+
# Ideally, we would want to close the previous file,
331+
# but since we do not know how subclasses will use
332+
# that setter, it is probably better to leave it to
333+
# the caller.
334+
self._imaplib_file = value
335+
336+
def _close_imaplib_file(self):
337+
file = self._imaplib_file
338+
if file is not None:
339+
try:
340+
file.close()
341+
except OSError:
342+
pass
338343

339344
def read(self, size):
340345
"""Read 'size' bytes from remote."""
@@ -420,7 +425,7 @@ def send(self, data):
420425

421426
def shutdown(self):
422427
"""Close I/O established in "open"."""
423-
self._file.close()
428+
self._close_imaplib_file()
424429
try:
425430
self.sock.shutdown(socket.SHUT_RDWR)
426431
except OSError as exc:
@@ -924,9 +929,10 @@ def starttls(self, ssl_context=None):
924929
ssl_context = ssl._create_stdlib_context()
925930
typ, dat = self._simple_command(name)
926931
if typ == 'OK':
932+
self._close_imaplib_file()
927933
self.sock = ssl_context.wrap_socket(self.sock,
928934
server_hostname=self.host)
929-
self._file = self.sock.makefile('rb')
935+
self._imaplib_file = self.sock.makefile('rb')
930936
self._tls_established = True
931937
self._get_capabilities()
932938
else:
@@ -1681,7 +1687,7 @@ def open(self, host=None, port=None, timeout=None):
16811687
self.host = None # For compatibility with parent class
16821688
self.port = None
16831689
self.sock = None
1684-
self._file = None
1690+
self._imaplib_file = None
16851691
self.process = subprocess.Popen(self.command,
16861692
bufsize=DEFAULT_BUFFER_SIZE,
16871693
stdin=subprocess.PIPE, stdout=subprocess.PIPE,

Lib/test/test_imaplib.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -665,11 +665,26 @@ def test_unselect(self):
665665

666666
# property tests
667667

668-
def test_file_property_should_not_be_accessed(self):
668+
def test_file_property_getter(self):
669669
client, _ = self._setup(SimpleIMAPHandler)
670-
# the 'file' property replaced a private attribute that is now unsafe
671-
with self.assertWarns(RuntimeWarning):
672-
client.file
670+
self.assertIsInstance(client.file.raw, socket.SocketIO)
671+
672+
def test_file_property_setter(self):
673+
client, _ = self._setup(SimpleIMAPHandler)
674+
# ensure that the caller closes the existing file
675+
client.file.close()
676+
for new_file in [mock.Mock(), None]:
677+
client.file = new_file
678+
self.assertIs(client.file, new_file)
679+
680+
def test_file_property_setter_should_not_close_previous_file(self):
681+
client, _ = self._setup(SimpleIMAPHandler)
682+
with mock.patch.object(client, "_imaplib_file", mock.Mock()) as f:
683+
f.close.assert_not_called()
684+
self.assertIs(client.file, f)
685+
client.file = None
686+
self.assertIsNone(client.file)
687+
f.close.assert_not_called()
673688

674689

675690
class NewIMAPTests(NewIMAPTestsMixin, unittest.TestCase):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`imaplib`: restore legacy support for altering ``IMAP4.file``. Patch by
2+
Bénédikt Tran.

0 commit comments

Comments
 (0)