Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit241d9e9

Browse files
committed
gh-123987: Fix issues in importlib.resources.
1 parent4ed7d1d commit241d9e9

File tree

7 files changed

+132
-21
lines changed

7 files changed

+132
-21
lines changed

‎Lib/importlib/resources/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
"""Read resources contained within a package."""
1+
"""
2+
Read resources contained within a package.
3+
4+
This codebase is shared between importlib.resources in the stdlib
5+
and importlib_resources in PyPI. See
6+
https://github.com/python/importlib_metadata/wiki/Development-Methodology
7+
for more detail.
8+
"""
29

310
from ._commonimport (
411
as_file,

‎Lib/importlib/resources/_common.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]:
6666
# zipimport.zipimporter does not support weak references, resulting in a
6767
# TypeError. That seems terrible.
6868
spec=package.__spec__
69-
reader=getattr(spec.loader,'get_resource_reader',None)# type: ignore
69+
reader=getattr(spec.loader,'get_resource_reader',None)# type: ignore[union-attr]
7070
ifreaderisNone:
7171
returnNone
72-
returnreader(spec.name)# type: ignore
72+
returnreader(spec.name)# type: ignore[union-attr]
7373

7474

7575
@functools.singledispatch
@@ -93,12 +93,13 @@ def _infer_caller():
9393
"""
9494

9595
defis_this_file(frame_info):
96-
returnframe_info.filename==__file__
96+
returnframe_info.filename==stack[0].filename
9797

9898
defis_wrapper(frame_info):
9999
returnframe_info.function=='wrapper'
100100

101-
not_this_file=itertools.filterfalse(is_this_file,inspect.stack())
101+
stack=inspect.stack()
102+
not_this_file=itertools.filterfalse(is_this_file,stack)
102103
# also exclude 'wrapper' due to singledispatch in the call stack
103104
callers=itertools.filterfalse(is_wrapper,not_this_file)
104105
returnnext(callers).frame

‎Lib/importlib/resources/readers.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__importannotations
2+
13
importcollections
24
importcontextlib
35
importitertools
@@ -6,6 +8,7 @@
68
importre
79
importwarnings
810
importzipfile
11+
fromcollections.abcimportIterator
912

1013
from .importabc
1114

@@ -135,27 +138,31 @@ class NamespaceReader(abc.TraversableResources):
135138
def__init__(self,namespace_path):
136139
if'NamespacePath'notinstr(namespace_path):
137140
raiseValueError('Invalid path')
138-
self.path=MultiplexedPath(*map(self._resolve,namespace_path))
141+
self.path=MultiplexedPath(*filter(bool,map(self._resolve,namespace_path)))
139142

140143
@classmethod
141-
def_resolve(cls,path_str)->abc.Traversable:
144+
def_resolve(cls,path_str)->abc.Traversable|None:
142145
r"""
143146
Given an item from a namespace path, resolve it to a Traversable.
144147
145148
path_str might be a directory on the filesystem or a path to a
146149
zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or
147150
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
151+
152+
path_str might also be a sentinel used by editable packages to
153+
trigger other behaviors (see python/importlib_resources#311).
154+
In that case, return None.
148155
"""
149-
(dir,)= (candforcandincls._candidate_paths(path_str)ifcand.is_dir())
150-
returndir
156+
dirs= (candforcandincls._candidate_paths(path_str)ifcand.is_dir())
157+
returnnext(dirs,None)
151158

152159
@classmethod
153-
def_candidate_paths(cls,path_str):
160+
def_candidate_paths(cls,path_str:str)->Iterator[abc.Traversable]:
154161
yieldpathlib.Path(path_str)
155162
yieldfromcls._resolve_zip_path(path_str)
156163

157164
@staticmethod
158-
def_resolve_zip_path(path_str):
165+
def_resolve_zip_path(path_str:str):
159166
formatchinreversed(list(re.finditer(r'[\\/]',path_str))):
160167
withcontextlib.suppress(
161168
FileNotFoundError,

‎Lib/importlib/resources/simple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class ResourceHandle(Traversable):
7777

7878
def__init__(self,parent:ResourceContainer,name:str):
7979
self.parent=parent
80-
self.name=name# type: ignore
80+
self.name=name# type: ignore[misc]
8181

8282
defis_file(self):
8383
returnTrue

‎Lib/test/test_importlib/resources/_path.py

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,44 @@
22
importfunctools
33

44
fromtypingimportDict,Union
5+
fromtypingimportruntime_checkable
6+
fromtypingimportProtocol
57

68

79
####
8-
# from jaraco.path 3.4.1
10+
# from jaraco.path 3.7.1
911

10-
FilesSpec=Dict[str,Union[str,bytes,'FilesSpec']]# type: ignore
1112

13+
classSymlink(str):
14+
"""
15+
A string indicating the target of a symlink.
16+
"""
17+
18+
19+
FilesSpec=Dict[str,Union[str,bytes,Symlink,'FilesSpec']]
20+
21+
22+
@runtime_checkable
23+
classTreeMaker(Protocol):
24+
def__truediv__(self,*args,**kwargs): ...# pragma: no cover
25+
26+
defmkdir(self,**kwargs): ...# pragma: no cover
27+
28+
defwrite_text(self,content,**kwargs): ...# pragma: no cover
29+
30+
defwrite_bytes(self,content): ...# pragma: no cover
1231

13-
defbuild(spec:FilesSpec,prefix=pathlib.Path()):
32+
defsymlink_to(self,target): ...# pragma: no cover
33+
34+
35+
def_ensure_tree_maker(obj:Union[str,TreeMaker])->TreeMaker:
36+
returnobjifisinstance(obj,TreeMaker)elsepathlib.Path(obj)# type: ignore[return-value]
37+
38+
39+
defbuild(
40+
spec:FilesSpec,
41+
prefix:Union[str,TreeMaker]=pathlib.Path(),# type: ignore[assignment]
42+
):
1443
"""
1544
Build a set of files/directories, as described by the spec.
1645
@@ -25,21 +54,25 @@ def build(spec: FilesSpec, prefix=pathlib.Path()):
2554
... "__init__.py": "",
2655
... },
2756
... "baz.py": "# Some code",
28-
... }
57+
... "bar.py": Symlink("baz.py"),
58+
... },
59+
... "bing": Symlink("foo"),
2960
... }
3061
>>> target = getfixture('tmp_path')
3162
>>> build(spec, target)
3263
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
3364
'# Some code'
65+
>>> target.joinpath('bing/bar.py').read_text(encoding='utf-8')
66+
'# Some code'
3467
"""
3568
forname,contentsinspec.items():
36-
create(contents,pathlib.Path(prefix)/name)
69+
create(contents,_ensure_tree_maker(prefix)/name)
3770

3871

3972
@functools.singledispatch
4073
defcreate(content:Union[str,bytes,FilesSpec],path):
4174
path.mkdir(exist_ok=True)
42-
build(content,prefix=path)# type: ignore
75+
build(content,prefix=path)# type: ignore[arg-type]
4376

4477

4578
@create.register
@@ -52,5 +85,10 @@ def _(content: str, path):
5285
path.write_text(content,encoding='utf-8')
5386

5487

88+
@create.register
89+
def_(content:Symlink,path):
90+
path.symlink_to(content)
91+
92+
5593
# end from jaraco.path
5694
####

‎Lib/test/test_importlib/resources/test_files.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
importos
2+
importpathlib
3+
importpy_compile
4+
importshutil
15
importtextwrap
26
importunittest
37
importwarnings
@@ -7,6 +11,7 @@
711
fromimportlibimportresources
812
fromimportlib.resources.abcimportTraversable
913
from .importutil
14+
fromtest.supportimportos_helper,import_helper
1015

1116

1217
@contextlib.contextmanager
@@ -55,6 +60,26 @@ class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
5560
classOpenNamespaceTests(FilesTests,util.DiskSetup,unittest.TestCase):
5661
MODULE='namespacedata01'
5762

63+
deftest_non_paths_in_dunder_path(self):
64+
"""
65+
Non-path items in a namespace package's ``__path__`` are ignored.
66+
67+
As reported in python/importlib_resources#311, some tools
68+
like Setuptools, when creating editable packages, will inject
69+
non-paths into a namespace package's ``__path__``, a
70+
sentinel like
71+
``__editable__.sample_namespace-1.0.finder.__path_hook__``
72+
to cause the ``PathEntryFinder`` to be called when searching
73+
for packages. In that case, resources should still be loadable.
74+
"""
75+
importnamespacedata01
76+
77+
namespacedata01.__path__.append(
78+
'__editable__.sample_namespace-1.0.finder.__path_hook__'
79+
)
80+
81+
resources.files(namespacedata01)
82+
5883

5984
classOpenNamespaceZipTests(FilesTests,util.ZipSetup,unittest.TestCase):
6085
ZIP_MODULE='namespacedata01'
@@ -81,7 +106,7 @@ def test_module_resources(self):
81106
"""
82107
A module can have resources found adjacent to the module.
83108
"""
84-
importmod
109+
importmod# type: ignore[import-not-found]
85110

86111
actual=resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
87112
assertactual==self.spec['res.txt']
@@ -97,8 +122,8 @@ class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.Test
97122

98123
classImplicitContextFiles:
99124
set_val=textwrap.dedent(
100-
"""
101-
importimportlib.resources as res
125+
f"""
126+
import{resources.__name__} as res
102127
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
103128
"""
104129
)
@@ -108,6 +133,10 @@ class ImplicitContextFiles:
108133
'submod.py':set_val,
109134
'res.txt':'resources are the best',
110135
},
136+
'frozenpkg': {
137+
'__init__.py':set_val.replace(resources.__name__,'c_resources'),
138+
'res.txt':'resources are the best',
139+
},
111140
}
112141

