gh-156404: Restore the 'fullname' variable for .pth import lines (#156406)
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
Co-authored-by: Barry Warsaw <barry@python.org>
diff --git a/Lib/site.py b/Lib/site.py
index 873c562..294a4d6 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 bcb51ed..fc5e636 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 0000000..475d09c
--- /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.