From 5957d58266e479f124b31f30e4322e798fdf386b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 10 Apr 2023 21:57:33 -0400 Subject: [PATCH 01/78] Remove unnecessary and incorrect copyright notice. Fixes jaraco/skeleton#78. --- LICENSE | 2 -- 1 file changed, 2 deletions(-) diff --git a/LICENSE b/LICENSE index 353924be..1bb5a443 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,3 @@ -Copyright Jason R. Coombs - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the From 791e45acd113716a57fc40b2e972f524657eafe2 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 15 Apr 2023 14:20:33 +0100 Subject: [PATCH 02/78] type annotations --- docs/conf.py | 2 + importlib_metadata/__init__.py | 102 ++++++++++++++++++--------------- importlib_metadata/_meta.py | 2 +- 3 files changed, 60 insertions(+), 46 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8e7762d0..0d6c66ae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,6 +64,8 @@ nitpick_ignore = [ # Workaround for #316 ('py:class', 'importlib_metadata.EntryPoints'), + ('py:class', 'importlib_metadata.PackagePath'), + ('py:class', 'importlib_metadata.PathDistribution'), ('py:class', 'importlib_metadata.SelectableGroups'), ('py:class', 'importlib_metadata._meta._T'), # Workaround for #435 diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 96571f4a..f0561401 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -5,6 +5,7 @@ import sys import zipp import email +import inspect import pathlib import operator import textwrap @@ -14,7 +15,6 @@ import posixpath import contextlib import collections -import inspect from . import _adapters, _meta, _py39compat from ._collections import FreezableDefaultDict, Pair @@ -31,8 +31,9 @@ from importlib import import_module from importlib.abc import MetaPathFinder from itertools import starmap -from typing import List, Mapping, Optional, cast +from typing import Iterator, List, Mapping, Optional, Set, Union, cast +StrPath = Union[str, "os.PathLike[str]"] __all__ = [ 'Distribution', @@ -53,11 +54,11 @@ class PackageNotFoundError(ModuleNotFoundError): """The package was not found.""" - def __str__(self): + def __str__(self) -> str: return f"No package metadata was found for {self.name}" @property - def name(self): + def name(self) -> str: # type: ignore[override] (name,) = self.args return name @@ -123,8 +124,8 @@ def read(text, filter_=None): yield Pair(name, value) @staticmethod - def valid(line): - return line and not line.startswith('#') + def valid(line: str) -> bool: + return bool(line) and not line.startswith('#') class DeprecatedTuple: @@ -198,7 +199,7 @@ class EntryPoint(DeprecatedTuple): dist: Optional['Distribution'] = None - def __init__(self, name, value, group): + def __init__(self, name: str, value: str, group: str) -> None: vars(self).update(name=name, value=value, group=group) def load(self): @@ -212,18 +213,21 @@ def load(self): return functools.reduce(getattr, attrs, module) @property - def module(self): + def module(self) -> str: match = self.pattern.match(self.value) + assert match is not None return match.group('module') @property - def attr(self): + def attr(self) -> str: match = self.pattern.match(self.value) + assert match is not None return match.group('attr') @property - def extras(self): + def extras(self) -> List[str]: match = self.pattern.match(self.value) + assert match is not None return re.findall(r'\w+', match.group('extras') or '') def _for(self, dist): @@ -271,7 +275,7 @@ def __repr__(self): f'group={self.group!r})' ) - def __hash__(self): + def __hash__(self) -> int: return hash(self._key()) @@ -282,7 +286,7 @@ class EntryPoints(tuple): __slots__ = () - def __getitem__(self, name): # -> EntryPoint: + def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] """ Get the EntryPoint in self matching name. """ @@ -299,14 +303,14 @@ def select(self, **params): return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params)) @property - def names(self): + def names(self) -> Set[str]: """ Return the set of all names of all entry points. """ return {ep.name for ep in self} @property - def groups(self): + def groups(self) -> Set[str]: """ Return the set of all groups of all entry points. """ @@ -327,24 +331,28 @@ def _from_text(text): class PackagePath(pathlib.PurePosixPath): """A reference to a path in a package""" - def read_text(self, encoding='utf-8'): + hash: Optional["FileHash"] + size: int + dist: "Distribution" + + def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override] with self.locate().open(encoding=encoding) as stream: return stream.read() - def read_binary(self): + def read_binary(self) -> bytes: with self.locate().open('rb') as stream: return stream.read() - def locate(self): + def locate(self) -> pathlib.Path: """Return a path-like object for this path""" return self.dist.locate_file(self) class FileHash: - def __init__(self, spec): + def __init__(self, spec: str) -> None: self.mode, _, self.value = spec.partition('=') - def __repr__(self): + def __repr__(self) -> str: return f'' @@ -360,14 +368,14 @@ def read_text(self, filename) -> Optional[str]: """ @abc.abstractmethod - def locate_file(self, path): + def locate_file(self, path: StrPath) -> pathlib.Path: """ Given a path to a file in this distribution, return a path to it. """ @classmethod - def from_name(cls, name: str): + def from_name(cls, name: str) -> "Distribution": """Return the Distribution for the given package name. :param name: The name of the distribution package to search for. @@ -385,14 +393,14 @@ def from_name(cls, name: str): raise PackageNotFoundError(name) @classmethod - def discover(cls, **kwargs): - """Return an iterable of Distribution objects for all packages. + def discover(cls, **kwargs) -> Iterator["Distribution"]: + """Return an iterator of Distribution objects for all packages. Pass a ``context`` or pass keyword arguments for constructing a context. :context: A ``DistributionFinder.Context`` object. - :return: Iterable of Distribution objects for all packages. + :return: Iterator of Distribution objects for all packages. """ context = kwargs.pop('context', None) if context and kwargs: @@ -403,7 +411,7 @@ def discover(cls, **kwargs): ) @staticmethod - def at(path): + def at(path: StrPath) -> "PathDistribution": """Return a Distribution for the indicated metadata path :param path: a string or path-like object @@ -438,7 +446,7 @@ def metadata(self) -> _meta.PackageMetadata: return _adapters.Message(email.message_from_string(text)) @property - def name(self): + def name(self) -> str: """Return the 'Name' metadata for the distribution package.""" return self.metadata['Name'] @@ -448,16 +456,16 @@ def _normalized_name(self): return Prepared.normalize(self.name) @property - def version(self): + def version(self) -> str: """Return the 'Version' metadata for the distribution package.""" return self.metadata['Version'] @property - def entry_points(self): + def entry_points(self) -> EntryPoints: return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self) @property - def files(self): + def files(self) -> Optional[List[PackagePath]]: """Files in this distribution. :return: List of PackagePath for this distribution or None @@ -540,7 +548,7 @@ def _read_files_egginfo_sources(self): return text and map('"{}"'.format, text.splitlines()) @property - def requires(self): + def requires(self) -> Optional[List[str]]: """Generated requirements specified for this Distribution""" reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() return reqs and list(reqs) @@ -619,7 +627,7 @@ def __init__(self, **kwargs): vars(self).update(kwargs) @property - def path(self): + def path(self) -> List[str]: """ The sequence of directory path that a distribution finder should search. @@ -630,11 +638,11 @@ def path(self): return vars(self).get('path', sys.path) @abc.abstractmethod - def find_distributions(self, context=Context()): + def find_distributions(self, context=Context()) -> Iterator[Distribution]: """ Find distributions. - Return an iterable of all Distribution instances capable of + Return an iterator of all Distribution instances capable of loading the metadata for packages matching the ``context``, a DistributionFinder.Context instance. """ @@ -765,11 +773,13 @@ class MetadataPathFinder(NullFinder, DistributionFinder): of Python that do not have a PathFinder find_distributions(). """ - def find_distributions(self, context=DistributionFinder.Context()): + def find_distributions( + self, context=DistributionFinder.Context() + ) -> Iterator["PathDistribution"]: """ Find distributions. - Return an iterable of all Distribution instances capable of + Return an iterator of all Distribution instances capable of loading the metadata for packages matching ``context.name`` (or all names if ``None`` indicated) along the paths in the list of directories ``context.path``. @@ -785,19 +795,19 @@ def _search_paths(cls, name, paths): path.search(prepared) for path in map(FastPath, paths) ) - def invalidate_caches(cls): + def invalidate_caches(cls) -> None: FastPath.__new__.cache_clear() class PathDistribution(Distribution): - def __init__(self, path: SimplePath): + def __init__(self, path: SimplePath) -> None: """Construct a distribution. :param path: SimplePath indicating the metadata directory. """ self._path = path - def read_text(self, filename): + def read_text(self, filename: StrPath) -> Optional[str]: with suppress( FileNotFoundError, IsADirectoryError, @@ -807,9 +817,11 @@ def read_text(self, filename): ): return self._path.joinpath(filename).read_text(encoding='utf-8') + return None + read_text.__doc__ = Distribution.read_text.__doc__ - def locate_file(self, path): + def locate_file(self, path: StrPath) -> pathlib.Path: return self._path.parent / path @property @@ -842,7 +854,7 @@ def _name_from_stem(stem): return name -def distribution(distribution_name): +def distribution(distribution_name) -> Distribution: """Get the ``Distribution`` instance for the named package. :param distribution_name: The name of the distribution package as a string. @@ -851,10 +863,10 @@ def distribution(distribution_name): return Distribution.from_name(distribution_name) -def distributions(**kwargs): +def distributions(**kwargs) -> Iterator[Distribution]: """Get all ``Distribution`` instances in the current environment. - :return: An iterable of ``Distribution`` instances. + :return: An iterator of ``Distribution`` instances. """ return Distribution.discover(**kwargs) @@ -868,7 +880,7 @@ def metadata(distribution_name) -> _meta.PackageMetadata: return Distribution.from_name(distribution_name).metadata -def version(distribution_name): +def version(distribution_name) -> str: """Get the version string for the named package. :param distribution_name: The name of the distribution package to query. @@ -902,7 +914,7 @@ def entry_points(**params) -> EntryPoints: return EntryPoints(eps).select(**params) -def files(distribution_name): +def files(distribution_name) -> Optional[List[PackagePath]]: """Return a list of files for the named package. :param distribution_name: The name of the distribution package to query. @@ -911,7 +923,7 @@ def files(distribution_name): return distribution(distribution_name).files -def requires(distribution_name): +def requires(distribution_name) -> Optional[List[str]]: """ Return a list of requirements for the named package. diff --git a/importlib_metadata/_meta.py b/importlib_metadata/_meta.py index e27d34aa..0c7e8791 100644 --- a/importlib_metadata/_meta.py +++ b/importlib_metadata/_meta.py @@ -49,7 +49,7 @@ class SimplePath(Protocol[_T]): A minimal subset of pathlib.Path required by PathDistribution. """ - def joinpath(self) -> _T: + def joinpath(self, other: Union[str, _T]) -> _T: ... # pragma: no cover def __truediv__(self, other: Union[str, _T]) -> _T: From b58d47f1e8a9bdf77bcca8bdf6a8909f98f146b3 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Tue, 18 Apr 2023 18:29:03 +0100 Subject: [PATCH 03/78] code review --- importlib_metadata/__init__.py | 33 ++++++++++++++++----------------- importlib_metadata/_compat.py | 10 ++++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index f0561401..515f2070 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -20,6 +20,7 @@ from ._collections import FreezableDefaultDict, Pair from ._compat import ( NullFinder, + StrPath, install, pypy_partial, ) @@ -31,9 +32,7 @@ from importlib import import_module from importlib.abc import MetaPathFinder from itertools import starmap -from typing import Iterator, List, Mapping, Optional, Set, Union, cast - -StrPath = Union[str, "os.PathLike[str]"] +from typing import Iterable, List, Mapping, Optional, Set, cast __all__ = [ 'Distribution', @@ -124,8 +123,8 @@ def read(text, filter_=None): yield Pair(name, value) @staticmethod - def valid(line: str) -> bool: - return bool(line) and not line.startswith('#') + def valid(line: str): + return line and not line.startswith('#') class DeprecatedTuple: @@ -388,19 +387,19 @@ def from_name(cls, name: str) -> "Distribution": if not name: raise ValueError("A distribution name is required.") try: - return next(cls.discover(name=name)) + return next(iter(cls.discover(name=name))) except StopIteration: raise PackageNotFoundError(name) @classmethod - def discover(cls, **kwargs) -> Iterator["Distribution"]: - """Return an iterator of Distribution objects for all packages. + def discover(cls, **kwargs) -> Iterable["Distribution"]: + """Return an iterable of Distribution objects for all packages. Pass a ``context`` or pass keyword arguments for constructing a context. :context: A ``DistributionFinder.Context`` object. - :return: Iterator of Distribution objects for all packages. + :return: Iterable of Distribution objects for all packages. """ context = kwargs.pop('context', None) if context and kwargs: @@ -411,7 +410,7 @@ def discover(cls, **kwargs) -> Iterator["Distribution"]: ) @staticmethod - def at(path: StrPath) -> "PathDistribution": + def at(path: StrPath) -> "Distribution": """Return a Distribution for the indicated metadata path :param path: a string or path-like object @@ -638,11 +637,11 @@ def path(self) -> List[str]: return vars(self).get('path', sys.path) @abc.abstractmethod - def find_distributions(self, context=Context()) -> Iterator[Distribution]: + def find_distributions(self, context=Context()) -> Iterable[Distribution]: """ Find distributions. - Return an iterator of all Distribution instances capable of + Return an iterable of all Distribution instances capable of loading the metadata for packages matching the ``context``, a DistributionFinder.Context instance. """ @@ -775,11 +774,11 @@ class MetadataPathFinder(NullFinder, DistributionFinder): def find_distributions( self, context=DistributionFinder.Context() - ) -> Iterator["PathDistribution"]: + ) -> Iterable["PathDistribution"]: """ Find distributions. - Return an iterator of all Distribution instances capable of + Return an iterable of all Distribution instances capable of loading the metadata for packages matching ``context.name`` (or all names if ``None`` indicated) along the paths in the list of directories ``context.path``. @@ -863,10 +862,10 @@ def distribution(distribution_name) -> Distribution: return Distribution.from_name(distribution_name) -def distributions(**kwargs) -> Iterator[Distribution]: +def distributions(**kwargs) -> Iterable[Distribution]: """Get all ``Distribution`` instances in the current environment. - :return: An iterator of ``Distribution`` instances. + :return: An iterable of ``Distribution`` instances. """ return Distribution.discover(**kwargs) @@ -927,7 +926,7 @@ def requires(distribution_name) -> Optional[List[str]]: """ Return a list of requirements for the named package. - :return: An iterator of requirements, suitable for + :return: An iterable of requirements, suitable for packaging.requirement.Requirement. """ return distribution(distribution_name).requires diff --git a/importlib_metadata/_compat.py b/importlib_metadata/_compat.py index 3d78566e..638e7791 100644 --- a/importlib_metadata/_compat.py +++ b/importlib_metadata/_compat.py @@ -1,6 +1,9 @@ +import os import sys import platform +from typing import Union + __all__ = ['install', 'NullFinder', 'Protocol'] @@ -70,3 +73,10 @@ def pypy_partial(val): """ is_pypy = platform.python_implementation() == 'PyPy' return val + is_pypy + + +if sys.version_info >= (3, 9): + StrPath = Union[str, os.PathLike[str]] +else: + # PathLike is only subscriptable at runtime in 3.9+ + StrPath = Union[str, "os.PathLike[str]"] # pragma: no cover From 4b61913ecef7a79cec4bc5b1d2759af67bb2c311 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Wed, 19 Apr 2023 02:41:20 +0200 Subject: [PATCH 04/78] Add test to demonstrate issue with symlinked packages --- tests/fixtures.py | 26 ++++++++++++++++++++++++++ tests/test_main.py | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/tests/fixtures.py b/tests/fixtures.py index 6d26bb91..b39a1c4b 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -169,6 +169,32 @@ def setUp(self): build_files(DistInfoPkg.files, self.site_dir) +class DistInfoSymlinkedPkg(OnSysPath, SiteDir): + files: FilesSpec = { + "symlinked_pkg-1.0.0.dist-info": { + "METADATA": """ + Name: symlinked-pkg + Version: 1.0.0 + """, + "RECORD": "symlinked,,\n", + }, + ".symlink.target": { + "__init__.py": """ + def main(): + print("hello world") + """, + }, + # "symlinked" -> ".symlink.target", see below + } + + def setUp(self): + super().setUp() + build_files(DistInfoSymlinkedPkg.files, self.site_dir) + target = self.site_dir / ".symlink.target" + assert target.is_dir() + (self.site_dir / "symlinked").symlink_to(target, target_is_directory=True) + + class EggInfoPkg(OnSysPath, SiteDir): files: FilesSpec = { "egginfo_pkg.egg-info": { diff --git a/tests/test_main.py b/tests/test_main.py index a7650172..bec1303b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -387,6 +387,32 @@ def test_packages_distributions_all_module_types(self): assert not any(name.endswith('.dist-info') for name in distributions) +class PackagesDistributionsDistTest( + fixtures.DistInfoPkg, + fixtures.DistInfoSymlinkedPkg, + unittest.TestCase, +): + def test_packages_distributions_on_dist_info(self): + """ + Test _top_level_inferred() on various dist-info packages. + """ + distributions = packages_distributions() + + def import_names_from_package(package_name): + return { + import_name + for import_name, package_names in distributions.items() + if package_name in package_names + } + + # distinfo-pkg has one import ('mod') inferred from RECORD + assert import_names_from_package('distinfo-pkg') == {'mod'} + + # symlinked-pkg has one import ('symlinked') inderred from RECORD which + # references a symlink to the real package dir elsewhere. + assert import_names_from_package('symlinked-pkg') == {'symlinked'} + + class PackagesDistributionsEggTest( fixtures.EggInfoPkg, fixtures.EggInfoPkgPipInstalledNoToplevel, From 0023c15d8fe1664184f6e0f80a9fca29fc3d159e Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Wed, 19 Apr 2023 02:41:44 +0200 Subject: [PATCH 05/78] Attempt to fix issue with symlinked packages --- importlib_metadata/__init__.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index e9ae0d19..76ccacbc 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -31,7 +31,7 @@ from importlib import import_module from importlib.abc import MetaPathFinder from itertools import starmap -from typing import List, Mapping, Optional, cast +from typing import Iterable, Iterator, List, Mapping, Optional, cast __all__ = [ @@ -961,10 +961,32 @@ def _top_level_declared(dist): return (dist.read_text('top_level.txt') or '').split() +def _walk_dirs(package_paths: Iterable[PackagePath]) -> Iterator[PackagePath]: + for package_path in package_paths: + + def make_file(name): + result = PackagePath(name) + result.hash = None + result.size = None + result.dist = package_path.dist + return result + + real_path = package_path.locate() + real_sitedir = package_path.dist.locate_file("") # type: ignore + if real_path.is_dir() and real_path.is_symlink(): + # .files only mentions symlink, we must recurse into it ourselves: + for root, dirs, files in os.walk(real_path): + for filename in files: + real_file = pathlib.Path(root, filename) + yield make_file(real_file.relative_to(real_sitedir)) + else: + yield package_path + + def _top_level_inferred(dist): opt_names = { f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f) - for f in always_iterable(dist.files) + for f in _walk_dirs(always_iterable(dist.files)) } @pass_none From a4c314793e0ab96cab1b711b4c4150327642356a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 22 Apr 2023 08:31:42 -0400 Subject: [PATCH 06/78] Remove nitpick_ignore no longer needed. --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 0d6c66ae..6f9deda9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,6 @@ # Workaround for #316 ('py:class', 'importlib_metadata.EntryPoints'), ('py:class', 'importlib_metadata.PackagePath'), - ('py:class', 'importlib_metadata.PathDistribution'), ('py:class', 'importlib_metadata.SelectableGroups'), ('py:class', 'importlib_metadata._meta._T'), # Workaround for #435 From 2e7816256d2b9aadf4299b945cc1b37ada0f367f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 22 Apr 2023 08:33:20 -0400 Subject: [PATCH 07/78] Update changelog --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6ec9d1f3..dbc28038 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,8 @@ +v6.6.0 +====== + +* #449: Expanded type annotations. + v6.3.0 ====== From d2ec0473f8d4c25cc6f696e70ba110e1061e4dfe Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 10 May 2023 20:27:17 -0400 Subject: [PATCH 08/78] Replace flake8 with ruff. Fixes jaraco/skeleton#79 and sheds debt. --- .flake8 | 9 --------- pyproject.toml | 6 +++--- pytest.ini | 8 -------- setup.cfg | 6 +----- 4 files changed, 4 insertions(+), 25 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 48b2e246..00000000 --- a/.flake8 +++ /dev/null @@ -1,9 +0,0 @@ -[flake8] -max-line-length = 88 - -# jaraco/skeleton#34 -max-complexity = 10 - -extend-ignore = - # Black creates whitespace before colon - E203 diff --git a/pyproject.toml b/pyproject.toml index 60de2424..d5f3487e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,8 +13,8 @@ addopts = "--black" [tool.pytest-enabler.mypy] addopts = "--mypy" -[tool.pytest-enabler.flake8] -addopts = "--flake8" - [tool.pytest-enabler.cov] addopts = "--cov" + +[tool.pytest-enabler.ruff] +addopts = "--ruff" diff --git a/pytest.ini b/pytest.ini index 99a25199..94515aaf 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,19 +7,11 @@ filterwarnings= # Ensure ResourceWarnings are emitted default::ResourceWarning - # Suppress deprecation warning in flake8 - ignore:SelectableGroups dict interface is deprecated::flake8 - # shopkeep/pytest-black#55 ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestDeprecationWarning ignore:BlackItem is an Item subclass and should not be a collector:pytest.PytestWarning - # tholo/pytest-flake8#83 - ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning - ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestDeprecationWarning - ignore:Flake8Item is an Item subclass and should not be a collector:pytest.PytestWarning - # shopkeep/pytest-black#67 ignore:'encoding' argument not specified::pytest_black diff --git a/setup.cfg b/setup.cfg index c062c7b9..6b31311e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,11 +30,6 @@ testing = # upstream pytest >= 6 pytest-checkdocs >= 2.4 - pytest-flake8; \ - # workaround for tholo/pytest-flake8#87 - python_version < "3.12" - # workaround for tholo/pytest-flake8#87 - flake8 < 5 pytest-black >= 0.3.7; \ # workaround for jaraco/skeleton#22 python_implementation != "PyPy" @@ -43,6 +38,7 @@ testing = # workaround for jaraco/skeleton#22 python_implementation != "PyPy" pytest-enabler >= 1.3 + pytest-ruff # local From a12a34537aa9566011ad8d9386e5c22d5425e6a5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 10 May 2023 21:27:42 -0400 Subject: [PATCH 09/78] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20?= =?UTF-8?q?(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- importlib_metadata/__init__.py | 1 - tests/test_main.py | 1 - 2 files changed, 2 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 281cfb00..857c9198 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -13,7 +13,6 @@ import functools import itertools import posixpath -import contextlib import collections from . import _adapters, _meta, _py39compat diff --git a/tests/test_main.py b/tests/test_main.py index a7650172..6eefe92b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -5,7 +5,6 @@ import importlib import importlib_metadata import contextlib -import itertools import pyfakefs.fake_filesystem_unittest as ffs from . import fixtures From 96ebfe14538c2279b54dd19567e5922880b4fdf3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 28 May 2023 12:31:51 -0400 Subject: [PATCH 10/78] Make substitution fields more prominent and distinct from true 'skeleton' references. (#71) Fixes #70 --- README.rst | 14 +++++++------- docs/index.rst | 2 +- setup.cfg | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index af0efb05..1f66d195 100644 --- a/README.rst +++ b/README.rst @@ -1,18 +1,18 @@ -.. image:: https://img.shields.io/pypi/v/skeleton.svg - :target: https://pypi.org/project/skeleton +.. image:: https://img.shields.io/pypi/v/PROJECT.svg + :target: https://pypi.org/project/PROJECT -.. image:: https://img.shields.io/pypi/pyversions/skeleton.svg +.. image:: https://img.shields.io/pypi/pyversions/PROJECT.svg -.. image:: https://github.com/jaraco/skeleton/workflows/tests/badge.svg - :target: https://github.com/jaraco/skeleton/actions?query=workflow%3A%22tests%22 +.. image:: https://github.com/PROJECT_PATH/workflows/tests/badge.svg + :target: https://github.com/PROJECT_PATH/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code style: Black -.. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest -.. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest +.. .. image:: https://readthedocs.org/projects/PROJECT_RTD/badge/?version=latest +.. :target: https://PROJECT_RTD.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2023-informational :target: https://blog.jaraco.com/skeleton diff --git a/docs/index.rst b/docs/index.rst index 325842bb..53117d16 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,7 +7,7 @@ Welcome to |project| documentation! history -.. automodule:: skeleton +.. automodule:: PROJECT :members: :undoc-members: :show-inheritance: diff --git a/setup.cfg b/setup.cfg index 6b31311e..0cee3d34 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,10 @@ [metadata] -name = skeleton +name = PROJECT author = Jason R. Coombs author_email = jaraco@jaraco.com -description = skeleton +description = PROJECT_DESCRIPTION long_description = file:README.rst -url = https://github.com/jaraco/skeleton +url = https://github.com/PROJECT_PATH classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers From 4ce054b47df31b4845968043c8772ee4a604390a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 28 May 2023 20:15:16 -0400 Subject: [PATCH 11/78] Suppress EncodingWarning in build.env. Ref pypa/build#615. --- pytest.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pytest.ini b/pytest.ini index 94515aaf..3d30458f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -21,4 +21,7 @@ filterwarnings= # python/cpython#100750 ignore:'encoding' argument not specified::platform + # pypa/build#615 + ignore:'encoding' argument not specified:EncodingWarning:build.env + ## end upstream From a0acaace3e29937d0711b3de8019cd3fe4799cf7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 28 May 2023 20:29:31 -0400 Subject: [PATCH 12/78] Remove reference to EncodingWarning as it doesn't exist on some Pythons. --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 3d30458f..d9a15ed1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -22,6 +22,6 @@ filterwarnings= ignore:'encoding' argument not specified::platform # pypa/build#615 - ignore:'encoding' argument not specified:EncodingWarning:build.env + ignore:'encoding' argument not specified::build.env ## end upstream From 6f754807a0abd25e0b52f024df2072d53f336974 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jun 2023 09:49:46 -0400 Subject: [PATCH 13/78] Update RTD boilerplate to new issue. Ref readthedocs/readthedocs.org#10401. --- .readthedocs.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6bef3493..053c7287 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,9 +5,8 @@ python: extra_requirements: - docs -# workaround for readthedocs/readthedocs.org#9623 +# required boilerplate readthedocs/readthedocs.org#10401 build: - # workaround for readthedocs/readthedocs.org#9635 os: ubuntu-22.04 tools: python: "3" From 70a6075f98e4ac6154a8b3de72dd39073b0d885b Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Wed, 3 May 2023 04:55:22 -0700 Subject: [PATCH 14/78] gh-98040: Backport python/cpython#98059 Removed ``fixtures.NullFinder``. --------- Co-authored-by: Oleg Iarygin Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> --- tests/fixtures.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 6d26bb91..c0b0fa32 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -349,11 +349,6 @@ def DALS(str): return textwrap.dedent(str).lstrip() -class NullFinder: - def find_module(self, name): - pass - - class ZipFixtures: root = 'tests.data' From 7d2c559119dcf2e20a66389590ee77adad6cfb88 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Jun 2023 15:36:46 -0400 Subject: [PATCH 15/78] Inline the NullFinder behavior. --- tests/test_integration.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index c382a506..93981669 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -29,11 +29,14 @@ def is_installed(package_spec): class FinderTests(fixtures.Fixtures, unittest.TestCase): def test_finder_without_module(self): - class ModuleFreeFinder(fixtures.NullFinder): + class ModuleFreeFinder: """ A finder without an __module__ attribute """ + def find_module(self, name): + pass + def __getattribute__(self, name): if name == '__module__': raise AttributeError(name) From d96e7e740dcdd8b28a01f7c573126b7d35873b9e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Jun 2023 15:45:51 -0400 Subject: [PATCH 16/78] Add docstring to test_integration to give some context. --- tests/test_integration.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_integration.py b/tests/test_integration.py index 93981669..7d0c13cc 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,3 +1,12 @@ +""" +Test behaviors specific to importlib_metadata. + +These tests are excluded downstream in CPython as they +test functionality only in importlib_metadata or require +behaviors ('packaging') that aren't available in the +stdlib. +""" + import unittest import packaging.requirements import packaging.version From 50e9f816c1cea50ab19ee58edf077c687af47fe5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Jun 2023 15:46:08 -0400 Subject: [PATCH 17/78] Remove Python 2 compatibility in _compat.NullFinder. --- importlib_metadata/_compat.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/importlib_metadata/_compat.py b/importlib_metadata/_compat.py index 638e7791..b7abd09b 100644 --- a/importlib_metadata/_compat.py +++ b/importlib_metadata/_compat.py @@ -56,14 +56,6 @@ class NullFinder: def find_spec(*args, **kwargs): return None - # In Python 2, the import system requires finders - # to have a find_module() method, but this usage - # is deprecated in Python 3 in favor of find_spec(). - # For the purposes of this finder (i.e. being present - # on sys.meta_path but having no other import - # system functionality), the two methods are identical. - find_module = find_spec - def pypy_partial(val): """ From c10a5aa0d1f53ee318ed91d42afe730ddaaa3732 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Jun 2023 15:53:06 -0400 Subject: [PATCH 18/78] Move test_interleaved_discovery from test_integration to test_main. --- tests/test_integration.py | 14 -------------- tests/test_main.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 7d0c13cc..5258bada 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -15,7 +15,6 @@ from importlib_metadata import ( MetadataPathFinder, _compat, - distributions, version, ) @@ -64,16 +63,3 @@ def test_search_dist_dirs(self): """ res = MetadataPathFinder._search_paths('any-name', []) assert list(res) == [] - - def test_interleaved_discovery(self): - """ - When the search is cached, it is - possible for searches to be interleaved, so make sure - those use-cases are safe. - - Ref #293 - """ - dists = distributions() - next(dists) - version('importlib_metadata') - next(dists) diff --git a/tests/test_main.py b/tests/test_main.py index 6eefe92b..ad007595 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -204,6 +204,20 @@ def test_invalid_usage(self): with self.assertRaises(ValueError): list(distributions(context='something', name='else')) + def test_interleaved_discovery(self): + """ + Ensure interleaved searches are safe. + + When the search is cached, it is possible for searches to be + interleaved, so make sure those use-cases are safe. + + Ref #293 + """ + dists = distributions() + next(dists) + version('egginfo-pkg') + next(dists) + class DirectoryTest(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): def test_egg_info(self): From 4ebe49067b980a932c785ab20f7ade27e879e10b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Jun 2023 15:59:37 -0400 Subject: [PATCH 19/78] Remove test_search_dist_dirs as it was never used. Ref python/importlib_metadata#111. --- tests/test_integration.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 5258bada..f7af67f3 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -13,7 +13,6 @@ from . import fixtures from importlib_metadata import ( - MetadataPathFinder, _compat, version, ) @@ -52,14 +51,3 @@ def __getattribute__(self, name): self.fixtures.enter_context(fixtures.install_finder(ModuleFreeFinder())) _compat.disable_stdlib_finder() - - -class DistSearch(unittest.TestCase): - def test_search_dist_dirs(self): - """ - Pip needs the _search_paths interface to locate - distribution metadata dirs. Protect it for PyPA - use-cases (only). Ref python/importlib_metadata#111. - """ - res = MetadataPathFinder._search_paths('any-name', []) - assert list(res) == [] From e7cd730d0d708c8f1f3eb28a29927f3475b3e855 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Jun 2023 21:27:17 -0400 Subject: [PATCH 20/78] Add badge for Ruff. --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 1f66d195..b703d490 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,10 @@ :target: https://github.com/PROJECT_PATH/actions?query=workflow%3A%22tests%22 :alt: tests +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff + .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code style: Black From 241541c07c9c30e48b57d59e527ef923d05c82d6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 16 Jun 2023 14:11:22 -0400 Subject: [PATCH 21/78] Remove inclusion of python version for docs --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3fa1c81e..93471ce8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,8 +83,6 @@ jobs: - uses: actions/checkout@v3 - name: Setup Python uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }}${{ matrix.dev }} - name: Install tox run: | python -m pip install tox From 851b921c33acf8fcfd9f009cda2bc6176ba26e94 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 16:17:57 -0400 Subject: [PATCH 22/78] Extract _topmost and _get_toplevel_name functions. --- importlib_metadata/__init__.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 857c9198..6f20fb63 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -973,11 +973,34 @@ def _top_level_declared(dist): return (dist.read_text('top_level.txt') or '').split() +def _topmost(name: PackagePath) -> Optional[str]: + """ + Return the top-most parent as long as there is a parent. + """ + top, *rest = name.parts + return top if rest else None + + +def _get_toplevel_name(name: PackagePath) -> str: + """ + Infer a possibly importable module name from a name presumed on + sys.path. + + >>> _get_toplevel_name(PackagePath('foo.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pyc')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo/__init__.py')) + 'foo' + """ + return _topmost(name) or ( + # python/typeshed#10328 + inspect.getmodulename(name) # type: ignore + ) + + def _top_level_inferred(dist): - opt_names = { - f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f) - for f in always_iterable(dist.files) - } + opt_names = set(map(_get_toplevel_name, always_iterable(dist.files))) @pass_none def importable_name(name): From e50ebd77363dd59af06fb0fb1633c5e1e7fa9151 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 16:17:57 -0400 Subject: [PATCH 23/78] Extract _topmost and _get_toplevel_name functions. --- importlib_metadata/__init__.py | 56 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 4bf232ee..37b664f4 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -31,8 +31,7 @@ from importlib import import_module from importlib.abc import MetaPathFinder from itertools import starmap -from typing import Iterable, Iterator, List, Mapping, Optional, Set, cast - +from typing import Iterable, List, Mapping, Optional, Set, cast __all__ = [ 'Distribution', @@ -974,35 +973,42 @@ def _top_level_declared(dist): return (dist.read_text('top_level.txt') or '').split() -def _walk_dirs(package_paths: Iterable[PackagePath]) -> Iterator[PackagePath]: - for package_path in package_paths: +def _topmost(name: PackagePath) -> Optional[str]: + """ + Return the top-most parent as long as there is a parent. + """ + top, *rest = name.parts + return top if rest else None - def make_file(name): - result = PackagePath(name) - result.hash = None - result.size = None - result.dist = package_path.dist - return result - real_path = package_path.locate() - real_sitedir = package_path.dist.locate_file("") # type: ignore - if real_path.is_dir() and real_path.is_symlink(): - # .files only mentions symlink, we must recurse into it ourselves: - for root, dirs, files in os.walk(real_path): - for filename in files: - real_file = pathlib.Path(root, filename) - yield make_file(real_file.relative_to(real_sitedir)) - else: - yield package_path +def _get_toplevel_name(name: PackagePath) -> str: + """ + Infer a possibly importable module name from a name presumed on + sys.path. + + >>> _get_toplevel_name(PackagePath('foo.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pyc')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.dist-info')) + 'foo.dist-info' + >>> _get_toplevel_name(PackagePath('foo.pth')) + 'foo.pth' + >>> _get_toplevel_name(PackagePath('foo/__init__.py')) + 'foo' + """ + return _topmost(name) or ( + # python/typeshed#10328 + inspect.getmodulename(name) # type: ignore + or str(name) + ) def _top_level_inferred(dist): - opt_names = { - f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f) - for f in _walk_dirs(always_iterable(dist.files)) - } + opt_names = set(map(_get_toplevel_name, always_iterable(dist.files))) - @pass_none def importable_name(name): return '.' not in name From 1c4b32878693c41843cd5f04a2c5d2ca8fd86a23 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 16:33:30 -0400 Subject: [PATCH 24/78] Capture that _get_toplevel_name can return None. --- importlib_metadata/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 6f20fb63..68329964 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -981,7 +981,7 @@ def _topmost(name: PackagePath) -> Optional[str]: return top if rest else None -def _get_toplevel_name(name: PackagePath) -> str: +def _get_toplevel_name(name: PackagePath) -> Optional[str]: """ Infer a possibly importable module name from a name presumed on sys.path. @@ -992,6 +992,8 @@ def _get_toplevel_name(name: PackagePath) -> str: 'foo' >>> _get_toplevel_name(PackagePath('foo/__init__.py')) 'foo' + >>> _get_toplevel_name(PackagePath('foo.pth')) + >>> _get_toplevel_name(PackagePath('foo.dist-info')) """ return _topmost(name) or ( # python/typeshed#10328 From 7a5e025f51e88cf49092fb33e2cb5bea82dc7ff5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 16:46:33 -0400 Subject: [PATCH 25/78] Streamline the test to check one expectation (the standard dist-info expectation is handled by other tests above. --- tests/test_main.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 5d3f39c6..5f653f3d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -401,29 +401,15 @@ def test_packages_distributions_all_module_types(self): class PackagesDistributionsDistTest( - fixtures.DistInfoPkg, fixtures.DistInfoSymlinkedPkg, unittest.TestCase, ): - def test_packages_distributions_on_dist_info(self): + def test_packages_distributions_symlinked_top_level(self): """ - Test _top_level_inferred() on various dist-info packages. + Distribution is resolvable from a simple top-level symlink in RECORD. + See #452. """ - distributions = packages_distributions() - - def import_names_from_package(package_name): - return { - import_name - for import_name, package_names in distributions.items() - if package_name in package_names - } - - # distinfo-pkg has one import ('mod') inferred from RECORD - assert import_names_from_package('distinfo-pkg') == {'mod'} - - # symlinked-pkg has one import ('symlinked') inderred from RECORD which - # references a symlink to the real package dir elsewhere. - assert import_names_from_package('symlinked-pkg') == {'symlinked'} + assert packages_distributions()['symlinked'] == ['symlinked-pkg'] class PackagesDistributionsEggTest( From fa705d37265d25581eb9bfd5c1f41ea11c94743b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 16:50:32 -0400 Subject: [PATCH 26/78] Inline the symlink setup. --- tests/fixtures.py | 26 -------------------------- tests/test_main.py | 26 ++++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index e0413cc8..c0b0fa32 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -169,32 +169,6 @@ def setUp(self): build_files(DistInfoPkg.files, self.site_dir) -class DistInfoSymlinkedPkg(OnSysPath, SiteDir): - files: FilesSpec = { - "symlinked_pkg-1.0.0.dist-info": { - "METADATA": """ - Name: symlinked-pkg - Version: 1.0.0 - """, - "RECORD": "symlinked,,\n", - }, - ".symlink.target": { - "__init__.py": """ - def main(): - print("hello world") - """, - }, - # "symlinked" -> ".symlink.target", see below - } - - def setUp(self): - super().setUp() - build_files(DistInfoSymlinkedPkg.files, self.site_dir) - target = self.site_dir / ".symlink.target" - assert target.is_dir() - (self.site_dir / "symlinked").symlink_to(target, target_is_directory=True) - - class EggInfoPkg(OnSysPath, SiteDir): files: FilesSpec = { "egginfo_pkg.egg-info": { diff --git a/tests/test_main.py b/tests/test_main.py index 5f653f3d..0bcb7ac9 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -401,14 +401,36 @@ def test_packages_distributions_all_module_types(self): class PackagesDistributionsDistTest( - fixtures.DistInfoSymlinkedPkg, - unittest.TestCase, + fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase ): def test_packages_distributions_symlinked_top_level(self): """ Distribution is resolvable from a simple top-level symlink in RECORD. See #452. """ + + files: fixtures.FilesSpec = { + "symlinked_pkg-1.0.0.dist-info": { + "METADATA": """ + Name: symlinked-pkg + Version: 1.0.0 + """, + "RECORD": "symlinked,,\n", + }, + ".symlink.target": { + "__init__.py": """ + def main(): + print("hello world") + """, + }, + # "symlinked" -> ".symlink.target", see below + } + + fixtures.build_files(files, self.site_dir) + target = self.site_dir / ".symlink.target" + assert target.is_dir() + (self.site_dir / "symlinked").symlink_to(target, target_is_directory=True) + assert packages_distributions()['symlinked'] == ['symlinked-pkg'] From 7a19e8a4a933f00b12d26719ddb5b474045817ae Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 16:52:51 -0400 Subject: [PATCH 27/78] Consolidate PackageDistributions tests. --- tests/test_main.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 0bcb7ac9..fbf79a62 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -399,10 +399,6 @@ def test_packages_distributions_all_module_types(self): assert not any(name.endswith('.dist-info') for name in distributions) - -class PackagesDistributionsDistTest( - fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase -): def test_packages_distributions_symlinked_top_level(self): """ Distribution is resolvable from a simple top-level symlink in RECORD. From 62144eb57ba48ed6dafc0d7e5694a1b34fe95141 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 17:17:30 -0400 Subject: [PATCH 28/78] Update _path to jaraco.path 3.6 with symlink support. --- tests/_path.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/_path.py b/tests/_path.py index 71a70438..9762c5ec 100644 --- a/tests/_path.py +++ b/tests/_path.py @@ -1,4 +1,4 @@ -# from jaraco.path 3.5 +# from jaraco.path 3.6 import functools import pathlib @@ -11,7 +11,13 @@ from typing_extensions import Protocol, runtime_checkable # type: ignore -FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore +class Symlink(str): + """ + A string indicating the target of a symlink. + """ + + +FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] # type: ignore @runtime_checkable @@ -28,6 +34,9 @@ def write_text(self, content, **kwargs): def write_bytes(self, content): ... # pragma: no cover + def symlink_to(self, target): + ... # pragma: no cover + def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker: return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore @@ -51,12 +60,16 @@ def build( ... "__init__.py": "", ... }, ... "baz.py": "# Some code", - ... } + ... "bar.py": Symlink("baz.py"), + ... }, + ... "bing": Symlink("foo"), ... } >>> target = getfixture('tmp_path') >>> build(spec, target) >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8') '# Some code' + >>> target.joinpath('bing/bar.py').read_text(encoding='utf-8') + '# Some code' """ for name, contents in spec.items(): create(contents, _ensure_tree_maker(prefix) / name) @@ -79,8 +92,8 @@ def _(content: str, path): @create.register -def _(content: str, path): - path.write_text(content, encoding='utf-8') +def _(content: Symlink, path): + path.symlink_to(content) class Recording: @@ -107,3 +120,6 @@ def write_text(self, content, **kwargs): def mkdir(self, **kwargs): return + + def symlink_to(self, target): + pass From d5f723f8ec5623740593011bae63df16be50a1c3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 17:19:51 -0400 Subject: [PATCH 29/78] Utilize the new Symlink in preparing the test case. --- tests/test_main.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index fbf79a62..4543e21d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -9,6 +9,7 @@ from . import fixtures from ._context import suppress +from ._path import Symlink from importlib_metadata import ( Distribution, EntryPoint, @@ -419,14 +420,10 @@ def main(): print("hello world") """, }, - # "symlinked" -> ".symlink.target", see below + "symlinked": Symlink(".symlink.target"), } fixtures.build_files(files, self.site_dir) - target = self.site_dir / ".symlink.target" - assert target.is_dir() - (self.site_dir / "symlinked").symlink_to(target, target_is_directory=True) - assert packages_distributions()['symlinked'] == ['symlinked-pkg'] From e8bc802866861ee32049fd60cf9e4f3e30592f26 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 17:23:58 -0400 Subject: [PATCH 30/78] Update changelog. --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c7e5889c..255bbd32 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v6.7.0 +====== + +* #453: When inferring top-level names that are importable for + distributions in ``package_distributions``, now symlinks to + other directories are honored. + v6.6.0 ====== From 53e47d9ae8e7784da051e264376bdb7221a45871 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 17:31:33 -0400 Subject: [PATCH 31/78] Remove '__init__.py', not needed. --- tests/test_main.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 4543e21d..79181bf4 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -414,12 +414,7 @@ def test_packages_distributions_symlinked_top_level(self): """, "RECORD": "symlinked,,\n", }, - ".symlink.target": { - "__init__.py": """ - def main(): - print("hello world") - """, - }, + ".symlink.target": {}, "symlinked": Symlink(".symlink.target"), } From 74b0d396c87892e9122c96994cf2c26329141208 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 19 Jun 2023 21:11:01 -0400 Subject: [PATCH 32/78] Adopt towncrier for managing changelog. Fixes jaraco/skeleton#83. Renamed CHANGES.rst to NEWS.rst to align with towncrier defaults. --- CHANGES.rst => NEWS.rst | 0 docs/conf.py | 2 +- docs/history.rst | 2 +- towncrier.toml | 2 ++ tox.ini | 9 +++++++++ 5 files changed, 13 insertions(+), 2 deletions(-) rename CHANGES.rst => NEWS.rst (100%) create mode 100644 towncrier.toml diff --git a/CHANGES.rst b/NEWS.rst similarity index 100% rename from CHANGES.rst rename to NEWS.rst diff --git a/docs/conf.py b/docs/conf.py index c2043393..32150488 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,7 @@ # Link dates and other references in the changelog extensions += ['rst.linker'] link_files = { - '../CHANGES.rst': dict( + '../NEWS.rst': dict( using=dict(GH='https://github.com'), replace=[ dict( diff --git a/docs/history.rst b/docs/history.rst index 8e217503..5bdc2320 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -5,4 +5,4 @@ History ******* -.. include:: ../CHANGES (links).rst +.. include:: ../NEWS (links).rst diff --git a/towncrier.toml b/towncrier.toml new file mode 100644 index 00000000..6fa480e4 --- /dev/null +++ b/towncrier.toml @@ -0,0 +1,2 @@ +[tool.towncrier] +title_format = "{version}" diff --git a/tox.ini b/tox.ini index 5a678211..32b031da 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,15 @@ commands = python -m sphinx -W --keep-going . {toxinidir}/build/html python -m sphinxlint +[testenv:finalize] +skip_install = True +deps = + towncrier + jaraco.develop +passenv = * +commands = + python -m jaraco.develop.towncrier build --yes + [testenv:release] skip_install = True deps = From 45c03bdc458c06d4f48aa997398a7810c0cd8425 Mon Sep 17 00:00:00 2001 From: Gryfenfer97 Date: Wed, 21 Jun 2023 11:19:19 +0200 Subject: [PATCH 33/78] add typing for simple functions --- importlib_metadata/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 8147d2f0..6ba414e5 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -873,7 +873,7 @@ def _name_from_stem(stem): return name -def distribution(distribution_name) -> Distribution: +def distribution(distribution_name: str) -> Distribution: """Get the ``Distribution`` instance for the named package. :param distribution_name: The name of the distribution package as a string. @@ -890,7 +890,7 @@ def distributions(**kwargs) -> Iterable[Distribution]: return Distribution.discover(**kwargs) -def metadata(distribution_name) -> _meta.PackageMetadata: +def metadata(distribution_name: str) -> _meta.PackageMetadata: """Get the metadata for the named package. :param distribution_name: The name of the distribution package to query. @@ -899,7 +899,7 @@ def metadata(distribution_name) -> _meta.PackageMetadata: return Distribution.from_name(distribution_name).metadata -def version(distribution_name) -> str: +def version(distribution_name: str) -> str: """Get the version string for the named package. :param distribution_name: The name of the distribution package to query. @@ -933,7 +933,7 @@ def entry_points(**params) -> EntryPoints: return EntryPoints(eps).select(**params) -def files(distribution_name) -> Optional[List[PackagePath]]: +def files(distribution_name: str) -> Optional[List[PackagePath]]: """Return a list of files for the named package. :param distribution_name: The name of the distribution package to query. @@ -942,7 +942,7 @@ def files(distribution_name) -> Optional[List[PackagePath]]: return distribution(distribution_name).files -def requires(distribution_name) -> Optional[List[str]]: +def requires(distribution_name: str) -> Optional[List[str]]: """ Return a list of requirements for the named package. From cd145f4080ef0e954aa4716fc3f240c508a5693c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 22 Jun 2023 23:34:21 -0400 Subject: [PATCH 34/78] Replace workaround for actions/setup-python#508 with 'allow-prereleases' --- .github/workflows/main.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93471ce8..00b21297 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -45,9 +45,6 @@ jobs: - "3.7" - "3.11" - "3.12" - # Workaround for actions/setup-python#508 - dev: - - -dev platform: - ubuntu-latest - macos-latest @@ -68,7 +65,8 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python }}${{ matrix.dev }} + python-version: ${{ matrix.python }} + allow-prereleases: true - name: Install tox run: | python -m pip install tox From 07a87ea9d8671ea4f529858201866e3f78fa3afc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Jun 2023 12:59:50 -0400 Subject: [PATCH 35/78] Remove tox boilerplate, no longer necessary with later versions of tox. --- tox.ini | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tox.ini b/tox.ini index 32b031da..4e8e7090 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,4 @@ [tox] -envlist = python -minversion = 3.2 -# https://github.com/jaraco/skeleton/issues/6 -tox_pip_extensions_ext_venv_update = true toxworkdir={env:TOX_WORK_DIR:.tox} From 3b7d8a912d54ccf88f79eea0dfc903d101067bb5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jun 2023 20:55:42 -0400 Subject: [PATCH 36/78] Require Python 3.8 or later. --- .github/workflows/main.yml | 4 +--- newsfragments/+drop-py37.feature.rst | 1 + setup.cfg | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 newsfragments/+drop-py37.feature.rst diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 00b21297..7cc4fb82 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: strategy: matrix: python: - - "3.7" + - "3.8" - "3.11" - "3.12" platform: @@ -50,8 +50,6 @@ jobs: - macos-latest - windows-latest include: - - python: "3.8" - platform: ubuntu-latest - python: "3.9" platform: ubuntu-latest - python: "3.10" diff --git a/newsfragments/+drop-py37.feature.rst b/newsfragments/+drop-py37.feature.rst new file mode 100644 index 00000000..ccabdaa3 --- /dev/null +++ b/newsfragments/+drop-py37.feature.rst @@ -0,0 +1 @@ +Require Python 3.8 or later. diff --git a/setup.cfg b/setup.cfg index 0cee3d34..75a50d4d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,7 @@ classifiers = [options] packages = find_namespace: include_package_data = true -python_requires = >=3.7 +python_requires = >=3.8 install_requires = [options.packages.find] From 8e83c3f0bc7baab5f2db37487526e374a1f68494 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Jun 2023 19:52:23 -0400 Subject: [PATCH 37/78] Expand 'finalize' to commit and tag the change. --- tox.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 4e8e7090..1093e028 100644 --- a/tox.ini +++ b/tox.ini @@ -25,10 +25,11 @@ commands = skip_install = True deps = towncrier - jaraco.develop + jaraco.develop >= 7.23 passenv = * commands = - python -m jaraco.develop.towncrier build --yes + python -m jaraco.develop.finalize + [testenv:release] skip_install = True From 74ba8acbe019de9f30dee6d319c8621caac070ae Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 25 Jun 2023 22:23:40 -0400 Subject: [PATCH 38/78] Leverage pytest-enabler 2.2 for the default config. --- pyproject.toml | 12 ------------ setup.cfg | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d5f3487e..dce944df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,15 +6,3 @@ build-backend = "setuptools.build_meta" skip-string-normalization = true [tool.setuptools_scm] - -[tool.pytest-enabler.black] -addopts = "--black" - -[tool.pytest-enabler.mypy] -addopts = "--mypy" - -[tool.pytest-enabler.cov] -addopts = "--cov" - -[tool.pytest-enabler.ruff] -addopts = "--ruff" diff --git a/setup.cfg b/setup.cfg index 75a50d4d..a9ca2a88 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,7 +37,7 @@ testing = pytest-mypy >= 0.9.1; \ # workaround for jaraco/skeleton#22 python_implementation != "PyPy" - pytest-enabler >= 1.3 + pytest-enabler >= 2.2 pytest-ruff # local From cca49a4481167049f6bdd0f8038e685e5b8e929f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 3 Jul 2023 15:18:58 -0400 Subject: [PATCH 39/78] Prefer 3.x for Python version (latest stable). --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7cc4fb82..f54dfbc6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -113,7 +113,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.11-dev + python-version: 3.x - name: Install tox run: | python -m pip install tox From c29955f9be8e44b2ea5fea12f86b7bd46a0b3958 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Jul 2023 11:53:15 -0400 Subject: [PATCH 40/78] Collapse skeleton history. Workaround for jaraco/skeleton#87. --- .coveragerc | 9 ++ .editorconfig | 19 ++++ .github/dependabot.yml | 8 ++ .github/workflows/main.yml | 124 +++++++++++++++++++++++++++ .pre-commit-config.yaml | 5 ++ .readthedocs.yaml | 12 +++ LICENSE | 17 ++++ NEWS.rst | 0 README.rst | 22 +++++ docs/conf.py | 42 +++++++++ docs/history.rst | 8 ++ docs/index.rst | 22 +++++ mypy.ini | 5 ++ newsfragments/+drop-py37.feature.rst | 1 + pyproject.toml | 8 ++ pytest.ini | 27 ++++++ setup.cfg | 55 ++++++++++++ towncrier.toml | 2 + tox.ini | 49 +++++++++++ 19 files changed, 435 insertions(+) create mode 100644 .coveragerc create mode 100644 .editorconfig create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/main.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yaml create mode 100644 LICENSE create mode 100644 NEWS.rst create mode 100644 README.rst create mode 100644 docs/conf.py create mode 100644 docs/history.rst create mode 100644 docs/index.rst create mode 100644 mypy.ini create mode 100644 newsfragments/+drop-py37.feature.rst create mode 100644 pyproject.toml create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 towncrier.toml create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..02879483 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,9 @@ +[run] +omit = + # leading `*/` for pytest-dev/pytest-cov#456 + */.tox/* +disable_warnings = + couldnt-parse + +[report] +show_missing = True diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..304196f8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +insert_final_newline = true +end_of_line = lf + +[*.py] +indent_style = space +max_line_length = 88 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.rst] +indent_style = space diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..89ff3396 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "all" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..f54dfbc6 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,124 @@ +name: tests + +on: [push, pull_request] + +permissions: + contents: read + +env: + # Environment variables to support color support (jaraco/skeleton#66): + # Request colored output from CLI tools supporting it. Different tools + # interpret the value differently. For some, just being set is sufficient. + # For others, it must be a non-zero integer. For yet others, being set + # to a non-empty value is sufficient. For tox, it must be one of + # , 0, 1, false, no, off, on, true, yes. The only enabling value + # in common is "1". + FORCE_COLOR: 1 + # MyPy's color enforcement (must be a non-zero number) + MYPY_FORCE_COLOR: -42 + # Recognized by the `py` package, dependency of `pytest` (must be "1") + PY_COLORS: 1 + # Make tox-wrapped tools see color requests + TOX_TESTENV_PASSENV: >- + FORCE_COLOR + MYPY_FORCE_COLOR + NO_COLOR + PY_COLORS + PYTEST_THEME + PYTEST_THEME_MODE + + # Suppress noisy pip warnings + PIP_DISABLE_PIP_VERSION_CHECK: 'true' + PIP_NO_PYTHON_VERSION_WARNING: 'true' + PIP_NO_WARN_SCRIPT_LOCATION: 'true' + + # Disable the spinner, noise in GHA; TODO(webknjaz): Fix this upstream + # Must be "1". + TOX_PARALLEL_NO_SPINNER: 1 + + +jobs: + test: + strategy: + matrix: + python: + - "3.8" + - "3.11" + - "3.12" + platform: + - ubuntu-latest + - macos-latest + - windows-latest + include: + - python: "3.9" + platform: ubuntu-latest + - python: "3.10" + platform: ubuntu-latest + - python: pypy3.9 + platform: ubuntu-latest + runs-on: ${{ matrix.platform }} + continue-on-error: ${{ matrix.python == '3.12' }} + steps: + - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + allow-prereleases: true + - name: Install tox + run: | + python -m pip install tox + - name: Run tests + run: tox + + docs: + runs-on: ubuntu-latest + env: + TOXENV: docs + steps: + - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + - name: Install tox + run: | + python -m pip install tox + - name: Run tests + run: tox + + check: # This job does nothing and is only used for the branch protection + if: always() + + needs: + - test + - docs + + runs-on: ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + + release: + permissions: + contents: write + needs: + - check + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.x + - name: Install tox + run: | + python -m pip install tox + - name: Release + run: tox -e release + env: + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..af502010 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: +- repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..053c7287 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,12 @@ +version: 2 +python: + install: + - path: . + extra_requirements: + - docs + +# required boilerplate readthedocs/readthedocs.org#10401 +build: + os: ubuntu-22.04 + tools: + python: "3" diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1bb5a443 --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/NEWS.rst b/NEWS.rst new file mode 100644 index 00000000..e69de29b diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..b703d490 --- /dev/null +++ b/README.rst @@ -0,0 +1,22 @@ +.. image:: https://img.shields.io/pypi/v/PROJECT.svg + :target: https://pypi.org/project/PROJECT + +.. image:: https://img.shields.io/pypi/pyversions/PROJECT.svg + +.. image:: https://github.com/PROJECT_PATH/workflows/tests/badge.svg + :target: https://github.com/PROJECT_PATH/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. .. image:: https://readthedocs.org/projects/PROJECT_RTD/badge/?version=latest +.. :target: https://PROJECT_RTD.readthedocs.io/en/latest/?badge=latest + +.. image:: https://img.shields.io/badge/skeleton-2023-informational + :target: https://blog.jaraco.com/skeleton diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..32150488 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,42 @@ +extensions = [ + 'sphinx.ext.autodoc', + 'jaraco.packaging.sphinx', +] + +master_doc = "index" +html_theme = "furo" + +# Link dates and other references in the changelog +extensions += ['rst.linker'] +link_files = { + '../NEWS.rst': dict( + using=dict(GH='https://github.com'), + replace=[ + dict( + pattern=r'(Issue #|\B#)(?P\d+)', + url='{package_url}/issues/{issue}', + ), + dict( + pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', + with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', + ), + dict( + pattern=r'PEP[- ](?P\d+)', + url='https://peps.python.org/pep-{pep_number:0>4}/', + ), + ], + ) +} + +# Be strict about any broken references +nitpicky = True + +# Include Python intersphinx mapping to prevent failures +# jaraco/skeleton#51 +extensions += ['sphinx.ext.intersphinx'] +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), +} + +# Preserve authored syntax for defaults +autodoc_preserve_defaults = True diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 00000000..5bdc2320 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,8 @@ +:tocdepth: 2 + +.. _changes: + +History +******* + +.. include:: ../NEWS (links).rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..53117d16 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +Welcome to |project| documentation! +=================================== + +.. toctree:: + :maxdepth: 1 + + history + + +.. automodule:: PROJECT + :members: + :undoc-members: + :show-inheritance: + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..b6f97276 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +ignore_missing_imports = True +# required to support namespace packages +# https://github.com/python/mypy/issues/14057 +explicit_package_bases = True diff --git a/newsfragments/+drop-py37.feature.rst b/newsfragments/+drop-py37.feature.rst new file mode 100644 index 00000000..ccabdaa3 --- /dev/null +++ b/newsfragments/+drop-py37.feature.rst @@ -0,0 +1 @@ +Require Python 3.8 or later. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..dce944df --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = ["setuptools>=56", "setuptools_scm[toml]>=3.4.1"] +build-backend = "setuptools.build_meta" + +[tool.black] +skip-string-normalization = true + +[tool.setuptools_scm] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..d9a15ed1 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,27 @@ +[pytest] +norecursedirs=dist build .tox .eggs +addopts=--doctest-modules +filterwarnings= + ## upstream + + # Ensure ResourceWarnings are emitted + default::ResourceWarning + + # shopkeep/pytest-black#55 + ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning + ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestDeprecationWarning + ignore:BlackItem is an Item subclass and should not be a collector:pytest.PytestWarning + + # shopkeep/pytest-black#67 + ignore:'encoding' argument not specified::pytest_black + + # realpython/pytest-mypy#152 + ignore:'encoding' argument not specified::pytest_mypy + + # python/cpython#100750 + ignore:'encoding' argument not specified::platform + + # pypa/build#615 + ignore:'encoding' argument not specified::build.env + + ## end upstream diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..a9ca2a88 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,55 @@ +[metadata] +name = PROJECT +author = Jason R. Coombs +author_email = jaraco@jaraco.com +description = PROJECT_DESCRIPTION +long_description = file:README.rst +url = https://github.com/PROJECT_PATH +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + +[options] +packages = find_namespace: +include_package_data = true +python_requires = >=3.8 +install_requires = + +[options.packages.find] +exclude = + build* + dist* + docs* + tests* + +[options.extras_require] +testing = + # upstream + pytest >= 6 + pytest-checkdocs >= 2.4 + pytest-black >= 0.3.7; \ + # workaround for jaraco/skeleton#22 + python_implementation != "PyPy" + pytest-cov + pytest-mypy >= 0.9.1; \ + # workaround for jaraco/skeleton#22 + python_implementation != "PyPy" + pytest-enabler >= 2.2 + pytest-ruff + + # local + +docs = + # upstream + sphinx >= 3.5 + jaraco.packaging >= 9 + rst.linker >= 1.9 + furo + sphinx-lint + + # local + +[options.entry_points] diff --git a/towncrier.toml b/towncrier.toml new file mode 100644 index 00000000..6fa480e4 --- /dev/null +++ b/towncrier.toml @@ -0,0 +1,2 @@ +[tool.towncrier] +title_format = "{version}" diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..1093e028 --- /dev/null +++ b/tox.ini @@ -0,0 +1,49 @@ +[tox] +toxworkdir={env:TOX_WORK_DIR:.tox} + + +[testenv] +deps = +setenv = + PYTHONWARNDEFAULTENCODING = 1 +commands = + pytest {posargs} +usedevelop = True +extras = + testing + +[testenv:docs] +extras = + docs + testing +changedir = docs +commands = + python -m sphinx -W --keep-going . {toxinidir}/build/html + python -m sphinxlint + +[testenv:finalize] +skip_install = True +deps = + towncrier + jaraco.develop >= 7.23 +passenv = * +commands = + python -m jaraco.develop.finalize + + +[testenv:release] +skip_install = True +deps = + build + twine>=3 + jaraco.develop>=7.1 +passenv = + TWINE_PASSWORD + GITHUB_TOKEN +setenv = + TWINE_USERNAME = {env:TWINE_USERNAME:__token__} +commands = + python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" + python -m build + python -m twine upload dist/* + python -m jaraco.develop.create-github-release From 972d1b3033afba89ffa20e6c492c4d02742e8a9d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 5 Jul 2023 22:20:28 -0400 Subject: [PATCH 41/78] Add links to project home page and pypi. Fixes jaraco/skeleton#77. --- docs/index.rst | 4 ++++ setup.cfg | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 53117d16..5a3c6770 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,10 @@ Welcome to |project| documentation! =================================== +.. sidebar-links:: + :home: + :pypi: + .. toctree:: :maxdepth: 1 diff --git a/setup.cfg b/setup.cfg index a9ca2a88..46f7bdf7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ testing = docs = # upstream sphinx >= 3.5 - jaraco.packaging >= 9 + jaraco.packaging >= 9.3 rst.linker >= 1.9 furo sphinx-lint From 747c2a36524f83b84a3d9497121313bb5751b877 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 6 Jul 2023 08:59:49 -0400 Subject: [PATCH 42/78] Replace redundant step names with simple 'Run'. --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f54dfbc6..b8224099 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -68,7 +68,7 @@ jobs: - name: Install tox run: | python -m pip install tox - - name: Run tests + - name: Run run: tox docs: @@ -82,7 +82,7 @@ jobs: - name: Install tox run: | python -m pip install tox - - name: Run tests + - name: Run run: tox check: # This job does nothing and is only used for the branch protection @@ -117,7 +117,7 @@ jobs: - name: Install tox run: | python -m pip install tox - - name: Release + - name: Run run: tox -e release env: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} From ee4f84ace3bb6914f155d67aa4811e309f90b836 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Jul 2023 10:06:59 -0400 Subject: [PATCH 43/78] Remove superfluous includes --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 303f5864..783bde8e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,10 +56,6 @@ jobs: platform: ubuntu-latest - python: pypy3.9 platform: ubuntu-latest - - platform: ubuntu-latest - python: "3.8" - - platform: ubuntu-latest - python: "3.9" runs-on: ${{ matrix.platform }} continue-on-error: ${{ matrix.python == '3.12' }} steps: From ec7bca0243886bdf25ec6f6d9f060b71d76ef4b5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Jul 2023 10:07:52 -0400 Subject: [PATCH 44/78] Disable tests on pypy due to #463 --- .github/workflows/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 783bde8e..f7ddc25c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,8 +54,9 @@ jobs: platform: ubuntu-latest - python: "3.10" platform: ubuntu-latest - - python: pypy3.9 - platform: ubuntu-latest + # disabled for #463 + # - python: pypy3.9 + # platform: ubuntu-latest runs-on: ${{ matrix.platform }} continue-on-error: ${{ matrix.python == '3.12' }} steps: From 97084d87cab99d09555fdff2cf04ed9f3faaa913 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Jul 2023 11:47:54 -0400 Subject: [PATCH 45/78] Remove reliance on typing-extensions, only required for Python 3.7 compatibility. --- importlib_metadata/_compat.py | 9 +-------- importlib_metadata/_meta.py | 2 +- tests/_path.py | 11 +++-------- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/importlib_metadata/_compat.py b/importlib_metadata/_compat.py index b7abd09b..c0f15c78 100644 --- a/importlib_metadata/_compat.py +++ b/importlib_metadata/_compat.py @@ -5,14 +5,7 @@ from typing import Union -__all__ = ['install', 'NullFinder', 'Protocol'] - - -try: - from typing import Protocol -except ImportError: # pragma: no cover - # Python 3.7 compatibility - from typing_extensions import Protocol # type: ignore +__all__ = ['install', 'NullFinder'] def install(cls): diff --git a/importlib_metadata/_meta.py b/importlib_metadata/_meta.py index 0c7e8791..f670016d 100644 --- a/importlib_metadata/_meta.py +++ b/importlib_metadata/_meta.py @@ -1,4 +1,4 @@ -from ._compat import Protocol +from typing import Protocol from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload diff --git a/tests/_path.py b/tests/_path.py index 9762c5ec..25c799fa 100644 --- a/tests/_path.py +++ b/tests/_path.py @@ -1,14 +1,9 @@ -# from jaraco.path 3.6 +# from jaraco.path 3.7 import functools import pathlib -from typing import Dict, Union - -try: - from typing import Protocol, runtime_checkable -except ImportError: # pragma: no cover - # Python 3.7 - from typing_extensions import Protocol, runtime_checkable # type: ignore +from typing import Dict, Protocol, Union +from typing import runtime_checkable class Symlink(str): From 34fd7365cad782511b7c824f65c054d654bb066c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Jul 2023 12:07:03 -0400 Subject: [PATCH 46/78] Finalize --- NEWS.rst | 9 +++++++++ newsfragments/+drop-py37.feature.rst | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 newsfragments/+drop-py37.feature.rst diff --git a/NEWS.rst b/NEWS.rst index 255bbd32..18166afb 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,12 @@ +v6.8.0 +====== + +Features +-------- + +- Require Python 3.8 or later. + + v6.7.0 ====== diff --git a/newsfragments/+drop-py37.feature.rst b/newsfragments/+drop-py37.feature.rst deleted file mode 100644 index ccabdaa3..00000000 --- a/newsfragments/+drop-py37.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Require Python 3.8 or later. From a7310562fad7a8834c9810c1edd8e00b03e1394b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Jul 2023 21:48:23 -0400 Subject: [PATCH 47/78] Increase visibility of security policy. (#4) * Create SECURITY.md Signed-off-by: Joyce * Remove the security contact from the README, as it's now redundant. Closes jaraco/tidelift#3. --------- Signed-off-by: Joyce Co-authored-by: Joyce --- README.rst | 7 ------- SECURITY.md | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 SECURITY.md diff --git a/README.rst b/README.rst index 7b317c71..087365cd 100644 --- a/README.rst +++ b/README.rst @@ -9,10 +9,3 @@ Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. - -Security Contact -================ - -To report a security vulnerability, please use the -`Tidelift security contact `_. -Tidelift will coordinate the fix and disclosure. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..54f99acb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Contact + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. From c43962adf34c28c22573093419e5e98b2e57cc07 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 10 Jul 2023 23:34:53 -0400 Subject: [PATCH 48/78] Remove TOX_WORK_DIR workaround, no longer necessary with tox 4. Ref tox-dev/tox#3050. --- tox.ini | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tox.ini b/tox.ini index 1093e028..e51d652d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,3 @@ -[tox] -toxworkdir={env:TOX_WORK_DIR:.tox} - - [testenv] deps = setenv = From ae9ca4393295d961fe6c1b0f1cbc957f07ab4afd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Jun 2023 16:47:44 -0400 Subject: [PATCH 49/78] Add origin property. Ref #404. --- importlib_metadata/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 6ba414e5..79a5b17b 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -3,8 +3,10 @@ import abc import csv import sys +import json import zipp import email +import types import inspect import pathlib import operator @@ -618,6 +620,16 @@ def url_req_space(req): space = url_req_space(section.value) yield section.value + space + quoted_marker(section.name) + @property + def origin(self): + return self._load_json('direct_url.json') + + def _load_json(self, filename): + return pass_none(json.loads)( + self.read_text(filename), + object_hook=lambda data: types.SimpleNamespace(**data), + ) + class DistributionFinder(MetaPathFinder): """ From 0e2032c4754c598ba75e467c64009ba4490ddea9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 31 Aug 2023 18:42:14 -0400 Subject: [PATCH 50/78] Pin against sphinx 7.2.5 as workaround for sphinx/sphinx-doc#11662. Closes jaraco/skeleton#88. --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 46f7bdf7..4f184c7e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,6 +45,8 @@ testing = docs = # upstream sphinx >= 3.5 + # workaround for sphinx/sphinx-doc#11662 + sphinx < 7.2.5 jaraco.packaging >= 9.3 rst.linker >= 1.9 furo From 92d2d8e1aff997f3877239230c9490ed9cdd1222 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Sep 2023 18:46:27 -0400 Subject: [PATCH 51/78] Allow GITHUB_* settings to pass through to tests. --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8224099..67d9d3bc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,6 +36,10 @@ env: # Must be "1". TOX_PARALLEL_NO_SPINNER: 1 + # Ensure tests can sense settings about the environment + TOX_OVERRIDE: >- + testenv.pass_env+=GITHUB_* + jobs: test: From f3dc1f4776c94a9a4a7c0e8c5b49c532b0a7d411 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Sep 2023 18:49:13 -0400 Subject: [PATCH 52/78] Remove spinner disablement. If it's not already fixed upstream, that's where it should be fixed. --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 67d9d3bc..30c9615d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,10 +32,6 @@ env: PIP_NO_PYTHON_VERSION_WARNING: 'true' PIP_NO_WARN_SCRIPT_LOCATION: 'true' - # Disable the spinner, noise in GHA; TODO(webknjaz): Fix this upstream - # Must be "1". - TOX_PARALLEL_NO_SPINNER: 1 - # Ensure tests can sense settings about the environment TOX_OVERRIDE: >- testenv.pass_env+=GITHUB_* From 0484daa8a6f72c9ad4e1784f9181c2488a191d8e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Sep 2023 18:53:55 -0400 Subject: [PATCH 53/78] Clean up 'color' environment variables. The TOX_TESTENV_PASSENV hasn't been useful for some time and by its mere presence wasted a lot of time today under the assumption that it's doing something. Instead, just rely on one variable FORCE_COLOR. If it's not honored, then that should be the fix upstream. --- .github/workflows/main.yml | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 30c9615d..f3028549 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,26 +6,8 @@ permissions: contents: read env: - # Environment variables to support color support (jaraco/skeleton#66): - # Request colored output from CLI tools supporting it. Different tools - # interpret the value differently. For some, just being set is sufficient. - # For others, it must be a non-zero integer. For yet others, being set - # to a non-empty value is sufficient. For tox, it must be one of - # , 0, 1, false, no, off, on, true, yes. The only enabling value - # in common is "1". + # Environment variable to support color support (jaraco/skeleton#66) FORCE_COLOR: 1 - # MyPy's color enforcement (must be a non-zero number) - MYPY_FORCE_COLOR: -42 - # Recognized by the `py` package, dependency of `pytest` (must be "1") - PY_COLORS: 1 - # Make tox-wrapped tools see color requests - TOX_TESTENV_PASSENV: >- - FORCE_COLOR - MYPY_FORCE_COLOR - NO_COLOR - PY_COLORS - PYTEST_THEME - PYTEST_THEME_MODE # Suppress noisy pip warnings PIP_DISABLE_PIP_VERSION_CHECK: 'true' From b02bf32bae729d53bdb7c9649d6ec36afdb793ee Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 10 Sep 2023 13:27:03 -0400 Subject: [PATCH 54/78] Add diff-cover check to Github Actions CI. Closes jaraco/skeleton#90. --- .github/workflows/main.yml | 18 ++++++++++++++++++ tox.ini | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f3028549..fa326a26 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,6 +53,24 @@ jobs: - name: Run run: tox + diffcov: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.x + - name: Install tox + run: | + python -m pip install tox + - name: Evaluate coverage + run: tox + env: + TOXENV: diffcov + docs: runs-on: ubuntu-latest env: diff --git a/tox.ini b/tox.ini index e51d652d..3b4414b4 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,14 @@ usedevelop = True extras = testing +[testenv:diffcov] +deps = + diff-cover +commands = + pytest {posargs} --cov-report xml + diff-cover coverage.xml --compare-branch=origin/main --html-report diffcov.html + diff-cover coverage.xml --compare-branch=origin/main --fail-under=100 + [testenv:docs] extras = docs From a6256e2935468b72a61aa7fda1e036faef3bfb3d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 10 Sep 2023 13:59:47 -0400 Subject: [PATCH 55/78] Add descriptions to the tox environments. Closes jaraco/skeleton#91. --- tox.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tox.ini b/tox.ini index 3b4414b4..1950b4ef 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,5 @@ [testenv] +description = perform primary checks (tests, style, types, coverage) deps = setenv = PYTHONWARNDEFAULTENCODING = 1 @@ -9,6 +10,7 @@ extras = testing [testenv:diffcov] +description = run tests and check that diff from main is covered deps = diff-cover commands = @@ -17,6 +19,7 @@ commands = diff-cover coverage.xml --compare-branch=origin/main --fail-under=100 [testenv:docs] +description = build the documentation extras = docs testing @@ -26,6 +29,7 @@ commands = python -m sphinxlint [testenv:finalize] +description = assemble changelog and tag a release skip_install = True deps = towncrier @@ -36,6 +40,7 @@ commands = [testenv:release] +description = publish the package to PyPI and GitHub skip_install = True deps = build From 928e9a86d61d3a660948bcba7689f90216cc8243 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 10 Sep 2023 14:10:31 -0400 Subject: [PATCH 56/78] Add FORCE_COLOR to the TOX_OVERRIDE for GHA. Requires tox 4.11.1. Closes jaraco/skeleton#89. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa326a26..28e36786 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ env: # Ensure tests can sense settings about the environment TOX_OVERRIDE: >- - testenv.pass_env+=GITHUB_* + testenv.pass_env+=GITHUB_*,FORCE_COLOR jobs: From ca1831c2148fe5ddbffd001de76ff5f6005f812c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 18 Sep 2023 11:05:36 -0400 Subject: [PATCH 57/78] Prefer ``pass_env`` in tox config. Preferred failure mode for tox-dev/tox#3127 and closes jaraco/skeleton#92. --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 1950b4ef..33da3deb 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ skip_install = True deps = towncrier jaraco.develop >= 7.23 -passenv = * +pass_env = * commands = python -m jaraco.develop.finalize @@ -46,7 +46,7 @@ deps = build twine>=3 jaraco.develop>=7.1 -passenv = +pass_env = TWINE_PASSWORD GITHUB_TOKEN setenv = From 9d6d8b5ee689769cf5a36475d4af3e9f65e3fca6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 21 Sep 2023 06:13:21 -0400 Subject: [PATCH 58/78] Update changelog to reflect backward-incompatible effect. Ref #459. --- NEWS.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 18166afb..92cc0da8 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -131,6 +131,10 @@ v4.11.4 duplicate entry points by packages varying only by non-normalized name are hidden. +Note (#459): This change had a backward-incompatible effect for +any installers that created metadata in the filesystem with dashes +in the package names (not replaced by underscores). + v4.11.3 ======= From 066e24d79994c69424ecdb51f7048d6e466fc392 Mon Sep 17 00:00:00 2001 From: Amund Hov Date: Wed, 11 Oct 2023 14:45:35 +0200 Subject: [PATCH 59/78] With commit 0c819641d314ac496eb32b55f2b15215fa6fa55f the behavior of Entrypoints gets a bit disorienting. Change __repr__ to reflect that Entrpoints is are longer indexable by integers like tuples, but signal our custom behavior. --- importlib_metadata/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 6ba414e5..bc8714b8 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -293,6 +293,14 @@ def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] except StopIteration: raise KeyError(name) + def __repr__(self): + """ + Repr with classname and tuple constructor to + signal that we deviate from regular tuple behavior. + """ + return '%s(%r)' % (self.__class__.__name__, tuple(self)) + + def select(self, **params): """ Select entry points from self that match the From 03f03e7802b0842b41f70b2b1c17ab26551a7533 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 5 Nov 2023 09:43:46 -0500 Subject: [PATCH 60/78] Limit sphinxlint jobs to 1. Workaround for sphinx-contrib/sphinx-lint#83. --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 33da3deb..331eeed9 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,9 @@ extras = changedir = docs commands = python -m sphinx -W --keep-going . {toxinidir}/build/html - python -m sphinxlint + python -m sphinxlint \ + # workaround for sphinx-contrib/sphinx-lint#83 + --jobs 1 [testenv:finalize] description = assemble changelog and tag a release From 75d9cc1b7cb6f84e7a16a83ec3abb9a478fdb130 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 15 Nov 2023 19:57:45 +0600 Subject: [PATCH 61/78] Upgrade GitHub Actions checkout (jaraco/skeleton#94) Also, upgrade from `pypy3.9` to `pypy3.10` and remove the `continue-on-error` for Python 3.12. As recommended at jaraco/cssutils#41 --- .github/workflows/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 28e36786..10828667 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,12 +36,12 @@ jobs: platform: ubuntu-latest - python: "3.10" platform: ubuntu-latest - - python: pypy3.9 + - python: pypy3.10 platform: ubuntu-latest runs-on: ${{ matrix.platform }} - continue-on-error: ${{ matrix.python == '3.12' }} + continue-on-error: ${{ matrix.python == '3.13' }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: @@ -56,7 +56,7 @@ jobs: diffcov: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python @@ -76,7 +76,7 @@ jobs: env: TOXENV: docs steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 - name: Install tox @@ -109,7 +109,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: From 5732ebeeaa9480f8cd80c96a3183d7b247f27214 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 15 Nov 2023 20:08:10 +0600 Subject: [PATCH 62/78] GitHub Actions: Combine tox jobs diffcov and docs (jaraco/skeleton#95) Code reuse Co-authored-by: Jason R. Coombs --- .github/workflows/main.yml | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 10828667..9682985c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,12 +48,15 @@ jobs: python-version: ${{ matrix.python }} allow-prereleases: true - name: Install tox - run: | - python -m pip install tox + run: python -m pip install tox - name: Run run: tox - diffcov: + collateral: + strategy: + fail-fast: false + matrix: + job: [diffcov, docs] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -64,33 +67,16 @@ jobs: with: python-version: 3.x - name: Install tox - run: | - python -m pip install tox - - name: Evaluate coverage - run: tox - env: - TOXENV: diffcov - - docs: - runs-on: ubuntu-latest - env: - TOXENV: docs - steps: - - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v4 - - name: Install tox - run: | - python -m pip install tox - - name: Run - run: tox + run: python -m pip install tox + - name: Eval ${{ matrix.job }} + run: tox -e ${{ matrix.job }} check: # This job does nothing and is only used for the branch protection if: always() needs: - test - - docs + - collateral runs-on: ubuntu-latest @@ -115,8 +101,7 @@ jobs: with: python-version: 3.x - name: Install tox - run: | - python -m pip install tox + run: python -m pip install tox - name: Run run: tox -e release env: From 5f2e291ddf44fe5f5dc105d1d57dd2551f4f24b0 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:16:20 +0200 Subject: [PATCH 63/78] Fix new typo found using codespell --- importlib_metadata/_adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/importlib_metadata/_adapters.py b/importlib_metadata/_adapters.py index e33cba5e..120e43a0 100644 --- a/importlib_metadata/_adapters.py +++ b/importlib_metadata/_adapters.py @@ -54,7 +54,7 @@ def __iter__(self): def __getitem__(self, item): """ Warn users that a ``KeyError`` can be expected when a - mising key is supplied. Ref python/importlib_metadata#371. + missing key is supplied. Ref python/importlib_metadata#371. """ res = super().__getitem__(item) if res is None: From 26f420a97e73a2ab695023f6cc21f5c786d2b289 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 28 Nov 2023 11:43:20 -0500 Subject: [PATCH 64/78] Remove news fragment after allowing time to be processed downstream. --- newsfragments/+drop-py37.feature.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 newsfragments/+drop-py37.feature.rst diff --git a/newsfragments/+drop-py37.feature.rst b/newsfragments/+drop-py37.feature.rst deleted file mode 100644 index ccabdaa3..00000000 --- a/newsfragments/+drop-py37.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Require Python 3.8 or later. From 33dd01267b6a886217bae3ebd5df5b689e2ab722 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 29 Nov 2023 13:21:17 -0500 Subject: [PATCH 65/78] Suppress deprecation warning in dateutil. Workaround for dateutil/dateutil#1284. --- pytest.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pytest.ini b/pytest.ini index d9a15ed1..f9533b57 100644 --- a/pytest.ini +++ b/pytest.ini @@ -24,4 +24,7 @@ filterwarnings= # pypa/build#615 ignore:'encoding' argument not specified::build.env + # dateutil/dateutil#1284 + ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:dateutil.tz.tz + ## end upstream From c3b80ebe0fe37b5e192342f66ddc09f63ed8e325 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Dec 2023 10:45:47 -0500 Subject: [PATCH 66/78] =?UTF-8?q?=E2=9A=AB=20Fade=20to=20black.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- importlib_metadata/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index bc8714b8..5e7e6ad7 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -299,8 +299,7 @@ def __repr__(self): signal that we deviate from regular tuple behavior. """ return '%s(%r)' % (self.__class__.__name__, tuple(self)) - - + def select(self, **params): """ Select entry points from self that match the From dfa7fede8223a69e22006a687f85009d8aecd81c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Dec 2023 10:59:09 -0500 Subject: [PATCH 67/78] =?UTF-8?q?=F0=9F=A7=8E=E2=80=8D=E2=99=80=EF=B8=8F?= =?UTF-8?q?=20Genuflect=20to=20the=20types.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a redundant type definition to a test to avoid warnings about the inner types being unchecked. Closes #478. --- tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 79181bf4..a0be5bae 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -400,7 +400,7 @@ def test_packages_distributions_all_module_types(self): assert not any(name.endswith('.dist-info') for name in distributions) - def test_packages_distributions_symlinked_top_level(self): + def test_packages_distributions_symlinked_top_level(self) -> None: """ Distribution is resolvable from a simple top-level symlink in RECORD. See #452. From 177ec8cd8158a97c9e3df33bba991a079491778e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Dec 2023 12:32:41 -0500 Subject: [PATCH 68/78] Add news fragment. Intended for #473. --- newsfragments/473.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/473.feature.rst diff --git a/newsfragments/473.feature.rst b/newsfragments/473.feature.rst new file mode 100644 index 00000000..a31a0c5c --- /dev/null +++ b/newsfragments/473.feature.rst @@ -0,0 +1 @@ +Added EntryPoints.__repr__ \ No newline at end of file From 4e118be4861fc0b2b15f856dacf52681f5be1b42 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Dec 2023 12:32:48 -0500 Subject: [PATCH 69/78] Finalize --- NEWS.rst | 9 +++++++++ newsfragments/473.feature.rst | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 newsfragments/473.feature.rst diff --git a/NEWS.rst b/NEWS.rst index 92cc0da8..3eed2bad 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,12 @@ +v6.9.0 +====== + +Features +-------- + +- Added EntryPoints.__repr__ (#473) + + v6.8.0 ====== diff --git a/newsfragments/473.feature.rst b/newsfragments/473.feature.rst deleted file mode 100644 index a31a0c5c..00000000 --- a/newsfragments/473.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added EntryPoints.__repr__ \ No newline at end of file From b3918e751538bac84c87c6fe94d9ef5edbcd5e5e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Dec 2023 16:06:52 -0500 Subject: [PATCH 70/78] Added diagnose script. Closes #461. --- importlib_metadata/diagnose.py | 21 +++++++++++++++++++++ newsfragments/461.feature.rst | 1 + 2 files changed, 22 insertions(+) create mode 100644 importlib_metadata/diagnose.py create mode 100644 newsfragments/461.feature.rst diff --git a/importlib_metadata/diagnose.py b/importlib_metadata/diagnose.py new file mode 100644 index 00000000..e405471a --- /dev/null +++ b/importlib_metadata/diagnose.py @@ -0,0 +1,21 @@ +import sys + +from . import Distribution + + +def inspect(path): + print("Inspecting", path) + dists = list(Distribution.discover(path=[path])) + if not dists: + return + print("Found", len(dists), "packages:", end=' ') + print(', '.join(dist.name for dist in dists)) + + +def run(): + for path in sys.path: + inspect(path) + + +if __name__ == '__main__': + run() diff --git a/newsfragments/461.feature.rst b/newsfragments/461.feature.rst new file mode 100644 index 00000000..bf07a91b --- /dev/null +++ b/newsfragments/461.feature.rst @@ -0,0 +1 @@ +Added diagnose script. \ No newline at end of file From 02bbfb0685ce826daa3e6d85d4002b90b70a5e30 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Dec 2023 16:08:37 -0500 Subject: [PATCH 71/78] Finalize --- NEWS.rst | 9 +++++++++ newsfragments/461.feature.rst | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 newsfragments/461.feature.rst diff --git a/NEWS.rst b/NEWS.rst index 3eed2bad..6f3a7ff0 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,12 @@ +v6.10.0 +======= + +Features +-------- + +- Added diagnose script. (#461) + + v6.9.0 ====== diff --git a/newsfragments/461.feature.rst b/newsfragments/461.feature.rst deleted file mode 100644 index bf07a91b..00000000 --- a/newsfragments/461.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added diagnose script. \ No newline at end of file From 72383028a3ca5c7e6ab9a58e6e1e06d30079c905 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 3 Dec 2023 10:55:25 -0500 Subject: [PATCH 72/78] Restore pypy tests now that 3.10 is the standard. Bypasses issue in #463. --- .github/workflows/main.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0bbaaf25..9682985c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,9 +36,8 @@ jobs: platform: ubuntu-latest - python: "3.10" platform: ubuntu-latest - # disabled for #463 - # - python: pypy3.10 - # platform: ubuntu-latest + - python: pypy3.10 + platform: ubuntu-latest runs-on: ${{ matrix.platform }} continue-on-error: ${{ matrix.python == '3.13' }} steps: From 84399188dd9792364603b2a95149f7c11961bca7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 30 Jul 2023 15:35:14 -0400 Subject: [PATCH 73/78] Add changelog --- newsfragments/404.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/404.feature.rst diff --git a/newsfragments/404.feature.rst b/newsfragments/404.feature.rst new file mode 100644 index 00000000..47cf2447 --- /dev/null +++ b/newsfragments/404.feature.rst @@ -0,0 +1 @@ +Added ``Distribution.origin`` supplying the ``direct_url.json`` in a ``SimpleNamespace``. \ No newline at end of file From f480907325bcb79e614da9f826834c25b53839a7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 30 Jul 2023 16:01:38 -0400 Subject: [PATCH 74/78] Add test capturing expectation. Ref #404 --- tests/fixtures.py | 26 ++++++++++++++++++++++++++ tests/test_main.py | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/tests/fixtures.py b/tests/fixtures.py index c0b0fa32..e39fc071 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -1,6 +1,7 @@ import os import sys import copy +import json import shutil import pathlib import tempfile @@ -127,6 +128,31 @@ def make_uppercase(self): build_files(files, self.site_dir) +class DistInfoPkgEditable(DistInfoPkg): + """ + Package with a PEP 660 direct_url.json. + """ + + some_hash = '524127ce937f7cb65665130c695abd18ca386f60bb29687efb976faa1596fdcc' + files: FilesSpec = { + 'distinfo_pkg-1.0.0.dist-info': { + 'direct_url.json': json.dumps( + { + "archive_info": { + "hash": f"sha256={some_hash}", + "hashes": {"sha256": f"{some_hash}"}, + }, + "url": "file:///path/to/distinfo_pkg-1.0.0.editable-py3-none-any.whl", + } + ) + }, + } + + def setUp(self): + super().setUp() + build_files(DistInfoPkgEditable.files, self.site_dir) + + class DistInfoPkgWithDot(OnSysPath, SiteDir): files: FilesSpec = { "pkg_dot-1.0.0.dist-info": { diff --git a/tests/test_main.py b/tests/test_main.py index 79181bf4..38377788 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -457,3 +457,10 @@ def import_names_from_package(package_name): # sources_fallback-pkg has one import ('sources_fallback') inferred from # SOURCES.txt (top_level.txt and installed-files.txt is missing) assert import_names_from_package('sources_fallback-pkg') == {'sources_fallback'} + + +class EditableDistributionTest(fixtures.DistInfoPkgEditable, unittest.TestCase): + def test_origin(self): + dist = Distribution.from_name('distinfo-pkg') + assert dist.origin.url.endswith('.whl') + assert dist.origin.archive_info.hashes.sha256 From e886c996ee154c26861520b8440351f931470373 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 3 Dec 2023 12:13:50 -0500 Subject: [PATCH 75/78] Use a SiteBuilder class to build files in the site, traversing the class hierarchy explicitly and avoiding the need for separate setUp calls for each. --- tests/fixtures.py | 62 ++++++++++++++--------------------------------- 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index c0b0fa32..8df0860c 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -85,7 +85,15 @@ def setUp(self): self.fixtures.enter_context(self.add_sys_path(self.site_dir)) -class DistInfoPkg(OnSysPath, SiteDir): +class SiteBuilder(SiteDir): + def setUp(self): + super().setUp() + for cls in self.__class__.mro(): + with contextlib.suppress(AttributeError): + build_files(cls.files, prefix=self.site_dir) + + +class DistInfoPkg(OnSysPath, SiteBuilder): files: FilesSpec = { "distinfo_pkg-1.0.0.dist-info": { "METADATA": """ @@ -112,10 +120,6 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(DistInfoPkg.files, self.site_dir) - def make_uppercase(self): """ Rewrite metadata with everything uppercase. @@ -127,7 +131,7 @@ def make_uppercase(self): build_files(files, self.site_dir) -class DistInfoPkgWithDot(OnSysPath, SiteDir): +class DistInfoPkgWithDot(OnSysPath, SiteBuilder): files: FilesSpec = { "pkg_dot-1.0.0.dist-info": { "METADATA": """ @@ -137,12 +141,8 @@ class DistInfoPkgWithDot(OnSysPath, SiteDir): }, } - def setUp(self): - super().setUp() - build_files(DistInfoPkgWithDot.files, self.site_dir) - -class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): +class DistInfoPkgWithDotLegacy(OnSysPath, SiteBuilder): files: FilesSpec = { "pkg.dot-1.0.0.dist-info": { "METADATA": """ @@ -158,18 +158,12 @@ class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): }, } - def setUp(self): - super().setUp() - build_files(DistInfoPkgWithDotLegacy.files, self.site_dir) - -class DistInfoPkgOffPath(SiteDir): - def setUp(self): - super().setUp() - build_files(DistInfoPkg.files, self.site_dir) +class DistInfoPkgOffPath(SiteBuilder): + files = DistInfoPkg.files -class EggInfoPkg(OnSysPath, SiteDir): +class EggInfoPkg(OnSysPath, SiteBuilder): files: FilesSpec = { "egginfo_pkg.egg-info": { "PKG-INFO": """ @@ -204,12 +198,8 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(EggInfoPkg.files, prefix=self.site_dir) - -class EggInfoPkgPipInstalledNoToplevel(OnSysPath, SiteDir): +class EggInfoPkgPipInstalledNoToplevel(OnSysPath, SiteBuilder): files: FilesSpec = { "egg_with_module_pkg.egg-info": { "PKG-INFO": "Name: egg_with_module-pkg", @@ -239,12 +229,8 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(EggInfoPkgPipInstalledNoToplevel.files, prefix=self.site_dir) - -class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteDir): +class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteBuilder): files: FilesSpec = { "egg_with_no_modules_pkg.egg-info": { "PKG-INFO": "Name: egg_with_no_modules-pkg", @@ -269,12 +255,8 @@ class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteDir): }, } - def setUp(self): - super().setUp() - build_files(EggInfoPkgPipInstalledNoModules.files, prefix=self.site_dir) - -class EggInfoPkgSourcesFallback(OnSysPath, SiteDir): +class EggInfoPkgSourcesFallback(OnSysPath, SiteBuilder): files: FilesSpec = { "sources_fallback_pkg.egg-info": { "PKG-INFO": "Name: sources_fallback-pkg", @@ -295,12 +277,8 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(EggInfoPkgSourcesFallback.files, prefix=self.site_dir) - -class EggInfoFile(OnSysPath, SiteDir): +class EggInfoFile(OnSysPath, SiteBuilder): files: FilesSpec = { "egginfo_file.egg-info": """ Metadata-Version: 1.0 @@ -316,10 +294,6 @@ class EggInfoFile(OnSysPath, SiteDir): """, } - def setUp(self): - super().setUp() - build_files(EggInfoFile.files, prefix=self.site_dir) - # dedent all text strings before writing orig = _path.create.registry[str] From 84418f85c38d2bba9925f576ef25dda1de9e4647 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 3 Dec 2023 12:25:45 -0500 Subject: [PATCH 76/78] Finalize --- NEWS.rst | 9 +++++++++ newsfragments/404.feature.rst | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 newsfragments/404.feature.rst diff --git a/NEWS.rst b/NEWS.rst index 6f3a7ff0..2a6cc7ba 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,12 @@ +v6.11.0 +======= + +Features +-------- + +- Added ``Distribution.origin`` supplying the ``direct_url.json`` in a ``SimpleNamespace``. (#404) + + v6.10.0 ======= diff --git a/newsfragments/404.feature.rst b/newsfragments/404.feature.rst deleted file mode 100644 index 47cf2447..00000000 --- a/newsfragments/404.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added ``Distribution.origin`` supplying the ``direct_url.json`` in a ``SimpleNamespace``. \ No newline at end of file From 37113c20d89b0646ffaf4922ba11dad81cd27e7f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 3 Dec 2023 12:23:20 -0500 Subject: [PATCH 77/78] Removed EntryPoint access by numeric index (tuple behavior). --- importlib_metadata/__init__.py | 30 +---------------------------- newsfragments/+9c754ffa.removal.rst | 1 + 2 files changed, 2 insertions(+), 29 deletions(-) create mode 100644 newsfragments/+9c754ffa.removal.rst diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index f9984697..312d6966 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -23,7 +23,6 @@ NullFinder, StrPath, install, - pypy_partial, ) from ._functools import method_cache, pass_none from ._itertools import always_iterable, unique_everseen @@ -128,34 +127,7 @@ def valid(line: str): return line and not line.startswith('#') -class DeprecatedTuple: - """ - Provide subscript item access for backward compatibility. - - >>> recwarn = getfixture('recwarn') - >>> ep = EntryPoint(name='name', value='value', group='group') - >>> ep[:] - ('name', 'value', 'group') - >>> ep[0] - 'name' - >>> len(recwarn) - 1 - """ - - # Do not remove prior to 2023-05-01 or Python 3.13 - _warn = functools.partial( - warnings.warn, - "EntryPoint tuple interface is deprecated. Access members by name.", - DeprecationWarning, - stacklevel=pypy_partial(2), - ) - - def __getitem__(self, item): - self._warn() - return self._key()[item] - - -class EntryPoint(DeprecatedTuple): +class EntryPoint: """An entry point as defined by Python packaging conventions. See `the packaging docs on entry points diff --git a/newsfragments/+9c754ffa.removal.rst b/newsfragments/+9c754ffa.removal.rst new file mode 100644 index 00000000..78e66eb9 --- /dev/null +++ b/newsfragments/+9c754ffa.removal.rst @@ -0,0 +1 @@ +Removed EntryPoint access by numeric index (tuple behavior). \ No newline at end of file From fb492e17faee9a6056ca31cf6feffec96c3b9d5a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 3 Dec 2023 12:31:24 -0500 Subject: [PATCH 78/78] Finalize --- NEWS.rst | 9 +++++++++ newsfragments/+9c754ffa.removal.rst | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 newsfragments/+9c754ffa.removal.rst diff --git a/NEWS.rst b/NEWS.rst index 2a6cc7ba..ae1e9cc8 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,12 @@ +v7.0.0 +====== + +Deprecations and Removals +------------------------- + +- Removed EntryPoint access by numeric index (tuple behavior). + + v6.11.0 ======= diff --git a/newsfragments/+9c754ffa.removal.rst b/newsfragments/+9c754ffa.removal.rst deleted file mode 100644 index 78e66eb9..00000000 --- a/newsfragments/+9c754ffa.removal.rst +++ /dev/null @@ -1 +0,0 @@ -Removed EntryPoint access by numeric index (tuple behavior). \ No newline at end of file