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

Commit38612a0

Browse files
authored
gh-93259: Validate arg toDistribution.from_name. (GH-94270)
Syncs with importlib_metadata 4.12.0.
1 parent9af6b75 commit38612a0

File tree

6 files changed

+135
-67
lines changed

6 files changed

+135
-67
lines changed

‎Doc/library/importlib.metadata.rst‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
**Source code:**:source:`Lib/importlib/metadata/__init__.py`
1515

16-
``importlib.metadata`` is a library that provides for access to installed
17-
package metadata. Built in part on Python's import system, this library
16+
``importlib.metadata`` is a library that provides access to installed
17+
package metadata, such as its entry points or its
18+
top-level name. Built in part on Python's import system, this library
1819
intends to replace similar functionality in the `entry point
1920
API`_ and `metadata API`_ of ``pkg_resources``. Along with
20-
:mod:`importlib.resources` (with new features backported to the
21-
`importlib_resources`_ package), this can eliminate the need to use the older
22-
and less efficient
21+
:mod:`importlib.resources`,
22+
this package can eliminate the need to use the older and less efficient
2323
``pkg_resources`` package.
2424

2525
By "installed package" we generally mean a third-party package installed into
@@ -32,6 +32,13 @@ By default, package metadata can live on the file system or in zip archives on
3232
anywhere.
3333

3434

35+
..seealso::
36+
37+
https://importlib-metadata.readthedocs.io/
38+
The documentation for ``importlib_metadata``, which supplies a
39+
backport of ``importlib.metadata``.
40+
41+
3542
Overview
3643
========
3744

@@ -54,9 +61,9 @@ You can get the version string for ``wheel`` by running the following:
5461
>>> version('wheel') # doctest: +SKIP
5562
'0.32.3'
5663
57-
You can also getthe set of entry pointskeyed by group, such as
64+
You can also geta collection of entry pointsselectable byproperties of the EntryPoint (typically 'group' or 'name'), such as
5865
``console_scripts``, ``distutils.commands`` and others. Each group contains a
59-
sequence of:ref:`EntryPoint<entry-points>` objects.
66+
collection of:ref:`EntryPoint<entry-points>` objects.
6067

6168
You can get the:ref:`metadata for a distribution<metadata>`::
6269

@@ -91,7 +98,7 @@ Query all entry points::
9198
>>> eps = entry_points() # doctest: +SKIP
9299

93100
The ``entry_points()`` function returns an ``EntryPoints`` object,
94-
asequence of all ``EntryPoint`` objects with ``names`` and ``groups``
101+
acollection of all ``EntryPoint`` objects with ``names`` and ``groups``
95102
attributes for convenience::
96103

97104
>>> sorted(eps.groups) # doctest: +SKIP
@@ -174,6 +181,13 @@ all the metadata in a JSON-compatible form per :PEP:`566`::
174181
>>> wheel_metadata.json['requires_python']
175182
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
176183

184+
..note::
185+
186+
The actual type of the object returned by ``metadata()`` is an
187+
implementation detail and should be accessed only through the interface
188+
described by the
189+
`PackageMetadata protocol <https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata.PackageMetadata>`.
190+
177191
..versionchanged::3.10
178192
The ``Description`` is now included in the metadata when presented
179193
through the payload. Line continuation characters have been removed.
@@ -295,6 +309,15 @@ The full set of available metadata is not described here. See :pep:`566`
295309
for additional details.
296310

297311

312+
Distribution Discovery
313+
======================
314+
315+
By default, this package provides built-in support for discovery of metadata for file system and zip file packages. This metadata finder search defaults to ``sys.path``, but varies slightly in how it interprets those values from how other import machinery does. In particular:
316+
317+
- ``importlib.metadata`` does not honor:class:`bytes` objects on ``sys.path``.
318+
- ``importlib.metadata`` will incidentally honor:py:class:`pathlib.Path` objects on ``sys.path`` even though such values will be ignored for imports.
319+
320+
298321
Extending the search algorithm
299322
==============================
300323

‎Lib/importlib/metadata/__init__.py‎

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -543,21 +543,21 @@ def locate_file(self, path):
543543
"""
544544

545545
@classmethod
546-
deffrom_name(cls,name):
546+
deffrom_name(cls,name:str):
547547
"""Return the Distribution for the given package name.
548548
549549
:param name: The name of the distribution package to search for.
550550
:return: The Distribution instance (or subclass thereof) for the named
551551
package, if found.
552552
:raises PackageNotFoundError: When the named package's distribution
553553
metadata cannot be found.
554+
:raises ValueError: When an invalid value is supplied for name.
554555
"""
555-
forresolverincls._discover_resolvers():
556-
dists=resolver(DistributionFinder.Context(name=name))
557-
dist=next(iter(dists),None)
558-
ifdistisnotNone:
559-
returndist
560-
else:
556+
ifnotname:
557+
raiseValueError("A distribution name is required.")
558+
try:
559+
returnnext(cls.discover(name=name))
560+
exceptStopIteration:
561561
raisePackageNotFoundError(name)
562562

563563
@classmethod
@@ -945,13 +945,26 @@ def _normalized_name(self):
945945
normalized name from the file system path.
946946
"""
947947
stem=os.path.basename(str(self._path))
948-
returnself._name_from_stem(stem)orsuper()._normalized_name
948+
return (
949+
pass_none(Prepared.normalize)(self._name_from_stem(stem))
950+
orsuper()._normalized_name
951+
)
949952

