Skip to content

Commit a71397f

Browse files
authored
[3.6] bpo-32303 - Consistency fixes for namespace loaders (GH-5481) (#5504)
* Make sure ``__spec__.loader`` matches ``__loader__`` for namespace packages. * Make sure ``__spec__.origin` matches ``__file__`` for namespace packages. https://bugs.python.org/issue32303 https://bugs.python.org/issue32305. (cherry picked from commit bbbcf86) Co-authored-by: Barry Warsaw <barry@python.org>
1 parent 7e4cf8e commit a71397f

File tree

9 files changed

+1473
-1439
lines changed

9 files changed

+1473
-1439
lines changed

Doc/library/importlib.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ find and load modules.
10771077
Name of the place from which the module is loaded, e.g. "builtin" for
10781078
built-in modules and the filename for modules loaded from source.
10791079
Normally "origin" should be set, but it may be ``None`` (the default)
1080-
which indicates it is unspecified.
1080+
which indicates it is unspecified (e.g. for namespace packages).
10811081

10821082
.. attribute:: submodule_search_locations
10831083

Lib/importlib/_bootstrap.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,18 @@ def _init_module_attrs(spec, module, *, override=False):
522522

523523
loader = _NamespaceLoader.__new__(_NamespaceLoader)
524524
loader._path = spec.submodule_search_locations
525+
spec.loader = loader
526+
# While the docs say that module.__file__ is not set for
527+
# built-in modules, and the code below will avoid setting it if
528+
# spec.has_location is false, this is incorrect for namespace
529+
# packages. Namespace packages have no location, but their
530+
# __spec__.origin is None, and thus their module.__file__
531+
# should also be None for consistency. While a bit of a hack,
532+
# this is the best place to ensure this consistency.
533+
#
534+
# See # https://docs.python.org/3/library/importlib.html#importlib.abc.Loader.load_module
535+
# and bpo-32305
536+
module.__file__ = None
525537
try:
526538
module.__loader__ = loader
527539
except AttributeError:

Lib/importlib/_bootstrap_external.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,9 @@ def find_spec(cls, fullname, path=None, target=None):
11601160
elif spec.loader is None:
11611161
namespace_path = spec.submodule_search_locations
11621162
if namespace_path:
1163-
# We found at least one namespace path. Return a
1164-
# spec which can create the namespace package.
1165-
spec.origin = 'namespace'
1163+
# We found at least one namespace path. Return a spec which
1164+
# can create the namespace package.
1165+
spec.origin = None
11661166
spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec)
11671167
return spec
11681168
else:

Lib/test/test_importlib/test_api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ def test_reload_namespace_changed(self):
307307
expected = {'__name__': name,
308308
'__package__': name,
309309
'__doc__': None,
310+
'__file__': None,
310311
}
311312
os.mkdir(name)
312313
with open(bad_path, 'w') as init_file:
@@ -318,8 +319,9 @@ def test_reload_namespace_changed(self):
318319
spec = ns.pop('__spec__')
319320
ns.pop('__builtins__', None) # An implementation detail.
320321
self.assertEqual(spec.name, name)
321-
self.assertIs(spec.loader, None)
322-
self.assertIsNot(loader, None)
322+
self.assertIsNotNone(spec.loader)
323+
self.assertIsNotNone(loader)
324+
self.assertEqual(spec.loader, loader)
323325
self.assertEqual(set(path),
324326
set([os.path.dirname(bad_path)]))
325327
with self.assertRaises(AttributeError):

Lib/test/test_importlib/test_namespace_pkgs.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,5 +317,21 @@ def test_dynamic_path(self):
317317
self.assertEqual(foo.two.attr, 'portion2 foo two')
318318

319319

320+
class LoaderTests(NamespacePackageTest):
321+
paths = ['portion1']
322+
323+
def test_namespace_loader_consistency(self):
324+
# bpo-32303
325+
import foo
326+
self.assertEqual(foo.__loader__, foo.__spec__.loader)
327+
self.assertIsNotNone(foo.__loader__)
328+
329+
def test_namespace_origin_consistency(self):
330+
# bpo-32305
331+
import foo
332+
self.assertIsNone(foo.__spec__.origin)
333+
self.assertIsNone(foo.__file__)
334+
335+
320336
if __name__ == "__main__":
321337
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make sure ``__spec__.loader`` matches ``__loader__`` for namespace packages.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
For namespace packages, ensure that both ``__file__`` and
2+
``__spec__.origin`` are set to None.

Python/importlib.h

Lines changed: 946 additions & 945 deletions
Large diffs are not rendered by default.

Python/importlib_external.h

Lines changed: 488 additions & 488 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)