Skip to content
Merged
37 changes: 27 additions & 10 deletions Doc/library/imaplib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ Three classes are provided by the :mod:`imaplib` module, :class:`IMAP4` is the
base class:


.. class:: IMAP4(host='', port=IMAP4_PORT)
.. class:: IMAP4(host='', port=IMAP4_PORT, timeout=None)
Comment thread
corona10 marked this conversation as resolved.
Outdated

This class implements the actual IMAP4 protocol. The connection is created and
protocol version (IMAP4 or IMAP4rev1) is determined when the instance is
initialized. If *host* is not specified, ``''`` (the local host) is used. If
*port* is omitted, the standard IMAP4 port (143) is used.
*port* is omitted, the standard IMAP4 port (143) is used. The optional *timeout*
parameter specifies a timeout in seconds for the connection attempt.
If timeout is not given or is None, the global default socket timeout is used.

The :class:`IMAP4` class supports the :keyword:`with` statement. When used
like this, the IMAP4 ``LOGOUT`` command is issued automatically when the
Expand All @@ -50,6 +52,9 @@ base class:
.. versionchanged:: 3.5
Support for the :keyword:`with` statement was added.

.. versionchanged:: 3.9
The optional *timeout* parameter was added.

Three exceptions are defined as attributes of the :class:`IMAP4` class:


Expand Down Expand Up @@ -78,7 +83,7 @@ There's also a subclass for secure connections:


.. class:: IMAP4_SSL(host='', port=IMAP4_SSL_PORT, keyfile=None, \
certfile=None, ssl_context=None)
certfile=None, ssl_context=None, timeout=None)

