aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/jit.yml4
-rw-r--r--Doc/c-api/init.rst12
-rw-r--r--Doc/deprecations/c-api-pending-removal-in-3.15.rst1
-rw-r--r--Doc/deprecations/c-api-pending-removal-in-3.16.rst4
-rw-r--r--Doc/howto/isolating-extensions.rst3
-rw-r--r--Doc/library/unittest.rst9
-rw-r--r--Doc/whatsnew/3.12.rst2
-rw-r--r--Doc/whatsnew/3.13.rst4
-rw-r--r--Doc/whatsnew/3.14.rst2
-rw-r--r--Doc/whatsnew/3.15.rst16
-rw-r--r--Include/cpython/lock.h11
-rw-r--r--Include/internal/pycore_lock.h7
-rw-r--r--Include/internal/pycore_object.h14
-rw-r--r--Include/internal/pycore_stackref.h8
-rw-r--r--Include/refcount.h14
-rw-r--r--Lib/test/test_capi/test_opt.py15
-rw-r--r--Lib/test/test_generated_cases.py47
-rw-r--r--Lib/test/test_types.py40
-rw-r--r--Lib/test/test_unittest/test_case.py16
-rw-r--r--Lib/unittest/_log.py5
-rw-r--r--Lib/unittest/case.py6
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst2
-rw-r--r--Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst2
-rw-r--r--Objects/descrobject.c5
-rw-r--r--Objects/namespaceobject.c8
-rw-r--r--Python/lock.c8
-rw-r--r--Python/optimizer_cases.c.h6
-rw-r--r--Tools/cases_generator/generators_common.py11
-rw-r--r--Tools/cases_generator/optimizer_generator.py1
-rwxr-xr-xconfigure8
-rw-r--r--configure.ac4
33 files changed, 248 insertions, 50 deletions
diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml
index 116e0c1e945..947badff816 100644
--- a/.github/workflows/jit.yml
+++ b/.github/workflows/jit.yml
@@ -5,6 +5,8 @@ on:
- '**jit**'
- 'Python/bytecodes.c'
- 'Python/optimizer*.c'
+ - 'Python/executor_cases.c.h'
+ - 'Python/optimizer_cases.c.h'
- '!Python/perf_jit_trampoline.c'
- '!**/*.md'
- '!**/*.ini'
@@ -13,6 +15,8 @@ on:
- '**jit**'
- 'Python/bytecodes.c'
- 'Python/optimizer*.c'
+ - 'Python/executor_cases.c.h'
+ - 'Python/optimizer_cases.c.h'
- '!Python/perf_jit_trampoline.c'
- '!**/*.md'
- '!**/*.ini'
diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst
index 41fd4ea14ef..409539dec17 100644
--- a/Doc/c-api/init.rst
+++ b/Doc/c-api/init.rst
@@ -2277,6 +2277,18 @@ The C-API provides a basic mutual exclusion lock.
.. versionadded:: 3.13
+.. c:function:: int PyMutex_IsLocked(PyMutex *m)
+
+ Returns non-zero if the mutex *m* is currently locked, zero otherwise.
+
+ .. note::
+
+ This function is intended for use in assertions and debugging only and
+ should not be used to make concurrency control decisions, as the lock
+ state may change immediately after the check.
+
+ .. versionadded:: next
+
.. _python-critical-section-api:
Python Critical Section API
diff --git a/Doc/deprecations/c-api-pending-removal-in-3.15.rst b/Doc/deprecations/c-api-pending-removal-in-3.15.rst
index b87f0a5ecde..a3e335ecaf4 100644
--- a/Doc/deprecations/c-api-pending-removal-in-3.15.rst
+++ b/Doc/deprecations/c-api-pending-removal-in-3.15.rst
@@ -1,7 +1,6 @@
Pending removal in Python 3.15
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-* The bundled copy of ``libmpdecimal``.
* The :c:func:`!PyImport_ImportModuleNoBlock`:
Use :c:func:`PyImport_ImportModule` instead.
* :c:func:`PyWeakref_GetObject` and :c:func:`PyWeakref_GET_OBJECT`:
diff --git a/Doc/deprecations/c-api-pending-removal-in-3.16.rst b/Doc/deprecations/c-api-pending-removal-in-3.16.rst
new file mode 100644
index 00000000000..9453f83799c
--- /dev/null
+++ b/Doc/deprecations/c-api-pending-removal-in-3.16.rst
@@ -0,0 +1,4 @@
+Pending removal in Python 3.16
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* The bundled copy of ``libmpdec``.
diff --git a/Doc/howto/isolating-extensions.rst b/Doc/howto/isolating-extensions.rst
index fbc426ba1d7..7da6dc8a397 100644
--- a/Doc/howto/isolating-extensions.rst
+++ b/Doc/howto/isolating-extensions.rst
@@ -626,8 +626,7 @@ Open Issues
Several issues around per-module state and heap types are still open.
-Discussions about improving the situation are best held on the `capi-sig
-mailing list <https://mail.python.org/mailman3/lists/capi-sig.python.org/>`__.
+Discussions about improving the situation are best held on the `discuss forum under c-api tag <https://discuss.python.org/c/core-dev/c-api/30>`__.
Per-Class Scope
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index dcdda1719bf..d526e835caa 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -1131,7 +1131,7 @@ Test cases
.. versionchanged:: 3.3
Added the *msg* keyword argument when used as a context manager.
- .. method:: assertLogs(logger=None, level=None)
+ .. method:: assertLogs(logger=None, level=None, formatter=None)
A context manager to test that at least one message is logged on
the *logger* or one of its children, with at least the given
@@ -1146,6 +1146,10 @@ Test cases
its string equivalent (for example either ``"ERROR"`` or
:const:`logging.ERROR`). The default is :const:`logging.INFO`.
+ If given, *formatter* should be a :class:`logging.Formatter` object.
+ The default is a formatter with format string
+ ``"%(levelname)s:%(name)s:%(message)s"``
+
The test passes if at least one message emitted inside the ``with``
block matches the *logger* and *level* conditions, otherwise it fails.
@@ -1173,6 +1177,9 @@ Test cases
.. versionadded:: 3.4
+ .. versionchanged:: next
+ Now accepts a *formatter* to control how messages are formatted.
+
.. method:: assertNoLogs(logger=None, level=None)
A context manager to test that no messages are logged on
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index a65f59c0a72..7cfdc287b7f 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -2233,6 +2233,8 @@ Deprecated
.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
+.. include:: ../deprecations/c-api-pending-removal-in-3.16.rst
+
.. include:: ../deprecations/c-api-pending-removal-in-future.rst
Removed
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index ef7c36d8539..0a3b3b30e01 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -2546,6 +2546,8 @@ Deprecated C APIs
.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
+.. include:: ../deprecations/c-api-pending-removal-in-3.16.rst
+
.. include:: ../deprecations/c-api-pending-removal-in-3.18.rst
.. include:: ../deprecations/c-api-pending-removal-in-future.rst
@@ -2592,7 +2594,7 @@ Build Changes
* The :file:`configure` option :option:`--with-system-libmpdec`
now defaults to ``yes``.
- The bundled copy of ``libmpdecimal`` will be removed in Python 3.15.
+ The bundled copy of ``libmpdec`` will be removed in Python 3.16.
* Python built with :file:`configure` :option:`--with-trace-refs`
(tracing references) is now ABI compatible with the Python release build
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 8260f8ddc4e..c108a94692d 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -3053,6 +3053,8 @@ Deprecated
.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
+.. include:: ../deprecations/c-api-pending-removal-in-3.16.rst
+
.. include:: ../deprecations/c-api-pending-removal-in-3.18.rst
.. include:: ../deprecations/c-api-pending-removal-in-future.rst
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index df636dc9051..706a816f888 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -291,6 +291,15 @@ typing
(Contributed by Bénédikt Tran in :gh:`133823`.)
+unittest
+--------
+
+* Lets users specify formatter in TestCase.assertLogs.
+ :func:`unittest.TestCase.assertLogs` will now accept a formatter
+ to control how messages are formatted.
+ (Contributed by Garry Cairns in :gh:`134567`.)
+
+
wave
----
@@ -346,6 +355,13 @@ Porting to Python 3.15
(Contributed by Serhiy Storchaka in :gh:`133595`.)
+* Private functions promoted to public C APIs:
+
+ * ``PyMutex_IsLocked()`` : :c:func:`PyMutex_IsLocked`
+
+ The |pythoncapi_compat_project| can be used to get most of these new
+ functions on Python 3.14 and older.
+
Deprecated C APIs
-----------------
diff --git a/Include/cpython/lock.h b/Include/cpython/lock.h
index 8ee03e82f74..63886fca28e 100644
--- a/Include/cpython/lock.h
+++ b/Include/cpython/lock.h
@@ -36,6 +36,9 @@ PyAPI_FUNC(void) PyMutex_Lock(PyMutex *m);
// exported function for unlocking the mutex
PyAPI_FUNC(void) PyMutex_Unlock(PyMutex *m);
+// exported function for checking if the mutex is locked
+PyAPI_FUNC(int) PyMutex_IsLocked(PyMutex *m);
+
// Locks the mutex.
//
// If the mutex is currently locked, the calling thread will be parked until
@@ -61,3 +64,11 @@ _PyMutex_Unlock(PyMutex *m)
}
}
#define PyMutex_Unlock _PyMutex_Unlock
+
+// Checks if the mutex is currently locked.
+static inline int
+_PyMutex_IsLocked(PyMutex *m)
+{
+ return (_Py_atomic_load_uint8(&m->_bits) & _Py_LOCKED) != 0;
+}
+#define PyMutex_IsLocked _PyMutex_IsLocked
diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h
index bd6011b60ac..585120108cf 100644
--- a/Include/internal/pycore_lock.h
+++ b/Include/internal/pycore_lock.h
@@ -25,13 +25,6 @@ PyMutex_LockFast(PyMutex *m)
return _Py_atomic_compare_exchange_uint8(lock_bits, &expected, _Py_LOCKED);
}
-// Checks if the mutex is currently locked.
-static inline int
-PyMutex_IsLocked(PyMutex *m)
-{
- return (_Py_atomic_load_uint8(&m->_bits) & _Py_LOCKED) != 0;
-}
-
// Re-initializes the mutex after a fork to the unlocked state.
static inline void
_PyMutex_at_fork_reinit(PyMutex *m)
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index d0fe1f63250..8fe9875fae0 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -1033,6 +1033,20 @@ enum _PyAnnotateFormat {
int _PyObject_SetDict(PyObject *obj, PyObject *value);
+#ifndef Py_GIL_DISABLED
+static inline Py_ALWAYS_INLINE void _Py_INCREF_MORTAL(PyObject *op)
+{
+ assert(!_Py_IsStaticImmortal(op));
+ op->ob_refcnt++;
+ _Py_INCREF_STAT_INC();
+#if defined(Py_REF_DEBUG) && !defined(Py_LIMITED_API)
+ if (!_Py_IsImmortal(op)) {
+ _Py_INCREF_IncRefTotal();
+ }
+#endif
+}
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h
index 10e7199269e..48a40a4c347 100644
--- a/Include/internal/pycore_stackref.h
+++ b/Include/internal/pycore_stackref.h
@@ -626,7 +626,7 @@ _PyStackRef_FromPyObjectNew(PyObject *obj)
if (_Py_IsImmortal(obj)) {
return (_PyStackRef){ .bits = ((uintptr_t)obj) | Py_TAG_REFCNT};
}
- Py_INCREF_MORTAL(obj);
+ _Py_INCREF_MORTAL(obj);
_PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj };
PyStackRef_CheckValid(ref);
return ref;
@@ -637,7 +637,7 @@ static inline _PyStackRef
_PyStackRef_FromPyObjectNewMortal(PyObject *obj)
{
assert(obj != NULL);
- Py_INCREF_MORTAL(obj);
+ _Py_INCREF_MORTAL(obj);
_PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj };
PyStackRef_CheckValid(ref);
return ref;
@@ -654,14 +654,14 @@ PyStackRef_FromPyObjectBorrow(PyObject *obj)
/* WARNING: This macro evaluates its argument more than once */
#ifdef _WIN32
#define PyStackRef_DUP(REF) \
- (PyStackRef_RefcountOnObject(REF) ? (Py_INCREF_MORTAL(BITS_TO_PTR(REF)), (REF)) : (REF))
+ (PyStackRef_RefcountOnObject(REF) ? (_Py_INCREF_MORTAL(BITS_TO_PTR(REF)), (REF)) : (REF))
#else
static inline _PyStackRef
PyStackRef_DUP(_PyStackRef ref)
{
assert(!PyStackRef_IsNull(ref));
if (PyStackRef_RefcountOnObject(ref)) {
- Py_INCREF_MORTAL(BITS_TO_PTR(ref));
+ _Py_INCREF_MORTAL(BITS_TO_PTR(ref));
}
return ref;
}
diff --git a/Include/refcount.h b/Include/refcount.h
index 65a4e63a8b0..457972b6dcf 100644
--- a/Include/refcount.h
+++ b/Include/refcount.h
@@ -244,20 +244,6 @@ PyAPI_FUNC(void) Py_DecRef(PyObject *);
PyAPI_FUNC(void) _Py_IncRef(PyObject *);
PyAPI_FUNC(void) _Py_DecRef(PyObject *);
-#ifndef Py_GIL_DISABLED
-static inline Py_ALWAYS_INLINE void Py_INCREF_MORTAL(PyObject *op)
-{
- assert(!_Py_IsStaticImmortal(op));
- op->ob_refcnt++;
- _Py_INCREF_STAT_INC();
-#if defined(Py_REF_DEBUG) && !defined(Py_LIMITED_API)
- if (!_Py_IsImmortal(op)) {
- _Py_INCREF_IncRefTotal();
- }
-#endif
-}
-#endif
-
static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
{
#if defined(Py_LIMITED_API) && (Py_LIMITED_API+0 >= 0x030c0000 || defined(Py_REF_DEBUG))
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index e4c9a463855..7be1c9eebb3 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -2451,6 +2451,21 @@ class TestUopsOptimization(unittest.TestCase):
self.assertNotIn("_GUARD_TOS_FLOAT", uops)
self.assertNotIn("_GUARD_NOS_FLOAT", uops)
+ def test_binary_op_constant_evaluate(self):
+ def testfunc(n):
+ for _ in range(n):
+ 2 ** 65
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+
+ # For now... until we constant propagate it away.
+ self.assertIn("_BINARY_OP", uops)
+
+
def global_identity(x):
return x
diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py
index eb01328b6ea..81d4e39f5be 100644
--- a/Lib/test/test_generated_cases.py
+++ b/Lib/test/test_generated_cases.py
@@ -2398,6 +2398,53 @@ class TestGeneratedAbstractCases(unittest.TestCase):
"""
self.run_cases_test(input, input2, output)
+ def test_replace_opcode_escaping_uop_body_copied_in_complex(self):
+ input = """
+ pure op(OP, (foo -- res)) {
+ if (foo) {
+ res = ESCAPING_CODE(foo);
+ }
+ else {
+ res = 1;
+ }
+ }
+ """
+ input2 = """
+ op(OP, (foo -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
+ res = sym_new_known(ctx, foo);
+ }
+ """
+ output = """
+ case OP: {
+ JitOptRef foo;
+ JitOptRef res;
+ foo = stack_pointer[-1];
+ if (
+ sym_is_safe_const(ctx, foo)
+ ) {
+ JitOptRef foo_sym = foo;
+ _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ if (foo) {
+ res_stackref = ESCAPING_CODE(foo);
+ }
+ else {
+ res_stackref = 1;
+ }
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-1] = res;
+ break;
+ }
+ res = sym_new_known(ctx, foo);
+ stack_pointer[-1] = res;
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
def test_replace_opocode_uop_reject_array_effects(self):
input = """
pure op(OP, (foo[2] -- res)) {
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index fc26e71ffcb..fccdcc975e6 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -21,6 +21,7 @@ import types
import unittest.mock
import weakref
import typing
+import re
c_types = import_fresh_module('types', fresh=['_types'])
py_types = import_fresh_module('types', blocked=['_types'])
@@ -1375,6 +1376,27 @@ class MappingProxyTests(unittest.TestCase):
view = self.mappingproxy(mapping)
self.assertEqual(hash(view), hash(mapping))
+ def test_richcompare(self):
+ mp1 = self.mappingproxy({'a': 1})
+ mp1_2 = self.mappingproxy({'a': 1})
+ mp2 = self.mappingproxy({'a': 2})
+
+ self.assertTrue(mp1 == mp1_2)
+ self.assertFalse(mp1 != mp1_2)
+ self.assertFalse(mp1 == mp2)
+ self.assertTrue(mp1 != mp2)
+
+ msg = "not supported between instances of 'mappingproxy' and 'mappingproxy'"
+
+ with self.assertRaisesRegex(TypeError, msg):
+ mp1 > mp2
+ with self.assertRaisesRegex(TypeError, msg):
+ mp1 < mp1_2
+ with self.assertRaisesRegex(TypeError, msg):
+ mp2 >= mp2
+ with self.assertRaisesRegex(TypeError, msg):
+ mp1_2 <= mp1
+
class ClassCreationTests(unittest.TestCase):
@@ -2009,6 +2031,24 @@ class SimpleNamespaceTests(unittest.TestCase):
self.assertEqual(ns1, ns2)
self.assertNotEqual(ns2, types.SimpleNamespace())
+ def test_richcompare_unsupported(self):
+ ns1 = types.SimpleNamespace(x=1)
+ ns2 = types.SimpleNamespace(y=2)
+
+ msg = re.escape(
+ "not supported between instances of "
+ "'types.SimpleNamespace' and 'types.SimpleNamespace'"
+ )
+
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 > ns2
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 >= ns2
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 < ns2
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 <= ns2
+
def test_nested(self):
ns1 = types.SimpleNamespace(a=1, b=2)
ns2 = types.SimpleNamespace()
diff --git a/Lib/test/test_unittest/test_case.py b/Lib/test/test_unittest/test_case.py
index d66cab146af..cf10e956bf2 100644
--- a/Lib/test/test_unittest/test_case.py
+++ b/Lib/test/test_unittest/test_case.py
@@ -1920,6 +1920,22 @@ test case
with self.assertLogs():
raise ZeroDivisionError("Unexpected")
+ def testAssertLogsWithFormatter(self):
+ # Check alternative formats will be respected
+ format = "[No.1: the larch] %(levelname)s:%(name)s:%(message)s"
+ formatter = logging.Formatter(format)
+ with self.assertNoStderr():
+ with self.assertLogs() as cm:
+ log_foo.info("1")
+ log_foobar.debug("2")
+ self.assertEqual(cm.output, ["INFO:foo:1"])
+ self.assertLogRecords(cm.records, [{'name': 'foo'}])
+ with self.assertLogs(formatter=formatter) as cm:
+ log_foo.info("1")
+ log_foobar.debug("2")
+ self.assertEqual(cm.output, ["[No.1: the larch] INFO:foo:1"])
+ self.assertLogRecords(cm.records, [{'name': 'foo'}])
+
def testAssertNoLogsDefault(self):
with self.assertRaises(self.failureException) as cm:
with self.assertNoLogs():
diff --git a/Lib/unittest/_log.py b/Lib/unittest/_log.py
index 94868e5bb95..3d69385ea24 100644
--- a/Lib/unittest/_log.py
+++ b/Lib/unittest/_log.py
@@ -30,7 +30,7 @@ class _AssertLogsContext(_BaseTestCaseContext):
LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
- def __init__(self, test_case, logger_name, level, no_logs):
+ def __init__(self, test_case, logger_name, level, no_logs, formatter=None):
_BaseTestCaseContext.__init__(self, test_case)
self.logger_name = logger_name
if level:
@@ -39,13 +39,14 @@ class _AssertLogsContext(_BaseTestCaseContext):
self.level = logging.INFO
self.msg = None
self.no_logs = no_logs
+ self.formatter = formatter
def __enter__(self):
if isinstance(self.logger_name, logging.Logger):
logger = self.logger = self.logger_name
else:
logger = self.logger = logging.getLogger(self.logger_name)
- formatter = logging.Formatter(self.LOGGING_FORMAT)
+ formatter = self.formatter or logging.Formatter(self.LOGGING_FORMAT)
handler = _CapturingHandler()
handler.setLevel(self.level)
handler.setFormatter(formatter)
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index db10de68e4a..eba50839cd3 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -849,7 +849,7 @@ class TestCase(object):
context = _AssertNotWarnsContext(expected_warning, self)
return context.handle('_assertNotWarns', args, kwargs)
- def assertLogs(self, logger=None, level=None):
+ def assertLogs(self, logger=None, level=None, formatter=None):
"""Fail unless a log message of level *level* or higher is emitted
on *logger_name* or its children. If omitted, *level* defaults to
INFO and *logger* defaults to the root logger.
@@ -861,6 +861,8 @@ class TestCase(object):
`records` attribute will be a list of the corresponding LogRecord
objects.
+ Optionally supply `formatter` to control how messages are formatted.
+
Example::
with self.assertLogs('foo', level='INFO') as cm:
@@ -871,7 +873,7 @@ class TestCase(object):
"""
# Lazy import to avoid importing logging if it is not needed.
from ._log import _AssertLogsContext
- return _AssertLogsContext(self, logger, level, no_logs=False)
+ return _AssertLogsContext(self, logger, level, no_logs=False, formatter=formatter)
def assertNoLogs(self, logger=None, level=None):
""" Fail unless no log messages of level *level* or higher are emitted
diff --git a/Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst b/Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst
new file mode 100644
index 00000000000..f060f09de19
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst
@@ -0,0 +1 @@
+Expose :c:func:`PyMutex_IsLocked` as part of the public C API.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst
new file mode 100644
index 00000000000..5a622bab8b8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst
@@ -0,0 +1,2 @@
+Improve :exc:`TypeError` error message, when richcomparing two
+:class:`types.MappingProxyType` objects.
diff --git a/Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst b/Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst
new file mode 100644
index 00000000000..801115202d0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst
@@ -0,0 +1,2 @@
+Improve :exc:`TypeError` error message, when richcomparing two
+:class:`types.SimpleNamespace` objects.
diff --git a/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst b/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
new file mode 100644
index 00000000000..42e4a01c0cc
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
@@ -0,0 +1,2 @@
+Expose log formatter to users in TestCase.assertLogs.
+:func:`unittest.TestCase.assertLogs` will now optionally accept a formatter that will be used to format the strings in output if provided.
diff --git a/Objects/descrobject.c b/Objects/descrobject.c
index 10c465b95ac..d3d17e92b6d 100644
--- a/Objects/descrobject.c
+++ b/Objects/descrobject.c
@@ -1233,7 +1233,10 @@ static PyObject *
mappingproxy_richcompare(PyObject *self, PyObject *w, int op)
{
mappingproxyobject *v = (mappingproxyobject *)self;
- return PyObject_RichCompare(v->mapping, w, op);
+ if (op == Py_EQ || op == Py_NE) {
+ return PyObject_RichCompare(v->mapping, w, op);
+ }
+ Py_RETURN_NOTIMPLEMENTED;
}
static int
diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c
index 0fc2bcea4cb..201cb8a7df8 100644
--- a/Objects/namespaceobject.c
+++ b/Objects/namespaceobject.c
@@ -194,10 +194,14 @@ namespace_clear(PyObject *op)
static PyObject *
namespace_richcompare(PyObject *self, PyObject *other, int op)
{
- if (PyObject_TypeCheck(self, &_PyNamespace_Type) &&
- PyObject_TypeCheck(other, &_PyNamespace_Type))
+ if (
+ (op == Py_EQ || op == Py_NE) &&
+ PyObject_TypeCheck(self, &_PyNamespace_Type) &&
+ PyObject_TypeCheck(other, &_PyNamespace_Type)
+ ) {
return PyObject_RichCompare(((_PyNamespaceObject *)self)->ns_dict,
((_PyNamespaceObject *)other)->ns_dict, op);
+ }
Py_RETURN_NOTIMPLEMENTED;
}
diff --git a/Python/lock.c b/Python/lock.c
index eb09019e0a2..a49d587a168 100644
--- a/Python/lock.c
+++ b/Python/lock.c
@@ -634,3 +634,11 @@ PyMutex_Unlock(PyMutex *m)
Py_FatalError("unlocking mutex that is not locked");
}
}
+
+
+#undef PyMutex_IsLocked
+int
+PyMutex_IsLocked(PyMutex *m)
+{
+ return _PyMutex_IsLocked(m);
+}
diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h
index 82660d02a4e..41402200c16 100644
--- a/Python/optimizer_cases.c.h
+++ b/Python/optimizer_cases.c.h
@@ -2723,9 +2723,6 @@
PyObject *lhs_o = PyStackRef_AsPyObjectBorrow(lhs);
PyObject *rhs_o = PyStackRef_AsPyObjectBorrow(rhs);
assert(_PyEval_BinaryOps[oparg]);
- stack_pointer[-2] = res;
- stack_pointer += -1;
- assert(WITHIN_STACK_BOUNDS());
PyObject *res_o = _PyEval_BinaryOps[oparg](lhs_o, rhs_o);
if (res_o == NULL) {
JUMP_TO_LABEL(error);
@@ -2733,6 +2730,9 @@
res_stackref = PyStackRef_FromPyObjectSteal(res_o);
/* End of uop copied from bytecodes for constant evaluation */
res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
bool lhs_int = sym_matches_type(lhs, &PyLong_Type);
diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py
index 47de205c0e9..4c210fbf8d2 100644
--- a/Tools/cases_generator/generators_common.py
+++ b/Tools/cases_generator/generators_common.py
@@ -106,8 +106,9 @@ class Emitter:
out: CWriter
labels: dict[str, Label]
_replacers: dict[str, ReplacementFunctionType]
+ cannot_escape: bool
- def __init__(self, out: CWriter, labels: dict[str, Label]):
+ def __init__(self, out: CWriter, labels: dict[str, Label], cannot_escape: bool = False):
self._replacers = {
"EXIT_IF": self.exit_if,
"DEOPT_IF": self.deopt_if,
@@ -127,6 +128,7 @@ class Emitter:
}
self.out = out
self.labels = labels
+ self.cannot_escape = cannot_escape
def dispatch(
self,
@@ -238,7 +240,8 @@ class Emitter:
next(tkn_iter)
self._print_storage("DECREF_INPUTS", storage)
try:
- storage.close_inputs(self.out)
+ if not self.cannot_escape:
+ storage.close_inputs(self.out)
except StackError as ex:
raise analysis_error(ex.args[0], tkn)
except Exception as ex:
@@ -476,7 +479,7 @@ class Emitter:
reachable = True
tkn = stmt.contents[-1]
try:
- if stmt in uop.properties.escaping_calls:
+ if stmt in uop.properties.escaping_calls and not self.cannot_escape:
escape = uop.properties.escaping_calls[stmt]
if escape.kills is not None:
self.stackref_kill(escape.kills, storage, True)
@@ -513,7 +516,7 @@ class Emitter:
self.out.emit(tkn)
else:
self.out.emit(tkn)
- if stmt in uop.properties.escaping_calls:
+ if stmt in uop.properties.escaping_calls and not self.cannot_escape:
self.emit_reload(storage)
return reachable, None, storage
except StackError as ex:
diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py
index 4556b6d5a74..81ae534bdda 100644
--- a/Tools/cases_generator/optimizer_generator.py
+++ b/Tools/cases_generator/optimizer_generator.py
@@ -245,6 +245,7 @@ class OptimizerConstantEmitter(OptimizerEmitter):
outp.name: self.emit_stackref_override for outp in self.original_uop.stack.outputs
}
self._replacers = {**self._replacers, **overrides}
+ self.cannot_escape = True
def emit_to_with_replacement(
self,
diff --git a/configure b/configure
index 3a103c39b62..9df366697b8 100755
--- a/configure
+++ b/configure
@@ -33180,13 +33180,13 @@ printf "%s\n" "$py_cv_module__decimal" >&6; }
if test "x$with_system_libmpdec" = xno
then :
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: the bundled copy of libmpdecimal is scheduled for removal in Python 3.16; consider using a system installed mpdecimal library." >&5
-printf "%s\n" "$as_me: WARNING: the bundled copy of libmpdecimal is scheduled for removal in Python 3.16; consider using a system installed mpdecimal library." >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: the bundled copy of libmpdec is scheduled for removal in Python 3.16; consider using a system installed mpdecimal library." >&5
+printf "%s\n" "$as_me: WARNING: the bundled copy of libmpdec is scheduled for removal in Python 3.16; consider using a system installed mpdecimal library." >&2;}
fi
if test "$with_system_libmpdec" = "yes" && test "$have_mpdec" = "no"
then :
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: no system libmpdecimal found; falling back to pure-Python version for the decimal module" >&5
-printf "%s\n" "$as_me: WARNING: no system libmpdecimal found; falling back to pure-Python version for the decimal module" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: no system libmpdec found; falling back to pure-Python version for the decimal module" >&5
+printf "%s\n" "$as_me: WARNING: no system libmpdec found; falling back to pure-Python version for the decimal module" >&2;}
fi
diff --git a/configure.ac b/configure.ac
index 761d1dbd45c..cb7f2144345 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8129,11 +8129,11 @@ PY_STDLIB_MOD([_decimal],
AS_VAR_IF([with_system_libmpdec], [no],
[AC_MSG_WARN([m4_normalize([
- the bundled copy of libmpdecimal is scheduled for removal in Python 3.16;
+ the bundled copy of libmpdec is scheduled for removal in Python 3.16;
consider using a system installed mpdecimal library.])])])
AS_IF([test "$with_system_libmpdec" = "yes" && test "$have_mpdec" = "no"],
[AC_MSG_WARN([m4_normalize([
- no system libmpdecimal found; falling back to pure-Python version
+ no system libmpdec found; falling back to pure-Python version
for the decimal module])])])
PY_STDLIB_MOD([_dbm],