]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-122255: Synchronize warnings in C and Python implementations of the warnings modul...
authorSerhiy Storchaka <storchaka@gmail.com>
Fri, 14 Nov 2025 14:49:28 +0000 (16:49 +0200)
committerGitHub <noreply@github.com>
Fri, 14 Nov 2025 14:49:28 +0000 (16:49 +0200)
In the linecache module and in the Python implementation of the
warnings module, a DeprecationWarning is issued when
m.__loader__ differs from m.__spec__.loader (like in the C
implementation of the warnings module).

Lib/linecache.py
Lib/test/test_linecache.py
Lib/test/test_warnings/__init__.py
Misc/NEWS.d/next/Library/2024-08-08-12-39-36.gh-issue-122255.J_gU8Y.rst [new file with mode: 0644]

index ef3b2d9136b4d20fc0175af7fdd359884c66a6a9..b5bf9dbdd3cbc7e582e4911eb853c0e68fb58f61 100644 (file)
@@ -224,21 +224,58 @@ def lazycache(filename, module_globals):
 def _make_lazycache_entry(filename, module_globals):
     if not filename or (filename.startswith('<') and filename.endswith('>')):
         return None
-    # Try for a __loader__, if available
-    if module_globals and '__name__' in module_globals:
-        spec = module_globals.get('__spec__')
-        name = getattr(spec, 'name', None) or module_globals['__name__']
-        loader = getattr(spec, 'loader', None)
-        if loader is None:
-            loader = module_globals.get('__loader__')
-        get_source = getattr(loader, 'get_source', None)
-
-        if name and get_source:
-            def get_lines(name=name, *args, **kwargs):
-                return get_source(name, *args, **kwargs)
-            return (get_lines,)
-    return None
 
+    if module_globals is not None and not isinstance(module_globals, dict):
+        raise TypeError(f'module_globals must be a dict, not {type(module_globals).__qualname__}')
+    if not module_globals or '__name__' not in module_globals:
+        return None
+
+    spec = module_globals.get('__spec__')
+    name = getattr(spec, 'name', None) or module_globals['__name__']
+    if name is None:
+        return None
+
+    loader = _bless_my_loader(module_globals)
+    if loader is None:
+        return None
+
+    get_source = getattr(loader, 'get_source', None)
+    if get_source is None:
+        return None
+
+    def get_lines(name=name, *args, **kwargs):
+        return get_source(name, *args, **kwargs)
+    return (get_lines,)
+
+def _bless_my_loader(module_globals):
+    # Similar to _bless_my_loader() in importlib._bootstrap_external,
+    # but always emits warnings instead of errors.
+    loader = module_globals.get('__loader__')
+    if loader is None and '__spec__' not in module_globals:
+        return None
+    spec = module_globals.get('__spec__')
+
+    # The __main__ module has __spec__ = None.
+    if spec is None and module_globals.get('__name__') == '__main__':
+        return loader
+
+    spec_loader = getattr(spec, 'loader', None)
+    if spec_loader is None:
+        import warnings
+        warnings.warn(
+            'Module globals is missing a __spec__.loader',
+            DeprecationWarning)
+        return loader
+
+    assert spec_loader is not None
+    if loader is not None and loader != spec_loader:
+        import warnings
+        warnings.warn(
+            'Module globals; __loader__ != __spec__.loader',
+            DeprecationWarning)
+        return loader
+
+    return spec_loader
 
 
 def _register_code(code, string, name):
index 02f65338428c8fe13aa78e7d22b8f75df3b0a3c2..fcd94edc611fac3fde6ceef86c2dd50e4e06a9df 100644 (file)
@@ -259,22 +259,44 @@ class LineCacheTests(unittest.TestCase):
     def test_loader(self):
         filename = 'scheme://path'
 
-        for loader in (None, object(), NoSourceLoader()):
+        linecache.clearcache()
+        module_globals = {'__name__': 'a.b.c', '__loader__': None}
+        self.assertEqual(linecache.getlines(filename, module_globals), [])
+
+        for loader in object(), NoSourceLoader():
             linecache.clearcache()
             module_globals = {'__name__': 'a.b.c', '__loader__': loader}
-            self.assertEqual(linecache.getlines(filename, module_globals), [])
+            with self.assertWarns(DeprecationWarning) as w:
+                self.assertEqual(linecache.getlines(filename, module_globals), [])
+            self.assertEqual(str(w.warning),
+                             'Module globals is missing a __spec__.loader')
 
         linecache.clearcache()
         module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader()}
-        self.assertEqual(linecache.getlines(filename, module_globals),
-                         ['source for a.b.c\n'])
+        with self.assertWarns(DeprecationWarning) as w:
+            self.assertEqual(linecache.getlines(filename, module_globals),
+                             ['source for a.b.c\n'])
+        self.assertEqual(str(w.warning),
+                         'Module globals is missing a __spec__.loader')
 
-        for spec in (None, object(), ModuleSpec('', FakeLoader())):
+        for spec in None, object():
             linecache.clearcache()
             module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(),
                               '__spec__': spec}
+            with self.assertWarns(DeprecationWarning) as w:
+                self.assertEqual(linecache.getlines(filename, module_globals),
+                                 ['source for a.b.c\n'])
+            self.assertEqual(str(w.warning),
+                             'Module globals is missing a __spec__.loader')
+
+        linecache.clearcache()
+        module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(),
+                          '__spec__': ModuleSpec('', FakeLoader())}
+        with self.assertWarns(DeprecationWarning) as w:
             self.assertEqual(linecache.getlines(filename, module_globals),
                              ['source for a.b.c\n'])
+        self.assertEqual(str(w.warning),
+                         'Module globals; __loader__ != __spec__.loader')
 
         linecache.clearcache()
         spec = ModuleSpec('x.y.z', FakeLoader())
index e6666ddc638037c5cb903349a33d83c4ed15ade0..a6af5057cc8968882e458abf0d003db2dfe5ddf2 100644 (file)
@@ -727,7 +727,7 @@ class WarnTests(BaseTest):
 
     def check_module_globals_error(self, module_globals, errmsg, errtype=ValueError):
         if self.module is py_warnings:
-            self.check_module_globals(module_globals)
+            self.check_module_globals_deprecated(module_globals, errmsg)
             return
         with self.module.catch_warnings(record=True) as w:
             self.module.filterwarnings('always')
@@ -738,9 +738,6 @@ class WarnTests(BaseTest):
         self.assertEqual(len(w), 0)
 
     def check_module_globals_deprecated(self, module_globals, msg):
-        if self.module is py_warnings:
-            self.check_module_globals(module_globals)
-            return
         with self.module.catch_warnings(record=True) as w:
             self.module.filterwarnings('always')
             self.module.warn_explicit(
diff --git a/Misc/NEWS.d/next/Library/2024-08-08-12-39-36.gh-issue-122255.J_gU8Y.rst b/Misc/NEWS.d/next/Library/2024-08-08-12-39-36.gh-issue-122255.J_gU8Y.rst
new file mode 100644 (file)
index 0000000..63e71c1
--- /dev/null
@@ -0,0 +1,4 @@
+In the :mod:`linecache` module and in the Python implementation of the
+:mod:`warnings` module, a ``DeprecationWarning`` is issued when
+``mod.__loader__`` differs from ``mod.__spec__.loader`` (like in the C
+implementation of the :mod:`!warnings` module).