8000 gh-55454: Add IMAP4 IDLE support to imaplib by foresto · Pull Request #122542 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

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

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

Merged
merged 80 commits into from
Feb 7, 2025
Merged
Changes from 1 commit
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
572d1e5
gh-55454: Add IMAP4 IDLE support to imaplib
foresto Jul 31, 2024
dc71241
gh-55454: Clarify imaplib idle() docs
foresto Sep 11, 2024
f41f2bb
docs: words instead of <=
gpshead Sep 21, 2024
8077f2e
docs: improve style in an example
gpshead Sep 21, 2024
24fcdbb
docs: grammatical edit
gpshead Sep 21, 2024
2c76b2f
docs consistency
gpshead Sep 21, 2024
5ef5bb2
comment -> docstring
gpshead Sep 21, 2024
91266e0
docs: refer to imaplib as "this module"
foresto Sep 22, 2024
19c98dc
imaplib: simplify & clarify idle debug message
foresto Sep 22, 2024
882bf2c
imaplib: elaborate in idle context manager comment
foresto Sep 22, 2024
f648fec
imaplib: re-raise BaseException instead of bare except
foresto Sep 22, 2024
48f6f76
imaplib: convert private doc string to comment
foresto Sep 22, 2024
013bbf1
docs: correct mistake in imaplib example
foresto Sep 22, 2024
acbc4a1
imaplib: simplify example code in doc string
foresto Sep 23, 2024
94c02e8
imaplib: rename _Idler to Idler, update its docs
foresto Sep 23, 2024
0c6c9a4
imaplib: add comment in Idler._pop()
foresto Sep 23, 2024
223c2fa
8000 imaplib: remove unnecessary blank line
foresto Sep 23, 2024
e64546c
imaplib: comment on use of unbuffered pipes
foresto Sep 23, 2024
f385e44
docs: imaplib: use the reStructuredText :class: role
foresto Sep 24, 2024
3aceaec
Revert "docs: imaplib: use the reStructuredText :class: role"
foresto Sep 24, 2024
c8d4d6d
docs: imaplib: use the reST :class: role, escaped
foresto Sep 26, 2024
75fbe8e
Merge branch 'main' into imaplib-idle
gvanrossum Dec 1, 2024
c7ed3c5
docs: refer to IMAP4 IDLE instead of just IDLE
foresto Dec 1, 2024
b01de95
imaplib: IDLE -> IMAP4 IDLE in exception message
foresto Dec 1, 2024
a3f21cd
docs: imaplib idle() phrasing and linking tweaks
foresto Dec 1, 2024
247e6b5
docs: imaplib: avoid linking to an invalid target
foresto Dec 2, 2024
79e3d83
imaplib: update test after recent exception change
foresto Dec 2, 2024
14dfd21
imaplib: rename idle() dur argument to duration
foresto Dec 2, 2024
19253d1
imaplib: bytes.index() -> bytes.find()
foresto Dec 2, 2024
b65074e
imaplib: remove no-longer-necessary statement
foresto Dec 2, 2024
5d8a40b
docs: imaplib: concise & valid method links
foresto Dec 3, 2024
fc13f75
imaplib: note data types present in IDLE responses
foresto Dec 3, 2024
3221dbc
docs: imaplib: add comma to reST changes header
foresto Dec 4, 2024
564c722
imaplib: sync doc strings with reST docs
foresto Dec 7, 2024
c9e8034
docs: imaplib: minor Idler clarifications
foresto Dec 7, 2024
9c4af2c
imaplib: idle: emit (type, [data, ...]) tuples
foresto Dec 7, 2024
59e0c6a
imaplib: while/yield instead of yield from iter()
foresto Dec 8, 2024
2e3e956
imaplib: idle: use deadline idiom when iterating
foresto Dec 9, 2024
bdde943
docs: imaplib: state duration/interval arg types
foresto Dec 9, 2024
b64c7a5
docs: imaplib: minor rephrasing of a sentence
foresto Dec 9, 2024
b73a365
docs: imaplib: reposition a paragraph
foresto Dec 9, 2024
ae74499
docs: imaplib: wrap long lines in idle() section
foresto Dec 9, 2024
92d7ce7
docs: imaplib: note: Idler objects require 'with'
foresto Dec 9, 2024
171ebf1
docs: imaplib: say that 29 minutes is 1740 seconds
foresto Dec 9, 2024
5682ef4
docs: imaplib: mark a paragraph as a 'tip'
foresto Dec 10, 2024
656e9f5
docs: imaplib: rephrase reference to MS Windows
foresto Dec 10, 2024
3b70534
imaplib: end doc string titles with a period
foresto Dec 10, 2024
60e2b6f
imaplib: idle: socket timeouts instead of select()
foresto Dec 15, 2024
def6ab5
imaplib: Idler: rename private state attributes
foresto Dec 15, 2024
8e0b6b0
imaplib: rephrase a comment in example code
foresto Dec 15, 2024
08a4536
docs: imaplib: idle: use Sphinx code-block:: pycon
foresto Dec 15, 2024
c881c8b
docs: whatsnew: imaplib: reformat IMAP4.idle entry
foresto Dec 15, 2024
266a292
imaplib: idle: make doc strings brief
foresto Dec 16, 2024
055a9bd
imaplib: Idler: split assert into two statements
foresto Dec 16, 2024
4d3f020
imaplib: Idler: move assignment out of try: block
foresto Dec 16, 2024
80aaf8d
imaplib: Idler: move __exit__() for readability
foresto Dec 16, 2024
03b5205
imaplib: Idler: move __next__() for readability
foresto Dec 16, 2024
83c8946
imaplib: test: make IdleCmdHandler a global class
foresto Dec 16, 2024
be2d2b0
docs: imaplib: idle: collapse double-spaces
foresto Dec 16, 2024
de62a3e
imaplib: warn on use of undocumented 'file' attr
foresto Dec 17, 2024
7fc8a24
imaplib: revert import reformatting
foresto Dec 17, 2024
33a1fed
imaplib: restore original exception msg formatting
foresto Dec 17, 2024
40e607a
docs: imaplib: idle: versionadded:: next
foresto Dec 17, 2024
9a07f3b
imaplib: move import statement to where it's used
foresto Dec 17, 2024
f47de53
imaplib test: RuntimeWarning on IMAP4.file access
foresto Dec 17, 2024
66d32a0
imaplib: use stacklevel=2 in warnings.warn()
foresto Dec 17, 2024
d619580
imaplib test: simplify IMAP4.file warning test
foresto Dec 17, 2024
fc8b6f4
imaplib test: pre-idle-continuation response
foresto Dec 21, 2024
b767ab6
imaplib test: post-done untagged response
foresto Dec 21, 2024
dcd0161
imaplib: downgrade idle-denied exception to error
foresto Dec 22, 2024
53c7a19
imaplib: simplify check for socket object
foresto Jan 9, 2025
fcaf355
imaplib: narrow the scope of IDLE socket timeouts
foresto Jan 9, 2025
a47bcb4
imaplib: preserve partial reads on exception
foresto Jan 10, 2025
7fc4b78
imaplib: read/readline: save multipart buffer tail
foresto Jan 10, 2025
be34141
imaplib: use TimeoutError subclass only if needed
foresto Jan 20, 2025
e8a8509
doc: imaplib: elaborate on IDLE response delivery
foresto Jan 20, 2025
8d78010
doc: imaplib: elaborate in note re: IMAP4.response
foresto Jan 20, 2025
e8f0532
Merge branch 'main' into imaplib-idle
foresto Feb 4, 2025
f650dfa
imaplib: comment on benefit of reading in chunks
foresto Feb 4, 2025
3512858
imaplib: readline(): treat ConnectionError as EOF
foresto Feb 5, 2025
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
Prev Previous commit
Next Next commit
imaplib: narrow the scope of IDLE socket timeouts
If an IDLE duration or burst() was in use, and an unsolicited response
contained a literal string, and crossed a packet boundary, and the
subsequent packet was delayed beyond the IDLE feature's time limit, the
timeout would leave the incoming protocol stream in a bad state (with
the tail of that response appearing where the start of a response is
expected).

