Fix starttls() compatibility with Python 3.14 - #663
Merged
Merged
Conversation
Python 3.14 turned imaplib.IMAP4.file into a read-only property, so the
manual socket wrap + file reassignment in starttls() crashed on every
STARTTLS connection:
AttributeError: property 'file' of 'IMAP4WithTimeout' object has no setter
Delegate to imaplib.IMAP4.starttls(), which manages the wrap and its own
read buffer (self._file on 3.14+) in the way the running Python expects -
the same direction as mjs#641. IMAPClient's documented default of a verifying
context is preserved by passing tls.create_default_context() explicitly
(imaplib's own None-fallback, ssl._create_stdlib_context(), does not
verify certificates); the helper is now shared with tls.wrap_socket().
Fixes mjs#662
LarsArtmann
force-pushed
the
fix-starttls-py314
branch
from
September 16, 2026 10:48
b2f92af to
9abf7cb
Compare
Owner
|
Thanks for the catch and the comprehensive solution. Having the integration test is excellent. |
mjs
approved these changes
Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Python 3.14 turned
imaplib.IMAP4.fileinto a read-only property, so every STARTTLS connection now dies:starttls()still does the manual socket wrap +self._imap.file = ...reassignment (imapclient/imapclient.py:387on master). #641 removed the same assignment fromIMAP4WithTimeout.open(); this is the remaining site (#662).What changed
starttls()delegates toimaplib.IMAP4.starttls(ssl_context), which manages the wrap and its own read buffer (self._fileon 3.14+) the way the running Python expects. Same direction as Fix IMAP4WithTimeout compatibility with Python 3.14 #641: leave buffering to imaplib instead of duplicating it. (The 2017 objections in Use imaplib's starttls if available #240/Use imaplib's starttls if available #314 were about swapping a then-working manual path for imaplib's; the manual path is the thing that is broken now.)tls.create_default_context()(hostname checking +CERT_REQUIRED) is passed explicitly, because imaplib's own None-fallback isssl._create_stdlib_context()- unverified. The helper is now shared withtls.wrap_socket()._checkokstays as a belt for non-raising stdlib paths.Two observable deltas: the return value is now
None(was the tagged OK text - undocumented, unused in-repo), and a refusal surfaces as imaplib's genericCouldn't establish TLS sessioninstead of the server's own text (same exception class -IMAPClientErroris an alias ofimaplib.IMAP4.error).While testing I noticed master fails on Python 3.8 independently of this change:
IMAP4WithTimeout.__init__passestimeout=toimaplib.IMAP4, which only accepts it since 3.9 (7 suite errors on pristine master). Left alone here; the new integration tests skip themselves below 3.9 for that reason.Test plan
tests/test_starttls.pyupdated to the delegation seam, incl. asserting the default-context passthroughtests/test_starttls_integration.py: real-socket STARTTLS round trip against an in-process fake IMAP server (throwaway CA intests/certs/, re-included in.gitignorefor global*.pem/*.keyignorers). Asserts the verifying default (CERT_REQUIRED+check_hostname), custom-context identity, post-TLS capability refresh, refusal error mapping. Fails on unfixed master at 3.14, passes with the fix.python -m unittestgreen on 3.14.7, 3.13, pypy3.9; 3.8 green with the integration class skipped (pre-existingtimeout=issue above)black --check .,isort --check .,flake8,pylint imapclient/(10.00/10),mypy- all cleanFixes: #662
Downstream: NixOS/nixpkgs#563652 (nixpkgs has defaulted to Python 3.14; parsedmarc STARTTLS is broken there)
💘 Generated with Crush