[Python-checkins] gh-93461: Invalidate sys.path_importer_cache entries with relative paths (GH-93653)

miss-islington webhook-mailer at python.org
Mon Jun 13 02:30:08 EDT 2022


https://github.com/python/cpython/commit/3d1c080591910c9c1ee80e5465ce7c91b67c907d
commit: 3d1c080591910c9c1ee80e5465ce7c91b67c907d
branch: 3.11
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2022-06-12T23:29:59-07:00
summary:

gh-93461: Invalidate sys.path_importer_cache entries with relative paths (GH-93653)

(cherry picked from commit 09243b898a13f3f61e275c1031143d1225e70916)

Co-authored-by: Christian Heimes <christian at python.org>

files:
A Misc/NEWS.d/next/Core and Builtins/2022-06-09-19-19-02.gh-issue-93461.5DqP1e.rst
M Lib/importlib/_bootstrap_external.py
M Lib/test/test_import/__init__.py
M Lib/test/test_importlib/import_/test_path.py
M Lib/test/test_importlib/test_api.py

diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py
index 5f67226adfc52..0061ad091c23b 100644
--- a/Lib/importlib/_bootstrap_external.py
+++ b/Lib/importlib/_bootstrap_external.py
@@ -1394,7 +1394,9 @@ def invalidate_caches():
         """Call the invalidate_caches() method on all path entry finders
         stored in sys.path_importer_caches (where implemented)."""
         for name, finder in list(sys.path_importer_cache.items()):
-            if finder is None:
+            # Drop entry if finder name is a relative path. The current
+            # working directory may have changed.
+            if finder is None or not _path_isabs(name):
                 del sys.path_importer_cache[name]
             elif hasattr(finder, 'invalidate_caches'):
                 finder.invalidate_caches()
@@ -1562,9 +1564,12 @@ def __init__(self, path, *loader_details):
             loaders.extend((suffix, loader) for suffix in suffixes)
         self._loaders = loaders
         # Base (directory) path
-        self.path = path or '.'
-        if not _path_isabs(self.path):
-            self.path = _path_join(_os.getcwd(), self.path)
+        if not path or path == '.':
+            self.path = _os.getcwd()
+        elif not _path_isabs(path):
+            self.path = _path_join(_os.getcwd(), path)
+        else:
+            self.path = path
         self._path_mtime = -1
         self._path_cache = set()
         self._relaxed_path_cache = set()
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index be2e91ddd9a9a..8357586b57f07 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -927,7 +927,7 @@ def test_missing_source_legacy(self):
         m = __import__(TESTFN)
         try:
             self.assertEqual(m.__file__,
-                             os.path.join(os.getcwd(), os.curdir, os.path.relpath(pyc_file)))
+                             os.path.join(os.getcwd(), os.path.relpath(pyc_file)))
         finally:
             os.remove(pyc_file)
 
@@ -935,7 +935,7 @@ def test___cached__(self):
         # Modules now also have an __cached__ that points to the pyc file.
         m = __import__(TESTFN)
         pyc_file = importlib.util.cache_from_source(TESTFN + '.py')
-        self.assertEqual(m.__cached__, os.path.join(os.getcwd(), os.curdir, pyc_file))
+        self.assertEqual(m.__cached__, os.path.join(os.getcwd(), pyc_file))
 
     @skip_if_dont_write_bytecode
     def test___cached___legacy_pyc(self):
@@ -951,7 +951,7 @@ def test___cached___legacy_pyc(self):
         importlib.invalidate_caches()
         m = __import__(TESTFN)
         self.assertEqual(m.__cached__,
-                         os.path.join(os.getcwd(), os.curdir, os.path.relpath(pyc_file)))
+                         os.path.join(os.getcwd(), os.path.relpath(pyc_file)))
 
     @skip_if_dont_write_bytecode
     def test_package___cached__(self):
@@ -971,10 +971,10 @@ def cleanup():
         m = __import__('pep3147.foo')
         init_pyc = importlib.util.cache_from_source(
             os.path.join('pep3147', '__init__.py'))
