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).
This commit is contained in:
Serhiy Storchaka 2025-11-14 16:49:28 +02:00 committed by GitHub
parent c10fa5be61
commit 8deaa9393e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 82 additions and 22 deletions

View file

@ -224,21 +224,58 @@ def lazycache(filename, module_globals):
def _make_lazycache_entry(filename, module_globals): def _make_lazycache_entry(filename, module_globals):
if not filename or (filename.startswith('<') and filename.endswith('>')): if not filename or (filename.startswith('<') and filename.endswith('>')):
return None 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: if module_globals is not None and not isinstance(module_globals, dict):
def get_lines(name=name, *args, **kwargs): raise TypeError(f'module_globals must be a dict, not {type(module_globals).__qualname__}')
return get_source(name, *args, **kwargs) if not module_globals or '__name__' not in module_globals:
return (get_lines,) return None
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): def _register_code(code, string, name):

View file

@ -259,22 +259,44 @@ def raise_memoryerror(*args, **kwargs):
def test_loader(self): def test_loader(self):
filename = 'scheme://path' 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() linecache.clearcache()
module_globals = {'__name__': 'a.b.c', '__loader__': loader} 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() linecache.clearcache()
module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader()} module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader()}
self.assertEqual(linecache.getlines(filename, module_globals), with self.assertWarns(DeprecationWarning) as w:
['source for a.b.c\n']) 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() linecache.clearcache()
module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(), module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(),
'__spec__': spec} '__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), self.assertEqual(linecache.getlines(filename, module_globals),
['source for a.b.c\n']) ['source for a.b.c\n'])
self.assertEqual(str(w.warning),
'Module globals; __loader__ != __spec__.loader')
linecache.clearcache() linecache.clearcache()
spec = ModuleSpec('x.y.z', FakeLoader()) spec = ModuleSpec('x.y.z', FakeLoader())

View file

@ -727,7 +727,7 @@ def check_module_globals(self, module_globals):
def check_module_globals_error(self, module_globals, errmsg, errtype=ValueError): def check_module_globals_error(self, module_globals, errmsg, errtype=ValueError):
if self.module is py_warnings: if self.module is py_warnings:
self.check_module_globals(module_globals) self.check_module_globals_deprecated(module_globals, errmsg)
return return
with self.module.catch_warnings(record=True) as w: with self.module.catch_warnings(record=True) as w:
self.module.filterwarnings('always') self.module.filterwarnings('always')
@ -738,9 +738,6 @@ def check_module_globals_error(self, module_globals, errmsg, errtype=ValueError)
self.assertEqual(len(w), 0) self.assertEqual(len(w), 0)
def check_module_globals_deprecated(self, module_globals, msg): 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: with self.module.catch_warnings(record=True) as w:
self.module.filterwarnings('always') self.module.filterwarnings('always')
self.module.warn_explicit( self.module.warn_explicit(

View file

@ -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).