Skip to content

Commit

Permalink
gh-55454: Clarify imaplib idle() docs
Browse files Browse the repository at this point in the history
- Add example idle response tuples, to make the minor difference from other
  imaplib response tuples more obvious.
- Merge the idle context manager's burst() method docs with the IMAP
  object's idle() method docs, for easier understanding.
- Upgrade the Windows note regarding lack of pipe timeouts to a warning.
- Rephrase various things for clarity.
  • Loading branch information
foresto committed Sep 11, 2024
1 parent 572d1e5 commit 9dd2562
Showing 1 changed file with 65 additions and 78 deletions.
143 changes: 65 additions & 78 deletions Doc/library/imaplib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -308,22 +308,28 @@ An :class:`IMAP4` instance has the following methods:
of the IMAP4 QUOTA extension defined in rfc2087.


.. method:: IMAP4.idle([dur])
.. method:: IMAP4.idle(dur=None)

Return an iterable context manager implementing the ``IDLE`` command
as defined in :rfc:`2177`.

The optional *dur* argument specifies a maximum duration (in seconds) to
keep idling. It defaults to ``None``, meaning no time limit.
To avoid inactivity timeouts on servers that impose them, callers are
advised to keep this <= 29 minutes. See the note below regarding
The context manager sends the ``IDLE`` command when activated by the
:keyword:`with` statement, produces IMAP untagged responses via the
:term:`iterator` protocol, and sends ``DONE`` upon context exit.

The *dur* argument sets a maximum duration (in seconds) to keep idling,
after which iteration will stop. It defaults to ``None``, meaning no time
limit. Callers wishing to avoid inactivity timeouts on servers that impose
them should keep this <= 29 minutes.
See the :ref:`warning below <windows-pipe-timeout-warning>` if using
:class:`IMAP4_stream` on Windows.

The context manager sends the ``IDLE`` command upon entry, produces
responses via iteration, and sends ``DONE`` upon exit.
It represents responses as ``(type, datum)`` tuples, rather than the
``(type, [data, ...])`` tuples returned by other methods, because only
one response is represented at a time.
Response tuples produced by the iterator almost exactly match those
returned by other imaplib methods. The difference is that the tuple's
second member is a single response datum, rather than a list of data.
Therefore, in a mailbox where calling ``imap.response('EXISTS')`` would
return ``('EXISTS', [b'1'])``, the idle iterator would produce
``('EXISTS', b'1')``.

Example::

Expand All @@ -332,22 +338,59 @@ An :class:`IMAP4` instance has the following methods:
typ, datum = response
print(typ, datum)

It is also possible to process a burst of responses all at once instead
of one at a time. See `IDLE Context Manager`_ for details.
('EXISTS', b'1')
('RECENT', b'1')

Responses produced by the iterator will not be returned by
:meth:`IMAP4.response`.
Instead of iterating one response at a time, it is also possible to retrieve
the next response along with any immediately available subsequent responses
(e.g. a rapid series of ``EXPUNGE`` events from a bulk delete). This
batch processing aid is provided by the context's ``burst()``
:term:`generator`:

.. note::
.. method:: idler.burst(interval=0.1)

Yield a burst of responses no more than *interval* seconds apart.

Example::

with M.idle() as idler:

# get the next response and any others following by < 0.1 seconds
batch = list(idler.burst())

print(f'processing {len(batch)} responses...')
print(batch)

processing 3 responses...
[('EXPUNGE', b'2'), ('EXPUNGE', b'1'), ('RECENT', b'0')]

The ``IDLE`` context's maximum duration (the *dur* argument to
:meth:`IMAP4.idle`) is respected when waiting for the first response
in a burst. Therefore, an expired idle context will cause this generator
to return immediately without producing anything. Callers should
consider this if using it in a loop.


.. _windows-pipe-timeout-warning:

.. warning::

Windows :class:`IMAP4_stream` connections have no way to accurately
respect *dur*, since Windows ``select()`` only works on sockets.
However, if the server regularly sends status messages during ``IDLE``,
they will wake our selector and keep iteration from blocking for long.
Dovecot's ``imap_idle_notify_interval`` is two minutes by default.
Assuming that's typical of IMAP servers, subtracting it from the 29
minutes needed to avoid server inactivity timeouts would make 27
minutes a sensible value for *dur* in this situation.
respect the *dur* or *interval* arguments, since Windows ``select()``
only works on sockets.

If the server regularly sends status messages during ``IDLE``, they will
wake our iterator anyway, allowing *dur* to behave roughly as intended,
although usually late. Dovecot's ``imap_idle_notify_interval`` default
setting does this every 2 minutes. Assuming that's typical of IMAP
servers, subtracting it from the 29 minutes needed to avoid server
inactivity timeouts would make 27 minutes a sensible value for *dur* in
this situation.

There is no such fallback for ``burst()``, which will yield endless
responses and block indefinitely for each one. It is therefore advised
not to use ``burst()`` with an :class:`IMAP4_stream` connection on
Windows.


.. method:: IMAP4.list([directory[, pattern]])
Expand Down Expand Up @@ -655,62 +698,6 @@ The following attributes are defined on instances of :class:`IMAP4`:
.. versionadded:: 3.5


.. _idle context manager:

IDLE Context Manager
--------------------

The object returned by :meth:`IMAP4.idle` implements the context management
protocol for the :keyword:`with` statement, and the :term:`iterator` protocol
for retrieving untagged responses while the context is active.
It also has the following method:

.. method:: IdleContextManager.burst([interval])

Yield a burst of responses no more than *interval* seconds apart.

This generator retrieves the next response along with any
immediately available subsequent responses (e.g. a rapid series of
``EXPUNGE`` responses after a bulk delete) so they can be efficiently
processed as a batch instead of one at a time.

The optional *interval* argument specifies a time limit (in seconds)
for each response after the first. It defaults to 0.1 seconds.
(The ``IDLE`` context's maximum duration is respected when waiting for the
first response.)

Represents responses as ``(type, datum)`` tuples, just as when
iterating directly on the context manager.

Example::

with M.idle() as idler:

# get the next response and any others following by < 0.1 seconds
batch = list(idler.burst())

print(f'processing {len(batch)} responses...')
for typ, datum in batch:
print(typ, datum)

Produces no responses and returns immediately if the ``IDLE`` context's
maximum duration (the *dur* argument to :meth:`IMAP4.idle`) has elapsed.
Callers should plan accordingly if using this method in a loop.

.. note::

Windows :class:`IMAP4_stream` connections will ignore the *interval*
argument, yielding endless responses and blocking indefinitely for each
one, since Windows ``select()`` only works on sockets. It is therefore
advised not to use this method with an :class:`IMAP4_stream` connection
on Windows.

.. note::

The context manager's type name is not part of its public interface,
and is subject to change.


.. _imap4-example:

IMAP4 Example
Expand Down

0 comments on commit 9dd2562

Please sign in to comment.