Skip to content

Commit

Permalink
Support for the importlib.metadata metadata implementation (#1632)
Browse files Browse the repository at this point in the history
  • Loading branch information
richafrank committed Jun 4, 2022
1 parent e2d0fae commit b9e38f4
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 8 deletions.
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


def pytest_collection_modifyitems(config, items):
Expand Down

0 comments on commit b9e38f4

Please sign in to comment.