https://github.com/python/cpython/commit/8deaa9393eadf84e6e571be611e0c5a377abf7cd
commit: 8deaa9393eadf84e6e571be611e0c5a377abf7cd
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-11-14T16:49:28+02:00
summary:
gh-122255: Synchronize warnings in C and Python implementations of the warnings
module (GH-122824)
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).
files:
A Misc/NEWS.d/next/Library/2024-08-08-12-39-36.gh-issue-122255.J_gU8Y.rst
M Lib/linecache.py
M Lib/test/test_linecache.py
M Lib/test/test_warnings/__init__.py
diff --git a/Lib/linecache.py b/Lib/linecache.py
index ef3b2d9136b4d2..b5bf9dbdd3cbc7 100644
--- a/Lib/linecache.py
+++ b/Lib/linecache.py
@@ -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):
diff --git a/Lib/test/test_linecache.py b/Lib/test/test_linecache.py
index 02f65338428c8f..fcd94edc611fac 100644
--- a/Lib/test/test_linecache.py
+++ b/Lib/test/test_linecache.py
@@ -259,22 +259,44 @@ def raise_memoryerror(*args, **kwargs):
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())
diff --git a/Lib/test/test_warnings/__init__.py
b/Lib/test/test_warnings/__init__.py
index e6666ddc638037..a6af5057cc8968 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -727,7 +727,7 @@ def check_module_globals(self, module_globals):
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 @@ def check_module_globals_error(self, module_globals,
errmsg, errtype=ValueError)
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
index 00000000000000..63e71c19f8b084
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-08-12-39-36.gh-issue-122255.J_gU8Y.rst
@@ -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).
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]