Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 62 additions & 143 deletions Lib/importlib/metadata/__init__.py

Large diffs are not rendered by default.

58 changes: 3 additions & 55 deletions Lib/importlib/metadata/_adapters.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,11 @@
import email.message
import email.policy
import re
import textwrap
import email.message

from ._text import FoldedCase


class RawPolicy(email.policy.EmailPolicy):
def fold(self, name, value):
folded = self.linesep.join(
textwrap.indent(value, prefix=' ' * 8, predicate=lambda line: True)
.lstrip()
.splitlines()
)
return f'{name}: {folded}{self.linesep}'


class Message(email.message.Message):
r"""
Specialized Message subclass to handle metadata naturally.

Reads values that may have newlines in them and converts the
payload to the Description.

>>> msg_text = textwrap.dedent('''
... Name: Foo
... Version: 3.0
... License: blah
... de-blah
... <BLANKLINE>
... First line of description.
... Second line of description.
... <BLANKLINE>
... Fourth line!
... ''').lstrip().replace('<BLANKLINE>', '')
>>> msg = Message(email.message_from_string(msg_text))
>>> msg['Description']
'First line of description.\nSecond line of description.\n\nFourth line!\n'

Message should render even if values contain newlines.

>>> print(msg)
Name: Foo
Version: 3.0
License: blah
de-blah
Description: First line of description.
Second line of description.
<BLANKLINE>
Fourth line!
<BLANKLINE>
<BLANKLINE>
"""

