Skip to content

Commit ce5e6f0

Browse files
authored
[3.9] bpo-40924: Revert "bpo-39791 native hooks for importlib.resources.files (GH-20576)" (#20760)
This reverts commit 9cf1be4 due to https://bugs.python.org/issue40924.
1 parent 6cb24a0 commit ce5e6f0

File tree

9 files changed

+2363
-2117
lines changed

9 files changed

+2363
-2117
lines changed

Lib/importlib/_bootstrap_external.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -982,10 +982,32 @@ def get_data(self, path):
982982
with _io.FileIO(path, 'r') as file:
983983
return file.read()
984984

985+
# ResourceReader ABC API.
986+
985987
@_check_name
986988
def get_resource_reader(self, module):
987-
from importlib.readers import FileReader
988-
return FileReader(self)
989+
if self.is_package(module):
990+
return self
991+
return None
992+
993+
def open_resource(self, resource):
994+
path = _path_join(_path_split(self.path)[0], resource)
995+
return _io.FileIO(path, 'r')
996+
997+
def resource_path(self, resource):
998+
if not self.is_resource(resource):
999+
raise FileNotFoundError
1000+
path = _path_join(_path_split(self.path)[0], resource)
1001+
return path
1002+
1003+
def is_resource(self, name):
1004+
if path_sep in name:
1005+
return False
1006+
path = _path_join(_path_split(self.path)[0], name)
1007+
return _path_isfile(path)
1008+
1009+
def contents(self):
1010+
return iter(_os.listdir(_path_split(self.path)[0]))
9891011

9901012

9911013
class SourceFileLoader(FileLoader, SourceLoader):

Lib/importlib/_common.py

Lines changed: 19 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,38 @@
11
import os
22
import pathlib
3+
import zipfile
34
import tempfile
45
import functools
56
import contextlib
6-
import types
7-
import importlib
87

9-
from typing import Union, Any, Optional
10-
from .abc import ResourceReader
118

12-
Package = Union[types.ModuleType, str]
13-
14-
15-
def files(package):
16-
"""
17-
Get a Traversable resource from a package
18-
"""
19-
return from_package(get_package(package))
20-
21-
22-
def normalize_path(path):
23-
# type: (Any) -> str
24-
"""Normalize a path by ensuring it is a string.
25-
26-
If the resulting string contains path separators, an exception is raised.
9+
def from_package(package):
2710
"""
28-
str_path = str(path)
29-
parent, file_name = os.path.split(str_path)
30-
if parent:
31-
raise ValueError('{!r} must be only a file name'.format(path))
32-
return file_name
33-
11+
Return a Traversable object for the given package.
3412
35-
def get_resource_reader(package):
36-
# type: (types.ModuleType) -> Optional[ResourceReader]
37-
"""
38-
Return the package's loader if it's a ResourceReader.
3913
"""
40-
# We can't use
41-
# a issubclass() check here because apparently abc.'s __subclasscheck__()
42-
# hook wants to create a weak reference to the object, but
43-
# zipimport.zipimporter does not support weak references, resulting in a
44-
# TypeError. That seems terrible.
4514
spec = package.__spec__
46-
reader = getattr(spec.loader, 'get_resource_reader', None)
47-
if reader is None:
48-
return None
49-
return reader(spec.name)
15+
return from_traversable_resources(spec) or fallback_resources(spec)
5016

5117

52-
def resolve(cand):
53-
# type: (Package) -> types.ModuleType
54-
return (
55-
cand if isinstance(cand, types.ModuleType)
56-
else importlib.import_module(cand)
57-
)
58-
59-
60-
def get_package(package):
61-
# type: (Package) -> types.ModuleType
62-
"""Take a package name or module object and return the module.
63-
64-
Raise an exception if the resolved module is not a package.
18+
def from_traversable_resources(spec):
6519
"""
66-
resolved = resolve(package)
67-
if resolved.__spec__.submodule_search_locations is None:
68-
raise TypeError('{!r} is not a package'.format(package))
69-
return resolved
70-
71-
72-
def from_package(package):
20+
If the spec.loader implements TraversableResources,
21+
directly or implicitly, it will have a ``files()`` method.
7322
"""
74-
Return a Traversable object for the given package.
23+
with contextlib.suppress(AttributeError):
24+
return spec.loader.files()
7525

76-
"""
77-
spec = package.__spec__
78-
reader = spec.loader.get_resource_reader(spec.name)
79-
return reader.files()
26+
27+
def fallback_resources(spec):
28+
package_directory = pathlib.Path(spec.origin).parent
29+
try:
30+
archive_path = spec.loader.archive
31+
rel_path = package_directory.relative_to(archive_path)
32+
return zipfile.Path(archive_path, str(rel_path) + '/')
33+
except Exception:
34+
pass
35+
return package_directory
8036

8137

8238
@contextlib.contextmanager

Lib/importlib/abc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ def resource_path(self, resource):
468468
raise FileNotFoundError(resource)
469469

470470
def is_resource(self, path):
471-
return self.files().joinpath(path).is_file()
471+
return self.files().joinpath(path).isfile()
472472

473473
def contents(self):
474474
return (item.name for item in self.files().iterdir())

Lib/importlib/readers.py

Lines changed: 0 additions & 30 deletions
This file was deleted.

Lib/importlib/resources.py

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import os
22

3+
from . import abc as resources_abc
34
from . import _common
4-
from ._common import as_file, files
5+
from ._common import as_file
56
from contextlib import contextmanager, suppress
7+
from importlib import import_module
68
from importlib.abc import ResourceLoader
79
from io import BytesIO, TextIOWrapper
810
from pathlib import Path
911
from types import ModuleType
10-
from typing import ContextManager, Iterable, Union
12+
from typing import ContextManager, Iterable, Optional, Union
1113
from typing import cast
1214
from typing.io import BinaryIO, TextIO
1315

@@ -31,11 +33,60 @@
3133
Resource = Union[str, os.PathLike]
3234

3335

36+
def _resolve(name) -> ModuleType:
37+
"""If name is a string, resolve to a module."""
38+
if hasattr(name, '__spec__'):
39+
return name
40+
return import_module(name)
41+
42+
43+
def _get_package(package) -> ModuleType:
44+
"""Take a package name or module object and return the module.
45+
46+
If a name, the module is imported. If the resolved module
47+
object is not a package, raise an exception.
48+
"""
49+
module = _resolve(package)
50+
if module.__spec__.submodule_search_locations is None:
51+
raise TypeError('{!r} is not a package'.format(package))
52+
return module
53+
54+
55+
def _normalize_path(path) -> str:
56+
"""Normalize a path by ensuring it is a string.
57+
58+
If the resulting string contains path separators, an exception is raised.
59+
"""
60+
parent, file_name = os.path.split(path)
61+
if parent:
62+
raise ValueError('{!r} must be only a file name'.format(path))
63+
return file_name
64+
65+
66+
def _get_resource_reader(
67+
package: ModuleType) -> Optional[resources_abc.ResourceReader]:
68+
# Return the package's loader if it's a ResourceReader. We can't use
69+
# a issubclass() check here because apparently abc.'s __subclasscheck__()
70+
# hook wants to create a weak reference to the object, but
71+
# zipimport.zipimporter does not support weak references, resulting in a
72+
# TypeError. That seems terrible.
73+
spec = package.__spec__
74+
if hasattr(spec.loader, 'get_resource_reader'):
75+
return cast(resources_abc.ResourceReader,
76+
spec.loader.get_resource_reader(spec.name))
77+
return None
78+
79+
80+
def _check_location(package):
81+
if package.__spec__.origin is None or not package.__spec__.has_location:
82+
raise FileNotFoundError(f'Package has no location {package!r}')
83+
84+
3485
def open_binary(package: Package, resource: Resource) -> BinaryIO:
3586
"""Return a file-like object opened for binary reading of the resource."""
36-
resource = _common.normalize_path(resource)
37-
package = _common.get_package(package)
38-
reader = _common.get_resource_reader(package)
87+
resource = _normalize_path(resource)
88+
package = _get_package(package)
89+
reader = _get_resource_reader(package)
3990
if reader is not None:
4091
return reader.open_resource(resource)
4192
absolute_package_path = os.path.abspath(
@@ -89,6 +140,13 @@ def read_text(package: Package,
89140
return fp.read()
90141

91142

143+
def files(package: Package) -> resources_abc.Traversable:
144+
"""
145+
Get a Traversable resource from a package
146+
"""
147+
return _common.from_package(_get_package(package))
148+
149+
92150
def path(
93151
package: Package, resource: Resource,
94152
) -> 'ContextManager[Path]':
@@ -100,18 +158,17 @@ def path(
100158
raised if the file was deleted prior to the context manager
101159
exiting).
102160
"""
103-
reader = _common.get_resource_reader(_common.get_package(package))
161+
reader = _get_resource_reader(_get_package(package))
104162
return (
105163
_path_from_reader(reader, resource)
106164
if reader else
107-
_common.as_file(
108-
_common.files(package).joinpath(_common.normalize_path(resource)))
165+
_common.as_file(files(package).joinpath(_normalize_path(resource)))
109166
)
110167

111168

112169
@contextmanager
113170
def _path_from_reader(reader, resource):
114-
norm_resource = _common.normalize_path(resource)
171+
norm_resource = _normalize_path(resource)
115172
with suppress(FileNotFoundError):
116173
yield Path(reader.resource_path(norm_resource))
117174
return
@@ -125,9 +182,9 @@ def is_resource(package: Package, name: str) -> bool:
125182
126183
Directories are *not* resources.
127184
"""
128-
package = _common.get_package(package)
129-
_common.normalize_path(name)
130-
reader = _common.get_resource_reader(package)
185+
package = _get_package(package)
186+
_normalize_path(name)
187+
reader = _get_resource_reader(package)
131188
if reader is not None:
132189
return reader.is_resource(name)
133190
package_contents = set(contents(package))
@@ -143,8 +200,8 @@ def contents(package: Package) -> Iterable[str]:
143200
not considered resources. Use `is_resource()` on each entry returned here
144201
to check if it is a resource or not.
145202
"""
146-
package = _common.get_package(package)
147-
reader = _common.get_resource_reader(package)
203+
package = _get_package(package)
204+
reader = _get_resource_reader(package)
148205
if reader is not None:
149206
return reader.contents()
150207
# Is the package a namespace package? By definition, namespace packages

0 commit comments

Comments
 (0)