This change moves the IDLE socket timeout to cover only the start
of a response, so it can no longer cause that problem.
  • Loading branch information
foresto committed Jan 9, 2025
commit fcaf355eb0b628e5bcb226ba7d5c49976dc72919
41 changes: 22 additions & 19 deletions Lib/imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class IMAP4:
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
class _responsetimeout(TimeoutError): pass # No response during IDLE

def __init__(self, host='', port=IMAP4_PORT, timeout=None):
self.debug = Debug
Expand Down Expand Up @@ -1154,14 +1155,28 @@ def _get_capabilities(self):
self.capabilities = tuple(dat.split())


def _get_response(self):
def _get_response(self, start_timeout=False):

# Read response and store.
#
# Returns None for continuation responses,
# otherwise first response line received.

resp = self._get_line()
#
# If start_timeout is given, temporarily uses it as a socket
# timeout while waiting for the start of a response, raising
# _responsetimeout if one doesn't arrive. (Used by Idler.)

if start_timeout is not False and self.sock:
assert start_timeout is None or start_timeout > 0
saved_timeout = self.sock.gettimeout()
self.sock.settimeout(start_timeout)
try:
resp = self._get_line()
except TimeoutError as err:
raise self._responsetimeout from err
finally:
if start_timeout is not False and self.sock:
self.sock.settimeout(saved_timeout)

# Command completion response?

Expand Down Expand Up @@ -1386,7 +1401,6 @@ def __init__(self, imap, duration=None):
self._deadline = None
self._imap = imap
self._tag = None
self._saved_timeout = None
self._saved_state = None

def __enter__(self):
Expand Down Expand Up @@ -1424,10 +1438,6 @@ def __enter__(self):
imap._idle_capture = False
raise

self._saved_timeout = imap.sock.gettimeout() if imap.sock else None
if self._saved_timeout is not None:
imap.sock.settimeout(None) # Socket timeout would break IDLE

if self._duration is not None:
self._deadline = time.monotonic() + self._duration

Expand All @@ -1443,10 +1453,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
imap._mesg('idle done')
imap.state = self._saved_state

if self._saved_timeout is not None:
imap.sock.settimeout(self._saved_timeout)
self._saved_timeout = None

# Stop intercepting untagged responses before sending DONE,
# since we can no longer deliver them via iteration.
imap._idle_capture = False
Expand Down Expand Up @@ -1514,19 +1520,16 @@ def _pop(self, timeout, default=('', None)):
imap._mesg(f'idle _pop({timeout}) reading')

if timeout is not None:
assert isinstance(imap.sock, socket.socket)
if timeout <= 0:
return default
imap.sock.settimeout(float(timeout))
timeout = float(timeout) # Required by socket.settimeout()

try:
imap._get_response() # Reads line, calls _append_untagged()
except TimeoutError:
imap._get_response(timeout) # Reads line, calls _append_untagged()
except IMAP4._responsetimeout:
if __debug__ and imap.debug >= 4:
imap._mesg(f'idle _pop({timeout}) done')
return default
finally:
if timeout is not None:
imap.sock.settimeout(None)

resp = imap._idle_responses.pop(0)

Expand Down
Loading
0