multiple_use_keys = set(
map(
FoldedCase,
Expand Down Expand Up @@ -104,20 +57,15 @@ def __getitem__(self, item):
def _repair_headers(self):
def redent(value):
"Correct for RFC822 indentation"
indent = ' ' * 8
if not value or '\n' + indent not in value:
if not value or '\n' not in value:
return value
return textwrap.dedent(indent + value)
return textwrap.dedent(' ' * 8 + value)

headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
if self._payload:
headers.append(('Description', self.get_payload()))
self.set_payload('')
return headers

def as_string(self):
return super().as_string(policy=RawPolicy())

@property
def json(self):
"""
Expand Down
6 changes: 1 addition & 5 deletions Lib/importlib/metadata/_collections.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import collections
import typing


# from jaraco.collections 3.3
Expand All @@ -25,10 +24,7 @@ def freeze(self):
self._frozen = lambda key: self.default_factory()


class Pair(typing.NamedTuple):
name: str
value: str

class Pair(collections.namedtuple('Pair', 'name value')):
@classmethod
def parse(cls, text):
return cls(*map(str.strip, text.split("=", 1)))
2 changes: 1 addition & 1 deletion Lib/importlib/metadata/_functools.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import functools
import types
import functools


# from jaraco.functools 3.3
Expand Down
24 changes: 10 additions & 14 deletions Lib/importlib/metadata/_meta.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
from __future__ import annotations

import os
from collections.abc import Iterator
from typing import (
Any,
Protocol,
TypeVar,
overload,
)
from typing import Protocol
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload


_T = TypeVar("_T")

Expand All @@ -24,25 +20,25 @@ def __iter__(self) -> Iterator[str]: ... # pragma: no cover
@overload
def get(
self, name: str, failobj: None = None
) -> str | None: ... # pragma: no cover
) -> Optional[str]: ... # pragma: no cover

@overload
def get(self, name: str, failobj: _T) -> str | _T: ... # pragma: no cover
def get(self, name: str, failobj: _T) -> Union[str, _T]: ... # pragma: no cover

# overload per python/importlib_metadata#435
@overload
def get_all(
self, name: str, failobj: None = None
) -> list[Any] | None: ... # pragma: no cover
) -> Optional[List[Any]]: ... # pragma: no cover

@overload
def get_all(self, name: str, failobj: _T) -> list[Any] | _T:
def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
"""
Return all values associated with a possibly multi-valued key.
"""

@property
def json(self) -> dict[str, str | list[str]]:
def json(self) -> Dict[str, Union[str, List[str]]]:
"""
A JSON-compatible form of the metadata.
"""
Expand All @@ -54,11 +50,11 @@ class SimplePath(Protocol):
"""

def joinpath(
self, other: str | os.PathLike[str]
self, other: Union[str, os.PathLike[str]]
) -> SimplePath: ... # pragma: no cover

def __truediv__(
self, other: str | os.PathLike[str]
self, other: Union[str, os.PathLike[str]]
) -> SimplePath: ... # pragma: no cover

@property
Expand Down
15 changes: 0 additions & 15 deletions Lib/importlib/metadata/_typing.py

This file was deleted.

44 changes: 21 additions & 23 deletions Lib/test/test_importlib/metadata/_path.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
# from jaraco.path 3.7.2

from __future__ import annotations
# from jaraco.path 3.7

import functools
import pathlib
from collections.abc import Mapping
from typing import TYPE_CHECKING, Protocol, Union, runtime_checkable

if TYPE_CHECKING:
from typing_extensions import Self
from typing import Dict, Protocol, Union
from typing import runtime_checkable


class Symlink(str):
Expand All @@ -17,25 +12,29 @@ class Symlink(str):
"""


FilesSpec = Mapping[str, Union[str, bytes, Symlink, 'FilesSpec']]
FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] # type: ignore


@runtime_checkable
class TreeMaker(Protocol):
def __truediv__(self, other, /) -> Self: ...
def mkdir(self, *, exist_ok) -> object: ...
def write_text(self, content, /, *, encoding) -> object: ...
def write_bytes(self, content, /) -> object: ...
def symlink_to(self, target, /) -> object: ...
def __truediv__(self, *args, **kwargs): ... # pragma: no cover

def mkdir(self, **kwargs): ... # pragma: no cover

def write_text(self, content, **kwargs): ... # pragma: no cover

def write_bytes(self, content): ... # pragma: no cover

def symlink_to(self, target): ... # pragma: no cover


def _ensure_tree_maker(obj: str | TreeMaker) -> TreeMaker:
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj)
def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore


def build(
spec: FilesSpec,
prefix: str | TreeMaker = pathlib.Path(),
prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore
):
"""
Build a set of files/directories, as described by the spec.
Expand Down Expand Up @@ -67,24 +66,23 @@ def build(


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


@create.register
def _(content: bytes, path: TreeMaker) -> None:
def _(content: bytes, path):
path.write_bytes(content)


@create.register
def _(content: str, path: TreeMaker) -> None:
def _(content: str, path):
path.write_text(content, encoding='utf-8')


@create.register
def _(content: Symlink, path: TreeMaker) -> None:
def _(content: Symlink, path):
path.symlink_to(content)


Expand Down
20 changes: 12 additions & 8 deletions Lib/test/test_importlib/metadata/fixtures.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import contextlib
import sys
import copy
import functools
import json
import pathlib
import shutil
import sys
import pathlib
import textwrap
import functools
import contextlib

from test.support import import_helper
from test.support import os_helper
Expand All @@ -14,10 +14,14 @@
from . import _path
from ._path import FilesSpec

if sys.version_info >= (3, 9):
from importlib import resources
else:
import importlib_resources as resources

try:
from importlib import resources # type: ignore

getattr(resources, 'files')
getattr(resources, 'as_file')
except (ImportError, AttributeError):
import importlib_resources as resources # type: ignore


@contextlib.contextmanager
Expand Down
5 changes: 2 additions & 3 deletions Lib/test/test_importlib/metadata/test_api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import importlib
import re
import textwrap
import unittest
import importlib

from . import fixtures
from importlib.metadata import (
Distribution,
PackageNotFoundError,
Expand All @@ -14,8 +15,6 @@
version,
)

from . import fixtures


class APITests(
fixtures.EggInfoPkg,
Expand Down
20 changes: 5 additions & 15 deletions Lib/test/test_importlib/metadata/test_main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import importlib
import pickle
import re
import pickle
import unittest
import importlib
import importlib.metadata
from test.support import os_helper

try:
import pyfakefs.fake_filesystem_unittest as ffs
except ImportError:
from .stubs import fake_filesystem_unittest as ffs

from . import fixtures
from ._path import Symlink
from importlib.metadata import (
Distribution,
EntryPoint,
Expand All @@ -21,9 +24,6 @@
version,
)

from . import fixtures
from ._path import Symlink


class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
version_pattern = r'\d+\.\d+(\.\d)?'
Expand Down Expand Up @@ -157,16 +157,6 @@ def test_valid_dists_preferred(self):
dist = Distribution.from_name('foo')
assert dist.version == "1.0"

def test_missing_metadata(self):
"""
Dists with a missing metadata file should return None.

Ref python/importlib_metadata#493.
"""
fixtures.build_files(self.make_pkg('foo-4.3', files={}), self.site_dir)
assert Distribution.from_name('foo').metadata is None
assert metadata('foo') is None


class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
@staticmethod
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_importlib/metadata/test_zip.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sys
import unittest

from . import fixtures
from importlib.metadata import (
PackageNotFoundError,
distribution,
Expand All @@ -10,8 +11,6 @@
version,
)

from . import fixtures


class TestZip(fixtures.ZipFixtures, unittest.TestCase):
def setUp(self):
Expand Down

This file was deleted.

Loading