113142
deftest_implicit_files_package(self):
@@ -122,6 +151,32 @@ def test_implicit_files_submodule(self):
122151
"""
123152
assertimportlib.import_module('somepkg.submod').val=='resources are the best'
124153

154+
def_compile_importlib(self):
155+
"""
156+
Make a compiled-only copy of the importlib resources package.
157+
"""
158+
bin_site=self.fixtures.enter_context(os_helper.temp_dir())
159+
c_resources=pathlib.Path(bin_site,'c_resources')
160+
sources=pathlib.Path(resources.__file__).parent
161+
shutil.copytree(sources,c_resources,ignore=lambda*_: ['__pycache__'])
162+
163+
fordirpath,_,filenamesinos.walk(c_resources):
164+
forfilenameinfilenames:
165+
source_path=pathlib.Path(dirpath)/filename
166+
cfile=source_path.with_suffix('.pyc')
167+
py_compile.compile(source_path,cfile)
168+
pathlib.Path.unlink(source_path)
169+
self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site))
170+
171+
deftest_implicit_files_with_compiled_importlib(self):
172+
"""
173+
Caller detection works for compiled-only resources module.
174+
175+
python/cpython#123085
176+
"""
177+
self._compile_importlib()
178+
assertimportlib.import_module('frozenpkg').val=='resources are the best'
179+
125180

126181
classImplicitContextFilesDiskTests(
127182
DirectSpec,util.DiskSetup,ImplicitContextFiles,unittest.TestCase
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed issue in NamespaceReader where a non-path item in a namespace path,
2+
such as a sentinel added by an editable installer, would break resource
3+
loading.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp