Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-55454: Add IMAP4 IDLE support to imaplib #122542

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
94 changes: 93 additions & 1 deletion Doc/library/imaplib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
.. changes for IMAP4_SSL by Tino Lange <[email protected]>, March 2002
.. changes for IMAP4_stream by Piers Lauder <[email protected]>,
November 2002
.. changes for IDLE by Forest <[email protected]> August 2024

**Source code:** :source:`Lib/imaplib.py`

Expand Down Expand Up @@ -187,7 +188,7 @@ However, the *password* argument to the ``LOGIN`` command is always quoted. If
you want to avoid having an argument string quoted (eg: the *flags* argument to
``STORE``) then enclose the string in parentheses (eg: ``r'(\Deleted)'``).

Each command returns a tuple: ``(type, [data, ...])`` where *type* is usually
Most commands return a tuple: ``(type, [data, ...])`` where *type* is usually
``'OK'`` or ``'NO'``, and *data* is either the text from the command response,
or mandated results from the command. Each *data* is either a ``bytes``, or a
tuple. If a tuple, then the first part is the header of the response, and the
Expand Down Expand Up @@ -307,6 +308,97 @@ An :class:`IMAP4` instance has the following methods:
of the IMAP4 QUOTA extension defined in rfc2087.


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

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

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 at most 29 minutes.
See the :ref:`warning below <windows-pipe-timeout-warning>` if using
:class:`IMAP4_stream` on Windows.

Response tuples produced by the iterator almost exactly match those
returned by other methods in this module. 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 ``M.response('EXISTS')`` would
return ``('EXISTS', [b'1'])``, the idle iterator would produce
``('EXISTS', b'1')``.

Example::

with M.idle(dur=29 * 60) as idler:
for typ, datum in idler:
print(typ, datum)

('EXISTS', b'1')
('RECENT', b'1')

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`:

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

.. note::

Note: The :class:`!Idler` class name and structure are internal interfaces,
subject to change. Calling code can rely on its context management,
iteration, and public method to remain stable, but should not
subclass, instantiate, or otherwise directly reference the class.


.. method:: IMAP4.list([directory[, pattern]])

List mailbox names in *directory* matching *pattern*. *directory* defaults to
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ Added support for converting any objects that have the
:meth:`!as_integer_ratio` method to a :class:`~fractions.Fraction`.
(Contributed by Serhiy Storchaka in :gh:`82017`.)

imaplib
-------

* Add :meth:`~imaplib.IMAP4.idle`, implementing the ``IDLE`` command
as defined in :rfc:`2177`. (Contributed by Forest in :gh:`55454`.)

json
----
Expand Down
Loading
Loading