| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 383b32f commit bbbcf86
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1291,7 +1291,7 @@ find and load modules. | |||
| 1291 | 1291 | Name of the place from which the module is loaded, e.g. "builtin" for | |
| 1292 | 1292 | built-in modules and the filename for modules loaded from source. | |
| 1293 | 1293 | Normally "origin" should be set, but it may be ``None`` (the default) | |
| 1294 | - which indicates it is unspecified. | ||
| 1294 | + which indicates it is unspecified (e.g. for namespace packages). | ||
| 1295 | 1295 | ||
| 1296 | 1296 | .. attribute:: submodule_search_locations | |
| 1297 | 1297 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -522,6 +522,18 @@ def _init_module_attrs(spec, module, *, override=False): | |||
| 522 | 522 | ||
| 523 | 523 | loader = _NamespaceLoader.__new__(_NamespaceLoader) | |
| 524 | 524 | 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 | ||
| 525 | 537 | try: | |
| 526 | 538 | module.__loader__ = loader | |
| 527 | 539 | except AttributeError: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1276,9 +1276,9 @@ def find_spec(cls, fullname, path=None, target=None): | |||
| 1276 | 1276 | elif spec.loader is None: | |
| 1277 | 1277 | namespace_path = spec.submodule_search_locations | |
| 1278 | 1278 | if namespace_path: | |
| 1279 | - # We found at least one namespace path. Return a | ||
| 1280 | - # spec which can create the namespace package. | ||
| 1281 | - spec.origin = 'namespace' | ||
| 1279 | + # We found at least one namespace path. Return a spec which | ||
| 1280 | + # can create the namespace package. | ||
| 1281 | + spec.origin = None | ||
| 1282 | 1282 | spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec) | |
| 1283 | 1283 | return spec | |
| 1284 | 1284 | else: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -66,13 +66,19 @@ def _get_resource_reader( | |||
| 66 | 66 | return None | |
| 67 | 67 | ||
| 68 | 68 | ||
| 69 | + def _check_location(package): | ||
| 70 | + if package.__spec__.origin is None or not package.__spec__.has_location: | ||
| 71 | + raise FileNotFoundError(f'Package has no location {package!r}') | ||
| 72 | + | ||
| 73 | + | ||
| 69 | 74 | def open_binary(package: Package, resource: Resource) -> BinaryIO: | |
| 70 | 75 | """Return a file-like object opened for binary reading of the resource.""" | |
| 71 | 76 | resource = _normalize_path(resource) | |
| 72 | 77 | package = _get_package(package) | |
| 73 | 78 | reader = _get_resource_reader(package) | |
| 74 | 79 | if reader is not None: | |
| 75 | 80 | return reader.open_resource(resource) | |
| 81 | + _check_location(package) | ||
| 76 | 82 | absolute_package_path = os.path.abspath(package.__spec__.origin) | |
| 77 | 83 | package_path = os.path.dirname(absolute_package_path) | |
| 78 | 84 | full_path = os.path.join(package_path, resource) | |
@@ -106,6 +112,7 @@ def open_text(package: Package, | |||
| 106 | 112 | reader = _get_resource_reader(package) | |
| 107 | 113 | if reader is not None: | |
| 108 | 114 | return TextIOWrapper(reader.open_resource(resource), encoding, errors) | |
| 115 | + _check_location(package) | ||
| 109 | 116 | absolute_package_path = os.path.abspath(package.__spec__.origin) | |
| 110 | 117 | package_path = os.path.dirname(absolute_package_path) | |
| 111 | 118 | full_path = os.path.join(package_path, resource) | |
@@ -172,6 +179,8 @@ def path(package: Package, resource: Resource) -> Iterator[Path]: | |||
| 172 | 179 | return | |
| 173 | 180 | except FileNotFoundError: | |
| 174 | 181 | pass | |
| 182 | + else: | ||
| 183 | + _check_location(package) | ||
| 175 | 184 | # Fall-through for both the lack of resource_path() *and* if | |
| 176 | 185 | # resource_path() raises FileNotFoundError. | |
| 177 | 186 | package_directory = Path(package.__spec__.origin).parent | |
@@ -232,9 +241,9 @@ def contents(package: Package) -> Iterator[str]: | |||
| 232 | 241 | yield from reader.contents() | |
| 233 | 242 | return | |
| 234 | 243 | # Is the package a namespace package? By definition, namespace packages | |
| 235 | - # cannot have resources. | ||
| 236 | - if (package.__spec__.origin == 'namespace' and | ||
| 237 | - not package.__spec__.has_location): | ||
| 244 | + # cannot have resources. We could use _check_location() and catch the | ||
| 245 | + # exception, but that's extra work, so just inline the check. | ||
| 246 | + if package.__spec__.origin is None or not package.__spec__.has_location: | ||
| 238 | 247 | return [] | |
| 239 | 248 | package_directory = Path(package.__spec__.origin).parent | |
| 240 | 249 | yield from os.listdir(str(package_directory)) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -305,6 +305,7 @@ def test_reload_namespace_changed(self): | |||
| 305 | 305 | expected = {'__name__': name, | |
| 306 | 306 | '__package__': name, | |
| 307 | 307 | '__doc__': None, | |
| 308 | + '__file__': None, | ||
| 308 | 309 | } | |
| 309 | 310 | os.mkdir(name) | |
| 310 | 311 | with open(bad_path, 'w') as init_file: | |
@@ -316,8 +317,9 @@ def test_reload_namespace_changed(self): | |||
| 316 | 317 | spec = ns.pop('__spec__') | |
| 317 | 318 | ns.pop('__builtins__', None) # An implementation detail. | |
| 318 | 319 | self.assertEqual(spec.name, name) | |
| 319 | - self.assertIs(spec.loader, None) | ||
| 320 | - self.assertIsNot(loader, None) | ||
| 320 | + self.assertIsNotNone(spec.loader) | ||
| 321 | + self.assertIsNotNone(loader) | ||
| 322 | + self.assertEqual(spec.loader, loader) | ||
| 321 | 323 | self.assertEqual(set(path), | |
| 322 | 324 | set([os.path.dirname(bad_path)])) | |
| 323 | 325 | with self.assertRaises(AttributeError): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -317,5 +317,21 @@ def test_dynamic_path(self): | |||
| 317 | 317 | self.assertEqual(foo.two.attr, 'portion2 foo two') | |
| 318 | 318 | ||
| 319 | 319 | ||
| 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 | + | ||
| 320 | 336 | if __name__ == "__main__": | |
| 321 | 337 | unittest.main() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + Make sure ``__spec__.loader`` matches ``__loader__`` for namespace packages. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + For namespace packages, ensure that both ``__file__`` and | ||
| 2 | + ``__spec__.origin`` are set to None. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments