diff --git a/Lib/site.py b/Lib/site.py index 30015b3f26b4b3..2ad5d53975529f 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -242,14 +242,14 @@ def addsitedir(sitedir, known_paths=None): if not sitedircase in known_paths: sys.path.append(sitedir) # Add path component known_paths.add(sitedircase) - try: - names = os.listdir(sitedir) - except OSError: - return - names = [name for name in names - if name.endswith(".pth") and not name.startswith(".")] - for name in sorted(names): - addpackage(sitedir, name, known_paths) + try: + names = os.listdir(sitedir) + except OSError: + return + names = [name for name in names + if name.endswith(".pth") and not name.startswith(".")] + for name in sorted(names): + addpackage(sitedir, name, known_paths) if reset: known_paths = None return known_paths diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index e7dc5e2611c2de..72fec8e1fd7dd4 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -116,6 +116,7 @@ def pth_file_tests(self, pth_file): "%s not in sys.modules" % pth_file.imported) self.assertIn(site.makepath(pth_file.good_dir_path)[0], sys.path) self.assertFalse(os.path.exists(pth_file.bad_dir_path)) + self.assertFalse(os.path.exists(pth_file.idempotent_fail_path)) def test_addpackage(self): # Make sure addpackage() imports if the line starts with 'import', @@ -199,6 +200,19 @@ def test_addsitedir(self): finally: pth_file.cleanup() + def test_addsitedir_idempotent(self): + pth_file = PthFile() + pth_file.cleanup(prep=True) + + try: + pth_file.create() + dirs = set() + dirs = site.addsitedir(pth_file.base_dir, dirs) + dirs = site.addsitedir(pth_file.base_dir, dirs) + self.pth_file_tests(pth_file) + finally: + pth_file.cleanup(prep=True) + def test_addsitedir_dotfile(self): pth_file = PthFile('.dotfile') pth_file.cleanup(prep=True) @@ -414,6 +428,7 @@ def __init__(self, filename_base=TESTFN, imported="time", self.bad_dirname = bad_dirname self.good_dir_path = os.path.join(self.base_dir, self.good_dirname) self.bad_dir_path = os.path.join(self.base_dir, self.bad_dirname) + self.idempotent_fail_path = os.path.join(self.base_dir, 'idempotent') def create(self): """Create a .pth file with a comment, blank lines, an ``import @@ -430,6 +445,13 @@ def create(self): try: print("#import @bad module name", file=FILE) print("\n", file=FILE) + + PROG = f'''\ +if {self.imported!r} in sys.modules: + open({self.idempotent_fail_path!r}, 'a+').close() +''' + print(f"import sys; exec({PROG!r})", file=FILE) + print("import %s" % self.imported, file=FILE) print(self.good_dirname, file=FILE) print(self.bad_dirname, file=FILE) @@ -454,6 +476,8 @@ def cleanup(self, prep=False): os.rmdir(self.good_dir_path) if os.path.exists(self.bad_dir_path): os.rmdir(self.bad_dir_path) + if os.path.exists(self.idempotent_fail_path): + os.remove(self.idempotent_fail_path) class ImportSideEffectTests(unittest.TestCase): """Test side-effects from importing 'site'.""" diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst new file mode 100644 index 00000000000000..a14e47435be567 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-16-15-15.gh-issue-75723.BZ4Rsn.rst @@ -0,0 +1 @@ +Idempotent ``.pth`` file exection in :meth:`site.addsitedir`.