950-
def_name_from_stem(self,stem):
951-
name,ext=os.path.splitext(stem)
953+
@staticmethod
954+
def_name_from_stem(stem):
955+
"""
956+
>>> PathDistribution._name_from_stem('foo-3.0.egg-info')
957+
'foo'
958+
>>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info')
959+
'CherryPy'
960+
>>> PathDistribution._name_from_stem('face.egg-info')
961+
'face'
962+
>>> PathDistribution._name_from_stem('foo.bar')
963+
"""
964+
filename,ext=os.path.splitext(stem)
952965
ifextnotin ('.dist-info','.egg-info'):
953966
return
954-
name,sep,rest=stem.partition('-')
967+
name,sep,rest=filename.partition('-')
955968
returnname
956969

957970

@@ -991,6 +1004,15 @@ def version(distribution_name):
9911004
returndistribution(distribution_name).version
9921005

9931006

1007+
_unique=functools.partial(
1008+
unique_everseen,
1009+
key=operator.attrgetter('_normalized_name'),
1010+
)
1011+
"""
1012+
Wrapper for ``distributions`` to return unique distributions by name.
1013+
"""
1014+
1015+
9941016
defentry_points(**params)->Union[EntryPoints,SelectableGroups]:
9951017
"""Return EntryPoint objects for all installed packages.
9961018
@@ -1008,10 +1030,8 @@ def entry_points(**params) -> Union[EntryPoints, SelectableGroups]:
10081030
10091031
:return: EntryPoints or SelectableGroups for all installed packages.
10101032
"""
1011-
norm_name=operator.attrgetter('_normalized_name')
1012-
unique=functools.partial(unique_everseen,key=norm_name)
10131033
eps=itertools.chain.from_iterable(
1014-
dist.entry_pointsfordistinunique(distributions())
1034+
dist.entry_pointsfordistin_unique(distributions())
10151035
)
10161036
returnSelectableGroups.load(eps).select(**params)
10171037

‎Lib/test/test_importlib/fixtures.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
importpathlib
66
importtempfile
77
importtextwrap
8+
importfunctools
89
importcontextlib
910

1011
fromtest.support.os_helperimportFS_NONASCII
@@ -296,3 +297,18 @@ def setUp(self):
296297
# Add self.zip_name to the front of sys.path.
297298
self.resources=contextlib.ExitStack()
298299
self.addCleanup(self.resources.close)
300+
301+
302+
defparameterize(*args_set):
303+
"""Run test method with a series of parameters."""
304+
305+
defwrapper(func):
306+
@functools.wraps(func)
307+
def_inner(self):
308+
forargsinargs_set:
309+
withself.subTest(**args):
310+
func(self,**args)
311+
312+
return_inner
313+
314+
returnwrapper

‎Lib/test/test_importlib/test_main.py‎

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
importre
22
importjson
33
importpickle
4-
importtextwrap
54
importunittest
65
importwarnings
76
importimportlib.metadata
@@ -16,6 +15,7 @@
1615
Distribution,
1716
EntryPoint,
1817
PackageNotFoundError,
18+
_unique,
1919
distributions,
2020
entry_points,
2121
metadata,
@@ -51,6 +51,14 @@ def test_package_not_found_mentions_metadata(self):
5151
deftest_new_style_classes(self):
5252
self.assertIsInstance(Distribution,type)
5353

54+
@fixtures.parameterize(
55+
dict(name=None),
56+
dict(name=''),
57+
)
58+
deftest_invalid_inputs_to_from_name(self,name):
59+
withself.assertRaises(Exception):
60+
Distribution.from_name(name)
61+
5462

5563
classImportTests(fixtures.DistInfoPkg,unittest.TestCase):
5664
deftest_import_nonexistent_module(self):
@@ -78,48 +86,50 @@ def test_resolve_without_attr(self):
7886

7987
classNameNormalizationTests(fixtures.OnSysPath,fixtures.SiteDir,unittest.TestCase):
8088
@staticmethod
81-
defpkg_with_dashes(site_dir):
89+
defmake_pkg(name):
8290
"""
83-
Create minimal metadata for a package with dashes
84-
inthe name(and thus underscores inthefilename).
91+
Create minimal metadata for adist-infopackage with
92+
theindicatednameonthefile system.
8593
"""
86-
metadata_dir=site_dir/'my_pkg.dist-info'
87-
metadata_dir.mkdir()
88-
metadata=metadata_dir/'METADATA'
89-
withmetadata.open('w',encoding='utf-8')asstrm:
90-
strm.write('Version: 1.0\n')
91-
return'my-pkg'
94+
return {
95+
f'{name}.dist-info': {
96+
'METADATA':'VERSION: 1.0\n',
97+
},
98+
}
9299

