Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: python/cpython
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main@{1day}
Choose a base ref
...
head repository: python/cpython
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 11 commits
  • 21 files changed
  • 10 contributors

Commits on Mar 29, 2025

  1. gh-131853: Fix test_msgfmt on big-endian platforms (GH-131879)

    Use a generated .mo file instead of a checked in one.
    serhiy-storchaka authored Mar 29, 2025
    Copy the full SHA
    c6b1a07 View commit details
  2. Copy the full SHA
    425f60b View commit details

Commits on Mar 30, 2025

  1. GH-129149: Add fast path for medium-sized integers in PyLong_From*

    …functions (#131211)
    
    Add a fast path for medium-sized integers in `PyLong_FromInt{32,64}` and `PyLong_FromUInt{32,64}`.
    chris-eibl authored Mar 30, 2025
    Copy the full SHA
    a175d64 View commit details
  2. Copy the full SHA
    044a1e1 View commit details
  3. Copy the full SHA
    edfbd8c View commit details
  4. Copy the full SHA
    bc5a028 View commit details
  5. Copy the full SHA
    28e476f View commit details
  6. Copy the full SHA
    46ada1e View commit details
  7. Copy the full SHA
    eaffc34 View commit details
  8. Copy the full SHA
    55150a7 View commit details
  9. gh-127794: Validate email header names according to RFC 5322 (#127820)

    `email.message.Message` objects now validate header names specified via `__setitem__`
    or `add_header` according to RFC 5322, §2.2 [1].
    
    In particular, callers should expect a ValueError to be raised for invalid header names.
    
    [1]: https://datatracker.ietf.org/doc/html/rfc5322#section-2.2
    
    ---------
    
    Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
    Co-authored-by: R. David Murray <rdmurray@bitdance.com>
    3 people authored Mar 30, 2025
    Copy the full SHA
    c432d01 View commit details
8 changes: 4 additions & 4 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
@@ -882,10 +882,10 @@ The following functions all create :ref:`socket objects <socket-objects>`.
, a default reasonable value is chosen.
*reuse_port* dictates whether to set the :data:`SO_REUSEPORT` socket option.

If *dualstack_ipv6* is true and the platform supports it the socket will
be able to accept both IPv4 and IPv6 connections, else it will raise
:exc:`ValueError`. Most POSIX platforms and Windows are supposed to support
this functionality.
If *dualstack_ipv6* is true, *family* is :data:`AF_INET6` and the platform
supports it the socket will be able to accept both IPv4 and IPv6 connections,
else it will raise :exc:`ValueError`. Most POSIX platforms and Windows are
supposed to support this functionality.
When this functionality is enabled the address returned by
:meth:`socket.getpeername` when an IPv4 connection occurs will be an IPv6
address represented as an IPv4-mapped IPv6 address.
4 changes: 2 additions & 2 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
@@ -4780,7 +4780,7 @@ can be used interchangeably to index the same dictionary entry.
such as an empty list. To get distinct values, use a :ref:`dict
comprehension <dict>` instead.

.. method:: get(key, default=None)
.. method:: get(key, default=None, /)

Return the value for *key* if *key* is in the dictionary, else *default*.
If *default* is not given, it defaults to ``None``, so that this method
@@ -4822,7 +4822,7 @@ can be used interchangeably to index the same dictionary entry.

.. versionadded:: 3.8

.. method:: setdefault(key, default=None)
.. method:: setdefault(key, default=None, /)

If *key* is in the dictionary, return its value. If not, insert *key*
with a value of *default* and return *default*. *default* defaults to
1 change: 1 addition & 0 deletions Include/internal/pycore_symtable.h
Original file line number Diff line number Diff line change
@@ -126,6 +126,7 @@ typedef struct _symtable_entry {
unsigned ste_method : 1; /* true if block is a function block defined in class scope */
unsigned ste_has_conditional_annotations : 1; /* true if block has conditionally executed annotations */
unsigned ste_in_conditional_block : 1; /* set while we are inside a conditionally executed block */
unsigned ste_in_unevaluated_annotation : 1; /* set while we are processing an annotation that will not be evaluated */
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
_Py_SourceLocation ste_loc; /* source location of block */
struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */
10 changes: 10 additions & 0 deletions Lib/email/_policybase.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
"""

import abc
import re
from email import header
from email import charset as _charset
from email.utils import _has_surrogates
@@ -14,6 +15,14 @@
'compat32',
]

# validation regex from RFC 5322, equivalent to pattern re.compile("[!-9;-~]+$")
valid_header_name_re = re.compile("[\041-\071\073-\176]+$")

def validate_header_name(name):
# Validate header name according to RFC 5322
if not valid_header_name_re.match(name):
raise ValueError(
f"Header field name contains invalid characters: {name!r}")

class _PolicyBase:

@@ -314,6 +323,7 @@ def header_store_parse(self, name, value):
"""+
The name and value are returned unmodified.
"""
validate_header_name(name)
return (name, value)

def header_fetch_parse(self, name, value):
9 changes: 8 additions & 1 deletion Lib/email/policy.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,13 @@

import re
import sys
from email._policybase import Policy, Compat32, compat32, _extend_docstrings
from email._policybase import (
Compat32,
Policy,
_extend_docstrings,
compat32,
validate_header_name
)
from email.utils import _has_surrogates
from email.headerregistry import HeaderRegistry as HeaderRegistry
from email.contentmanager import raw_data_manager
@@ -138,6 +144,7 @@ def header_store_parse(self, name, value):
CR or LF characters.
"""
validate_header_name(name)
if hasattr(value, 'name') and value.name.lower() == name.lower():
return (name, value)
if isinstance(value, str) and len(value.splitlines())>1:
2 changes: 1 addition & 1 deletion Lib/test/test_ctypes/test_dlerror.py
Original file line number Diff line number Diff line change
@@ -120,7 +120,7 @@ def test_null_dlsym(self):
# Assert that the IFUNC was called
self.assertEqual(os.read(pipe_r, 2), b'OK')


@test.support.thread_unsafe('setlocale is not thread-safe')
@unittest.skipUnless(os.name != 'nt', 'test requires dlerror() calls')
class TestLocalization(unittest.TestCase):

25 changes: 25 additions & 0 deletions Lib/test/test_email/test_email.py
Original file line number Diff line number Diff line change
@@ -728,6 +728,31 @@ def test_nonascii_add_header_with_tspecial(self):
"attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt",
msg['Content-Disposition'])

def test_invalid_header_names(self):
invalid_headers = [
('Invalid Header', 'contains space'),
('Tab\tHeader', 'contains tab'),
('Colon:Header', 'contains colon'),
('', 'Empty name'),
(' LeadingSpace', 'starts with space'),
('TrailingSpace ', 'ends with space'),
('Header\x7F', 'Non-ASCII character'),
('Header\x80', 'Extended ASCII'),
]
for policy in (email.policy.default, email.policy.compat32):
for setter in (Message.__setitem__, Message.add_header):
for name, value in invalid_headers:
self.do_test_invalid_header_names(
policy, setter,name, value)

def do_test_invalid_header_names(self, policy, setter, name, value):
with self.subTest(policy=policy, setter=setter, name=name, value=value):
message = Message(policy=policy)
pattern = r'(?i)(?=.*invalid)(?=.*header)(?=.*name)'
with self.assertRaisesRegex(ValueError, pattern) as cm:
setter(message, name, value)
self.assertIn(f"{name!r}", str(cm.exception))

def test_binary_quopri_payload(self):
for charset in ('latin-1', 'ascii'):
msg = Message()
24 changes: 24 additions & 0 deletions Lib/test/test_email/test_message.py
Original file line number Diff line number Diff line change
@@ -1004,6 +1004,30 @@ def test_folding_with_long_nospace_http_policy_1(self):
parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default)
self.assertEqual(parsed_msg['Message-ID'], m['Message-ID'])

def test_invalid_header_names(self):
invalid_headers = [
('Invalid Header', 'contains space'),
('Tab\tHeader', 'contains tab'),
('Colon:Header', 'contains colon'),
('', 'Empty name'),
(' LeadingSpace', 'starts with space'),
('TrailingSpace ', 'ends with space'),
('Header\x7F', 'Non-ASCII character'),
('Header\x80', 'Extended ASCII'),
]
for email_policy in (policy.default, policy.compat32):
for setter in (EmailMessage.__setitem__, EmailMessage.add_header):
for name, value in invalid_headers:
self.do_test_invalid_header_names(email_policy, setter, name, value)

def do_test_invalid_header_names(self, policy, setter, name, value):
with self.subTest(policy=policy, setter=setter, name=name, value=value):
message = EmailMessage(policy=policy)
pattern = r'(?i)(?=.*invalid)(?=.*header)(?=.*name)'
with self.assertRaisesRegex(ValueError, pattern) as cm:
setter(message, name, value)
self.assertIn(f"{name!r}", str(cm.exception))

def test_get_body_malformed(self):
"""test for bpo-42892"""
msg = textwrap.dedent("""\
4 changes: 1 addition & 3 deletions Lib/test/test_timeit.py
Original file line number Diff line number Diff line change
@@ -297,9 +297,7 @@ def test_main_negative_reps(self):
@unittest.skipIf(sys.flags.optimize >= 2, "need __doc__")
def test_main_help(self):
s = self.run_main(switches=['-h'])
# Note: It's not clear that the trailing space was intended as part of
# the help text, but since it's there, check for it.
self.assertEqual(s, timeit.__doc__ + ' ')
self.assertEqual(s, timeit.__doc__)

def test_main_verbose(self):
s = self.run_main(switches=['-v'])
9 changes: 6 additions & 3 deletions Lib/test/test_tools/test_msgfmt.py
Original file line number Diff line number Diff line change
@@ -42,8 +42,11 @@ def test_compilation(self):
self.assertDictEqual(actual._catalog, expected._catalog)

def test_binary_header(self):
with open(data_dir / "general.mo", "rb") as f:
mo_data = f.read()
with temp_cwd():
tmp_mo_file = 'messages.mo'
compile_messages(data_dir / "general.po", tmp_mo_file)
with open(tmp_mo_file, 'rb') as f:
mo_data = f.read()

(
magic,
@@ -53,7 +56,7 @@ def test_binary_header(self):
trans_table_offset,
hash_table_size,
hash_table_offset,
) = struct.unpack("=Iiiiiii", mo_data[:28])
) = struct.unpack("=7I", mo_data[:28])

self.assertEqual(magic, 0x950412de)
self.assertEqual(version, 0)
13 changes: 12 additions & 1 deletion Lib/test/test_type_annotations.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import textwrap
import types
import unittest
from test.support import run_code, check_syntax_error
from test.support import run_code, check_syntax_error, cpython_only


class TypeAnnotationTests(unittest.TestCase):
@@ -109,6 +109,16 @@ class D(metaclass=C):
del D.__annotations__
self.assertEqual(D.__annotations__, {})

@cpython_only
def test_no_cell(self):
# gh-130924: Test that uses of annotations in local scopes do not
# create cell variables.
def f(x):
a: x
return x

self.assertEqual(f.__code__.co_cellvars, ())


def build_module(code: str, name: str = "top") -> types.ModuleType:
ns = run_code(code)
@@ -352,6 +362,7 @@ def test_no_exotic_expressions_in_unevaluated_annotations(self):
check_syntax_error(self, prelude + "(x): (yield)", "yield expression cannot be used within an annotation")
check_syntax_error(self, prelude + "(x): (yield from x)", "yield expression cannot be used within an annotation")
check_syntax_error(self, prelude + "(x): (y := 3)", "named expression cannot be used within an annotation")
check_syntax_error(self, prelude + "(x): (__debug__ := 3)", "named expression cannot be used within an annotation")
check_syntax_error(self, prelude + "(x): (await 42)", "await expression cannot be used within an annotation")

def test_ignore_non_simple_annotations(self):
3 changes: 1 addition & 2 deletions Lib/timeit.py
Original file line number Diff line number Diff line change
@@ -44,7 +44,6 @@
timeit(string, string) -> float
repeat(string, string) -> list
default_timer() -> float
"""

import gc
@@ -302,7 +301,7 @@ def main(args=None, *, _wrap_timer=None):
precision += 1
verbose += 1
if o in ("-h", "--help"):
print(__doc__, end=' ')
print(__doc__, end="")
return 0
setup = "\n".join(setup) or "pass"

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add fast path for small and medium-size integers in
:c:func:`PyLong_FromInt32`, :c:func:`PyLong_FromUInt32`,
:c:func:`PyLong_FromInt64` and
:c:func:`PyLong_FromUInt64`. Patch by Chris Eibl.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Usage of a name in a function-scope annotation no longer triggers creation
of a cell for that variable. This fixes a regression in earlier alphas of
Python 3.14.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
When headers are added to :class:`email.message.Message` objects, either through
:meth:`email.message.Message.__setitem__` or :meth:`email.message.Message.add_header`,
the field name is now validated according to :rfc:`RFC 5322, Section 2.2 <5322#section-2.2>`
and a :exc:`ValueError` is raised if the field name contains any invalid characters.
41 changes: 30 additions & 11 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
@@ -109,6 +109,7 @@ bytes(cdata)
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
#include "pycore_pyatomic_ft_wrappers.h"
#ifdef MS_WIN32
# include "pycore_modsupport.h" // _PyArg_NoKeywords()
#endif
@@ -710,13 +711,15 @@ StructUnionType_init(PyObject *self, PyObject *args, PyObject *kwds, int isStruc
if (baseinfo == NULL) {
return 0;
}

int ret = 0;
STGINFO_LOCK(baseinfo);
/* copy base info */
if (PyCStgInfo_clone(info, baseinfo) < 0) {
return -1;
ret = PyCStgInfo_clone(info, baseinfo);
if (ret >= 0) {
stginfo_set_dict_final_lock_held(baseinfo); /* set the 'final' flag in the baseclass info */
}
info->flags &= ~DICTFLAG_FINAL; /* clear the 'final' flag in the subclass info */
baseinfo->flags |= DICTFLAG_FINAL; /* set the 'final' flag in the baseclass info */
STGINFO_UNLOCK();
return ret;
}
return 0;
}
@@ -3122,6 +3125,7 @@ PyCData_MallocBuffer(CDataObject *obj, StgInfo *info)
* access.
*/
assert (Py_REFCNT(obj) == 1);
assert(stginfo_get_dict_final(info) == 1);

if ((size_t)info->size <= sizeof(obj->b_value)) {
/* No need to call malloc, can use the default buffer */
@@ -3167,7 +3171,7 @@ PyCData_FromBaseObj(ctypes_state *st,
return NULL;
}

info->flags |= DICTFLAG_FINAL;
stginfo_set_dict_final(info);
cmem = (CDataObject *)((PyTypeObject *)type)->tp_alloc((PyTypeObject *)type, 0);
if (cmem == NULL) {
return NULL;
@@ -3216,7 +3220,7 @@ PyCData_AtAddress(ctypes_state *st, PyObject *type, void *buf)
return NULL;
}

info->flags |= DICTFLAG_FINAL;
stginfo_set_dict_final(info);

pd = (CDataObject *)((PyTypeObject *)type)->tp_alloc((PyTypeObject *)type, 0);
if (!pd) {
@@ -3451,7 +3455,7 @@ generic_pycdata_new(ctypes_state *st,
return NULL;
}

info->flags |= DICTFLAG_FINAL;
stginfo_set_dict_final(info);

obj = (CDataObject *)type->tp_alloc(type, 0);
if (!obj)
@@ -4402,7 +4406,7 @@ _build_result(PyObject *result, PyObject *callargs,
}

static PyObject *
PyCFuncPtr_call(PyObject *op, PyObject *inargs, PyObject *kwds)
PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
{
PyObject *restype;
PyObject *converters;
@@ -4540,6 +4544,16 @@ PyCFuncPtr_call(PyObject *op, PyObject *inargs, PyObject *kwds)
outmask, inoutmask, numretvals);
}

static PyObject *
PyCFuncPtr_call(PyObject *op, PyObject *inargs, PyObject *kwds)
{
PyObject *result;
Py_BEGIN_CRITICAL_SECTION(op);
result = PyCFuncPtr_call_lock_held(op, inargs, kwds);
Py_END_CRITICAL_SECTION();
return result;
}

static int
PyCFuncPtr_traverse(PyObject *op, visitproc visit, void *arg)
{
@@ -6089,14 +6103,19 @@ _ctypes_mod_exec(PyObject *mod)
}

#ifdef WORDS_BIGENDIAN
st->swapped_suffix = PyUnicode_InternFromString("_le");
st->swapped_suffix = PyUnicode_InternFromString("_le");
#else
st->swapped_suffix = PyUnicode_InternFromString("_be");
st->swapped_suffix = PyUnicode_InternFromString("_be");
#endif
if (st->swapped_suffix == NULL) {
return -1;
}

st->error_object_name = PyUnicode_InternFromString("ctypes.error_object");
if (st->error_object_name == NULL) {
return -1;
}

if (_ctypes_add_types(mod) < 0) {
return -1;
}
7 changes: 1 addition & 6 deletions Modules/_ctypes/callproc.c
Original file line number Diff line number Diff line change
@@ -164,12 +164,7 @@ _ctypes_get_errobj(ctypes_state *st, int **pspace)
"cannot get thread state");
return NULL;
}
if (st->error_object_name == NULL) {
st->error_object_name = PyUnicode_InternFromString("ctypes.error_object");
if (st->error_object_name == NULL) {
return NULL;
}
}
assert(st->error_object_name != NULL);
if (PyDict_GetItemRef(dict, st->error_object_name, &errobj) < 0) {
return NULL;
}
Loading