This is a subclass derived from :class:`IMAP4` that connects over an SSL
encrypted socket (to use this class you need a socket module that was compiled
Expand All @@ -95,8 +100,12 @@ There's also a subclass for secure connections:
mutually exclusive with *ssl_context*, a :class:`ValueError` is raised
if *keyfile*/*certfile* is provided along with *ssl_context*.

The optional *timeout* parameter specifies a timeout in seconds for the
connection attempt. If timeout is not given or is None, the global default
socket timeout is used.

.. versionchanged:: 3.3
*ssl_context* parameter added.
*ssl_context* parameter was added.

.. versionchanged:: 3.4
The class now supports hostname check with
Expand All @@ -110,6 +119,8 @@ There's also a subclass for secure connections:
:func:`ssl.create_default_context` select the system's trusted CA
certificates for you.

.. versionchanged:: 3.9
The optional *timeout* parameter was added.

The second subclass allows for connections created by a child process:

Expand Down Expand Up @@ -353,16 +364,22 @@ An :class:`IMAP4` instance has the following methods:
Send ``NOOP`` to server.


.. method:: IMAP4.open(host, port)
.. method:: IMAP4.open(host, port, timeout=None)

Opens socket to *port* at *host*. This method is implicitly called by
the :class:`IMAP4` constructor. The connection objects established by this
method will be used in the :meth:`IMAP4.read`, :meth:`IMAP4.readline`,
:meth:`IMAP4.send`, and :meth:`IMAP4.shutdown` methods. You may override
this method.
Opens socket to *port* at *host*. The optional *timeout* parameter
specifies a timeout in seconds for the connection attempt.
If timeout is not given or is None, the global default socket timeout
is used. Also note that if the *timeout* parameter is set to be zero,
it will raise a :class:`ValueError` to reject creating a non-blocking socket.
This method is implicitly called by the :class:`IMAP4` constructor.
The connection objects established by this method will be used in
the :meth:`IMAP4.read`, :meth:`IMAP4.readline`, :meth:`IMAP4.send`,
and :meth:`IMAP4.shutdown` methods. You may override this method.

.. audit-event:: imaplib.open self,host,port imaplib.IMAP4.open

.. versionchanged:: 3.9
The *timeout* parameter was added.

.. method:: IMAP4.partial(message_num, message_part, start, length)

Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ When the garbage collector makes a collection in which some objects resurrect
been executed), do not block the collection of all objects that are still
unreachable. (Contributed by Pablo Galindo and Tim Peters in :issue:`38379`.)

imaplib
-------

:class:`~imaplib.IMAP4` and :class:`~imaplib.IMAP4_SSL` now have
an optional *timeout* parameter for their constructors.
Comment thread
corona10 marked this conversation as resolved.
Outdated
Also, the :meth:`~imaplib.IMAP4.open` method now has an optional *timeout* parameter
with this change. The overridden methods of :class:`~imaplib.IMAP4_SSL` and
:class:`~imaplib.IMAP4_stream` were applied to this change.
(Contributed by Dong-hee Na in :issue:`38615`.)

os
--

Expand Down
40 changes: 25 additions & 15 deletions Lib/imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,13 @@ class IMAP4:

r"""IMAP4 client class.

Instantiate with: IMAP4([host[, port]])
Instantiate with: IMAP4([host[, port[, timeout=None]]])

host - host's name (default: localhost);
port - port number (default: standard IMAP4 port).
timeout - socket timeout (default: None)
If timeout is not given or is None,
the global default socket timeout is used

All IMAP4rev1 commands are supported by methods of the same
name (in lower-case).
Expand Down Expand Up @@ -181,7 +184,7 @@ class error(Exception): pass # Logical errors - debug required
class abort(error): pass # Service errors - close and retry
class readonly(abort): pass # Mailbox status changed to READ-ONLY

def __init__(self, host='', port=IMAP4_PORT):
def __init__(self, host='', port=IMAP4_PORT, timeout=None):
self.debug = Debug
self.state = 'LOGOUT'
self.literal = None # A literal argument to a command
Expand All @@ -195,7 +198,7 @@ def __init__(self, host='', port=IMAP4_PORT):

# Open socket to server.

self.open(host, port)
self.open(host, port, timeout)

try:
self._connect()
Expand Down Expand Up @@ -284,23 +287,28 @@ def __exit__(self, *args):
# Overridable methods


def _create_socket(self):
def _create_socket(self, timeout):
# Default value of IMAP4.host is '', but socket.getaddrinfo()
# (which is used by socket.create_connection()) expects None
# as a default value for host.
if timeout is not None and not timeout:
raise ValueError('Non-blocking socket (timeout=0) is not supported')
host = None if not self.host else self.host
sys.audit("imaplib.open", self, self.host, self.port)
return socket.create_connection((host, self.port))
address = (host, self.port)
if timeout is not None:
return socket.create_connection(address, timeout)
return socket.create_connection(address)

def open(self, host = '', port = IMAP4_PORT):
def open(self, host='', port=IMAP4_PORT, timeout=None):
"""Setup connection to remote server on "host:port"
(default: localhost:standard IMAP4 port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
self.host = host
self.port = port
self.sock = self._create_socket()
self.sock = self._create_socket(timeout)
self.file = self.sock.makefile('rb')


Expand Down Expand Up @@ -1261,7 +1269,7 @@ class IMAP4_SSL(IMAP4):

"""IMAP4 client class over SSL connection

Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context[, timeout=None]]]]]])

host - host's name (default: localhost);
port - port number (default: standard IMAP4 SSL port);
Expand All @@ -1271,13 +1279,15 @@ class IMAP4_SSL(IMAP4):
and private key (default: None)
Note: if ssl_context is provided, then parameters keyfile or
certfile should not be set otherwise ValueError is raised.
timeout - socket timeout (default: None) If timeout is not given or is None,
the global default socket timeout is used

for more documentation see the docstring of the parent class IMAP4.
"""


def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
certfile=None, ssl_context=None):
certfile=None, ssl_context=None, timeout=None):
if ssl_context is not None and keyfile is not None:
raise ValueError("ssl_context and keyfile arguments are mutually "
"exclusive")
Expand All @@ -1294,20 +1304,20 @@ def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None,
ssl_context = ssl._create_stdlib_context(certfile=certfile,
keyfile=keyfile)
self.ssl_context = ssl_context
IMAP4.__init__(self, host, port)
IMAP4.__init__(self, host, port, timeout)

def _create_socket(self):
sock = IMAP4._create_socket(self)
def _create_socket(self, timeout):
sock = IMAP4._create_socket(self, timeout)
return self.ssl_context.wrap_socket(sock,
server_hostname=self.host)

def open(self, host='', port=IMAP4_SSL_PORT):
def open(self, host='', port=IMAP4_SSL_PORT, timeout=None):
"""Setup connection to remote server on "host:port".
(default: localhost:standard IMAP4 SSL port).
This connection will be used by the routines:
read, readline, send, shutdown.
"""
IMAP4.open(self, host, port)
IMAP4.open(self, host, port, timeout)

__all__.append("IMAP4_SSL")

Expand All @@ -1329,7 +1339,7 @@ def __init__(self, command):
IMAP4.__init__(self)


def open(self, host = None, port = None):
def open(self, host=None, port=None, timeout=None):
"""Setup a stream connection.
This connection will be used by the routines:
read, readline, send, shutdown.
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,29 @@ def test_simple_with_statement(self):
with self.imap_class(*server.server_address):
pass

def test_imaplib_timeout_test(self):
_, server = self._setup(SimpleIMAPHandler)
addr = server.server_address[1]
client = self.imap_class("localhost", addr, timeout=None)
self.assertEqual(client.sock.timeout, None)
client.shutdown()
client = self.imap_class("localhost", addr, timeout=support.LOOPBACK_TIMEOUT)
self.assertEqual(client.sock.timeout, support.LOOPBACK_TIMEOUT)
client.shutdown()
with self.assertRaises(ValueError):
client = self.imap_class("localhost", addr, timeout=0)

def test_imaplib_timeout_functionality_test(self):
class TimeoutHandler(SimpleIMAPHandler):
def handle(self):
time.sleep(1)
SimpleIMAPHandler.handle(self)

_, server = self._setup(TimeoutHandler)
addr = server.server_address[1]
with self.assertRaises(socket.timeout):
client = self.imap_class("localhost", addr, timeout=0.001)

def test_with_statement(self):
_, server = self._setup(SimpleIMAPHandler, connect=False)
with self.imap_class(*server.server_address) as imap:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:class:`~imaplib.IMAP4` and :class:`~imaplib.IMAP4_SSL` now have an
optional *timeout* parameter for their constructors.
Also, the :meth:`~imaplib.IMAP4.open` method now has an optional *timeout* parameter
with this change. The overridden methods of :class:`~imaplib.IMAP4_SSL` and
:class:`~imaplib.IMAP4_stream` were applied to this change. Patch by Dong-hee Na.