Skip to content

Commit b67ac80

Browse files
committed
Merge tag 'v8.7.0' into cpython
2 parents 55c9652 + 708dff4 commit b67ac80

11 files changed

Lines changed: 284 additions & 118 deletions

File tree

‎Lib/importlib/metadata/__init__.py‎

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

‎Lib/importlib/metadata/_adapters.py‎

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,58 @@
1+
importemail.message
2+
importemail.policy
13
importre
24
importtextwrap
3-
importemail.message
45

56
from ._textimportFoldedCase
67

78

9+
classRawPolicy(email.policy.EmailPolicy):
10+
deffold(self, name, value):
11+
folded=self.linesep.join(
12+
textwrap.indent(value, prefix=' '*8, predicate=lambdaline: True)
13+
.lstrip()
14+
.splitlines()
15+
)
16+
returnf'{name}: {folded}{self.linesep}'
17+
18+
819
classMessage(email.message.Message):
20+
r"""
21+
Specialized Message subclass to handle metadata naturally.
22+
23+
Reads values that may have newlines in them and converts the
24+
payload to the Description.
25+
26+
>>> msg_text = textwrap.dedent('''
27+
... Name: Foo
28+
... Version: 3.0
29+
... License: blah
30+
... de-blah
31+
... <BLANKLINE>
32+
... First line of description.
33+
... Second line of description.
34+
... <BLANKLINE>
35+
... Fourth line!
36+
... ''').lstrip().replace('<BLANKLINE>', '')
37+
>>> msg = Message(email.message_from_string(msg_text))
38+
>>> msg['Description']
39+
'First line of description.\nSecond line of description.\n\nFourth line!\n'
40+
41+
Message should render even if values contain newlines.
42+
43+
>>> print(msg)
44+
Name: Foo
45+
Version: 3.0
46+
License: blah
47+
de-blah
48+
Description: First line of description.
49+
Second line of description.
50+
<BLANKLINE>
51+
Fourth line!
52+
<BLANKLINE>
53+
<BLANKLINE>
54+
"""
55+
956
multiple_use_keys=set(
1057
map(
1158
FoldedCase,
@@ -57,15 +104,20 @@ def __getitem__(self, item):
57104
def_repair_headers(self):
58105
defredent(value):
59106
"Correct for RFC822 indentation"
60-
ifnotvalueor'\n'notinvalue:
107+
indent=' '*8
108+
ifnotvalueor'\n'+indentnotinvalue:
61109
returnvalue
62-
returntextwrap.dedent(' '*8+value)
110+
returntextwrap.dedent(indent+value)
63111

64112
headers= [(key, redent(value)) forkey, valueinvars(self)['_headers']]
65113
ifself._payload:
66114
headers.append(('Description', self.get_payload()))
115+
self.set_payload('')
67116
returnheaders
68117

118+
defas_string(self):
119+
returnsuper().as_string(policy=RawPolicy())
120+
69121
@property
70122
defjson(self):
71123
"""

‎Lib/importlib/metadata/_collections.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
importcollections
2+
importtyping
23

34

45
# from jaraco.collections 3.3
@@ -24,7 +25,10 @@ def freeze(self):
2425
self._frozen=lambdakey: self.default_factory()
2526

2627

27-
classPair(collections.namedtuple('Pair', 'name value')):
28+
classPair(typing.NamedTuple):
29+
name: str
30+
value: str
31+
2832
@classmethod
2933
defparse(cls, text):
3034
returncls(*map(str.strip, text.split("=", 1)))

‎Lib/importlib/metadata/_functools.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
importtypes
21
importfunctools
2+
importtypes
33

44

55
# from jaraco.functools 3.3

‎Lib/importlib/metadata/_meta.py‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from __future__ importannotations
22

33
importos
4-
fromtypingimportProtocol
5-
fromtypingimportAny, Dict, Iterator, List, Optional, TypeVar, Union, overload
6-
4+
fromcollections.abcimportIterator
5+
fromtypingimport (
6+
Any,
7+
Protocol,
8+
TypeVar,
9+
overload,
10+
)
711

812
_T=TypeVar("_T")
913

@@ -20,25 +24,25 @@ def __iter__(self) -> Iterator[str]: ... # pragma: no cover
2024
@overload
2125
defget(
2226
self, name: str, failobj: None=None
23-
) ->Optional[str]: ... # pragma: no cover
27+
) ->str|None: ... # pragma: no cover
2428

2529
@overload
26-
defget(self, name: str, failobj: _T) ->Union[str, _T]: ... # pragma: no cover
30+
defget(self, name: str, failobj: _T) ->str|_T: ... # pragma: no cover
2731

2832
# overload per python/importlib_metadata#435
2933
@overload
3034
defget_all(
3135
self, name: str, failobj: None=None
32-
) ->Optional[List[Any]]: ... # pragma: no cover
36+
) ->list[Any]|None: ... # pragma: no cover
3337

3438
@overload
35-
defget_all(self, name: str, failobj: _T) ->Union[List[Any], _T]:
39+
defget_all(self, name: str, failobj: _T) ->list[Any]|_T:
3640
"""
3741
Return all values associated with a possibly multi-valued key.
3842
"""
3943

4044
@property
41-
defjson(self) ->Dict[str, Union[str, List[str]]]:
45+
defjson(self) ->dict[str, str|list[str]]:
4246
"""
4347
A JSON-compatible form of the metadata.
4448
"""
@@ -50,11 +54,11 @@ class SimplePath(Protocol):
5054
"""
5155

5256
defjoinpath(
53-
self, other: Union[str, os.PathLike[str]]
57+
self, other: str|os.PathLike[str]
5458
) ->SimplePath: ... # pragma: no cover
5559

5660
def__truediv__(
57-
self, other: Union[str, os.PathLike[str]]
61+
self, other: str|os.PathLike[str]
5862
) ->SimplePath: ... # pragma: no cover
5963

6064
@property

‎Lib/importlib/metadata/_typing.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
importfunctools
2+
importtyping
3+
4+
from ._metaimportPackageMetadata
5+
6+
md_none=functools.partial(typing.cast, PackageMetadata)
7+
"""
8+
Suppress type errors for optional metadata.
9+
10+
Although Distribution.metadata can return None when metadata is corrupt
11+
and thus None, allow callers to assume it's not None and crash if
12+
that's the case.
13+
14+
# python/importlib_metadata#493
15+
"""

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

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
# from jaraco.path 3.7
1+
# from jaraco.path 3.7.2
2+
3+
from __future__ importannotations
24

35
importfunctools
46
importpathlib
5-
fromtypingimportDict, Protocol, Union
6-
fromtypingimportruntime_checkable
7+
fromcollections.abcimportMapping
8+
fromtypingimportTYPE_CHECKING, Protocol, Union, runtime_checkable
9+
10+
ifTYPE_CHECKING:
11+
fromtyping_extensionsimportSelf
712

813

914
classSymlink(str):
@@ -12,29 +17,25 @@ class Symlink(str):
1217
"""
1318

1419

15-
FilesSpec=Dict[str, Union[str, bytes, Symlink, 'FilesSpec']]# type: ignore
20+
FilesSpec=Mapping[str, Union[str, bytes, Symlink, 'FilesSpec']]
1621

1722

1823
@runtime_checkable
1924
classTreeMaker(Protocol):
20-
def__truediv__(self, *args, **kwargs): ... # pragma: no cover
21-
22-
defmkdir(self, **kwargs): ... # pragma: no cover
23-
24-
defwrite_text(self, content, **kwargs): ... # pragma: no cover
25-
26-
defwrite_bytes(self, content): ... # pragma: no cover
27-
28-
defsymlink_to(self, target): ... # pragma: no cover
25+
def__truediv__(self, other, /) ->Self: ...
26+
defmkdir(self, *, exist_ok) ->object: ...
27+
defwrite_text(self, content, /, *, encoding) ->object: ...
28+
defwrite_bytes(self, content, /) ->object: ...
29+
defsymlink_to(self, target, /) ->object: ...
2930

3031

31-
def_ensure_tree_maker(obj: Union[str, TreeMaker]) ->TreeMaker:
32-
returnobjifisinstance(obj, TreeMaker) elsepathlib.Path(obj)# type: ignore
32+
def_ensure_tree_maker(obj: str|TreeMaker) ->TreeMaker:
33+
returnobjifisinstance(obj, TreeMaker) elsepathlib.Path(obj)
3334

3435

3536
defbuild(
3637
spec: FilesSpec,
37-
prefix: Union[str, TreeMaker]=pathlib.Path(),# type: ignore
38+
prefix: str|TreeMaker=pathlib.Path(),
3839
):
3940
"""
4041
Build a set of files/directories, as described by the spec.
@@ -66,23 +67,24 @@ def build(
6667

6768

6869
@functools.singledispatch
69-
defcreate(content: Union[str, bytes, FilesSpec], path):
70+
defcreate(content: str|bytes|FilesSpec, path: TreeMaker) ->None:
7071
path.mkdir(exist_ok=True)
71-
build(content, prefix=path) # type: ignore
72+
# Mypy only looks at the signature of the main singledispatch method. So it must contain the complete Union
73+
build(content, prefix=path) # type: ignore[arg-type] # python/mypy#11727
7274

7375

7476
@create.register
75-
def_(content: bytes, path):
77+
def_(content: bytes, path: TreeMaker) ->None:
7678
path.write_bytes(content)
7779

7880

7981
@create.register
80-
def_(content: str, path):
82+
def_(content: str, path: TreeMaker) ->None:
8183
path.write_text(content, encoding='utf-8')
8284

8385

8486
@create.register
85-
def_(content: Symlink, path):
87+
def_(content: Symlink, path: TreeMaker) ->None:
8688
path.symlink_to(content)
8789

8890

‎Lib/test/test_importlib/metadata/fixtures.py‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
importsys
1+
importcontextlib
22
importcopy
3+
importfunctools
34
importjson
4-
importshutil
55
importpathlib
6+
importshutil
7+
importsys
68
importtextwrap
7-
importfunctools
8-
importcontextlib
99

1010
fromtest.supportimportimport_helper
1111
fromtest.supportimportos_helper
@@ -14,14 +14,10 @@
1414
from . import_path
1515
from ._pathimportFilesSpec
1616

17-
18-
try:
19-
fromimportlibimportresources# type: ignore
20-
21-
getattr(resources, 'files')
22-
getattr(resources, 'as_file')
23-
except (ImportError, AttributeError):
24-
importimportlib_resourcesasresources# type: ignore
17+
ifsys.version_info>= (3, 9):
18+
fromimportlibimportresources
19+
else:
20+
importimportlib_resourcesasresources
2521

2622

2723
@contextlib.contextmanager

‎Lib/test/test_importlib/metadata/test_api.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
importimportlib
12
importre
23
importtextwrap
34
importunittest
4-
importimportlib
55

6-
from . importfixtures
76
fromimportlib.metadataimport (
87
Distribution,
98
PackageNotFoundError,
@@ -15,6 +14,8 @@
1514
version,
1615
)
1716

17+
from . importfixtures
18+
1819

1920
classAPITests(
2021
fixtures.EggInfoPkg,

‎Lib/test/test_importlib/metadata/test_main.py‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
importre
1+
importimportlib
22
importpickle
3+
importre
34
importunittest
4-
importimportlib
5-
importimportlib.metadata
65
fromtest.supportimportos_helper
76

87
try:
98
importpyfakefs.fake_filesystem_unittestasffs
109
exceptImportError:
1110
from .stubsimportfake_filesystem_unittestasffs
1211

13-
from . importfixtures
14-
from ._pathimportSymlink
1512
fromimportlib.metadataimport (
1613
Distribution,
1714
EntryPoint,
@@ -24,6 +21,9 @@
2421
version,
2522
)
2623

24+
from . importfixtures
25+
from ._pathimportSymlink
26+
2727

2828
classBasicTests(fixtures.DistInfoPkg, unittest.TestCase):
2929
version_pattern=r'\d+\.\d+(\.\d)?'
@@ -157,6 +157,16 @@ def test_valid_dists_preferred(self):
157157
dist=Distribution.from_name('foo')
158158
assertdist.version=="1.0"
159159

160+
deftest_missing_metadata(self):
161+
"""
162+
Dists with a missing metadata file should return None.
163+
164+
Ref python/importlib_metadata#493.
165+
"""
166+
fixtures.build_files(self.make_pkg('foo-4.3', files={}), self.site_dir)
167+
assertDistribution.from_name('foo').metadataisNone
168+
assertmetadata('foo') isNone
169+
160170

161171
classNonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
162172
@staticmethod

0 commit comments

Comments
 (0)