-        self.assertEqual(m.__cached__, os.path.join(os.getcwd(), os.curdir, init_pyc))
+        self.assertEqual(m.__cached__, os.path.join(os.getcwd(), init_pyc))
         foo_pyc = importlib.util.cache_from_source(os.path.join('pep3147', 'foo.py'))
         self.assertEqual(sys.modules['pep3147.foo'].__cached__,
-                         os.path.join(os.getcwd(), os.curdir, foo_pyc))
+                         os.path.join(os.getcwd(), foo_pyc))
 
     def test_package___cached___from_pyc(self):
         # Like test___cached__ but ensuring __cached__ when imported from a
@@ -998,10 +998,10 @@ def cleanup():
         m = __import__('pep3147.foo')
         init_pyc = importlib.util.cache_from_source(
             os.path.join('pep3147', '__init__.py'))
-        self.assertEqual(m.__cached__, os.path.join(os.getcwd(), os.curdir, init_pyc))
+        self.assertEqual(m.__cached__, os.path.join(os.getcwd(), init_pyc))
         foo_pyc = importlib.util.cache_from_source(os.path.join('pep3147', 'foo.py'))
         self.assertEqual(sys.modules['pep3147.foo'].__cached__,
-                         os.path.join(os.getcwd(), os.curdir, foo_pyc))
+                         os.path.join(os.getcwd(), foo_pyc))
 
     def test_recompute_pyc_same_second(self):
         # Even when the source file doesn't change timestamp, a change in
diff --git a/Lib/test/test_importlib/import_/test_path.py b/Lib/test/test_importlib/import_/test_path.py
index 6f1d0cabd28a6..de620842bbc52 100644
--- a/Lib/test/test_importlib/import_/test_path.py
+++ b/Lib/test/test_importlib/import_/test_path.py
@@ -202,10 +202,11 @@ def __init__(self):
             def invalidate_caches(self):
                 self.called = True
 
-        cache = {'leave_alone': object(), 'finder_to_invalidate': FakeFinder()}
+        key = os.path.abspath('finder_to_invalidate')
+        cache = {'leave_alone': object(), key: FakeFinder()}
         with util.import_state(path_importer_cache=cache):
             self.machinery.PathFinder.invalidate_caches()
-        self.assertTrue(cache['finder_to_invalidate'].called)
+        self.assertTrue(cache[key].called)
 
     def test_invalidate_caches_clear_out_None(self):
         # Clear out None in sys.path_importer_cache() when invalidating caches.
@@ -214,6 +215,16 @@ def test_invalidate_caches_clear_out_None(self):
             self.machinery.PathFinder.invalidate_caches()
         self.assertEqual(len(cache), 0)
 
+    def test_invalidate_caches_clear_out_relative_path(self):
+        class FakeFinder:
+            def invalidate_caches(self):
+                pass
+
+        cache = {'relative_path': FakeFinder()}
+        with util.import_state(path_importer_cache=cache):
+            self.machinery.PathFinder.invalidate_caches()
+        self.assertEqual(cache, {})
+
 
 class FindModuleTests(FinderTests):
     def find(self, *args, **kwargs):
diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py
index 1f8f7c00bda53..1beb7835d4f16 100644
--- a/Lib/test/test_importlib/test_api.py
+++ b/Lib/test/test_importlib/test_api.py
@@ -395,7 +395,7 @@ def find_module(self, *args):
             def invalidate_caches(self):
                 self.called = True
 
-        key = 'gobledeegook'
+        key = os.path.abspath('gobledeegook')
         meta_ins = InvalidatingNullFinder()
         path_ins = InvalidatingNullFinder()
         sys.meta_path.insert(0, meta_ins)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-06-09-19-19-02.gh-issue-93461.5DqP1e.rst b/Misc/NEWS.d/next/Core and Builtins/2022-06-09-19-19-02.gh-issue-93461.5DqP1e.rst
new file mode 100644
index 0000000000000..f6ed14887eae1
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-06-09-19-19-02.gh-issue-93461.5DqP1e.rst	
@@ -0,0 +1,6 @@
+:func:`importlib.invalidate_caches` now drops entries from
+:data:`sys.path_importer_cache` with a relative path as name. This solves a
+caching issue when a process changes its current working directory.
+
+``FileFinder`` no longer inserts a dot in the path, e.g.
+``/egg/./spam`` is now ``/egg/spam``.



More information about the Python-checkins mailing list