Source code

Revision control

Copy as Markdown

Other Tools

from __future__ import annotations
import optparse
from dataclasses import dataclass
from typing import TYPE_CHECKING, Iterable, Iterator, Set, cast
import pip
from pip._internal.cache import WheelCache
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution
from pip._internal.metadata.pkg_resources import Distribution as _PkgResourcesDist
from pip._internal.models.direct_url import DirectUrl
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
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(".")))
# 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
# (and later), but is overridable. `select_backend` returns what's being used.
if TYPE_CHECKING:
from pip._internal.metadata.importlib import Distribution as _ImportLibDist
@dataclass(frozen=True)
class Distribution:
key: str
version: str
requires: Iterable[Requirement]
direct_url: DirectUrl | None
@classmethod
def from_pip_distribution(cls, dist: BaseDistribution) -> Distribution:
# TODO: Use only the BaseDistribution protocol properties and methods
# instead of specializing by type.
if isinstance(dist, _PkgResourcesDist):
return cls._from_pkg_resources(dist)
else:
return cls._from_importlib(dist)
@classmethod
def _from_pkg_resources(cls, dist: _PkgResourcesDist) -> Distribution:
return cls(
dist._dist.key, dist._dist.version, dist._dist.requires(), dist.direct_url
)
@classmethod
def _from_importlib(cls, dist: _ImportLibDist) -> Distribution:
"""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._dist.requires or ()))
requires = [
req
for req in reqs
if not req.marker or req.marker.evaluate({"extra": None})
]
return cls(dist._dist.name, dist._dist.version, requires, dist.direct_url)
def parse_requirements(
filename: str,
session: PipSession,
finder: PackageFinder | None = None,
options: optparse.Values | None = None,
constraint: bool = False,
isolated: bool = False,
) -> Iterator[InstallRequirement]:
for parsed_req in _parse_requirements(
filename, session, finder=finder, options=options, constraint=constraint
):
yield install_req_from_parsed_requirement(parsed_req, isolated=isolated)
def create_wheel_cache(cache_dir: str, format_control: str | None = None) -> WheelCache:
kwargs: dict[str, str | None] = {"cache_dir": cache_dir}
if PIP_VERSION[:2] <= (23, 0):
kwargs["format_control"] = format_control
return WheelCache(**kwargs)
def get_dev_pkgs() -> set[str]:
if PIP_VERSION[:2] <= (23, 1):
from pip._internal.commands.freeze import DEV_PKGS
return cast(Set[str], DEV_PKGS)
from pip._internal.commands.freeze import _dev_pkgs
return cast(Set[str], _dev_pkgs())