From 7b2146f732a08265369a92696fe5c6cdae4d18b3 Mon Sep 17 00:00:00 2001 From: Albert Tugushev Date: Wed, 5 Apr 2023 23:54:51 +0200 Subject: [PATCH] Refactor to parse_requirements_from_metadata --- piptools/_compat/__init__.py | 4 ++-- piptools/_compat/pip_compat.py | 25 +-------------------- piptools/scripts/compile.py | 20 +++++++---------- piptools/utils.py | 41 ++++++++++++++++++++++++++++++++-- 4 files changed, 50 insertions(+), 40 deletions(-) diff --git a/piptools/_compat/__init__.py b/piptools/_compat/__init__.py index 6d68b9c3a..943143253 100644 --- a/piptools/_compat/__init__.py +++ b/piptools/_compat/__init__.py @@ -1,5 +1,5 @@ from __future__ import annotations -from .pip_compat import PIP_VERSION, install_req_from_line, parse_requirements +from .pip_compat import PIP_VERSION, parse_requirements -__all__ = ["PIP_VERSION", "install_req_from_line", "parse_requirements"] +__all__ = ["PIP_VERSION", "parse_requirements"] diff --git a/piptools/_compat/pip_compat.py b/piptools/_compat/pip_compat.py index de5eff5cc..4c0544be5 100644 --- a/piptools/_compat/pip_compat.py +++ b/piptools/_compat/pip_compat.py @@ -8,10 +8,7 @@ from pip._internal.network.session import PipSession from pip._internal.req import InstallRequirement from pip._internal.req import parse_requirements as _parse_requirements -from pip._internal.req.constructors import ( - install_req_from_parsed_requirement, - parse_req_from_line, -) +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 @@ -38,26 +35,6 @@ def parse_requirements( yield install_req_from_parsed_requirement(parsed_req, isolated=isolated) -def install_req_from_line( - name: str, - comes_from: str | InstallRequirement | None = None, - base_package: str | None = None, - base_dir: str | None = None, -) -> InstallRequirement: - parts = parse_req_from_line(name, comes_from) - if base_package and base_dir and parts.requirement.name == base_package: - name = name.replace(base_package, base_dir, 1) - parts = parse_req_from_line(name, comes_from) - - return InstallRequirement( - parts.requirement, - comes_from, - link=parts.link, - markers=parts.markers, - extras=parts.extras, - ) - - # 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.metadata became the default on Python 3.11 diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py index dba2753d6..c6c4dd91b 100755 --- a/piptools/scripts/compile.py +++ b/piptools/scripts/compile.py @@ -13,9 +13,10 @@ from click.utils import LazyFile, safecall from pip._internal.commands import create_command from pip._internal.req import InstallRequirement +from pip._internal.req.constructors import install_req_from_line from pip._internal.utils.misc import redact_auth_from_url -from .._compat import install_req_from_line, parse_requirements +from .._compat import parse_requirements from ..cache import DependencyCache from ..exceptions import NoCandidateFound, PipToolsError from ..locations import CACHE_DIR @@ -29,6 +30,7 @@ drop_extras, is_pinned_requirement, key_from_ireq, + parse_requirements_from_wheel_metadata, ) from ..writer import OutputWriter @@ -497,19 +499,13 @@ def cli( log.error(str(e)) log.error(f"Failed to parse {os.path.abspath(src_file)}") sys.exit(2) - package_name = metadata.get_all("Name")[0] - comes_from = f"{package_name} ({src_file})" + constraints.extend( - [ - install_req_from_line( - req, - comes_from, - package_name, - os.path.dirname(os.path.abspath(src_file)), - ) - for req in metadata.get_all("Requires-Dist") or [] - ] + parse_requirements_from_wheel_metadata( + metadata=metadata, src_file=src_file + ) ) + if all_extras: if extras: msg = "--extra has no effect when used with --all-extras" diff --git a/piptools/utils.py b/piptools/utils.py index f18ba9c97..32e6a28b7 100644 --- a/piptools/utils.py +++ b/piptools/utils.py @@ -7,12 +7,12 @@ import os import re import shlex -from typing import Any, Callable, Iterable, Iterator, TypeVar, cast +from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, TypeVar, cast import click from click.utils import LazyFile from pip._internal.req import InstallRequirement -from pip._internal.req.constructors import install_req_from_line +from pip._internal.req.constructors import install_req_from_line, parse_req_from_line from pip._internal.utils.misc import redact_auth_from_url from pip._internal.vcs import is_url from pip._vendor.packaging.markers import Marker @@ -23,6 +23,12 @@ from piptools.subprocess_utils import run_python_snippet +if TYPE_CHECKING: + from typing import Protocol +else: + Protocol = object + + _KT = TypeVar("_KT") _VT = TypeVar("_VT") _T = TypeVar("_T") @@ -482,3 +488,34 @@ def copy_install_requirement( ) return ireq + + +class PackageMetadata(Protocol): + def get_all(self, name: str, failobj: _T = ...) -> list[str] | _T: + ... + + +def parse_requirements_from_wheel_metadata( + metadata: PackageMetadata, src_file: str +) -> Iterator[InstallRequirement]: + package_name = metadata.get_all("Name")[0] + comes_from = f"{package_name} ({src_file})" + + for req in metadata.get_all("Requires-Dist") or []: + parts = parse_req_from_line(req, comes_from) + if parts.requirement.name == package_name: + package_dir = os.path.dirname(os.path.abspath(src_file)) + # Replace package name with package directory in the requirement + # string so that pip can find the package as self-referential. + # Note the string can contain extras, so we need to replace only + # the package name, not the whole string. + replaced_package_name = req.replace(package_name, package_dir, 1) + parts = parse_req_from_line(replaced_package_name, comes_from) + + yield InstallRequirement( + parts.requirement, + comes_from, + link=parts.link, + markers=parts.markers, + extras=parts.extras, + )