From 47d2f68df215b5bcbc76a50d3f4e0b68791e3e79 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Tue, 21 Oct 2025 08:12:04 +0100 Subject: [PATCH 1/4] gh-139707: Better `ModuleNotFoundError` message for missing stdlib modules (GH-140219) --- Lib/test/test_traceback.py | 16 ++++++++++++++-- Lib/traceback.py | 13 ++++++++----- ...025-10-16-16-10-11.gh-issue-139707.zR6Qtn.rst | 2 ++ 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-16-16-10-11.gh-issue-139707.zR6Qtn.rst diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index bd3ecfd9a3863d..bf57867a8715c0 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -5034,7 +5034,8 @@ def test_no_site_package_flavour(self): self.assertIn( (b"Site initialization is disabled, did you forget to " - b"add the site-packages directory to sys.path?"), stderr + b"add the site-packages directory to sys.path " + b"or to enable your virtual environment?"), stderr ) code = """ @@ -5046,9 +5047,20 @@ def test_no_site_package_flavour(self): self.assertNotIn( (b"Site initialization is disabled, did you forget to " - b"add the site-packages directory to sys.path?"), stderr + b"add the site-packages directory to sys.path " + b"or to enable your virtual environment?"), stderr ) + def test_missing_stdlib_package(self): + code = """ + import sys + sys.stdlib_module_names |= {'spam'} + import spam + """ + _, _, stderr = assert_python_failure('-S', '-c', code) + + self.assertIn(b"Standard library module 'spam' was not found", stderr) + class TestColorizedTraceback(unittest.TestCase): maxDiff = None diff --git a/Lib/traceback.py b/Lib/traceback.py index 692d44837936ee..9b4b8c7d566fe8 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1107,11 +1107,14 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name) if suggestion: self._str += f". Did you mean: '{suggestion}'?" - elif exc_type and issubclass(exc_type, ModuleNotFoundError) and \ - sys.flags.no_site and \ - getattr(exc_value, "name", None) not in sys.stdlib_module_names: - self._str += (". Site initialization is disabled, did you forget to " - + "add the site-packages directory to sys.path?") + elif exc_type and issubclass(exc_type, ModuleNotFoundError): + module_name = getattr(exc_value, "name", None) + if module_name in sys.stdlib_module_names: + self._str = f"Standard library module '{module_name}' was not found" + elif sys.flags.no_site: + self._str += (". Site initialization is disabled, did you forget to " + + "add the site-packages directory to sys.path " + + "or to enable your virtual environment?") elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \ getattr(exc_value, "name", None) is not None: wrong_name = getattr(exc_value, "name", None) diff --git a/Misc/NEWS.d/next/Library/2025-10-16-16-10-11.gh-issue-139707.zR6Qtn.rst b/Misc/NEWS.d/next/Library/2025-10-16-16-10-11.gh-issue-139707.zR6Qtn.rst new file mode 100644 index 00000000000000..c5460aae8b3638 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-16-16-10-11.gh-issue-139707.zR6Qtn.rst @@ -0,0 +1,2 @@ +Improve :exc:`ModuleNotFoundError` error message when a :term:`standard library` +module is missing. From e8e0f411bae1dde634fb152a81e1db1ad1cd2eaa Mon Sep 17 00:00:00 2001 From: Shamil Date: Tue, 21 Oct 2025 10:54:34 +0300 Subject: [PATCH 2/4] gh-140398: fix memory leaks in `readline` module when `PySys_Audit` fails (#140400) --- .../2025-10-21-09-20-03.gh-issue-140398.SoABwJ.rst | 4 ++++ Modules/readline.c | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-21-09-20-03.gh-issue-140398.SoABwJ.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-21-09-20-03.gh-issue-140398.SoABwJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-21-09-20-03.gh-issue-140398.SoABwJ.rst new file mode 100644 index 00000000000000..481dac7f26dd5e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-21-09-20-03.gh-issue-140398.SoABwJ.rst @@ -0,0 +1,4 @@ +Fix memory leaks in :mod:`readline` functions +:func:`~readline.read_init_file`, :func:`~readline.read_history_file`, +:func:`~readline.write_history_file`, and +:func:`~readline.append_history_file` when :c:func:`PySys_Audit` fails. diff --git a/Modules/readline.c b/Modules/readline.c index 630a6879990949..e89755b0cb4b2a 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -255,6 +255,7 @@ readline_read_init_file_impl(PyObject *module, PyObject *filename_obj) if (!PyUnicode_FSConverter(filename_obj, &filename_bytes)) return NULL; if (PySys_Audit("open", "OCi", filename_obj, 'r', 0) < 0) { + Py_DECREF(filename_bytes); return NULL; } errno = rl_read_init_file(PyBytes_AS_STRING(filename_bytes)); @@ -298,6 +299,7 @@ readline_read_history_file_impl(PyObject *module, PyObject *filename_obj) if (!PyUnicode_FSConverter(filename_obj, &filename_bytes)) return NULL; if (PySys_Audit("open", "OCi", filename_obj, 'r', 0) < 0) { + Py_DECREF(filename_bytes); return NULL; } errno = read_history(PyBytes_AS_STRING(filename_bytes)); @@ -343,6 +345,7 @@ readline_write_history_file_impl(PyObject *module, PyObject *filename_obj) return NULL; filename = PyBytes_AS_STRING(filename_bytes); if (PySys_Audit("open", "OCi", filename_obj, 'w', 0) < 0) { + Py_DECREF(filename_bytes); return NULL; } } else { @@ -400,6 +403,7 @@ readline_append_history_file_impl(PyObject *module, int nelements, return NULL; filename = PyBytes_AS_STRING(filename_bytes); if (PySys_Audit("open", "OCi", filename_obj, 'a', 0) < 0) { + Py_DECREF(filename_bytes); return NULL; } } else { From 5c41666ec48e643485628ef06e98373b48744c6e Mon Sep 17 00:00:00 2001 From: Cycloctane Date: Tue, 21 Oct 2025 16:52:57 +0800 Subject: [PATCH 3/4] gh-133951: Add venv changes to documentation and whatsnew (GH-139704) --- Doc/library/venv.rst | 5 +++++ Doc/whatsnew/3.15.rst | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst index b0eb8ee18fa25f..4c000a92e87d0e 100644 --- a/Doc/library/venv.rst +++ b/Doc/library/venv.rst @@ -407,6 +407,8 @@ creation according to their needs, the :class:`EnvBuilder` class. * ``lib_path`` - The purelib path for the virtual environment. + * ``platlib_path`` - The platlib path for the virtual environment. + * ``bin_path`` - The script path for the virtual environment. * ``bin_name`` - The name of the script path relative to the virtual @@ -431,6 +433,9 @@ creation according to their needs, the :class:`EnvBuilder` class. The attribute ``lib_path`` was added to the context, and the context object was documented. + .. versionchanged:: 3.15 + The attribute ``platlib_path`` was added to the context. + .. method:: create_configuration(context) Creates the ``pyvenv.cfg`` configuration file in the environment. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d406b263f174e3..8503a4c7f973fc 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -601,6 +601,17 @@ unittest (Contributed by Garry Cairns in :gh:`134567`.) +venv +---- + +* On POSIX platforms, platlib directories will be created if needed when + creating virtual environments, instead of using ``lib64 -> lib`` symlink. + This means purelib and platlib of virtual environments no longer share the + same ``lib`` directory on platforms where :data:`sys.platlibdir` is not + equal to ``lib``. + (Contributed by Rui Xi in :gh:`133951`.) + + xml.parsers.expat ----------------- From fe4b60208e4edfdd781eac6b706b29a60be2bb07 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 21 Oct 2025 12:24:49 +0200 Subject: [PATCH 4/4] gh-83714: Fix os.statx() tests on tmpfs: st_birthtime can be None (#140407) --- Lib/test/test_os/test_os.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index 9bb4cb7e526b4f..67599537736c8f 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -648,9 +648,10 @@ def check_timestamp_agreement(self, result, names): # Make sure that the st_?time and st_?time_ns fields roughly agree # (they should always agree up to around tens-of-microseconds) for name in names: - floaty = int(getattr(result, name) * 100_000) - nanosecondy = getattr(result, name + "_ns") // 10_000 - self.assertAlmostEqual(floaty, nanosecondy, delta=2, msg=name) + with self.subTest(name=name): + floaty = int(getattr(result, name) * 100_000) + nanosecondy = getattr(result, name + "_ns") // 10_000 + self.assertAlmostEqual(floaty, nanosecondy, delta=2, msg=name) def check_stat_attributes(self, fname): result = os.stat(fname) @@ -741,14 +742,19 @@ def test_stat_result_pickle(self): unpickled = pickle.loads(p) self.assertEqual(result, unpickled) - def check_statx_attributes(self, fname): + def check_statx_attributes(self, filename): maximal_mask = 0 for name in dir(os): if name.startswith('STATX_'): maximal_mask |= getattr(os, name) - result = os.statx(self.fname, maximal_mask) + result = os.statx(filename, maximal_mask) + basic_result = os.stat(filename) time_attributes = ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime') + # gh-83714: st_birthtime can be None on tmpfs even if STATX_BTIME mask + # is used + time_attributes = [name for name in time_attributes + if getattr(result, name) is not None] self.check_timestamp_agreement(result, time_attributes) # Check that valid attributes match os.stat. @@ -773,7 +779,6 @@ def check_statx_attributes(self, fname): ('st_rdev', 0), ('st_dev', 0), ) - basic_result = os.stat(self.fname) for name, bits in requirements: if result.stx_mask & bits == bits and hasattr(basic_result, name): x = getattr(result, name)