93100
deftest_dashes_in_dist_name_found_as_underscores(self):
94101
"""
95102
For a package with a dash in the name, the dist-info metadata
96103
uses underscores in the name. Ensure the metadata loads.
97104
"""
98-
pkg_name=self.pkg_with_dashes(self.site_dir)
99-
assertversion(pkg_name)=='1.0'
100-
101-
@staticmethod
102-
defpkg_with_mixed_case(site_dir):
103-
"""
104-
Create minimal metadata for a package with mixed case
105-
in the name.
106-
"""
107-
metadata_dir=site_dir/'CherryPy.dist-info'
108-
metadata_dir.mkdir()
109-
metadata=metadata_dir/'METADATA'
110-
withmetadata.open('w',encoding='utf-8')asstrm:
111-
strm.write('Version: 1.0\n')
112-
return'CherryPy'
105+
fixtures.build_files(self.make_pkg('my_pkg'),self.site_dir)
106+
assertversion('my-pkg')=='1.0'
113107

114108
deftest_dist_name_found_as_any_case(self):
115109
"""
116110
Ensure the metadata loads when queried with any case.
117111
"""
118-
pkg_name=self.pkg_with_mixed_case(self.site_dir)
112+
pkg_name='CherryPy'
113+
fixtures.build_files(self.make_pkg(pkg_name),self.site_dir)
119114
assertversion(pkg_name)=='1.0'
120115
assertversion(pkg_name.lower())=='1.0'
121116
assertversion(pkg_name.upper())=='1.0'
122117

118+
deftest_unique_distributions(self):
119+
"""
120+
Two distributions varying only by non-normalized name on
121+
the file system should resolve as the same.
122+
"""
123+
fixtures.build_files(self.make_pkg('abc'),self.site_dir)
124+
before=list(_unique(distributions()))
125+
126+
alt_site_dir=self.fixtures.enter_context(fixtures.tempdir())
127+
self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
128+
fixtures.build_files(self.make_pkg('ABC'),alt_site_dir)
129+
after=list(_unique(distributions()))
130+
131+
assertlen(after)==len(before)
132+
123133

124134
classNonASCIITests(fixtures.OnSysPath,fixtures.SiteDir,unittest.TestCase):
125135
@staticmethod
@@ -128,11 +138,12 @@ def pkg_with_non_ascii_description(site_dir):
128138
Create minimal metadata for a package with non-ASCII in
129139
the description.
130140
"""
131-
metadata_dir=site_dir/'portend.dist-info'
132-
metadata_dir.mkdir()
133-
metadata=metadata_dir/'METADATA'
134-
withmetadata.open('w',encoding='utf-8')asfp:
135-
fp.write('Description: pôrˈtend')
141+
contents= {
142+
'portend.dist-info': {
143+
'METADATA':'Description: pôrˈtend',
144+
},
145+
}
146+
fixtures.build_files(contents,site_dir)
136147
return'portend'
137148

138149
@staticmethod
@@ -141,19 +152,15 @@ def pkg_with_non_ascii_description_egg_info(site_dir):
141152
Create minimal metadata for an egg-info package with
142153
non-ASCII in the description.
143154
"""
144-
metadata_dir=site_dir/'portend.dist-info'
145-
metadata_dir.mkdir()
146-
metadata=metadata_dir/'METADATA'
147-
withmetadata.open('w',encoding='utf-8')asfp:
148-
fp.write(
149-
textwrap.dedent(
150-
"""
155+
contents= {
156+
'portend.dist-info': {
157+
'METADATA':"""
151158
Name: portend
152159
153-
pôrˈtend
154-
"""
155-
).strip()
156-
)
160+
pôrˈtend""",
161+
},
162+
}
163+
fixtures.build_files(contents,site_dir)
157164
return'portend'
158165

159166
deftest_metadata_loads(self):

‎Lib/test/test_importlib/test_metadata_api.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ def test_entry_points_distribution(self):
8989
self.assertIn(ep.dist.name, ('distinfo-pkg','egginfo-pkg'))
9090
self.assertEqual(ep.dist.version,"1.0.0")
9191

92-
deftest_entry_points_unique_packages(self):
92+
deftest_entry_points_unique_packages_normalized(self):
9393
"""
9494
Entry points should only be exposed for the first package
95-
on sys.path with a given name.
95+
on sys.path with a given name (even when normalized).
9696
"""
9797
alt_site_dir=self.fixtures.enter_context(fixtures.tempdir())
9898
self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
9999
alt_pkg= {
100-
"distinfo_pkg-1.1.0.dist-info": {
100+
"DistInfo_pkg-1.1.0.dist-info": {
101101
"METADATA":"""
102102
Name: distinfo-pkg
103103
Version: 1.1.0
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Now raise ``ValueError`` when ``None`` or an empty string are passed to
2+
``Distribution.from_name`` (and other callers).

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp