Skip to content

Commit 8deaa93

Browse files
pythongh-122255: Synchronize warnings in C and Python implementations of the warnings module (pythonGH-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).
1 parent c10fa5b commit 8deaa93

File tree

4 files changed

+83
-23
lines changed

4 files changed

+83
-23
lines changed

Lib/linecache.py

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -224,21 +224,58 @@ def lazycache(filename, module_globals):
224224
def _make_lazycache_entry(filename, module_globals):
225225
if not filename or (filename.startswith('<') and filename.endswith('>')):
226226
return None
227-
# Try for a __loader__, if available
228-
if module_globals and '__name__' in module_globals:
229-
spec = module_globals.get('__spec__')
230-
name = getattr(spec, 'name', None) or module_globals['__name__']
231-
loader = getattr(spec, 'loader', None)
232-
if loader is None:
233-
loader = module_globals.get('__loader__')
234-
get_source = getattr(loader, 'get_source', None)
235-
236-
if name and get_source:
237-
def get_lines(name=name, *args, **kwargs):
238-
return get_source(name, *args, **kwargs)
239-
return (get_lines,)
240-
return None
241227

228+
if module_globals is not None and not isinstance(module_globals, dict):
229+
raise TypeError(f'module_globals must be a dict, not {type(module_globals).__qualname__}')
230+
if not module_globals or '__name__' not in module_globals:
231+
return None
232+
233+
spec = module_globals.get('__spec__')
234+
name = getattr(spec, 'name', None) or module_globals['__name__']
235+
if name is None:
236+
return None
237+
238+
loader = _bless_my_loader(module_globals)
239+
if loader is None:
240+
return None
241+
242+
get_source = getattr(loader, 'get_source', None)
243+
if get_source is None:
244+
return None
245+
246+
def get_lines(name=name, *args, **kwargs):
247+
return get_source(name, *args, **kwargs)
248+
return (get_lines,)
249+
250+
def _bless_my_loader(module_globals):
251+
# Similar to _bless_my_loader() in importlib._bootstrap_external,
252+
# but always emits warnings instead of errors.
253+
loader = module_globals.get('__loader__')
254+
if loader is None and '__spec__' not in module_globals:
255+
return None
256+
spec = module_globals.get('__spec__')
257+
258+
# The __main__ module has __spec__ = None.
259+
if spec is None and module_globals.get('__name__') == '__main__':
260+
return loader
261+
262+
spec_loader = getattr(spec, 'loader', None)
263+
if spec_loader is None:
264+
import warnings
265+
warnings.warn(
266+
'Module globals is missing a __spec__.loader',
267+
DeprecationWarning)
268+
return loader
269+
270+
assert spec_loader is not None
271+
if loader is not None and loader != spec_loader:
272+
import warnings
273+
warnings.warn(
274+
'Module globals; __loader__ != __spec__.loader',
275+
DeprecationWarning)
276+
return loader
277+
278+
return spec_loader
242279

243280

244281
def _register_code(code, string, name):

Lib/test/test_linecache.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,22 +259,44 @@ def raise_memoryerror(*args, **kwargs):
259259
def test_loader(self):
260260
filename = 'scheme://path'
261261

262-
for loader in (None, object(), NoSourceLoader()):
262+
linecache.clearcache()
263+
module_globals = {'__name__': 'a.b.c', '__loader__': None}
264+
self.assertEqual(linecache.getlines(filename, module_globals), [])
265+
266+
for loader in object(), NoSourceLoader():
263267
linecache.clearcache()
264268
module_globals = {'__name__': 'a.b.c', '__loader__': loader}
265-
self.assertEqual(linecache.getlines(filename, module_globals), [])
269+
with self.assertWarns(DeprecationWarning) as w:
270+
self.assertEqual(linecache.getlines(filename, module_globals), [])
271+
self.assertEqual(str(w.warning),
272+
'Module globals is missing a __spec__.loader')
266273

267274
linecache.clearcache()
268275
module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader()}
269-
self.assertEqual(linecache.getlines(filename, module_globals),
270-
['source for a.b.c\n'])
276+
with self.assertWarns(DeprecationWarning) as w:
277+
self.assertEqual(linecache.getlines(filename, module_globals),
278+
['source for a.b.c\n'])
279+
self.assertEqual(str(w.warning),
280+
'Module globals is missing a __spec__.loader')
271281

272-
for spec in (None, object(), ModuleSpec('', FakeLoader())):
282+
for spec in None, object():
273283
linecache.clearcache()
274284
module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(),
275285
'__spec__': spec}
286+
with self.assertWarns(DeprecationWarning) as w:
287+
self.assertEqual(linecache.getlines(filename, module_globals),
288+
['source for a.b.c\n'])
289+
self.assertEqual(str(w.warning),
290+
'Module globals is missing a __spec__.loader')
291+
292+
linecache.clearcache()
293+
module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(),
294+
'__spec__': ModuleSpec('', FakeLoader())}
295+
with self.assertWarns(DeprecationWarning) as w:
276296
self.assertEqual(linecache.getlines(filename, module_globals),
277297
['source for a.b.c\n'])
298+
self.assertEqual(str(w.warning),
299+
'Module globals; __loader__ != __spec__.loader')
278300

279301
linecache.clearcache()
280302
spec = ModuleSpec('x.y.z', FakeLoader())

Lib/test/test_warnings/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ def check_module_globals(self, module_globals):
727727

728728
def check_module_globals_error(self, module_globals, errmsg, errtype=ValueError):
729729
if self.module is py_warnings:
730-
self.check_module_globals(module_globals)
730+
self.check_module_globals_deprecated(module_globals, errmsg)
731731
return
732732
with self.module.catch_warnings(record=True) as w:
733733
self.module.filterwarnings('always')
@@ -738,9 +738,6 @@ def check_module_globals_error(self, module_globals, errmsg, errtype=ValueError)
738738
self.assertEqual(len(w), 0)
739739

740740
def check_module_globals_deprecated(self, module_globals, msg):
741-
if self.module is py_warnings:
742-
self.check_module_globals(module_globals)
743-
return
744741
with self.module.catch_warnings(record=True) as w:
745742
self.module.filterwarnings('always')
746743
self.module.warn_explicit(
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
In the :mod:`linecache` module and in the Python implementation of the
2+
:mod:`warnings` module, a ``DeprecationWarning`` is issued when
3+
``mod.__loader__`` differs from ``mod.__spec__.loader`` (like in the C
4+
implementation of the :mod:`!warnings` module).

0 commit comments

Comments
 (0)