From 6485c8583a112c343525a14cf519275267b95185 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:10:00 +0100 Subject: [PATCH 1/7] GH-144679: Switch to windows-2025-vs2026 build image in GitHub Actions (GH-145005) --- .github/workflows/jit.yml | 4 ++-- .github/workflows/reusable-windows-msi.yml | 2 +- .github/workflows/reusable-windows.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 5a564b63f9d120..9188839b26ee71 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -74,10 +74,10 @@ jobs: include: - target: i686-pc-windows-msvc/msvc architecture: Win32 - runner: windows-2025 + runner: windows-2025-vs2026 - target: x86_64-pc-windows-msvc/msvc architecture: x64 - runner: windows-2025 + runner: windows-2025-vs2026 - target: aarch64-pc-windows-msvc/msvc architecture: ARM64 runner: windows-11-arm diff --git a/.github/workflows/reusable-windows-msi.yml b/.github/workflows/reusable-windows-msi.yml index 96fc338c47bf29..42c0dfd9636d30 100644 --- a/.github/workflows/reusable-windows-msi.yml +++ b/.github/workflows/reusable-windows-msi.yml @@ -17,7 +17,7 @@ env: jobs: build: name: installer for ${{ inputs.arch }} - runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2025' }} + runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2025-vs2026' }} timeout-minutes: 60 env: ARCH: ${{ inputs.arch }} diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index 2f6caf2f0044d4..2f667ace9194d7 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -21,7 +21,7 @@ env: jobs: build: name: Build and test (${{ inputs.arch }}) - runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2025' }} + runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2025-vs2026' }} timeout-minutes: 60 env: ARCH: ${{ inputs.arch }} From 0f7cd5544a4dd1d7cf892c93c661510d619caaa7 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Thu, 19 Feb 2026 19:29:05 +0100 Subject: [PATCH 2/7] gh-144156: Fix email header folding concatenating encoded words (#144692) The fix for gh-92081 (gh-92281) was unfortunately flawed, and broke whitespace handling for encoded word patterns that had previously been working correctly but had no corresponding tests, unfortunately in a way that made the resulting headers not RFC compliant, in such a way that Yahoo started rejecting the resulting emails. This fix was released in 3.14 alpha 1, 3.13 beta 2 and 3.12.5. This PR fixes the original problem in a way that does not break anything, and in fact fixes a small pre-existing bug (a spurious whitespace after the ':' of the header label if the header value is immediately wrapped on to the next line). (RDM) Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: R. David Murray --- Lib/email/_header_value_parser.py | 73 ++++++++++--------- Lib/test/test_email/test_generator.py | 44 +++++++++++ Lib/test/test_email/test_headerregistry.py | 4 +- Lib/test/test_email/test_policy.py | 2 +- ...-02-10-22-05-51.gh-issue-144156.UbrC7F.rst | 1 + 5 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-10-22-05-51.gh-issue-144156.UbrC7F.rst diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index 172f9ef9e5f096..4c5394ab6353ac 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -80,7 +80,8 @@ # Useful constants and functions # -WSP = set(' \t') +_WSP = ' \t' +WSP = set(_WSP) CFWS_LEADER = WSP | set('(') SPECIALS = set(r'()<>@,:;.\"[]') ATOM_ENDS = SPECIALS | WSP @@ -2835,6 +2836,7 @@ def _steal_trailing_WSP_if_exists(lines): lines.pop() return wsp + def _refold_parse_tree(parse_tree, *, policy): """Return string of contents of parse_tree folded according to RFC rules. @@ -2843,11 +2845,9 @@ def _refold_parse_tree(parse_tree, *, policy): maxlen = policy.max_line_length or sys.maxsize encoding = 'utf-8' if policy.utf8 else 'us-ascii' lines = [''] # Folded lines to be output - leading_whitespace = '' # When we have whitespace between two encoded - # words, we may need to encode the whitespace - # at the beginning of the second word. - last_ew = None # Points to the last encoded character if there's an ew on - # the line + last_word_is_ew = False + last_ew = None # if there is an encoded word in the last line of lines, + # points to the encoded word's first character last_charset = None wrap_as_ew_blocked = 0 want_encoding = False # This is set to True if we need to encode this part @@ -2882,6 +2882,7 @@ def _refold_parse_tree(parse_tree, *, policy): if part.token_type == 'mime-parameters': # Mime parameter folding (using RFC2231) is extra special. _fold_mime_parameters(part, lines, maxlen, encoding) + last_word_is_ew = False continue if want_encoding and not wrap_as_ew_blocked: @@ -2898,6 +2899,7 @@ def _refold_parse_tree(parse_tree, *, policy): # XXX what if encoded_part has no leading FWS? lines.append(newline) lines[-1] += encoded_part + last_word_is_ew = False continue # Either this is not a major syntactic break, so we don't # want it on a line by itself even if it fits, or it @@ -2916,11 +2918,16 @@ def _refold_parse_tree(parse_tree, *, policy): (last_charset == 'unknown-8bit' or last_charset == 'utf-8' and charset != 'us-ascii')): last_ew = None - last_ew = _fold_as_ew(tstr, lines, maxlen, last_ew, - part.ew_combine_allowed, charset, leading_whitespace) - # This whitespace has been added to the lines in _fold_as_ew() - # so clear it now. - leading_whitespace = '' + last_ew = _fold_as_ew( + tstr, + lines, + maxlen, + last_ew, + part.ew_combine_allowed, + charset, + last_word_is_ew, + ) + last_word_is_ew = True last_charset = charset want_encoding = False continue @@ -2933,28 +2940,19 @@ def _refold_parse_tree(parse_tree, *, policy): if len(tstr) <= maxlen - len(lines[-1]): lines[-1] += tstr + last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP)) continue # This part is too long to fit. The RFC wants us to break at # "major syntactic breaks", so unless we don't consider this # to be one, check if it will fit on the next line by itself. - leading_whitespace = '' if (part.syntactic_break and len(tstr) + 1 <= maxlen): newline = _steal_trailing_WSP_if_exists(lines) if newline or part.startswith_fws(): - # We're going to fold the data onto a new line here. Due to - # the way encoded strings handle continuation lines, we need to - # be prepared to encode any whitespace if the next line turns - # out to start with an encoded word. lines.append(newline + tstr) - - whitespace_accumulator = [] - for char in lines[-1]: - if char not in WSP: - break - whitespace_accumulator.append(char) - leading_whitespace = ''.join(whitespace_accumulator) + last_word_is_ew = (last_word_is_ew + and not bool(lines[-1].strip(_WSP))) last_ew = None continue if not hasattr(part, 'encode'): @@ -2994,10 +2992,11 @@ def _refold_parse_tree(parse_tree, *, policy): else: # We can't fold it onto the next line either... lines[-1] += tstr + last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP)) return policy.linesep.join(lines) + policy.linesep -def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, leading_whitespace): +def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, last_word_is_ew): """Fold string to_encode into lines as encoded word, combining if allowed. Return the new value for last_ew, or None if ew_combine_allowed is False. @@ -3012,6 +3011,16 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, to_encode = str( get_unstructured(lines[-1][last_ew:] + to_encode)) lines[-1] = lines[-1][:last_ew] + elif last_word_is_ew: + # If we are following up an encoded word with another encoded word, + # any white space between the two will be ignored when decoded. + # Therefore, we encode all to-be-displayed whitespace in the second + # encoded word. + len_without_wsp = len(lines[-1].rstrip(_WSP)) + leading_whitespace = lines[-1][len_without_wsp:] + lines[-1] = (lines[-1][:len_without_wsp] + + (' ' if leading_whitespace else '')) + to_encode = leading_whitespace + to_encode elif to_encode[0] in WSP: # We're joining this to non-encoded text, so don't encode # the leading blank. @@ -3040,20 +3049,13 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, while to_encode: remaining_space = maxlen - len(lines[-1]) - text_space = remaining_space - chrome_len - len(leading_whitespace) + text_space = remaining_space - chrome_len if text_space <= 0: - lines.append(' ') + newline = _steal_trailing_WSP_if_exists(lines) + lines.append(newline or ' ') + new_last_ew = len(lines[-1]) continue - # If we are at the start of a continuation line, prepend whitespace - # (we only want to do this when the line starts with an encoded word - # but if we're folding in this helper function, then we know that we - # are going to be writing out an encoded word.) - if len(lines) > 1 and len(lines[-1]) == 1 and leading_whitespace: - encoded_word = _ew.encode(leading_whitespace, charset=encode_as) - lines[-1] += encoded_word - leading_whitespace = '' - to_encode_word = to_encode[:text_space] encoded_word = _ew.encode(to_encode_word, charset=encode_as) excess = len(encoded_word) - remaining_space @@ -3065,7 +3067,6 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, excess = len(encoded_word) - remaining_space lines[-1] += encoded_word to_encode = to_encode[len(to_encode_word):] - leading_whitespace = '' if to_encode: lines.append(' ') diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py index 3ca79edf6a65d9..c2d7d09d591e86 100644 --- a/Lib/test/test_email/test_generator.py +++ b/Lib/test/test_email/test_generator.py @@ -393,6 +393,50 @@ def test_defaults_handle_spaces_at_start_of_continuation_line(self): g.flatten(msg) self.assertEqual(s.getvalue(), expected) + # gh-144156: fold between non-encoded and encoded words don't need to encoded + # the separating space + def test_defaults_handle_spaces_at_start_of_continuation_line_2(self): + source = ("Re: [SOS-1495488] Commande et livraison - Demande de retour - " + "bibijolie - 251210-AABBCC - Abo actualités digitales 20 semaines " + "d’abonnement à 24 heures, Bilan, Tribune de Genève et tous les titres Tamedia") + expected = ( + b"Subject: " + b"Re: [SOS-1495488] Commande et livraison - Demande de retour -\n" + b" bibijolie - 251210-AABBCC - Abo =?utf-8?q?actualit=C3=A9s?= digitales 20\n" + b" semaines =?utf-8?q?d=E2=80=99abonnement_=C3=A0?= 24 heures, Bilan, Tribune de\n" + b" =?utf-8?q?Gen=C3=A8ve?= et tous les titres Tamedia\n\n" + ) + msg = EmailMessage() + msg['Subject'] = source + s = io.BytesIO() + g = BytesGenerator(s) + g.flatten(msg) + self.assertEqual(s.getvalue(), expected) + + def test_ew_folding_round_trip_1(self): + print() + source = "aaaaaaaaa фффффффф " + msg = EmailMessage() + msg['Subject'] = source + s = io.BytesIO() + g = BytesGenerator(s, maxheaderlen=30) + g.flatten(msg) + flat = s.getvalue() + reparsed = message_from_bytes(flat, policy=policy.default)['Subject'] + self.assertMultiLineEqual(reparsed, source) + + def test_ew_folding_round_trip_2(self): + print() + source = "aaa aaaaaaa aaa ффф фффф " + msg = EmailMessage() + msg['Subject'] = source + s = io.BytesIO() + g = BytesGenerator(s, maxheaderlen=30) + g.flatten(msg) + flat = s.getvalue() + reparsed = message_from_bytes(flat, policy=policy.default)['Subject'] + self.assertMultiLineEqual(reparsed, source) + def test_cte_type_7bit_handles_unknown_8bit(self): source = ("Subject: Maintenant je vous présente mon " "collègue\n\n").encode('utf-8') diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py index 95c6afbee41ef5..c9c63951597244 100644 --- a/Lib/test/test_email/test_headerregistry.py +++ b/Lib/test/test_email/test_headerregistry.py @@ -1711,7 +1711,7 @@ def test_fold_unstructured_with_overlong_word(self): 'singlewordthatwontfit') self.assertEqual( h.fold(policy=policy.default.clone(max_line_length=20)), - 'Subject: \n' + 'Subject:\n' ' =?utf-8?q?thisisa?=\n' ' =?utf-8?q?verylon?=\n' ' =?utf-8?q?glineco?=\n' @@ -1727,7 +1727,7 @@ def test_fold_unstructured_with_two_overlong_words(self): 'singlewordthatwontfit plusanotherverylongwordthatwontfit') self.assertEqual( h.fold(policy=policy.default.clone(max_line_length=20)), - 'Subject: \n' + 'Subject:\n' ' =?utf-8?q?thisisa?=\n' ' =?utf-8?q?verylon?=\n' ' =?utf-8?q?glineco?=\n' diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py index 71ec0febb0fd86..90e8e5580295f9 100644 --- a/Lib/test/test_email/test_policy.py +++ b/Lib/test/test_email/test_policy.py @@ -273,7 +273,7 @@ def test_non_ascii_chars_do_not_cause_inf_loop(self): actual = policy.fold('Subject', 'ą' * 12) self.assertEqual( actual, - 'Subject: \n' + + 'Subject:\n' + 12 * ' =?utf-8?q?=C4=85?=\n') def test_short_maxlen_error(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-10-22-05-51.gh-issue-144156.UbrC7F.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-10-22-05-51.gh-issue-144156.UbrC7F.rst new file mode 100644 index 00000000000000..c4a065528512e1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-10-22-05-51.gh-issue-144156.UbrC7F.rst @@ -0,0 +1 @@ +Fix the folding of headers by the :mod:`email` library when :rfc:`2047` encoded words are used. Now whitespace is correctly preserved and also correctly added between adjacent encoded words. The latter property was broken by the fix for gh-92081, which mostly fixed previous failures to preserve whitespace. From b5c8e6e1334f1756ead5e2cd5966731844428e2c Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 19 Feb 2026 20:45:59 +0100 Subject: [PATCH 3/7] gh-100239: Use ``PyFloat_AS_DOUBLE`` and `_PyLong_IsZero`` in the float / compactlong specializations (#144826) --- .../2026-02-17-21-04-03.gh-issue-100239.LyVabQ.rst | 2 ++ Python/specialize.c | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-17-21-04-03.gh-issue-100239.LyVabQ.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-17-21-04-03.gh-issue-100239.LyVabQ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-17-21-04-03.gh-issue-100239.LyVabQ.rst new file mode 100644 index 00000000000000..3cfc3e930d1e9d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-17-21-04-03.gh-issue-100239.LyVabQ.rst @@ -0,0 +1,2 @@ +Speedup ``BINARY_OP_EXTEND`` for exact floats and medium-size integers by up +to 15%. Patch by Chris Eibl. diff --git a/Python/specialize.c b/Python/specialize.c index 4d3ba4acbbf038..1eabdb1b5b194e 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2100,7 +2100,7 @@ float_compactlong_guard(PyObject *lhs, PyObject *rhs) { return ( PyFloat_CheckExact(lhs) && - !isnan(PyFloat_AsDouble(lhs)) && + !isnan(PyFloat_AS_DOUBLE(lhs)) && PyLong_CheckExact(rhs) && _PyLong_IsCompact((PyLongObject *)rhs) ); @@ -2110,7 +2110,7 @@ static inline int nonzero_float_compactlong_guard(PyObject *lhs, PyObject *rhs) { return ( - float_compactlong_guard(lhs, rhs) && !PyLong_IsZero(rhs) + float_compactlong_guard(lhs, rhs) && !_PyLong_IsZero((PyLongObject*)rhs) ); } @@ -2118,7 +2118,7 @@ nonzero_float_compactlong_guard(PyObject *lhs, PyObject *rhs) static PyObject * \ (NAME)(PyObject *lhs, PyObject *rhs) \ { \ - double lhs_val = PyFloat_AsDouble(lhs); \ + double lhs_val = PyFloat_AS_DOUBLE(lhs); \ Py_ssize_t rhs_val = _PyLong_CompactValue((PyLongObject *)rhs); \ return PyFloat_FromDouble(lhs_val OP rhs_val); \ } @@ -2137,7 +2137,7 @@ compactlong_float_guard(PyObject *lhs, PyObject *rhs) PyLong_CheckExact(lhs) && _PyLong_IsCompact((PyLongObject *)lhs) && PyFloat_CheckExact(rhs) && - !isnan(PyFloat_AsDouble(rhs)) + !isnan(PyFloat_AS_DOUBLE(rhs)) ); } @@ -2145,7 +2145,7 @@ static inline int nonzero_compactlong_float_guard(PyObject *lhs, PyObject *rhs) { return ( - compactlong_float_guard(lhs, rhs) && PyFloat_AsDouble(rhs) != 0.0 + compactlong_float_guard(lhs, rhs) && PyFloat_AS_DOUBLE(rhs) != 0.0 ); } @@ -2153,7 +2153,7 @@ nonzero_compactlong_float_guard(PyObject *lhs, PyObject *rhs) static PyObject * \ (NAME)(PyObject *lhs, PyObject *rhs) \ { \ - double rhs_val = PyFloat_AsDouble(rhs); \ + double rhs_val = PyFloat_AS_DOUBLE(rhs); \ Py_ssize_t lhs_val = _PyLong_CompactValue((PyLongObject *)lhs); \ return PyFloat_FromDouble(lhs_val OP rhs_val); \ } From 87c4fc1321b8e5dae4db8d2ca9e335276c2ccfc3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Feb 2026 15:18:37 -0500 Subject: [PATCH 4/7] Add myself as a codeowner for the C API documentation (#145017) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 67c08c9ddaee9e..f656e8e52cd3dc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -111,6 +111,7 @@ Doc/tools/ @AA-Turner @hugovk .readthedocs.yml @AA-Turner # Sections +Doc/c-api/ @ZeroIntensity Doc/reference/ @willingc @AA-Turner Doc/whatsnew/ @AA-Turner From 50c14719fbd47f500dd1a468998201d22475126d Mon Sep 17 00:00:00 2001 From: Shamil Date: Thu, 19 Feb 2026 23:42:55 +0300 Subject: [PATCH 5/7] gh-144986: Fix memory leak in atexit.register() (#144987) --- .../2026-02-19-00-00-00.gh-issue-144986.atexit-leak.rst | 2 ++ Modules/atexitmodule.c | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-02-19-00-00-00.gh-issue-144986.atexit-leak.rst diff --git a/Misc/NEWS.d/next/Library/2026-02-19-00-00-00.gh-issue-144986.atexit-leak.rst b/Misc/NEWS.d/next/Library/2026-02-19-00-00-00.gh-issue-144986.atexit-leak.rst new file mode 100644 index 00000000000000..841c3758ec4df1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-19-00-00-00.gh-issue-144986.atexit-leak.rst @@ -0,0 +1,2 @@ +Fix a memory leak in :func:`atexit.register`. +Patch by Shamil Abdulaev. diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c index 1c901d9124d9ca..3ddbbd59a1ef0c 100644 --- a/Modules/atexitmodule.c +++ b/Modules/atexitmodule.c @@ -185,6 +185,9 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs) return NULL; } PyObject *func_args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args)); + if (func_args == NULL) { + return NULL; + } PyObject *func_kwargs = kwargs; if (func_kwargs == NULL) @@ -192,6 +195,7 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs) func_kwargs = Py_None; } PyObject *callback = PyTuple_Pack(3, func, func_args, func_kwargs); + Py_DECREF(func_args); if (callback == NULL) { return NULL; From beb8e3f2763d35ed85ae4bb82ef0073dfaaa3a67 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Feb 2026 22:13:16 +0100 Subject: [PATCH 6/7] gh-141510: Document ParameterizedMIMEHeader.params change (#145003) Document also the dataclasses.field() metadata change. --- Doc/library/dataclasses.rst | 4 ++++ Doc/library/email.headerregistry.rst | 4 ++++ .../Library/2026-02-19-16-26-08.gh-issue-141510.4Qxy8_.rst | 3 +++ .../Library/2026-02-19-18-02-54.gh-issue-141510.qzvYsO.rst | 3 +++ 4 files changed, 14 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-02-19-16-26-08.gh-issue-141510.4Qxy8_.rst create mode 100644 Misc/NEWS.d/next/Library/2026-02-19-18-02-54.gh-issue-141510.qzvYsO.rst diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst index cff36e258224d3..2864149a08fd50 100644 --- a/Doc/library/dataclasses.rst +++ b/Doc/library/dataclasses.rst @@ -333,6 +333,10 @@ Module contents :attr:`!C.t` will be ``20``, and the class attributes :attr:`!C.x` and :attr:`!C.y` will not be set. + .. versionchanged:: next + If *metadata* is ``None``, use an empty :class:`frozendict`, instead + of a :func:`~types.MappingProxyType` of an empty :class:`dict`. + .. class:: Field :class:`!Field` objects describe each defined field. These objects diff --git a/Doc/library/email.headerregistry.rst b/Doc/library/email.headerregistry.rst index ff8b601fe3d1bb..f4c11821c4b91d 100644 --- a/Doc/library/email.headerregistry.rst +++ b/Doc/library/email.headerregistry.rst @@ -269,6 +269,10 @@ variant, :attr:`~.BaseHeader.max_count` is set to 1. A dictionary mapping parameter names to parameter values. + .. versionchanged:: next + It is now a :class:`frozendict` instead of a + :class:`types.MappingProxyType`. + .. class:: ContentTypeHeader diff --git a/Misc/NEWS.d/next/Library/2026-02-19-16-26-08.gh-issue-141510.4Qxy8_.rst b/Misc/NEWS.d/next/Library/2026-02-19-16-26-08.gh-issue-141510.4Qxy8_.rst new file mode 100644 index 00000000000000..cf22e82b8415b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-19-16-26-08.gh-issue-141510.4Qxy8_.rst @@ -0,0 +1,3 @@ +``ParameterizedMIMEHeader.params`` of :mod:`email.headerregistry` is now a +:class:`frozendict` instead of a :class:`types.MappingProxyType`. Patch by +Victor Stinner. diff --git a/Misc/NEWS.d/next/Library/2026-02-19-18-02-54.gh-issue-141510.qzvYsO.rst b/Misc/NEWS.d/next/Library/2026-02-19-18-02-54.gh-issue-141510.qzvYsO.rst new file mode 100644 index 00000000000000..ae46ff0cbdd8b1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-19-18-02-54.gh-issue-141510.qzvYsO.rst @@ -0,0 +1,3 @@ +:func:`dataclasses.field`: if *metadata* is ``None``, use an empty +:class:`frozendict`, instead of a :func:`~types.MappingProxyType` of an +empty :class:`dict`. Patch by Victor Stinner. From 4141f0a1ee6a6e9d5b4ba24f15a9d17df6933321 Mon Sep 17 00:00:00 2001 From: J Berg Date: Thu, 19 Feb 2026 22:48:01 +0000 Subject: [PATCH 7/7] Correct MAX_N in Lib/zipfile ZipExtFile (GH-144973) "<<" has lower precedence than "-". --- Lib/test/test_compile.py | 4 ++-- Lib/test/test_unpack_ex.py | 4 ++-- Lib/zipfile/__init__.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 591332cb5b9bcf..302b2c21935efe 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -249,8 +249,8 @@ def test_32_63_bit_values(self): d = -281474976710656 # 1 << 48 e = +4611686018427387904 # 1 << 62 f = -4611686018427387904 # 1 << 62 - g = +9223372036854775807 # 1 << 63 - 1 - h = -9223372036854775807 # 1 << 63 - 1 + g = +9223372036854775807 # (1 << 63) - 1 + h = -9223372036854775807 # (1 << 63) - 1 for variable in self.test_32_63_bit_values.__code__.co_consts: if variable is not None: diff --git a/Lib/test/test_unpack_ex.py b/Lib/test/test_unpack_ex.py index d147cd96d207db..904cf4f626ae78 100644 --- a/Lib/test/test_unpack_ex.py +++ b/Lib/test/test_unpack_ex.py @@ -543,13 +543,13 @@ Some size constraints (all fail.) - >>> s = ", ".join("a%d" % i for i in range(1<<8)) + ", *rest = range(1<<8 + 1)" + >>> s = ", ".join("a%d" % i for i in range(1<<8)) + ", *rest = range((1<<8) + 1)" >>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS Traceback (most recent call last): ... SyntaxError: too many expressions in star-unpacking assignment - >>> s = ", ".join("a%d" % i for i in range(1<<8 + 1)) + ", *rest = range(1<<8 + 2)" + >>> s = ", ".join("a%d" % i for i in range((1<<8) + 1)) + ", *rest = range((1<<8) + 2)" >>> compile(s, 'test', 'exec') # doctest:+ELLIPSIS Traceback (most recent call last): ... diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 8234bf52d39c5f..51e0ce9fa36d7e 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -950,7 +950,7 @@ class ZipExtFile(io.BufferedIOBase): """ # Max size supported by decompressor. - MAX_N = 1 << 31 - 1 + MAX_N = (1 << 31) - 1 # Read from compressed files in 4k blocks. MIN_READ_SIZE = 4096