Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for the importlib.metadata metadata implementation #1632

Merged
merged 2 commits into from Jun 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 49 additions & 1 deletion piptools/_compat/pip_compat.py
@@ -1,5 +1,5 @@
import optparse
from typing import Iterator, Optional
from typing import Callable, Iterable, Iterator, Optional, cast

import pip
from pip._internal.index.package_finder import PackageFinder
Expand All @@ -8,13 +8,17 @@
from pip._internal.req import parse_requirements as _parse_requirements
from pip._internal.req.constructors import install_req_from_parsed_requirement
from pip._vendor.packaging.version import parse as parse_version
from pip._vendor.pkg_resources import Requirement

PIP_VERSION = tuple(map(int, parse_version(pip.__version__).base_version.split(".")))


__all__ = [
"get_build_tracker",
"update_env_context_manager",
"dist_requires",
"uses_pkg_resources",
"Distribution",
]


Expand Down Expand Up @@ -42,3 +46,47 @@ def parse_requirements(
get_build_tracker,
update_env_context_manager,
)


# The Distribution interface has changed between pkg_resources and
# importlib.metadata, so this compat layer allows for a consistent access
# pattern. In pip 22.1, importlib.metdata became the default on Python 3.11
# (and later), but is overrideable. `select_backend` returns what's being used.


def _uses_pkg_resources() -> bool:

if PIP_VERSION[:2] < (22, 1):
return True
else:
from pip._internal.metadata import select_backend
from pip._internal.metadata.pkg_resources import Distribution as _Dist

return select_backend().Distribution is _Dist


uses_pkg_resources = _uses_pkg_resources()

if uses_pkg_resources:
from operator import methodcaller

from pip._vendor.pkg_resources import Distribution

dist_requires = cast(
Callable[[Distribution], Iterable[Requirement]], methodcaller("requires")
)
else:
from pip._internal.metadata import select_backend

Distribution = select_backend().Distribution

def dist_requires(dist: "Distribution") -> Iterable[Requirement]:
"""Mimics pkg_resources.Distribution.requires for the case of no
extras. This doesn't fulfill that API's `extras` parameter but
satisfies the needs of pip-tools."""
reqs = (Requirement.parse(req) for req in (dist.requires or ()))
return [
req
for req in reqs
if not req.marker or req.marker.evaluate({"extra": None})
]
5 changes: 2 additions & 3 deletions piptools/scripts/sync.py
Expand Up @@ -10,10 +10,10 @@
from pip._internal.commands.install import InstallCommand
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import get_environment
from pip._vendor.pkg_resources import Distribution

from .. import sync
from .._compat import IS_CLICK_VER_8_PLUS, parse_requirements
from .._compat.pip_compat import Distribution
from ..exceptions import PipToolsError
from ..logging import log
from ..repositories import PyPIRepository
Expand Down Expand Up @@ -275,12 +275,11 @@ def _get_installed_distributions(
paths: Optional[List[str]] = None,
) -> List[Distribution]:
"""Return a list of installed Distribution objects."""
from pip._internal.metadata.pkg_resources import Distribution as _Dist

env = get_environment(paths)
dists = env.iter_installed_distributions(
local_only=local_only,
user_only=user_only,
skip=[],
)
return [cast(_Dist, dist)._dist for dist in dists]
return [cast(Distribution, dist)._dist for dist in dists]
4 changes: 2 additions & 2 deletions piptools/sync.py
Expand Up @@ -19,8 +19,8 @@
from pip._internal.commands.freeze import DEV_PKGS
from pip._internal.req import InstallRequirement
from pip._internal.utils.compat import stdlib_pkgs
from pip._vendor.pkg_resources import Distribution

from ._compat.pip_compat import Distribution, dist_requires
from .exceptions import IncompatibleRequirements
from .logging import log
from .utils import (
Expand Down Expand Up @@ -69,7 +69,7 @@ def dependency_tree(

dependencies.add(key)

for dep_specifier in v.requires():
for dep_specifier in dist_requires(v):
dep_name = key_from_req(dep_specifier)
if dep_name in installed_keys:
dep = installed_keys[dep_name]
Expand Down
16 changes: 14 additions & 2 deletions tests/conftest.py
Expand Up @@ -19,6 +19,7 @@
from pip._vendor.packaging.version import Version
from pip._vendor.pkg_resources import Requirement

from piptools._compat.pip_compat import uses_pkg_resources
from piptools.cache import DependencyCache
from piptools.exceptions import NoCandidateFound
from piptools.repositories import PyPIRepository
Expand Down Expand Up @@ -106,6 +107,7 @@ class FakeInstalledDistribution:
def __init__(self, line, deps=None):
if deps is None:
deps = []
self.dep_strs = deps
self.deps = [Requirement.parse(d) for d in deps]

self.req = Requirement.parse(line)
Expand All @@ -115,8 +117,18 @@ def __init__(self, line, deps=None):

self.version = line.split("==")[1]

def requires(self):
return self.deps
# The Distribution interface has changed between pkg_resources and
# importlib.metadata.
if uses_pkg_resources:

def requires(self):
return self.deps

else:

@property
def requires(self):
return self.dep_strs
atugushev marked this conversation as resolved.
Show resolved Hide resolved


def pytest_collection_modifyitems(config, items):
Expand Down