From 8148a34434e23c53f3475f8b0dbfaab73006676b Mon Sep 17 00:00:00 2001 From: Arcadiy Ivanov Date: Wed, 26 Aug 2026 04:20:46 -0400 Subject: [PATCH] gh-156404: Restore the 'fullname' variable for .pth import lines PEP 829 moved .pth handling from site.addpackage() into StartupState._exec_imports(), and the local holding the path of the .pth file being processed was renamed from 'fullname' to 'filename'. Import lines in .pth files generated before Python 3.15 read that local, so they now fail with NameError on every interpreter startup. Inject 'fullname' into the frame which executes pth code, next to the 'sitedir' shim added for gh-149671. Reported downstream as https://github.com/karellen/wheel-axle/issues/38 --- Lib/site.py | 6 ++++++ Lib/test/test_site.py | 17 +++++++++++++++++ ...26-08-26-08-20-07.gh-issue-156404.2gP33I.rst | 3 +++ 3 files changed, 26 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-26-08-20-07.gh-issue-156404.2gP33I.rst diff --git a/Lib/site.py b/Lib/site.py index 873c562d890a3bb..294a4d6f641c39d 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -510,6 +510,12 @@ def _exec_imports(self): # generated by setuptools use: sys._getframe(1).f_locals['sitedir']. sitedir = os.path.dirname(filename) + # Inject 'fullname' local variable in the current frame for + # compatibility with Python 3.14, where the path of the ".pth" + # file being processed was available to import lines under that + # name. + fullname = filename + # Given "/path/to/foo.pth", check whether "/path/to/foo.start" was # registered in this same batch. name, dot, pth = filename.rpartition(".") diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index bcb51ed7d8476a8..fc5e6369eacaada 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -247,6 +247,23 @@ def test_sitedir_variable(self): sitedir = stdout.getvalue().rstrip() self.assertEqual(sitedir, pth_dir) + def test_fullname_variable(self): + # gh-156404: ".pth" files generated before Python 3.15 read the path + # of the file being processed from the frame executing the import + # line, under the name "fullname". For example, "wheel-axle" + # generates: "import wheel_axle.runtime; + # wheel_axle.runtime.finalize(fullname);" + code = '; '.join(( + "import sys", + "print(fullname)", + "print(filename)", + )) + pth_dir, pth_fn = self.make_pth(code) + with support.captured_stdout() as stdout: + site.addpackage(pth_dir, pth_fn, set()) + expected = os.path.join(pth_dir, pth_fn) + self.assertEqual(stdout.getvalue().splitlines(), [expected, expected]) + # This tests _getuserbase, hence the double underline # to distinguish from a test for getuserbase def test__getuserbase(self): diff --git a/Misc/NEWS.d/next/Library/2026-08-26-08-20-07.gh-issue-156404.2gP33I.rst b/Misc/NEWS.d/next/Library/2026-08-26-08-20-07.gh-issue-156404.2gP33I.rst new file mode 100644 index 000000000000000..475d09c764aa3b4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-26-08-20-07.gh-issue-156404.2gP33I.rst @@ -0,0 +1,3 @@ +Restore compatibility with :file:`.pth` files generated before Python 3.15 in +the :mod:`site` module. Inject the ``fullname`` variable in the frame which +executes pth code.